지난 포스팅에서 ArgoCD를 통해 애플리케이션의 자동 배포를 구축했습니다.
ArgoCD로 자동 배포를 구축하는 과정은 이 링크를 확인해 주세요!
이번 포스팅은 GitHub Actions를 통해 CI 부분을 자동화하는 부분을 중점적으로 다루며, 이전 포스팅과 함께 읽으신다면 최종적으로 GitOps 방식으로 쿠버네티스 클러스터 환경에서 CI/CD 파이프라인을 구축하는 과정을 보실 수 있습니다.
CI/CD 파이프라인 전체 흐름
- 단일 진실의 원천(SSOT)인 GitHub Repository에 코드를 Push 합니다.
- GitHub Actions의 workflow가 트리거 됩니다.
- JDK로 Spring 애플리케이션을 빌드하고, 빌드된 JAR 파일로 Docker 이미지를 만든 다음 Docker Hub에 Push 합니다.
- Docker 이미지 버전이 변경되었음을 알리기 위해 쿠버네티스 매니페스트 파일을 수정합니다.
- 변경 사항을 GitHub Actions Bot이 Commit / Push 합니다.
- ArgoCD가 Git 저장소에 있는 쿠버네티스 매니페스트 파일의 변경을 감지합니다.
- ArgoCD가 Git 저장소와 쿠버네티스 클러스터의 Sync를 맞춥니다.
이제 위의 1번부터 5번까지 GitHub Actions의 workflow를 작성해서 CI 부분을 처리하는 워크로드를 다루겠습니다.
Step 1. GitHub Repository에 Push 했을 때 워크플로우 트리거 시키기
name: CI # 지속적 통합(CI) 워크플로우 이름
on: # 워크플로우를 트리거하는 이벤트를 정의
push: # push 이벤트 발생 시 트리거
branches: [ "main", "dev" ] # 'main' 또는 'dev' 브랜치로 push될 때만 실행
- GitHub Repository에 `main` 브랜치나 `dev` 브랜치에 Push 이벤트가 발생했을 때 트리거를 발생시키는 부분입니다.
Step 2. JDK로 Spring 애플리케이션을 빌드하기
jobs:
build:
name: Build and Push Docker Image # 작업에 대한 설명 이름
runs-on: ubuntu-latest # 작업이 실행될 환경(운영체제)
steps: # 작업에서 실행할 단계 정의
- uses: actions/checkout@v4 # GitHub 저장소 코드를 체크아웃
- name: Setup Java JDK # Java JDK 설정
uses: actions/setup-java@v4.6.0 # GitHub 제공 Java 설정 액션 사용
with:
distribution: 'corretto' # Amazon Corretto JDK 사용
java-version: '17' # Java 17 버전 사용
- name: Grant execute permission for gradlew # gradlew 파일 실행 권한 부여
run: chmod +x gradlew # 실행 가능하도록 권한 변경
- name: Build with Gradle # Gradle을 사용하여 빌드
run: ./gradlew clean build -x test # 테스트를 제외하고 클린 빌드 실행
- `runs-on` 부분에 워크플로우 작업들을 실행할 운영체제를 작성합니다. Public Repository인 경우 GitHub에서 제공하는 인스턴스에서 작성한 운영체제로 실행됩니다. (Private Repository인 경우 제한된 무료 사용 시간을 제공하고, 초과 시 분당 요금이 청구되니 주의하세요!)
- `steps` 원하는 작업들을 작성합니다. job은 병렬적으로 실행되지만 step은 작성한 순서대로 실행됩니다.
- GitHub Actions 마켓플레이스에서 제공하는 `actions/setup-java@v4.6.0` Docker 이미지를 사용해서 JDK를 설치합니다.
- distribution과 java-version은 프로젝트에 알맞게 설정할 수 있으며, 마켓플레이스에서 `name`으로 검색할 수 있습니다. Documentation 부분에서 필요한 옵션을 설정합니다.
- JDK를 설치한 후엔 `chmod +x gradlew`로 실행 권한을 부여한 후 Spring 애플리케이션을 빌드합니다.
Step 3. JAR 파일을 Docker 이미지로 빌드 후 Docker Hub에 Push 하기
jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
# 윗 단계 생략 ..
- name: Get current time # 현재 시간 가져오기
uses: josStorer/get-current-time@v2.1.2 # 현재 시간을 가져오는 액션 사용
id: current_time # 이 단계의 ID를 설정하여 이후 사용 가능
with:
format: YYYY-MM-DDTHH-mm-ss # 시간 형식 지정
utcOffset: "+09:00" # UTC+9 시간대 설정(한국 시간)
- name: Docker Login # Docker 로그인
uses: docker/login-action@v3.3.0 # Docker 로그인 액션 사용
with:
username: ${{ secrets.DOCKER_USERNAME }} # GitHub Secrets에 저장된 Docker 사용자 이름
password: ${{ secrets.DOCKER_PASSWORD }} # GitHub Secrets에 저장된 Docker 비밀번호
- name: Build and push Docker images # Docker 이미지 빌드 및 푸시
uses: docker/build-push-action@v6.10.0 # Docker 빌드 및 푸시 액션 사용
with:
context: . # 현재 디렉터리를 컨텍스트로 사용
file: Dockerfile-dev # Docker 빌드에 사용할 Dockerfile 지정
tags: myRegistry/upup-radio-backend:${{ steps.current_time.outputs.formattedTime }} # 이미지 태그에 현재 시간을 포함
push: true # 빌드된 이미지를 Docker Hub로 푸시
- Gradle로 빌드를 하면 `${프로젝트 루트}/build` 디렉터리에 JAR 파일이 생성됩니다. 이 JAR 파일을 프로젝트에 있는 Dockerfile를 이용해 Docker 이미지로 빌드해야 합니다.
- 먼저 마켓플레이스에 있는 `josStorer/get-current-time@v2.1.2`를 통해 현재 시간을 가져옵니다.
- 이 값은 빌드한 Docker 이미지의 `tag`로 사용될 예정입니다.
- 그리고나서 Docker 이미지로 빌드하기 전 Docker Hub 계정으로 로그인하는 작업을 수행했습니다.
- 아이디와 비밀번호는 프로젝트 Repository의 Secret 설정을 통해 입력을 합니다.
- Secret 설정은 프로젝트 Repository의 `Settings`-왼쪽 사이드 바에서 `Secrets and variables`-`Actions` 부분에서 설정할 수 있습니다.
- Repository secrets 부분에서 `New repository secret` 버튼을 클릭해 아이디와 비밀번호를 시크릿으로 등록합니다.
- 마지막으로 GitHub Actions 마켓플레이스에서 제공하는 `docker/build-push-action@v6.10.0`를 이용해 Docker 이미지를 빌드하고 `tags`에 해당하는 Docker Hub 레지스트리에 이미지를 Push 합니다. 저는 개발 서버 전용 Dockerfile을 이용해 빌드하길 원해 `context` 부분을 추가했습니다.
- 빌드할 때 윗 단계 step에서 가져온 시간 정보를 이미지의 tag로 설정합니다.
- `push` 값은 기본값이 `false`이므로 `true`로 설정합니다.
Step 4. 쿠버네티스 매티페스트 파일 수정하기
jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
# 윗 단계 생략 ..
- name: Update deployment version
run: |
sed -i "s|image: myRegistry/upup-radio-backend:.*|image: myRegistry/upup-radio-backend:${{ steps.current_time.outputs.formattedTime }}|g" ./kubernetes/api.yaml
- 이 `step`은 Docker 이미지의 버전이 변경되었음을 ArgoCD에 알리기 위한 작업입니다.
- linux의`sed`(stream editor)를 이용해 `./kubenetes/api.yaml` 파일의 내용을 치환합니다.
- `s|`와 `|g`의 사이에서 구분자 `|`를 사용해 치환합니다.
- 치환 대상 문자열 (from): `image: myRegistry/upup-radio-backend:.*`부분
- 치환할 문자열 (to): `image: myRegistry/upup-radio-backend:${{ steps.current_time.outputs.formattedTime }}` 부분
Step 5. 변경 사항을 GitHub Actions Bot을 통해 Commit / Push 시키기
jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
# 윗 단계 생략 ..
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Commit and push changes
run: |
git add ./kubernetes/api.yaml
git commit -m ":tada: Update deployment version to ${{ steps.current_time.outputs.formattedTime }}"
git push origin dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- 이제 매니페스트 파일의 변경 사항을 Commit / Push를 해서 ArgoCD에게 알려야 합니다.
- ArgoCD에서 매니페스트 파일의 변경 사항을 감지해 쿠버네트스 클러스터 환경과 Sync 작업을 수행할 것입니다.
- 이 과정에서 Commit / Push 작업을 위해 GitHub Actions Bot을 이용합니다.
- `41898282+github-actions[bot]@users.noreply.github.com` 계정은 GitHub Actions Bot의 계정입니다.
- GitHub Actions Bot을 이용하기 위해 권한을 설정해야 합니다. 프로젝트의 `Settings`-왼쪽 사이드 바의 `Actions`-`General` 메뉴로 이동한 뒤 `Workflow permissions`에서 `Read and write permissions`에 체크한 후 `Save` 버튼을 눌러 저장합니다.
- 권한 설정을 마치면 별도의 Secret을 등록하지 않아도 워크플로우 스크립트에서 `${{ secrets.GITHUB_TOKEN }}`를 사용할 수 있습니다.
최종 워크플로우 파일
name: CI
on:
push:
branches: [ "main", "dev" ]
jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java JDK
uses: actions/setup-java@v4.6.0
with:
distribution: 'corretto'
java-version: '17'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean build -x test
- name: Get current time
uses: josStorer/get-current-time@v2.1.2
id: current_time
with:
format: YYYY-MM-DDTHH-mm-ss
utcOffset: "+09:00"
- name: Docker Login
uses: docker/login-action@v3.3.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker images
uses: docker/build-push-action@v6.10.0
with:
context: .
file: Dockerfile-dev
tags: myRegistry/upup-radio-backend:${{ steps.current_time.outputs.formattedTime }}
push: true
- name: Update deployment version
run: |
sed -i "s|image: myRegistry/upup-radio-backend:.*|image: myRegistry/upup-radio-backend:${{ steps.current_time.outputs.formattedTime }}|g" ./kubernetes/api.yaml
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Commit and push changes
run: |
git add ./kubernetes/api.yaml
git commit -m ":tada: Update deployment version to ${{ steps.current_time.outputs.formattedTime }}"
git push origin dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
동작 확인
이제 `main` 브랜치 또는 `dev` 브랜치에 Push 이벤트가 발생할 때마다 워크플로우가 트리거되는지 확인해 보겠습니다.
프로젝트 Repository에서 `Actions` 탭에서 성공적으로 작동하는 것을 확인할 수 있습니다.
ArgoCD에서도 `41898282+github-actions[bot]@users.noreply.github.com` 이메일을 사용하는 GitHub Actions Bot이 수정한 매니페스트 파일을 통해 CD가 작동한 것을 확인할 수 있습니다.
참고
'DevOps' 카테고리의 다른 글
Argo Rollouts를 이용한 Blue-Green 배포 전략 적용하기 (0) | 2025.01.07 |
---|---|
GitOps에서 SealedSecret으로 안전하게 Secret 관리하기 (0) | 2025.01.04 |
GitOps와 ArgoCD (1) | 2025.01.04 |