Programming/CICD

GitHub Actions + Docker CI/CD 구축하기

Jan92 2025. 1. 5. 01:29
반응형

github actions + docker ci/cd 구축하기

github actions + docker cicd

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의 위치

* 추가적으로 Dockerfile은 프로젝트 디렉터리 최상단에 존재합니다.

 

 


2. workflow 파일 생성 및 수정

default worflow 'Java with Gradle'

프로젝트가 저장된 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'라는 디렉터리 이름처럼 필요한 작업에 따라 여러 개의 워크플로가 존재할 수 있습니다.

 

 

생성된 workflow 파일을 기반으로 필요한 내용을 수정

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이라는 변수를 실행 시 함께 넘기도록 정의하였습니다.

 

https://docs.github.com/ko/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows

(예시의 두 가지 트리거 외에도 다양한 트리거가 있으며, 더 자세한 내용은 위 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 트리거 실행 방법

'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

'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 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 동작 확인

github actions + docker ci/cd 구축 결과

workflow 파일 수정 후, 해당 워크플로를 수동으로 실행하였으며 build 및 deploy 작업이 정상적으로 완료되어 애플리케이션이 배포된 것을 확인할 수 있었습니다.

build 및 deploy 부분을 클릭하면 해당 작업의 상세 과정을 확인할 수 있으며, 작업 내부적으로는 각각의 step에 대한 상세 내용도 확인할 수 있습니다.

 

 

여기까지 github actions + docker를 통한 ci/cd 구축 과정을 정리하였으며, 내용 중 잘못된 부분이나 추가로 궁금하신 부분은 댓글 주시면 확인하여 답변드리도록 하겠습니다. 감사합니다.

 

 

 

< 관련 참고 자료 >

https://velog.io/@leeeeeyeon/Github-Actions%EA%B3%BC-Docker%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-CICD-%EA%B5%AC%EC%B6%95

반응형

'Programming > CICD' 카테고리의 다른 글

Spring Boot 프로젝트 GitHub Actions CI/CD 구축하기  (1) 2024.12.29