이전에 프로젝트를 배포하기 위해서는 서버에 접속하여 git pull을 하여 최신화된 코드를 배포중인 어플리케이션을 중단하고 재배포하였다. 그러나 github actions와 docker를 사용한다면 이러한 번거로움이 없이 배포를 자동화할 수 있어 사용하게 되었다.
github actions는 ci/cd 플랫폼으로 자동으로 어떤 이벤트가 발생하면 특정 작업이 일어나거나 주기적으로 어떤 작업을 반복해서 실행시킬 수 있는 서비스이다. 나는 코드를 수정하고 github에 푸시하면 자동으로 docker hub에 이미지를 업데이트고 서버에서는 자동으로 배포할 수 있도록 설정하였다.
이 github actions를 사용하기 위해서 리모트 레포지토리에서 새로운 workflow를 만들었다. 여러 CI CD actions가 있는데 나는 spirng project를 사용했기 때문에 Java with Gradle actions를 사용하였다.
이를 선택하면 yml 파일이 자동으로 생성되는데 나는 이 자동으로 만들어진 파일을 수정하여 작성하였다.
name: CI/CD
#event trigger : main 브랜치에 push 이벤트 발생시 jobs가 실행된다.
on:
push:
branches: [ "main" ]
#권한 설정
permissions:
contents: read
#jobs 정의
jobs:
#build job - jdk 설정, gradle 설정, docker 배포
build:
#ubuntu 최신 버전
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# JDK 17 설정
- name: Set up JDK 17
uses: actions/setup-java@v3
# build를 위해서 jdk를 설치해줘야 하기 때문에 첫 단계로 설정하다.
with:
java-version: '17'
distribution: 'oracle'
# Gradle 설정 - gradle이 제공하는 action으로 build하는데 도움을 준다.
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
# gradlew 파일 권한 지정 - 기본적으로 gradlew에 대한 권한 설정이 되어있지 않기 때문에 권한 설정을 해준다.
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# gradle bootBuildImage를 이용해 이미지를 만들고 원격 저장소에 Push
# env에 민감 정보(환경변수)를 추가한다.
- name: Build Image with Gradle and Push to DockerHub
run: ./gradlew bootBuildImage
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
# ssh로 접속해 재배포
# script에 명령어 지정하여 자동으로 실행되게 한다.
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.REMOTE_IP }}
username: ${{ secrets.REMOTE_SSH_ID }}
key: ${{ secrets.REMOTE_SSH_KEY }}
port: ${{ secrets.REMOTE_SSH_PORT }}
script: |
cd docker
docker compose down
docker compose pull
docker compose up -d
이와 action을 yml파일에 정의해두고 이 workflow에 맞게 action들이 작동한다. ${{}}는 환경변수로 remote repository - setting에 가면 security에 Secrets and variables에 actions에 설정해주면 된다.
이번에 도커라는 플랫폼을 처음 사용해보았는데 아직 docker에 대해서는 잘 알지 못한다. 그러나 이 애플리케이션을 신속하고 편리하게 구축하고 테스트 및 배포할 수 있도록 해주는 플랫폼이라는 것이라는 것은 알게되었다. 왜냐하면 전에는 내가 서버 인스턴스를 구성하고 접속해서 db 설치하고, 권한 설정하고, 이것 저것 설치하고 설정해야만 했다. 그러나 docker를 이용하면 이럴 필요가 없었다. docker 허브에 내가 이미지를 저장한다면 이를 서버에서 가져와 컨테이너라는 곳에 이미지를 설정하고 실행한다면 따로 설치할 필요없이 실행이 가능하다.
아직 컨테이너, 이미지, 허브에 대해 개념적으로 많이 부족하지만 여러 블로그와 게시물들을 보면서 이러한 것들이 있구나 라는 것을 알게 되었으니 앞으로 docker에 대해 공부한다면 보다 더 복잡하고 어려운 배포도 손쉽게 할 수 있을 것 같다.
나는 AWS 인스턴스를 사용하여 배포를 진행할 예정이었기 때문에 인스턴스 생성 후 접속한 후에 git, docker, docker-compose를 설치하였다. 그 후 docker-hub에 로그인한 후 이미지를 pull해와 컨테이너에 들어갈 이미지를 설정하고 실행하였다.
내가 이해하기로는 docker-compose는 docker를 이용하기 위한 툴이라고 이해하였다. docker를 실행하기 위해서는 다양한 명령어와 설정 명령을 알아야 한다. 그러나 docker-compose를 이용하면 간단한 명령어를 통해 docker만을 이용한다면 여러 복잡한 명령어를 간단히 실행할 수 있다.
또한 중요한 차이점이 있는데 docker는 dockerfile을 통해 이미지를 생성하고 빌드한다. docker-composes는 앱 실행 중에 yaml 파일을 통해 여러 이미지를 설정하고 volum, port, enviroment 등 다양한 설정을 할 수 있고 컨테이너에 저장한다. 그 후 컨테이너를 쉽게 실행(docker-compose up)할 수 있다.
version: '3'
services:
nginx:
image: nginx:1.15-alpine
restart: unless-stopped
volumes:
- ./data/nginx:/etc/nginx/conf.d
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
ports:
- "80:80"
- "443:443"
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot
restart: unless-stopped
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
app:
image: app_name
ports:
- 465:465
- 8080:8080
mem_limit: "1g"
environment:
app_enviroment: ${app_enviroment}
TZ: "Asia/Seoul"
내가 작성한 yaml파일이다. services 밑에 어플리케이션에 필요한 이미지를 등록하는 형식이다. 나는 간단히 nginx, cerbot, app 이렇게 등록해놓았다.
이렇게 yaml 파일을 설정한 뒤 docker-compose up을 실행하면 컨테이너가 실행된다.
참조:
https://devzzi.tistory.com/m/76
https://aws.amazon.com/ko/docker/
https://www.daleseo.com/github-actions-basics/