본문으로 건너뛰기

카페인 팀의 CI/CD

· 약 8분
제이

안녕하세요. 카페인 팀의 제이입니다. 저희 팀에서 CI/CD는 어떻게 진행되는지 작성하겠습니다.

CI (지속적 통합)

ci

카페인 팀에서는 지속적 통합 즉 CI를 진행하기 위해서 위에 사진과 같이 Github Actions를 사용합니다.

main, develop 브랜치에 Push, Pull Request 요청이 들어간다면 이벤트가 발생하고, Github Actions를 통해 저희가 작성해둔 스크립트가 실행 됩니다.

이 스크립트에 여러가지를 등록할 순 있지만, 저희는 자동으로 테스트를 진행하도록 하였습니다. 자동으로 테스트를 돌리면서 테스트가 통과를 해야지만 Merge를 진행할 수 있습니다.

이를 통해 개발자의 실수를 줄일 수 있고 안정적으로 지속적 통합을 이룰 수 있게 됩니다.


CD (지속적 배포)

cd

저희의 지속적 배포 아키텍처입니다.

순서를 요약하자면 다음과 같습니다.
  1. Release 브랜치에 Push를 한다.
  2. Github Actions를 통해 Docker Hub에 레포지토리의 소스코드를 Docker Image로 빌드해서 Push 한다.
  3. 인프라 서버에서 Self Hosted Runner가 작동한다.
  4. 인프라 서버에서 배포 서버로 들어간다.
  5. 배포 서버 안에서 Docker Hub에 미리 업로드한 Docker Image를 Pull 해온다.
  6. 배포 서버 안에서 Docker Image를 컨테이너에 띄운다.

배포 자동화 툴 선택하기

먼저 배포 자동화 과정을 구축하기 위해서 여러가지 툴이 있습니다.

Travis, Jenkins, Github Actions 등등 여러가지가 있는데요. 저희 팀은 Github Actions를 선택했습니다.

이를 선택한 여러가지 이유가 있었지만 저희 팀 누누를 제외하고 CI/CD 경험이 부족해서 비교적 쉽고 설치 및 큰 세팅이 없는 점이 저희한테는 매력적으로 다가왔습니다.

또한 Docker를 사용하는데, 이유는 다음과 같습니다.

  1. JDK 혹은 Node 버전을 관리할 수 있다.
  2. Docker Image를 빌드한 후 배포하기 때문에 서버 환경 차이로 발생하는 문제를 최소화할 수 있다.
  3. 배포 서버에서 Docker만 설치하고 Image를 받고 실행시키면 돼서 빠르고 쉽게 배포 환경을 구축할 수 있다.

과정

본격적으로 저희의 배포 자동화를 구축하는 과정을 설명하겠습니다.


1. Github Actions에 Runners 등록

runner 먼저 Self Hosted Runner를 이용하기 때문에 저희는 위에 사진과 같이 Runners를 등록을 해줬습니다.

이를 등록을 할 때 제공해주는 설정 코드가 나오는데요. 이 코드들을 infra 서버에 모두 입력을 해주면서 설정을 해주시면 됩니다.


2. Github workflow 만들기다음으로는 저희가 수행하고자 하는 Task를 등록해주기 위해서 yml 파일을 만들어줍니다.

yml 파일의 경로는 ./github/workflows/ 안에 만들어주면 됩니다.

name: deploy

# release/backend push 할 때
on:
push:
branches:
- release/backend

jobs:
# Docker
docker-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
# Docker Hub 로그인
- name: Log in to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- uses: actions/checkout@v3

- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'

- 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-

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build with Gradle
run: ./gradlew bootjar

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: Docker Hub 사용자명/이미지 이름

# Build 및 Docker image를 Docker Hub에 push
- name: Build and push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: .
file: ./backend/Dockerfile
push: true
platforms: linux/arm64
tags: woowacarffeine/backend:latest
labels: ${{ steps.meta.outputs.labels }}

deploy:
runs-on: self-hosted
if: ${{ needs.docker-build.result == 'success' }}
needs: [ docker-build ]
steps:
# EC2 배포 서버로 접속
- name: Join EC2 dev server
uses: appleboy/ssh-action@master
env:
JASYPT_KEY: ${{ secrets.JASYPT_KEY }}
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_KEY }}
port: ${{ secrets.SERVER_PORT }}
envs: JASYPT_KEY

# 1. 도커 이미지 받기
# 2. 기존에 켜진 백엔드 서버(도커 이미지) stop
# 3. 최신 백엔드 서버 run
# 4. 사용하지 않는 이미지와 컨테이너 삭제
script: |
sudo docker pull woowacarffeine/backend:latest
sudo docker stop backend || true
sudo docker run -d --rm -p 8080:8080 \
-e "ENCRYPT_KEY=${{secrets.JASYPT_KEY}}" \
--name backend \
Docker Hub 사용자명/이미지 이름:latest

sudo docker image prune -f

저희 팀은 위와 같이 backend-deploy.yml 파일을 만들어주었습니다.

위에 yml에서 저희는 키를 숨겼는데요.

img 위에 사진과 같이 설정을 해주시면 됩니다.

그리고 이를 yml에서 사용하기 위해선 secrets.Key이름으로 사용해주시면 됩니다.


이제 마지막으로 Dockerfile을 만들어줍니다.

저희는 /backend/ 경로에 만들어주었습니다.

FROM amazoncorretto:17-alpine-jdk
ARG JAR_FILE=./backend/build/libs/carffeine-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-jar","/app.jar"]

저희는 위처럼 절대 경로를 기준으로 JAR_FILE 위치를 지정하고, profiles는 dev로 설정해서 만들어주었습니다.


3. 배포하기

트리거를 작동시켜서 저희가 yml 파일에서 지정해준 것들이 잘 작동하는지 확인합니다.

jobSuccess 위에 사진처럼 모든 Job이 성공적으로 통과하는 것을 보실 수 있습니다.

dockerPs 이렇게 인프라 서버에서 배포 서버로 들어가서 성공적으로 서버를 도커로 띄운 것을 보실 수 있습니다.

EC2 배포 서버에서 docker ps를 입력했을 때에도 잘 실행이 되네요!


CD 배포 과정 요약

지속적 배포 과정을 요약 하자면 다음과 같습니다.

  1. Self Hosted Runner를 EC2 인프라 서버에 등록해준다.
  2. yml 파일과 Dockerfile을 만들어준다.
  3. 트리거를 작동시켜서 Github Actions의 태스크가 모두 잘 되는지 확인한다.
  4. 잘 됐다면 EC2 배포 서버에 Docker image가 성공적으로 띄워진다.