프로젝트를 하면서 CI/CD를 먼저 구축하고 프로젝트를 진행하기로 했다.
(이 기능을 구현하면서 여러 에러들이 발생했었는데, 해결했던 과정은 기회가 되면 별도로 기록할 예정이다.)
- CI(Continuous Integration): 자동화된 빌드 및 테스트 수행 후, 코드 변경 사항을 중앙 레포지토리에 정기적으로 병합하는 개발방식
- CD(Continuous Deployment): CI 에서 빌드, 테스트가 완료된 코드를 자동으로 배포하는 방식
Github Actions, docker 선택 이유
CI/CD 도구로는 대표적인 Jenkins, Github Actions 외에도 다양한 도구가 있었는데, 그 중 Github Actions를 선택했다.
Jenkins와 달리 별도 서버 없이 사용 가능했다. 또한 public repositoy는 무료로 사용 가능하고, Jenkins에 비해 초기 도입이 쉽다는 점이 장점이라고 생각했다.
또한 docker를 사용하기로 했는데, docker를 통해 개발과 배포 환경을 동일하게 하여 오류를 최소화 할 수 있다는 점에서 장점이 있다고 판단했다.
CI/CD Flow
- 로컬에서 코드를 작성하고 커밋, 특정 브랜치에 push 한다.
- Github Actions에서 변경을 감지하고 스크립트를 실행한다.
- 스크립트에 의해 아래와 같은 기능을 수행한다.
JDK설정 - build - test - Docker image 생성 - Docker hub로 push - EC2에서 Docker image pull - docker image 실행
그런데 우테코 과정 중 여기에서 한가지 문제가 발생했었다.
github actions를 통해 EC2에 접속하려고 했는데, 우테코에서는 보안상의 의유로 22번 포트의 접속 IP를 제한해두었다.
따라서 Github Actions에서 EC2에 바로 접속하는 것은 불가능했고, EC2에 배포하는 과정을 Self-Hosted Runner를 사용해 분리하였다. 아래 그림과 같이, EC2에서 docker image를 받아오고 실행하는 부분은 Self-Hosted Runner를 통해 동작한다.
Docker, Github Actions 적용 코드
먼저, Docker 설정을 위해 루트 경로에 Dockerfile을 정의해주었다.
코드에 대한 세부 설명은 주석으로 달아두었다.
# Docker 이미지의 기반이 되는 이미지를 지정한다.
# arm64 기반의 linux 운영체제를 사용하며, java 21버전을 설정해주었다.
FROM --platform=linux/arm64 openjdk:21
# Dockerfile 내에서 사용할 수 있는 변수 JAR_FILE을 정의한다.
# backend/build/libs/*.jar는 파일 경로를 나타낸다.
ARG JAR_FILE=backend/build/libs/*.jar
# jar 파일을 Docker 이미지 내부의 app.jar로 복사한다.
# ${JAR_FILE}는 앞서 정의한 변수를 사용하는 부분이다.
COPY ${JAR_FILE} app.jar
# Docker 컨테이너가 시작될 때 실행될 명령을 지정한다.
# Docker 컨테이너가 시작되면 /app.jar 파일을 실행한다.
ENTRYPOINT ["java", "-jar", "/app.jar"]
Github Actions를 사용하기 위해서는 .github/workflows 경로에 yml파일을 정의해주면 된다.
이 때, 중요한 값들을 스크립트에 작성하고 Github에 올리게 되면 보안상 문제가 생길 수 있다.
따라서 중요한 값들은 해당 레포지토리의 Settings - Security - Secrets and variables - Actions에서 Secret으로 관리한다.
한번 등록된 값은 다시 볼 수 없으며, 수정이나 삭제는 가능하다.
우리 팀에서는 아래와 같은 코드를 사용하였다. (현재 MySQL 등 추가 설정이 필요한 부분이 있어 수정 예정이다.)
name: Spring Boot CI/CD # 파이프라인의 이름
on:
push:
branches: [ "be/develop" ] # be/develop 브랜치에 push가 발생하면 실행된다.
jobs:
build-and-test: # Self-hosted Runner에서 수행할 내용과 분리하였다. build-test 부분을 먼저 수행한다.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21 # JDK 21을 설치한다.
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'oracle'
- name: Build with Gradle # 프로젝트를 build 한다.
run: ./gradlew build
working-directory: ./backend
- name: Run tests # 테스트를 수행한다.
run: ./gradlew test
working-directory: ./backend
- name: Build Docker image # 빌드, 테스트가 완료되면 docker image를 빌드한다.
run: docker build --platform linux/arm64 -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_IMAGE_NAME }}:${{ github.sha }} .
- name: Login to Docker Hub # Docker hub에 로그인한다.
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push Docker image # 생성된 docker image를 docker hub에 push한다.
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_IMAGE_NAME }}:${{ github.sha }}
deploy:
needs: build-and-test # build-and-test 작업이 성공적으로 수행되어야 실행된다.
runs-on: self-hosted # 지정된 Runner에서 동작한다. 등록한 self-hosted runner의 이름을 작성한다.
steps:
- name: Pull Docker image from DockerHub # docker image를 pull 해온다.
run: docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_IMAGE_NAME }}:${{ github.sha }}
- name: Stop existing Docker container # 실행중인 docker container가 있다면 중지한다.
run: docker stop ${{ secrets.DOCKERHUB_CONTAINER_NAME }} || true
- name: Remove existing Docker container # 기존의 docker container가 있다면 삭제한다.
run: docker rm ${{ secrets.DOCKERHUB_CONTAINER_NAME }} || true
- name: Run new Docker container # 새로운 docker container를 실행한다.
run: docker run -d --name ${{ secrets.DOCKERHUB_CONTAINER_NAME }} -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_IMAGE_NAME }}:${{ github.sha }}
Self-Hosted Runner 설정
Settings - Actions - Runners에서 runner를 등록한다.
원하는 image, architecture를 설정하고 생성된 명령어를 등록하려는 등록하려는 서버에서 실행하면 된다.
우리의 경우 EC2를 사용하므로, ssh 접속 후 해당 명령어를 실행했다.
실행결과
build-and-test에 성공하면 deploy가 실행된다.
설정한 workflow에 따라 수행 과정, 결과를 확인할 수 있다.
아직 Docker Compose를 작성하지 않았고, mysql 등 추가로 설정해야할 부분이 남아 있어 완전하지는 않은 상태이다. 그러나 지정한 브랜치에 push했을 때 CI/CD가 동작하고 서버가 정상적으로 돌아가는 것을 확인할 수 있었다.
참고자료
Github Actions과 Docker을 활용한 CI/CD 구축
Docker를 활용한 Spring Boot 프로젝트 EC2 배포
Github Action, Docker compose를 활용한 배포 자동화 CI/CD + (Spring boot, MySQL)
'우아한테크코스 > 레벨 3 - 프로젝트' 카테고리의 다른 글
[JPA] QueryDSL로 리스트 여러개 있는 데이터 가져오기 (0) | 2024.08.18 |
---|---|
[트러블슈팅] TLS/SSL Handshake Failed (안드로이드-서버) (0) | 2024.08.11 |
[AWS] Spring Boot, S3를 활용한 이미지 업로드 (0) | 2024.07.28 |
[Git] Git branch 전략 (부제: branch 병합 방법 merge, rebase, squash) (1) | 2024.07.24 |