코딩은 마라톤

[KUSITMS] SPOT x 네이버 클라우드 : Container Registry를 이용하여 서버 배포 자동화하기 본문

대외활동/KUSITMS

[KUSITMS] SPOT x 네이버 클라우드 : Container Registry를 이용하여 서버 배포 자동화하기

anxi 2025. 6. 27. 15:34

🍀 네이버 클라우드를 이용해 SPOT 서버 개발

큐시즘 30기에 이어 31기에서도 네이버 클라우드에서 다양한 서비스를 프로덕트에 적용해 볼 수 있도록 크레딧을 제공해 주셨다 🥳🥳🥳

이번 SPOT을 개발하면서 네이버 클라우드의 Container Registry와 Server를 이용해 서버를 구축했는데 구축 과정을 소개하고자 한다!

 

🚗 SPOT x 네이버 클라우드

📍SPOT 링크 : https://www.pickspot.co.kr/

📍SPOT Client Github : https://github.com/Team-MEET-UP/spot-client

📍SPOT Server Github : https://github.com/Team-MEET-UP/spot-server

 

SPOT은 약속 중간 지점 선정의 불편함을 해소하기 위해 약속의 목적을 고려한 장소를 추천하고 중간 지점을 산출해주는 서비스다.

 

특히 약속 제안자가 약속 참여자의 출발지를 취합하는 기존의 과정은 카카오톡에서 개개인의 참여자에게 연락을 보내고 중간지점 후보를 지정해 어떤지 물어보고 별로면 다시 정하는, 즉 약속 제안자의 불필요한 시간을 소모할 수 밖에 없다. 

하지만 SPOT에서는 제안자가 약속을 만들고 링크를 참여자에게 제공함으로써, 제안자의 수고로움을 덜 수 있다. 중간지점 후보를 찾고 정하는 과정도 SPOT에서 제공하기 때문에 안 쓰면 손해인 서비스다 😆

 

네이버 클라우드 기술 플랫폼

 

초대규모 AI인 HyperCLOVA X로 비개발자도 네이버 클라우드를 들어봤을 거라 생각한다.

네이버 클라우드는 사진에 적힌 기술과 솔루션을 제공한다. 즉, 네이버 클라우드는 클라우드 서비스만 제공하는 것은 아니다 🙅

 

NAVER Cloud는 국내 CSP 기준 시장 점유 1위로 Naver Cloud Platform은 220개 이상의 클라우드 서비스를 제공한다.

이번에 네이버 클라우드에서는 NAVER Cloud Platform의 서비스를 사용할 수 있도록 큐시즘에 크레딧을 제공해 주셔서 무료로 클라우드 서비스를 사용해 보았다!


👀 서비스 아키텍처

현재 SPOT 개발하면서 사용한 NCP (NAVER Cloud Platform) 서비스는 아래와 같다.

  • VPC : 논리적으로 격리된 네트워크 공간을 의미한다. Subnet이 VPC 내 포함된다. VPC -> Subnet 내 Server를 위치한다.
  • Server : 백엔드 서버를 사용하기 위해 사용했다.
  • Container Registry & Object Storage : 도커 컨테이너 이미지를 간편하게 저장, 관리, 배포할 수 있는 서비스이다. 컨테이너 이미지를 쉽게 전달 및 배포할 수 있으며, 이미지 저장을 위해 Object Storage도 같이 사용한다.
  • ACG : 서버 그룹으로의 네트워크 접근을 제어 및 관리하는 데 사용되며, 서버 접속을 허용할 인바운드 트래픽 규칙을 안전하고 편리하게 추가, 변경, 삭제할 수 있다.

이제 위 서비스를 가지고 서버를 구축해 보자 😎


🔍 SPOT x NCP 서버 배포 자동화하기

0) VPC와 Subnet 생성하기

Server를 생성하기에 앞서, VPC와 Subnet 생성은 필수적이다. SPOT의 독립적인 네트워크 공간을 만들어봅시다!

VPC 생성

 

사설 VPC IP 주소 범위인 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 내에서 IP 주소 범위를 지정해 주면 된다.

VPC 생성 완료

 

SPOT의 VPC를 만들었으니, Subnet을 만들러 가봅시다.

Subnet 생성

 

Subnet의 IP 주소 범위는 VPC의 CIDR 블록을 기반으로 하기 때문에, VPC 서브넷 마스크 비트를 더 쪼개서 구성해야 한다.

 

만약 VPC의 CIDR 블록이 10.0.0.0/16이라면, Subnet은 10.0.0.0/17~ 이어야 가능하다.

 

Subnet 생성 완료

 

이렇게 해서 VPC와 Subnet을 손쉽게 생성해 봤다.

CIDR 블록을 잘 확인하고 설정하면 문제없이 생성될 것이다 🤩


1) Server 생성하기

Server를 생성하기에 앞서 어떤 스펙의 Server를 사용할지, 비용적인 측면도 고려해야 한다.

 

👇👇👇 상세한 서버 스펙과 비용은 아래를 참고해 주세요 👇👇👇

https://www.ncloud.com/product/compute/server#pricing

 

첫 번째로 서버 이미지를 선택해야 한다.

SPOT은 Ubuntu OS를 사용하고, 고사양의 서버는 필요 없기에 Standard 타입으로 설정했다.

 

 

두 번째로 서버 타입과 요금제를 선택해야 한다.
앞서 말한 것과 같이, Standard 서버로도 충분했기에 이로 설정하였고 Public IP를 할당하였다.

 

인증키 생성
ACG 생성

 

끝으로 서버 접근을 위한 인증키와 인바운드 트래픽 제어를 위해 ACG를 생성한다.

시간이 좀 지나고 나서, 서버가 운영되는 것을 볼 수 있다 😮

서버 생성 완료!


2) Container Registry 생성하기

Container Registry를 생성하기 앞서, Object Storage를 생성해야만 한다.

Object Storage를 생성하는 과정은 간단하기 때문에 아래를 참고해 생성해 주세요!

https://guide.ncloud-docs.com/docs/objectstorage-start

 

 

사실 Container Registry 생성하는 게 더 간단하다.!

레지스트리 이름을 설정하고 앞서 만든 버킷을 설정해 주면 바로 생성할 수 있다.

Container Registry 생성 완료!


3) 배포 파이프라인 작성하기

SPOT의 CI/CD 과정은 다음과 같다.

  1. Pull Request 생성 또는 병합 시, Test Workflow가 실행되어 테스트 코드를 수행하고, 에러가 있을 경우 병합을 못하게 막는다.
  2. Pull Request가 develop 브랜치에 병합되면, Deploy Workflow가 실행되어 서버에 변경 사항이 배포된다.

Container Registry가 쓰인 부분은 Deploy Workflow이다.

 

name: Deploy

on:
  push:
    branches: [ "develop" ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          submodules: true
          token: ${{ secrets.GH_TOKEN }}

      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Cache Gradle
        uses: actions/cache@v4.2.3
        with:
          path: ~/.gradle/caches
          key: gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: gradle-

      - name: Grant execute permission to gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew build -x test

      - name: move jar file to deploy
        run: mv ./build/libs/*.jar ./deploy

      - name: Docker Setup Buildx
        uses: docker/setup-buildx-action@v3.10.0

      - name: Login to NCP Container Registry
        uses: docker/login-action@v3.4.0
        with:
          registry: ${{ secrets.NCP_CONTAINER_REGISTRY }}
          username: ${{ secrets.NCP_ACCESS_KEY }}
          password: ${{ secrets.NCP_SECRET_KEY }}

      - name: Build and push Docker images
        uses: docker/build-push-action@v6.15.0
        with:
          context: ./deploy
          platforms: linux/amd64
          push: true
          tags: |
            ${{ secrets.NCP_CONTAINER_REGISTRY }}/spot-backend:${{ github.sha }}

  deploy:
    needs: build
    name: deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          submodules: true
          token: ${{ secrets.GH_TOKEN }}

      - name: Copy docker-compose.yml to Server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.NCP_HOST }}
          username: ${{ secrets.NCP_USERNAME }}
          password: ${{ secrets.NCP_PASSWORD }}
          source: deploy/docker-compose.yml
          target: /home/ubuntu/deploy
          strip_components: 1

      - name: Deploy to NCP Server and Run
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.NCP_HOST }}
          username: ${{ secrets.NCP_USERNAME }}
          password: ${{ secrets.NCP_PASSWORD }}
          port: ${{ secrets.NCP_PORT }}
          script: |
            cd /home/ubuntu/deploy
            echo "SPOT_IMAGE=${{ secrets.NCP_CONTAINER_REGISTRY }}/spot-backend:${{ github.sha }}" > .env
            
            sudo docker compose down
            sudo docker compose pull
            sudo docker compose up -d

 

위 과정을 간단히 살펴보자!

 

build 과정

1. Checkout Code

현재 Github 리포지토리에 있는 최신 코드를 읽어온다.

 

2. Set Up JDK21

Github Actions의 JDK 21을 설치한다. SPOT은 JDK 21을 사용했기 때문에 21을 설치했지만, 프로젝트에 맞게 변경하면 된다.

 

3. Cache Gradle

빌드 속도 개선을 위해 Gradle 캐시를 적용한다.

`~/.gradle/caches` 에 Gradle 캐시를 저장하고, 만약 Key에 맞는 캐시가 없을 경우 `restore-keys`를 이용해서 가장 최근의 캐시를 복원한다.

 

4. Gradle 권한 허용 및 빌드

gradlew 권한을 허용하고, 빌드하는데 deploy Workflow 실행 전에 Test Workflow가 실행되기 때문에 중복을 막고자 테스트는 배제한다.

 

5. jar 파일 이동 및 Docker 설치

jar 파일을 Docker 이미지로 만들기 위해 특정 폴더(deploy)로 이동하고 Docker를 설치한다.

 

💡 아래 과정은 build 시 Naver Cloud PlatformContainer Registry를 사용하는 부분이다.

 

6. NCP Container Registry 로그인

      - name: Login to NCP Container Registry
        uses: docker/login-action@v3.4.0
        with:
          registry: ${{ secrets.NCP_CONTAINER_REGISTRY }}
          username: ${{ secrets.NCP_ACCESS_KEY }}
          password: ${{ secrets.NCP_SECRET_KEY }}

 

도커 이미지를 빌드하고 올리기 위해 로그인이 이뤄져야 한다.

Container Registry의 이름(registry), username(NCP Access Key) , password(NCP Secrey Key)를 환경변수로 넣어 로그인하였다.

 

Access Key, Secret Key 확인

 

AccessKey와 Secret Key는 "마이페이지 -> 계정 관리 -> 인증키 관리"에서 확인 가능하다!

 

7. 도커 이미지 Build & Push

      - name: Build and push Docker images
        uses: docker/build-push-action@v6.15.0
        with:
          context: ./deploy
          platforms: linux/amd64
          push: true
          tags: |
            ${{ secrets.NCP_CONTAINER_REGISTRY }}/spot-backend:${{ github.sha }}

 

5번 과정에서 deploy 폴더로 이동한 jar 파일을 도커 이미지로 만들어야 한다.

 

# deploy/Dockerfile

FROM eclipse-temurin:21-alpine

ARG JAR_FILE=./*.jar
COPY ${JAR_FILE} spot.jar

ENTRYPOINT ["java", "-jar", "spot.jar", "--spring.profiles.active=dev"]

 

deploy 폴더 하위에 Dockerfile을 이용해서 SPOT의 도커 이미지가 만들어진다. 만들어진 도커 이미지는 Container Registry로 옮겨진다.

 

deploy 과정

Container Registry에 SPOT 도커 이미지를 이제 Server에서 실행해야 한다.

SPOT은 Redis를 컨테이너로 만들어 사용하기 때문에 docker-compose를 사용해 여러 개의 도커 컨테이너를 한 번에 정의하고 실행하게끔 하였다.

 

1. docker-compose.yml을 NCP Server에 옮기기

      - name: Copy docker-compose.yml to Server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.NCP_HOST }}
          username: ${{ secrets.NCP_USERNAME }}
          password: ${{ secrets.NCP_PASSWORD }}
          source: deploy/docker-compose.yml
          target: /home/ubuntu/deploy
          strip_components: 1
          
---
# deploy/docker-compose.yml

services:

  app:
    image: ${SPOT_IMAGE}
    container_name: spot-backend
    ports:
      - "8080:8080"
    depends_on:
      - redis
    restart: always
    environment:
      TZ: Asia/Seoul

  redis:
    image: redis:7.4
    container_name: spot-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: always

volumes:
  redis-data:

 

 

docker-compose.yml에서는 "Container Registry에 저장된 SPOT 이미지를 컨테이너화 하는 app"과 "redis 컨테이너"를 정의해 두었다.

Server에서 실행하기 위해 Server의 `/home/ubuntu/deploy`에 docker-compose.yml을 붙여 넣는다.

 

전송하기 위해 사용되는 host, username, password는 Server 생성하고 나서 만들어진 공인 IP, 관리자 이름, 비밀번호이다.

 

2. NCP Server에서 docker-compose 실행하기

      - name: Deploy to NCP Server and Run
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.NCP_HOST }}
          username: ${{ secrets.NCP_USERNAME }}
          password: ${{ secrets.NCP_PASSWORD }}
          port: ${{ secrets.NCP_PORT }}
          script: |
            cd /home/ubuntu/deploy
            echo "SPOT_IMAGE=${{ secrets.NCP_CONTAINER_REGISTRY }}/spot-backend:${{ github.sha }}" > .env
            
            sudo docker compose down
            sudo docker compose pull
            sudo docker compose up -d

 

배포 마지막 단계이다!

 

docker-compose.yml이 존재하는 `/home/ubuntu/deploy`로 이동해서 SPOT_IMAGE 태그를 환경변수(.env)로 만들어준다.

build 과정에서 7. 도커 이미지 Build & Push의 SPOT 이미지 태그를 붙여서 만들었기 때문에 만들어진 이미지를 Pull 하기 위해 환경변수로 만들어야 한다!

 

그리고 현재 docker compose로 띄워진 컨테이너를 정리하고 (down), 이미지를 저장하고 (pull), 컨테이너를 실행한다(up).


🍀 NAVER Cloud Platform를 사용하며

👍 좋았던 점

대다수의 개발자라면 AWS나 GCP를 사용했을 거라 생각한다. 이전에 개발했던 프로젝트도 AWS를 사용했었는데 AWS도 정말 좋은 CSP 회사이다! 다만, 아쉬웠던 점이 있다면 한글 설명이 번역체로 작성되어 있어 글의 내용을 이해하기 어려운 부분도 있었다.

 

하지만 NCP는 Naver Cloud에서 운영하는 국내 회사인 만큼, 문서가 한글로 초심자도 이해하기 쉽게 잘 작성되어 있어 개발하다가 막히는 부분이 있을 때 활용하기 매우 좋았다!

Container Registry 생성 과정

 

사실 위에 내가 작성한 것보다 훨씬 잘 적혀 있다..😅

 

또한 개발자 포럼과 같이 질문 소통 창구도 마련되어 있어서 '이런 게 가능할까?'라는 생각이 들 때 포럼을 찾아보면서 어느 정도 궁금증을 해소할 수 있었다.

 

 

지도 서비스다 보니 위/경도 좌표만으로 네이버 지도를 띄워줄 수 있는 방법을 찾던 도중, 위처럼 할 수 있는 것도 개발자 포럼을 통해 알게 되었다!

 

요약하자면, NCP는 인프라를 잘 모르는 개발자가 참고할 수 있는 문서와 여러 궁금증을 해소할 수 있는 개발자 포럼을 통해 많은 도움을 얻을 수 있을 거라 생각한다!

 

🥲 아쉬웠던 점

모든 인프라를 NCP를 사용해 구축해보고 싶었는데, DB는 AWS RDS를 사용했다. NCP의 DB 성능이 AWS Free tier 보다 훨씬 좋지만, 그만큼 비용 측면에서도 비용이 높다.

 

프로덕트 개발 초기 단계에서 그 정도의 성능이 필요하지는 않기 때문에 개발용으로 사용할 성능이 조금 낮고, 비용도 저렴한 DB가 있으면 더 좋지 않을까 생각했다 💭

 

🎬 마무리

네이버 클라우드에서 크레딧을 제공해 주셔서 Container Registry로 배포를 자동화하는 과정을 작성했다. 이뿐만 아니라 Clova Studio를 이용해 구글 리뷰를 번역하는 기능도 개발하고 있다!

 

앞으로의 욕심이 있다면, 현재 Kubernetes를 공부하고 있어서 Naver Kubernetes Service를 도입해서 배포 과정을 변경해보고 싶다. 또한 SPOT은 릴리즈 목표까지 있기 때문에 중간 지점 계산에 대해 확장이 필요하다. 확장을 위해 Clova Studio를 사용해서 AI로 중간 지점을 산출하고 주변 장소도 추천하게끔 하면 현재 산출/추천 방식보다 고도화될 것으로 생각하기 때문에 Clova Studio도 이번 스프린트에서 더욱 활용해 볼 예정이다.

 

배포 과정에서 궁금한 점이 있다면 글 서두에 있는 SPOT Github 또는 댓글 남겨주세요!

 

🤗 SPOT 많관부 🤗