학교 전공 수업에서 Spring Boot 애플리케이션을 배포할 일이 생겨 간단하게 GitHub Actions와 Docker를 통해 자동 배포 환경을 구축해보았습니다
완성된 전체 yaml 파일은 다음과 같습니다
name: Java CI with Gradle
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build-docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name : Setup Environment
run : |
echo -e "\n${{ secrets.APPLICATION }}" >> ./src/main/resources/application.properties
- name: Setup JDK 21
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Set up Docker
uses: docker/setup-buildx-action@v3
- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: yunuo46
password: ${{ secrets.GHCR_TOKEN }}
- name: docker Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/yunuo46/getaguitar:latest
deploy-docker:
needs: build-docker
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to Server via SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u yunuo46 --password-stdin
docker pull ghcr.io/yunuo46/getaguitar:latest
docker stop getaguitar || true
docker rm getaguitar || true
docker run -d --name getaguitar -p 3000:3000 ghcr.io/yunuo46/getaguitar:latest
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
워크플로우에 대한 트리거를 지정합니다
main branch에 push, pull_request될 경우에 동작하도록 했습니다
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
최신 버전의 Ubuntu Linux Runner에서 실행되도록 했습니다
Runner는 GitHub에서 제공하는 워크플로우를 실행할 수 있는 서버입니다
actions/checkout
을 통해 해당 서버에 소스코드를 올릴 수 있습니다
- name : Setup Environment
run : |
echo -e "\n${{ secrets.APPLICATION }}" >> ./src/main/resources/application.properties
- name: Setup JDK 21
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
환경변수를 설정해주고
Runner 환경을 로컬 환경과 동일하게 세팅해줍니다
gradle/actions/setup-gradle@v3
에서 gradle 버전을 따로 지정하지 않아도 버전이 동일해서 찾아보니 default로 wrapper의 설정을 사용한다고 합니다!
참고 : https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#build-with-a-specific-gradle-version
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
window는 기본 권한이 644로 생성되기 때문에 gradlew에 실행권한을 주고 build합니다
- name: Set up Docker
uses: docker/setup-buildx-action@v3
- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: yunuo46
password: ${{ secrets.GHCR_TOKEN }}
- name: docker Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/yunuo46/getaguitar:latest
Docker를 셋업하고, github container registry에 로그인합니다
이후 Docker Image를 Build하고 ghcr에 Push합니다
이 작업이 완료되면 pakage에 해당 image가 private로 올라가 있는 것을 확인할 수 있습니다
docker hub는 1개의 이미지만 private로 가능한 반면에, ghcr은 500MB까지 무료입니다
- name: Deploy to Server via SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u yunuo46 --password-stdin
docker pull ghcr.io/yunuo46/getaguitar:latest
docker stop getaguitar || true
docker rm getaguitar || true
docker run -d --name getaguitar -p 3000:3000 ghcr.io/yunuo46/getaguitar:latest
서버에 ssh로그인을 한 뒤, ghcr에 접속해 최신 image를 pull하고 기존에 존재하는 container를 삭제한 뒤 run해줍니다
Dokerfile은 container 실행 시 빌드된 jar파일을 실행하도록 작성했습니다
FROM bellsoft/liberica-openjdk-alpine:21
WORKDIR /app
COPY ./build/libs/*.jar app.jar
EXPOSE 3000
ENTRYPOINT ["java","-jar","app.jar"]
spring boot 환경에서 CI/CD를 구성하는게 처음이라 다양한 글들을 참고하며 구축했습니다
그런데 gradle build를 Dockerfile에서(서버에서) 하는 글을 보기도 해서, github actions에서 build하는 것과 어느 쪽이 맞는 방향인 지 헷갈려서 조금 헤멨던 것 같습니다
결론은 github-hosted runner에서 build하는 것이 CI의 장점을 활용한다고 볼 수 있습니다
변동이 생길 수 있는 서버 환경과 달리 Runner에서는 build환경을 일관성 있게 구축할 수 있고, build가 성공한 경우만 image를 push함으로서 안정적인 배포 또한 가능합니다
그리고 서버 환경에 불필요한 베이스 이미지들과 Layer를 제외하고 아래와 같이 jar파일만 갖고 실행하면 되도록 구성함으로써 이미지의 크기를 줄이고 서버의 부담을 줄일 수 있습니다
Dockerfile에서 COPY명령문 실행 시 executable jar를 복사해야합니다
plain.jar 파일이 생성되지 않도록 build.gradle에 옵션을 추가했습니다
jar {
enabled = false
}
최대한 github문서들을 참고하며 개발하다보니 github actions의 동작 과정에 대해 깊이 이해할 수 있었습니다
추가로 Gradle Caching이나 bootbuildimage, qemu 등 공부해보고 적용해볼 것들이 남았습니다
이후에 다른 프로젝트는 Jenkins+Docker Hub로 CI/CD를 구축해 볼 생각입니다
감사합니다