github actions + docker ci/cd 구축하기
GitHub Actions를 통해 CI/CD를 구축한 지난 포스팅에 이어 이번 포스팅에서는 GitHub Actions + Docker를 사용한 CI/CD를 구축해 보았습니다.
Docker를 사용한 CI/CD의 경우 애플리케이션과 그 의존성을 컨테이너화 하여 실행하기 때문에 환경의 일관성을 유지할 수 있다는 장점과, 서버가 여러 개로 확장되었을 때 생성된 Docker Image를 통해 동일한 이미지를 사용할 수 있다는 이식성 측면에서의 장점 등, 여러 가지 장점을 가지고 있으며 때문에 최근에는 CI/CD 파이프라인에 도커를 함께 사용하는 방식이 많이 도입되고 있습니다.
전체적인 CI/CD 구축 과정은 이전 포스팅의 내용과 비슷하며, Docker를 사용하는 부분을 위주로 조금 더 자세한 설명을 하도록 하겠습니다.
(CI/CD가 처음이시라면 아래 포스팅을 통해 전체적인 구축 과정을 참고해 주시면 해당 포스팅의 내용을 이해하시기 더 좋을 것 같습니다.)
2024.12.29 - [Programming/CICD] - Spring Boot 프로젝트 GitHub Actions CI/CD 구축하기
1. Dockerfile
# java 애플리케이션 실행을 위한 jdk17 버전의 베이스 이미지 구성
FROM meddream/jdk17:latest
# ./build/libs/ 디렉터리의 모든 *SNAPSHOT.jar 파일을 Docker 이미지 내의 cicdtest.jar 이름으로 복사
COPY ./build/libs/*SNAPSHOT.jar cicdtest.jar
# 컨테이너가 실행될 때 기본적으로 실행될 명령을 정의 java -jar cicdtest.jar
ENTRYPOINT ["java", "-jar", "cicdtest.jar"]
# ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "cicdtest.jar"]
# 다음과 같이 실행 옵션을 추가로 줄 수도 있습니다.
먼저 Dockerfile입니다. Dockerfile은 도커 이미지를 빌드하기 위한 명령어가 있는 텍스트 파일로 Docker는 해당 파일의 명령어를 순차적으로 실행합니다.
Dockerfile은 FROM 명령어로 실행되어야 하며, FROM 명령어는 빌드하는 기본 이미지를 지정하는 명령어입니다.
위 예시에서는 java 애플리케이션을 실행하기 위한 jdk17 버전의 베이스 이미지를 구성하였습니다.
COPY 명령어는 'COPY <src> <dest>' 형식을 가지며, <src> 경로의 파일을 복사하여 <dest> 경로에 추가하는 명령어입니다.
여기서 <src>는 호스트 소스 경로이고 <dest>는 도커 컨테이너 내부의 파일 시스템 경로를 뜻합니다.
위 예시에서는 ./build/libs/ 디렉터리에 생성된 *SNAPSHOT.jar 파일을 도커 컨테이너에 cicdtest.jar라는 파일로 복사한다는 의미입니다.
(*SNAPSHOT.jar에서 *는 여러 글자를 뜻하는 와일드카드입니다.)
ENTRYPOINT 명령어는 컨테이너 시작 시 반드시 실행할 명령어를 지정합니다.
CMD와 유사하지만 ENTRYPOINT는 항상 실행되며, CMD는 컨테이너가 실행될 때 명시적으로 다른 명령이 제공되지 않으면 실행됩니다.
때문에 컨테이너의 주요 실행 프로세스를 정의하는데 ENTRYPOINT가 주로 사용됩니다.
(CMD는 기본 명령을 설정하는 것으로 docker run 명령을 통해 덮어쓸 수 있습니다.)
* 추가적으로 Dockerfile은 프로젝트 디렉터리 최상단에 존재합니다.
2. workflow 파일 생성 및 수정
프로젝트가 저장된 git repository 상단의 Action 탭에서는 workflow를 생성할 수 있는데요.
이전 포스팅과 마찬가지로 ci/cd 구축 대상 프로젝트가 spring boot 프로젝트이기 때문에 'Java with Gradle'을 통해 기본 workflow를 생성합니다.
(Java with Gradle은 github actions에서 java + gradle 프로젝트에 제공되는 기본 workflow 양식입니다.)
workflow는 프로세스를 구성하는 가장 큰 단위이며, 내부적으로 하나 이상의 작업(jobs)을 실행하게 되는데요.
workflow 파일은 '(해당 git repository)/.github/workflows' 디렉터리에 위치하며, YAML(.yml) 형식으로 정의됩니다.
'workflows'라는 디렉터리 이름처럼 필요한 작업에 따라 여러 개의 워크플로가 존재할 수 있습니다.
git repository에서 해당 파일의 내용을 직접 수정하는 경우, 우측 상단의 'Commit changes ...' 버튼을 통해 수정된 내용을 반영할 수 있습니다.
3. 수정된 workflow 파일 소스 코드 및 세부 내용
name: Java CI with Gradle
# 0. workflow 실행 트리거
on:
pull_request:
branches: [ "main" ]
workflow_dispatch:
inputs:
testVariable:
description: 'test variable'
required: true
default: 'defaultValue'
jobs:
# 1. 빌드
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# 1-1. repository checkout
- uses: actions/checkout@v4
# 1-2. jdk 환경 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# 1-3. '*.properties' 파일 세팅
- name: application.properties 파일 설정
run: |
mkdir -p src/main/resources
mkdir -p src/test/resources
echo "${{ secrets.APPLICATION_PROPERTIES }}" > ./src/main/resources/application.properties
echo "${{ secrets.TEST_APPLICATION_PROPERTIES }}" > ./src/test/resources/application.properties
# 1-4. gradle 환경 설치
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
# 1-5. 빌드
- name: Build with Gradle Wrapper
run: ./gradlew clean build
# 1-6. 도커 이미지 빌드 및 이미지 게시
- name: build docker image & push image to docker hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/cicdtest .
docker push ${{ secrets.DOCKER_USERNAME }}/cicdtest
# 2. 배포
deploy:
needs: build
runs-on: ubuntu-latest
steps:
# 2-1. EC2 서버 접속 및 도커 작업 처리
- name: SSH로 EC2에 접속하여 도커 작업 처리하기
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script_stop: true
script: |
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker pull ${{ secrets.DOCKER_USERNAME }}/cicdtest
docker run -d -p 8080:8080 --name cicdtestcontainer ${{ secrets.DOCKER_USERNAME }}/cicdtest
docker image prune -f
다음 workflow 파일에는 docker를 사용한 ci/cd 파이프라인 구축의 기본 내용이 담겨있는데요.
적용하시는 프로젝트 및 서버 환경에 따라 workflow 파일을 구성하는 세부 내용이 달라질 수 있기 때문에 필요한 부분은 적절하게 수정하여 적용해 주시면 될 것 같습니다.
# 0. workflow 실행 트리거
on:
pull_request:
branches: [ "main" ]
workflow_dispatch:
inputs:
testVariable:
description: 'test variable'
required: true
default: 'defaultValue'
먼저 해당 workflow가 실행되는 트리거 이벤트가 정의됩니다.
위 내용에서는 두 가지 트리거가 설정되었으며, 첫 번째 트리거는 'pull_request'로 main branch에 pull_request가 발생했을 때 동작합니다.
두 번째 트리거는 'workflow_dispatch'이며, 해당 트리거는 workflow를 수동으로 실행할 수 있는 방식입니다.
'inputs' 부분을 통해 workflow 실행 시 전달할 파라미터를 지정할 수 있으며, 위 예시에서는 testVariable이라는 변수를 실행 시 함께 넘기도록 정의하였습니다.
(예시의 두 가지 트리거 외에도 다양한 트리거가 있으며, 더 자세한 내용은 위 event trigger에 대한 공식 문서를 참고 부탁드립니다.)
steps:
# workflow_dispatch test
- name: workflow_dispatch test(Echo variables)
run: |
echo "env: ${{ github.event.inputs.testVariable }}"
workflow_dispatch를 통한 수동 실행에서 넘겨지는 파라미터는 다음과 같이 'github.event.inputs.설정한변수명' 형식의 키워드를 통해 사용할 수 있습니다.
'workflow_dispatch' 수동 실행은 'repository' -> 'Actions' -> '좌측의 해당 workflow' -> 'Run workflow' 버튼을 통해 실행할 수 있으며, 이때 inputs로 지정된 파라미터 값을 입력할 수 있습니다.
jobs:
# 1. 빌드
build:
runs-on: ubuntu-latest
permissions:
contents: read
해당 워크플로는 크게 'build' 작업과 'deploy' 작업으로 나눠져 있으며, 먼저 'build' 작업에 대한 환경 정의입니다.
'runs-on'은 작업을 실행시키는 인스턴스의 os와 버전을 정의하는 부분으로, 각 작업은 독립된 runner(github actions에서 제공되는 가상 머신)에서 실행됩니다.
'permission' 부분에 정의된 내용은 git repository에 대한 읽기만 가능함을 의미합니다.
steps:
# 1-1. repository checkout
- uses: actions/checkout@v4
# 1-2. jdk 환경 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
github actions에서는 각 step에서 자주 실행되는 복잡한 명령들을 action으로 만들어 사용자들이 사용하기 쉽게 했는데요.
runner에서는 'checkout action'을 통해 repository의 코드를 체크아웃하고 'setup-java action'을 통해 애플리케이션 실행에 필요한 jdk 환경을 설치합니다.
# 1-3. '*.properties' 파일 세팅
- name: application.properties 파일 설정
run: |
mkdir -p src/main/resources
mkdir -p src/test/resources
echo "${{ secrets.APPLICATION_PROPERTIES }}" > ./src/main/resources/application.properties
echo "${{ secrets.TEST_APPLICATION_PROPERTIES }}" > ./src/test/resources/application.properties
spring boot 프로젝트의 경우 '.properties' 파일에 데이터베이스 접속 정보 등, 보안적으로 민감할 수 있는 정보들이 포함되어 있는데요.
때문에 properties 파일들에 대한 gitignore 처리를 하는데, 이렇게 되면 git repository에 properties 파일이 없기 때문에 프로젝트 빌드 과정에서 문제가 발생하게 됩니다.
이런 경우처럼 github actions에서는 외부에 노출되면 안 되는 보안적으로 민감한 데이터를 아래 'Repository secrets'을 통해 관리하도록 하였는데요.
'Repository secrets'는 git repository 상단의 'Settings' -> 'Secrets and variables' -> 'Actions'에서 확인할 수 있으며, 'New repository secret' 버튼을 통해 신규 secret 변수를 등록할 수 있습니다.
이미지에서 보시는 것처럼 properties 파일 외에도 ssh 접속에 필요한 ip, private key, docker password 등의 정보들을 해당 기능을 통해 관리하게 됩니다.
* secret 변수의 사용은 위 코드에서 확인할 수 있는 것처럼 'secrets.변수명' 형식의 키워드를 통해 사용할 수 있습니다.
# 1-4. gradle 환경 설치
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
# 1-5. 빌드
- name: Build with Gradle Wrapper
run: ./gradlew clean build
runner에서 gradle 환경 설치 및 build 작업을 실행합니다.
gradle build 과정에서 테스트를 제외하고 싶은 경우 './gradlew clean build -x test'와 같이 -x test 옵션을 추가하여 빌드 과정에서 테스트를 제외할 수 있습니다.
# 1-6. 도커 이미지 빌드 및 이미지 게시
- name: build docker image & push image to docker hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/cicdtest .
docker push ${{ secrets.DOCKER_USERNAME }}/cicdtest
docker image를 빌드하고 docker hub에 push 하는 작업을 수행합니다.
작업 실행 과정은 'docker login' -> 'image build' -> 'push' 순서로 진행되며, 도커 비밀번호 등의 보안 정보는 secrets 변수를 통해 사용됩니다.
* docker hub repository에 docker image를 push 하기 위해서는 해당 repository가 미리 생성되어 있어야 합니다.
추가적으로 이 과정에서 들었던 의문점은 job이 실행되는 runner에 docker 환경을 설치하지 않았는데 docker가 어떻게 동작하지? 였는데요.
위 코드에서 해당 runner는 'runs-on' 설정을 통해 'ubuntu-latest' 환경에서 동작되도록 하였는데, github actions의 ubuntu-latest 환경에서는 기본적으로 docker가 설치되어 있기 때문에 docker에 대한 환경 설치를 따로 하지 않아도 docker 관련 명령어를 바로 사용할 수 있는 것이라고 합니다.
# 2. 배포
deploy:
needs: build
runs-on: ubuntu-latest
steps:
# 2-1. EC2 서버 접속 및 도커 작업 처리
- name: SSH로 EC2에 접속하여 도커 작업 처리하기
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script_stop: true
script: |
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker pull ${{ secrets.DOCKER_USERNAME }}/cicdtest
docker run -d -p 8080:8080 --name cicdtestcontainer ${{ secrets.DOCKER_USERNAME }}/cicdtest
docker image prune -f
'deploy' 작업은 'needs' 부분의 정의를 통해 'build' 작업이 완료된 후 동작하도록 처리하였는데요.
deploy 작업의 과정은 ssh를 통해 ec2 서버에 접속하여, 실행 중인 컨테이너를 중지 및 삭제한 뒤, docker pull 명령어로 docker hub repository에 올라가 있는 도커 이미지를 다운로드하고, 해당 이미지를 실행하는 순서로 진행됩니다.
(docker image prune -f는 사용하지 않는 docker image를 정리하는 명령어입니다.)
* deploy 작업의 script 경우 애플리케이션이 배포되는 ec2 환경에서 실행되기 때문에 docker가 설치되어 있어야 docker 관련 명령어가 정상적으로 동작하게 됩니다.
4. CI/CD 동작 확인
workflow 파일 수정 후, 해당 워크플로를 수동으로 실행하였으며 build 및 deploy 작업이 정상적으로 완료되어 애플리케이션이 배포된 것을 확인할 수 있었습니다.
build 및 deploy 부분을 클릭하면 해당 작업의 상세 과정을 확인할 수 있으며, 작업 내부적으로는 각각의 step에 대한 상세 내용도 확인할 수 있습니다.
여기까지 github actions + docker를 통한 ci/cd 구축 과정을 정리하였으며, 내용 중 잘못된 부분이나 추가로 궁금하신 부분은 댓글 주시면 확인하여 답변드리도록 하겠습니다. 감사합니다.
< 관련 참고 자료 >
'Programming > CICD' 카테고리의 다른 글
Spring Boot 프로젝트 GitHub Actions CI/CD 구축하기 (1) | 2024.12.29 |
---|