[GCP] 스프링부트를 도커에 넣어서 GCE에 올려보겠나?

이정진·2023년 12월 19일
0

개발

목록 보기
2/8
post-thumbnail

GCP를 쓰게 된 계기

기존 소프트웨어 마에스트로를 통해 서버비를 지원받아 인프라를 구축했었는데, 마지막 지원 단계인 고도화 과정까지 끝나가므로, 300달러의 크레딧을 주는 GCP를 통해서 인프라를 구축하기로 결정했다. 이런 결정 이후, 최대한 빠른 시간 안에 인프라를 구축해야 하기에, 이렇게 GCP로 인프라를 구축하면서 관련 글을 작성하게 되었다.

GCP 인프라 구축

인프라 설계


위와 같이 인프라를 설계했다. 사실 GCP 기반으로 설계는 처음이기에 AWS와 비슷하게 설계를 진행했다.
기존의 Redis를 분리해서 구축했던 과거의 인프라와 달리 비용 경감이 목적이기에, 단일 인스턴스 내 Docker Container로 띄워놓는 것으로 변경했다. 또한, 기존에는 Docker를 사용하지 않고 Jar 파일을 배포했었지만 이번에는 Dockerfile을 활용하는 방식을 선택했다. DB는 Public 접근이 되면 안 되기에, Private Subnet으로 분리했으며, 이를 GCE를 Bastion host로 접속 가능하도록 설정하는 것으로 판단했다.

잘못된 부분이 있다면, 알려주시면 감사하겠습니다 :)

Compute Engine

GCE 생성하기

  1. VM 인스턴스 페이지를 접속한다.

  2. 인스턴스 만들기를 클릭한다.

  1. 인스턴스 이름, 리전 및 성능 설정을 진행한다. (필요한 수준에 맞추어 유동적으로 진행하면 된다.)

  1. 모든 설정을 완료했다면 만들기를 통해 생성을 진행하면 된다. 생성되면 아래와 같이 확인할 수 있다.

외부 IP 고정하기

GCP에서 생성한 VM 인스턴스는, 고정적으로 외부 IP를 지정해주지 않으면 임시 IP로 계속 접근할 수 있는 IP가 변경될 수 있다. 그렇기에, 나는 고정적으로 외부 IP를 연결하여 사용하기로 결정했다.

  1. VPC 네트워크의 IP 주소로 접속한다.

  1. 접속하면 아래에 2개의 내부/외부 IP가 임시로 생성되는데, 고정 IP를 생성하기 위해 외부 고정 IP 주소 예약으로 접속한다.

  1. 설정할 IP 이름, 설명, 버전, 유형을 선택하여 예약 버튼을 클릭한다.

  1. 2번과 동일한 페이지에서 임시로 적혀 있던 외부 IP가 고정으로 바뀐 것을 확인할 수 있다.

ssh로 연결하기

GCE를 접속할 때는, Google Cloud Console로 접속하는 방법으로 일반적으로 안내한다. 다만, ssh를 활용한 접속이 익숙하다보니 가이드를 참고하여 해당 방식으로 접속할 수 있도록 설정을 진행했다.

  1. 터미널 내에서 아래와 같이 입력하여 ssh 키 쌍을 생성한다.
ssh-keygen -t rsa -f ~/.ssh/KEY_FILENAME -C USERNAME -b 2048
  1. passphrase를 설정한다.

passphrase는 추후, ssh 연결 시 계속 입력하게 되므로 까먹으면 안 된다.

  1. 생성된 키와 연결되는 *.pub 내용을 복사해서 GCP에 등록한다. *.pub은 아래와 같이 조회한다.
cat ~/.ssh/KEY_FILENAME.pub
  1. GCP 등록 (Compute Engine -> 메타데이터 -> ssh 키)

  1. Compute Engine 접속
ssh -i ~/.ssh/KEY_FILENAME.pub USER_NAME@IP_ADDRESS

터미널에서 위와 같이 입력하면, passphrase를 입력하라고 뜨는데, 최초 생성 시에 설정한 passphrase를 입력해주면 접속된다.

Cloud SQL

Private Subnet 필요성

DB는 퍼블릭에서 접근이 가능하면 안 되기 때문에, Private Subnet에 생성하고 스프링부트 서버가 위치한 인스턴스에서만 접근이 되도록 설정해야 한다. 그러기 위해서는 먼저 VPC 위에서 Private Subnet을 만들고, 해당 Subnet에 Cloud SQL이 위치하도록 설정해야 한다.

Cloud SQL 생성 및 연결

  1. 인스턴스 만들기 접속

  1. 사용할 DB 엔진 선택

  1. 인스턴스 정보 선택

금액을 최대한 낮추기 위해서 예상 가격을 맞추어서 선택을 진행했다. Enterprise Plus가 아닌 Enterprise로 진행을 했다.
(만약, Private Subnet으로 하실 분들은 공개 IP 설정을 취소하시고, 비공개 IP만 설정을 누르셔서 진행해주시면 됩니다.)

  1. 인스턴스 생성 완료

  1. 데이터베이스 생성하기


위와 같은 리스트에서 데이터베이스를 클릭하여 접속하면, 데이터베이스 만들기가 보인다. 데이터베이스 만들기를 클릭해서 생성을 진행하면 된다. 나는 기존과 동일하게 utm8mb4와 기본대조를 선택했다.

DB Connection Test

  1. Compute Engine에서 Cloud SQL로 3306 포트가 열려있는지 확인하기

(1) Compute Engine 터미널 접속

ssh -i ~/.ssh/KEY_FILENAME.pub USER_NAME@IP_ADDRESS

(2) 아래와 같은 명령어를 통해 해당 포트 열려있는지 확인

ecoh > /dev/tcp/IP_ADDRESS/PORT

(3) 연결되어 있다면 아래 명령어 입력 시 0이 나오며, 연결되어 있지 않다면 1이 나온다.

echo $?
  1. DB Tool 연결하기

데이터베이스 관리 툴로는 TablePlus를 사용하고 있기에, Table Plus에서 Cloud SQL 연결을 진행해야 한다.

  • Host: 사설 IP
  • Port: 3306 (별도의 설정을 하지 않았다면)
  • User: 기본 설정이면 root
  • 비밀번호: SQL 생성 시 설정한 비밀번호를
  • Database: 생성한 DB 이름

Private Subnet으로 생성하셨다면, Over SSH를 통해 추가 정보 기입

  • Server: Bastion Host로 사용되는 서버 IP
  • Port: SSH 연결이므로 22
  • Use SSH Key: 기존에 ssh-keygen으로 발급한 key 선택

이후 Test가 성공하면, Connect를 진행하면 된다.
(성공 시에는 초록색으로 입력 칸이 변하게 됩니다!)

Docker & Workflow

기본적인 Compute Engine과 Cloud SQL 설정이 완료되었으므로 도커를 서버에 설치하고 띄울 수 있도록 진행한다.

Docker

Devian에 도커 설치하기

공식문서를 기반으로 진행한다.

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 도커 버전 확인
sudo docker --version

GCP의 Compute Engine의 버전을 유의해서 살펴야 한다. 나는 Devian으로 되어 있기에, 공식 문서에서 Devian으로 안내되어 있는 부분으로 진행했다.

Docker compose 설정

  1. Docker Compose 다운로드
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose -v
  1. docker-compose.yml 파일 등록
version: '3'
services:
  redis:
    container_name: redis
    image: redis:latest
    ports:
      - "6379:6379"
    networks:
      - network
  springboot:
    container_name: server
    image: springboot
    ports:
      - "8080:8080"
    depends_on:
      - redis
    networks:
      - network

networks:
  network:
    driver: bridge

위는 Redis와 Spring Boot을 Docker Compose로 올릴 때 사용하는 예시 yml이다. 이를 각자 방식에 맞추어 재구성해서 사용하면 된다.

인스턴스 내에서 해당 파일을 생성하고자 한다면, 아래와 같은 명령어를 입력해서 파일을 생성하면 된다.

vi docker-compose.yml

Docker hub 설정

  1. Docker hub 홈페이지 접속 & 로그인

  1. Create Repository 버튼을 통해 레포지토리 생성 (private으로 생성)

Workflow

CD Workflow 설정

name: GCE Deploy

on:
  push:
    branches:
      - release

jobs:
  build:
    # ubuntu 버전 지정
    runs-on: ubuntu-22.04
    steps:
      # Checkout 진행
      - uses: actions/checkout@v3

      # JDK 11 설치
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      # Gradle 캐싱
      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      # Gradle 권한 부여
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      # secret.yml 반영
      - name: Make application-secret.yml
        run: |
          cd ./src/main/resources
          touch ./application-secret.yml
          echo "${{ secrets.APPLICATION_SECRET}}" > ./application-secret.yml
        shell: bash

      # release.yml 반영
      - name: Make application-release.yml
        run: |
          cd ./src/main/resources
          echo "${{ secrets.GCP_APPLICATION_RELEASE}}" > ./application-release.yml
        shell: bash

      # Gradle BootJar
      - name: BootJar with Gradle
        run: ./gradlew clean bootJar -Dspring.profiles.active=release

      # Docker Image Push
      - name: Docker Image push
        run: |
          docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
          docker build -t ${{ secrets.DOCKERHUB_USERNAME}}/${{ secrets.DOCKERHUB_REPOSITORY}} ./
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY}}

      # Docker Compose
      - name: Docker Compose
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.GCP_SERVER_IP }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          passphrase: ${{ secrets.SSH_PASSPHRASE }}
          envs: GITHUB_SHA
          script: |
            sudo docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
            sudo docker-compose stop ${{ secrets.DOCKER_SERVICE_NAME }}
            sudo docker-compose rm -f ${{ secrets.DOCKER_SERVICE_NAME }}
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY}}
            sudo docker tag ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY}} ${{ secrets.DOCKER_IMAGE_NAME }}
            sudo docker-compose up -d

위와 같이 CD Workflow를 구성해서 배포를 진행했다.

마주친 오류 (= 내 실수)

수동으로 배포했을 때는, 정상적으로 SpringBoot 서버가 올라가는 것을 확인하고, 동일한 방식으로 Workflow를 작성했다. 그러나, Workflow로 작성했을 때는 정상적으로 작동하지 않는 것으로 확인되었다. (Workflow 자체는 성공이지만, Docker compose하는 스크립트에서 오류 발생하는 것으로 추정)

  1. Docker Hub 레포지토리의 Pull, Push 기록 확인
    확인 결과, Docker Image Push는 정상적으로 스크립트가 동작한 시간대에 Push된 것을 확인할 수 있었다. 다만, Pull은 정상적으로 동작하지 않음을 확인했다.

  2. 수동으로 Docker Compose 부분 진행해본 결과, 정상적으로 작동함을 확인

  3. 오타 발견

    scripts가 아니라 script였다.....

위 과정을 GCE 위에서 진행한 이후, 아래의 명령어를 통해서 docker에서 구동되고 있는지를 확인할 수 있다.

sudo docker ps -a

-a 옵션을 통해, 에러가 발생하여 구동되지 않는 컨테이너 또한 확인할 수 있으니, 이를 활용하여 에러가 무엇인지 찾아가면 될 것 같다.

레퍼런스

0개의 댓글