주변에서 Jenkins를 많이 사용하는 것을 보고 처음에는 막연히 Jenkins를 도입하려고 했다. 하지만 Jenkins는 별도의 서버 구축이 필요해 비용이 발생한다는 단점이 있었다. 반면 Github Actions는 Github에 내장된 CI/CD 기능을 별도의 서버 없이 사용할 수 있다.
Github Actions는 추가 비용이 들지 않고 초기 설정도 간단해서 더 유리했다.
spring:
profiles:
active: local
include: secret
---
spring:
config:
activate:
on-profile: dev
transaction:
default-timeout: '${custom.transaction.default-timeout}'
datasource:
hikari:
maximum-pool-size: '${custom.hikari.maximum-pool-size}'
idle-timeout: '${custom.hikari.idle-timeout}'
driver-class-name: com.mysql.cj.jdbc.Driver
url: '${custom.db.url}'
username: '${custom.db.username}'
password: '${custom.db.password}'
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
default_batch_fetch_size: 1000
...
워크플로우가 시작되면 가장 먼저 application-secret.yml 파일을 생성한다. 프로필 설정에 secret이 포함되어 있어, 워크플로우에서 생성된 application-secret.yml을 통해 데이터소스와 같은 중요 변수들을 관리할 수 있다.
custom:
db:
url: 데이터소스 url
username: 유저 이름
password: 비밀 번호
transaction:
default-timeout: 100
hikari:
maximum-pool-size: 10
idle-timeout: 600000
max-lifetime: 1800000
minimum-idle: 20
application-secret.yml에서 db, 트랜잭션, 커넥션풀 관련 변수를 관리한다.
워크플로우에서 application-secret.yml을 만들기 위해 github에 secret을 등록 해야한다.
시크릿은 다음과 같이 등록 되어 있다. (base64로 인코딩 후 등록했다)
CI/CD 파이프라인을 도식화 한 것이다. 워크플로우 절차는 다음과 같다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: create application-secret.yml
run: |
cd ./src/main/resources
touch ./application-secret.yml
echo "${{ secrets.APPLICATION_SECRET }}" | base64 --decode > ./application-secret.yml
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build -x test
- name: Docker build
run: |
echo "${{ secrets.DOCKER_TOKEN }}" | base64 --decode | docker login -u $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode) --password-stdin
docker build -t app .
docker tag app $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
docker push $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
script: |
docker pull $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
docker stop $(docker ps -a -q)
docker run -d --log-driver=syslog -p 8080:8080 $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
docker rm $(docker ps --filter 'status=exited' -a -q)
docker image prune -a -f
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
## load application
- uses: actions/checkout@v4
## jdk setting
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
## create application-secret.yml
- name: create application-secret.yml
run: |
cd ./src/main/resources
touch ./application-secret.yml
echo "${{ secrets.APPLICATION_SECRET }}" | base64 --decode > ./application-secret.yml
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build -x test
## docker build & push
- name: Docker build
run: |
echo "${{ secrets.DOCKER_TOKEN }}" | base64 --decode | docker login -u $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode) --password-stdin
docker build -t app .
docker tag app $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
docker push $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
## docker deploy to EC2
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
script: |
docker pull $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
docker stop $(docker ps -a -q)
docker run -d --log-driver=syslog -p 8080:8080 $(echo "${{ secrets.DOCKER_USERNAME }}" | base64 --decode)/solta:latest
docker rm $(docker ps --filter 'status=exited' -a -q)
docker image prune -a -f
CI/CD를 구축하며 AWS, 도커 등 익숙치 않은 기술에 대한 여러 어려움이 있었는데 한 포스트에 다 적기는 어려워서 나중에 따로 정리하기로 했다.
앞으로는 액추에이터를 활용하여 스프링 부트 어플리케이션 헬스 체크를 도입할 예정이다.