많은 회사에서 사용중이고 관련 레퍼런스가 많은
Jenkins
와Github Actions
중에 고민했으나Github Actions
를 사용하기로 결정
AWS EC2
의 t2.micro
인스턴스를 활용 → GitHub Actions
는 추가적인 인프라 설정 없이 GitHub의 클라우드 인프라에서 실행이 가능 (리소스가 제한적인 환경에 보다 적합)Docker
사용한 이유학습 목적
컨테이너 기술
에 대한 실질적인 경험 쌓기
Docker
를 통한 애플리케이션의 배포 및 관리 방법에 익숙해지기
Docker
의 이식성
vue
코드가 수정될 경우
npm run build
를 실행하고, 정상적으로 build
가 되는지 검사spring
코드가 수정될 경우
gradlew test
를 실행하고, 정상적으로 test
가 통과하는지 검사
gradlew build
를 실행하고, 정상적으로 build
가 되는지 검사
jacoco
를 이용하여, 수정한 파일의 테스트 커버리지를 측정 후 커버지리를 PR Comment에 첨부
※ 테스트 커버리지를 PR Comment에 첨부하는 기능은 다른 프로젝트를 둘러보다가 발견한 기능인데 너무 좋아보여서, 저도 바로 도입을 해봤습니다.
제 repository
는 vue 폴더와 spring 폴더가 같이 들어있는 형태라서, 저랑 폴더 구조가 다르신 분들은 본인의 repo에 맞게 수정해서 사용하시면 될 것 같습니다.
※ 사용한
actions
들은 모두 최신 버전을 사용했습니다.
continuous-integration-front
name: Vue build test
# master 브랜치로의 PR이 있으면 workflow를 실행함
# paths 설정이 있으므로 -> my-garden-fe의 하위 파일들이 변경된 경우에만 실행됨
on:
pull_request:
branches:
- master
paths:
- my-garden-fe/**
# 환경 변수 정의
env:
NODE_VERSION: 20 # 본인이 사용하는 node 버전에 맞춰서 정의해주세요!
jobs:
front-build:
runs-on: ubuntu-latest
# steps에 대한 기본 작업 디렉토리 설정
defaults:
run:
working-directory: ./my-garden-fe
steps:
# 저장소 코드를 체크아웃합니다. (PR 올린 코드를 가져오는 행위)
- uses: actions/checkout@v4
# Node.js 환경 설정
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }} # 지정된 Node.js 버전 사용
cache: npm # setup-node 의 캐시 기능을 사용함 (저는 npm을 사용하므로 npm을 썼습니다. -> 본인의 세팅에 맞춰서 변경해야 합니다.)
cache-dependency-path: my-garden-fe/package-lock.json # 캐시 기능을 사용할 때 캐시의 기준이 될 파일을 지정
- name: Install Dependencies
run: npm install
- name: Build with npm
run: npm run build
vue
쪽 코드에 대해서 PR이 올라오면 실행되는 workflow
입니다.
크게 어려운 내용이 없으므로 주석으로 설명을 대체하고 넘어가겠습니다.
setup-node
의 cache
는 Github에 들어가 보시면 더 많은 내용이 있습니다.
continuous-integration-back
name: Java CI with Gradle & Add Jacoco Report
# master 브랜치로의 PR이 있으면 workflow를 실행함
# paths 설정이 있으므로 -> my-garden-be의 하위 파일들이 변경된 경우에만 실행됨
on:
pull_request:
branches:
- master
paths:
- my-garden-be/**
jobs:
back-test-and-build:
runs-on: ubuntu-latest
# job을 처리할 때 필요한 권한들을 여기서 설정
permissions:
contents: read
pull-requests: write # PR Comment를 달기 위해서 필요함
# steps에 대한 기본 작업 디렉토리 설정
defaults:
run:
working-directory: ./my-garden-be
steps:
# 저장소 코드를 체크아웃합니다. (PR 올린 코드를 가져오는 행위)
- uses: actions/checkout@v4
# JDK 17 설정
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# gradlew에 실행 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Gradle 설정
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
# Gradle을 사용하여 테스트 실행
- name: Test with Gradle
run: ./gradlew test
# PR에 커버리지 추가
- name: Add coverage to PR
id: jacoco
uses: madrapps/jacoco-report@v1.6.1
with:
# paths에 들어가는 경로는 본인의 jacoco report가 나오는 경로를 확인하셔야 합니다.
paths: |
${{ github.workspace }}/**/build/reports/jacoco/**/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
# 성공과 실패 기준에 대해서 설정
min-coverage-overall: 50
min-coverage-changed-files: 65
# Gradle Wrapper로 빌드
- name: Build with Gradle Wrapper
run: ./gradlew build -x test # 위에서 테스트를 실행하므로, 테스트는 제외
spring
쪽 코드에 대해서 PR이 올라오면 실행되는 workflow
입니다.
크게 어려운 내용이 없으므로 주석으로 설명을 대체하고 넘어가겠습니다.
jacoco-report
에 관한 더 자세한 내용은 해당 블로그를 참고하시면 큰 도움이 됩니다.
[TestCode] jacoco를 사용하여 test coverage report 적용하기
master
브랜치에 PR이 머지가 되면, 머지된 코드를 바탕으로 Docker Image
를 만들고 AWS EC2
에서 Docker Image
를 Pull 받아 머지된 코드가 배포 되도록 해보겠습니다.
참고한 블로그 (두 글 모두 설명이 자세해서 큰 도움이 되었습니다.)
(Developer
)부터 AWS EC2
까지 진행되는 구성입니다.로컬 PC에서 개발 후 Github에 Push
Github Actions workflow
에 지정한 특정 branch
에 push가 되면 Github Action
이 동작
Github Actions
가 신규 소스코드를 기반으로 Dockerize
를 진행하고 Github Container Registry
에 Docker
이미지를 Push
AWS EC2
에서 빌드된 이미지를 Pull 받아온 후 기존 이미지를 삭제하고 새로운 이미지로 실행
Docker
파일 생성하기
Docker
파일 생성하기. # 용량을 줄이기 위해 jdk 17 alpine 버전을 사용함
FROM openjdk:17-jdk-alpine3.14
# Actions에서 Build job을 진행하므로 Build 된 jar 파일을 복사한다.
ARG JAR_FILE=my-garden-be/build/libs/*.jar
COPY ${JAR_FILE} app.jar
# 해당 이미지가 Container가 되면 실행할 명령어
# prod 프로필로 실행한다.
ENTRYPOINT ["java","-jar", "-Dspring.profiles.active=prod", "/app.jar"]
Github Access Token
발급 받기
본인 계정을 클릭하여 Setting
에 들어간다.
왼쪽 제일 하단에 있는 Developer Settings
을 클릭
Personal access tokens
클릭 후 Tokens (classic)
에서 Generate new token
으로 classic 으로 Token을 새로 생성한다.
유효기간은 적당히 아무렇게나 설정하셔도 되고, 권한은 workflow
, write:packages
, delete:packages
를 선택 후 Token을 생성합니다.
Token
값을 복사합니다.
Actions Secrets
설정하기
CD를 진행할 Repo의 Setting
에 들어갑니다.
왼쪽 중간쯤에 있는 Security
- Secrets and variables
- Actions
를 클릭
new repository secret
를 클릭
Name은 GHCR_TOKEN
, Value는 아까 복사한 Token
값을 입력 후 저장합니다.
Actions 설정하기
Repo의 Actions를 클릭 후 Action을 새로 생성합니다.
경로는 다음과 같습니다. .github/workflows/continuous-deployment.yml
주의할 사항은 Docker Image를 만들 때 대문자가 포함되어서는 안됩니다.
name: Continuous Deployment With Docker
# master 브랜치로 PUSH 되면 workflow를 실행함
# paths 설정이 있으므로 -> my-garden-be 혹은 my-garden-fe 의 하위 파일들이 변경된 경우에만 실행됨
on:
push:
branches:
- master
paths:
- my-garden-be/**
- my-garden-fe/**
env:
# GitHub Container Registry에 푸시할 이미지 정보 (ACTOR: GitHub 사용자명 / DOCKER_IMAGE: 이미지명 / VERSION: GitHub SHA / NAME: 컨테이너명)
# DOCKER_IMAGE를 만들때는 대문자가 포함되어서는 안됨, ACTOR에는 대문자가 포함되어도 상관없지만, 통일성을 위해 소문자로 작성 => 본인 계정에 맞게 변경해서 사용할 것
ACTOR: denia-park
DOCKER_IMAGE: ghcr.io/denia-park/my-garden
VERSION: ${{ github.sha }}
NAME: my-garden
jobs:
# spring을 빌드
build:
name: Build
runs-on: ubuntu-latest
# 해당 action에 대해서 권한 설정
permissions:
contents: read
packages: write
defaults:
run:
working-directory: ./my-garden-be
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
- name: Build with Gradle Wrapper
run: ./gradlew build
# docker build 수행시에 필요한 builder
- name: Set up docker buildx
id: buildx
uses: docker/setup-buildx-action@v3
# docker layer 캐시 (같은 소스 코드에 대해서 캐시 적용)
- name: Cache docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ env.VERSION }}
restore-keys: |
${{ runner.os }}-buildx-
# GitHub Container Registry에 로그인
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ env.ACTOR }}
password: ${{ secrets.GITHUB_TOKEN }}
# Docker 빌드 & GitHub Container Registry에 푸시
# context를 설정하지 않으면 임시 폴더를 만들고 해당 폴더에서 build가 진행되므로, . 으로 root를 지정해줬습니다.
# cache-from, cache-to 는 Docker Layer에서 중복되는 Layer를 캐싱합니다.
- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
with:
context: .
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha # 여기서 gha 는 Guthub Actions 용 캐시를 의미합니다.
cache-to: type=gha,mode=max
# Dokcer 이미지 배포
deploy:
name: Deploy
needs: build # build 후에 실행되도록 정의
runs-on: [ self-hosted, gardener ] # AWS ./configure에서 사용할 label명
steps:
- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ env.ACTOR }}
password: ${{ secrets.GITHUB_TOKEN }}
# 조건부 실행을 위해 컨테이너가 존재하는지 확인 -> 중지, 삭제 (이미지 포함) -> 새로운 이미지로 실행
# 저는 https를 사용하고 있고, http로 접속시 https로 리다이렉트 하도록 설정이 되어있습니다.
# -e 를 통해 TimeZone을 서울로 설정, --restart always 를 통해 재부팅되도 해당 이미지를 실행합니다.
- name: Docker run
run: |
if [ $(docker ps -aq -f name=${{ env.NAME }}) ]; then
docker stop ${{ env.NAME }}
docker rm ${{ env.NAME }}
docker rmi ${{ env.DOCKER_IMAGE }}:latest
fi
docker run -d -p 443:443 -p 80:80 -e TZ=Asia/Seoul --name ${{ env.NAME }} --restart always ${{ env.DOCKER_IMAGE }}:latest
AWS EC2
에 Docker
설치
저는 AWS EC2 OS
가 Amazon Linux 2023
이라서 해당 링크에 따라서 Docker
를 설치했습니다. 다른 버전을 사용하시면 그에 맞춰서 진행하시면 될 것 같습니다.
보안 그룹의 인바운드 설정이 제대로 되어있는지 확인해주세요.
AWS EC2
에 Github Actions Runner
설치
CD를 진행할 Repo의 Settings를 들어간다
Code and automation
→ Actions
→ Runners
클릭
New self-hosted runner
클릭
Runner image는 Linux
클릭 → Architecture는 본인의 AWS EC2
에 맞게 설정
Download 및 Configure를 순서대로 차근 차근 수행합니다.
./config.sh
실행하면 몇가지 입력하라고 하는데, Enter any additional label
말고는 그냥 다 default
로 했습니다.
label
은 Runner를 알아보기 위해 필요한 설정인데, 저는 yaml에 설정되어 있는 대로 gardener
로 했습니다. 마지막 ./run.sh
는 백그라운드로 돌 수 있도록 nohup ./run.sh &
로 합니다.
./config.sh
수행시에 에러가 발생할 수 있습니다. 저 같은 경우는 닷넷코어
가 설치되어 있지 않아서 에러가 발생했습니다.
다음 링크를 참고하여 문제를 해결했습니다.
sudo dnf install dotnet-sdk-6.0
본인 Repo의 파일 경로를 제대로 파악하고 지정해주셔야 합니다.
저도 진행하면서 경로를 잘못지정해서 시간을 엄청 썼습니다.
다른 분들은 꼭 경로를 제대로 확인하셔서 시간을 아끼셨으면 좋겠습니다.
yaml에 보면 해당 job에 대해서 권한을 주고 있는데, 이래도 ghcr.io
에 push시에 403 에러가 발생한다면 (failed to solve: failed to push ghcr.io/
)
다음과 같이 설정해주세요.
Repo의 Settings
클릭 → Code and automation
의 Actions
클릭 → General
클릭 → 맨 아래에 있는 Workflow permissions
에서 권한을 Read and write permissions
로 설정한다.