기존 소프트웨어 마에스트로를 통해 서버비를 지원받아 인프라를 구축했었는데, 마지막 지원 단계인 고도화 과정까지 끝나가므로, 300달러의 크레딧을 주는 GCP를 통해서 인프라를 구축하기로 결정했다. 이런 결정 이후, 최대한 빠른 시간 안에 인프라를 구축해야 하기에, 이렇게 GCP로 인프라를 구축하면서 관련 글을 작성하게 되었다.
위와 같이 인프라를 설계했다. 사실 GCP 기반으로 설계는 처음이기에 AWS와 비슷하게 설계를 진행했다.
기존의 Redis를 분리해서 구축했던 과거의 인프라와 달리 비용 경감이 목적이기에, 단일 인스턴스 내 Docker Container로 띄워놓는 것으로 변경했다. 또한, 기존에는 Docker를 사용하지 않고 Jar 파일을 배포했었지만 이번에는 Dockerfile을 활용하는 방식을 선택했다. DB는 Public 접근이 되면 안 되기에, Private Subnet으로 분리했으며, 이를 GCE를 Bastion host로 접속 가능하도록 설정하는 것으로 판단했다.
잘못된 부분이 있다면, 알려주시면 감사하겠습니다 :)
VM 인스턴스 페이지를 접속한다.
인스턴스 만들기를 클릭한다.
GCP에서 생성한 VM 인스턴스는, 고정적으로 외부 IP를 지정해주지 않으면 임시 IP로 계속 접근할 수 있는 IP가 변경될 수 있다. 그렇기에, 나는 고정적으로 외부 IP를 연결하여 사용하기로 결정했다.
GCE를 접속할 때는, Google Cloud Console로 접속하는 방법으로 일반적으로 안내한다. 다만, ssh를 활용한 접속이 익숙하다보니 가이드를 참고하여 해당 방식으로 접속할 수 있도록 설정을 진행했다.
ssh-keygen -t rsa -f ~/.ssh/KEY_FILENAME -C USERNAME -b 2048
passphrase는 추후, ssh 연결 시 계속 입력하게 되므로 까먹으면 안 된다.
cat ~/.ssh/KEY_FILENAME.pub
ssh -i ~/.ssh/KEY_FILENAME.pub USER_NAME@IP_ADDRESS
터미널에서 위와 같이 입력하면, passphrase를 입력하라고 뜨는데, 최초 생성 시에 설정한 passphrase를 입력해주면 접속된다.
DB는 퍼블릭에서 접근이 가능하면 안 되기 때문에, Private Subnet에 생성하고 스프링부트 서버가 위치한 인스턴스에서만 접근이 되도록 설정해야 한다. 그러기 위해서는 먼저 VPC 위에서 Private Subnet을 만들고, 해당 Subnet에 Cloud SQL이 위치하도록 설정해야 한다.
금액을 최대한 낮추기 위해서 예상 가격을 맞추어서 선택을 진행했다. Enterprise Plus가 아닌 Enterprise로 진행을 했다.
(만약, Private Subnet으로 하실 분들은 공개 IP 설정을 취소하시고, 비공개 IP만 설정을 누르셔서 진행해주시면 됩니다.)
위와 같은 리스트에서 데이터베이스를 클릭하여 접속하면, 데이터베이스 만들기가 보인다. 데이터베이스 만들기를 클릭해서 생성을 진행하면 된다. 나는 기존과 동일하게 utm8mb4와 기본대조를 선택했다.
(1) Compute Engine 터미널 접속
ssh -i ~/.ssh/KEY_FILENAME.pub USER_NAME@IP_ADDRESS
(2) 아래와 같은 명령어를 통해 해당 포트 열려있는지 확인
ecoh > /dev/tcp/IP_ADDRESS/PORT
(3) 연결되어 있다면 아래 명령어 입력 시 0이 나오며, 연결되어 있지 않다면 1이 나온다.
echo $?
데이터베이스 관리 툴로는 TablePlus를 사용하고 있기에, Table Plus에서 Cloud SQL 연결을 진행해야 한다.
Private Subnet으로 생성하셨다면, Over SSH를 통해 추가 정보 기입
이후 Test가 성공하면, Connect를 진행하면 된다.
(성공 시에는 초록색으로 입력 칸이 변하게 됩니다!)
기본적인 Compute Engine과 Cloud SQL 설정이 완료되었으므로 도커를 서버에 설치하고 띄울 수 있도록 진행한다.
공식문서를 기반으로 진행한다.
# 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으로 안내되어 있는 부분으로 진행했다.
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
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
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하는 스크립트에서 오류 발생하는 것으로 추정)
Docker Hub 레포지토리의 Pull, Push 기록 확인
확인 결과, Docker Image Push는 정상적으로 스크립트가 동작한 시간대에 Push된 것을 확인할 수 있었다. 다만, Pull은 정상적으로 동작하지 않음을 확인했다.
수동으로 Docker Compose 부분 진행해본 결과, 정상적으로 작동함을 확인
오타 발견
scripts가 아니라 script였다.....
위 과정을 GCE 위에서 진행한 이후, 아래의 명령어를 통해서 docker에서 구동되고 있는지를 확인할 수 있다.
sudo docker ps -a
-a 옵션을 통해, 에러가 발생하여 구동되지 않는 컨테이너 또한 확인할 수 있으니, 이를 활용하여 에러가 무엇인지 찾아가면 될 것 같다.
레퍼런스