GitHub Actions를 통한 Spring CI/CD 구축(feat. docker & ghcr)

송현우·2024년 5월 2일
4

CI/CD

목록 보기
1/1

학교 전공 수업에서 Spring Boot 애플리케이션을 배포할 일이 생겨 간단하게 GitHub Actions와 Docker를 통해 자동 배포 환경을 구축해보았습니다

YAML

완성된 전체 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

Trigger

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

워크플로우에 대한 트리거를 지정합니다
main branch에 push, pull_request될 경우에 동작하도록 했습니다

Checkout

runs-on: ubuntu-latest
steps:
   - name: Checkout
     uses: actions/checkout@v4

최신 버전의 Ubuntu Linux Runner에서 실행되도록 했습니다
Runner는 GitHub에서 제공하는 워크플로우를 실행할 수 있는 서버입니다
actions/checkout을 통해 해당 서버에 소스코드를 올릴 수 있습니다

Set Up

- 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 Build

- name: Grant execute permission for gradlew
  run: chmod +x gradlew

- name: Build with Gradle
  run: ./gradlew build

window는 기본 권한이 644로 생성되기 때문에 gradlew에 실행권한을 주고 build합니다

Push Image to ghcr.io

- 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까지 무료입니다

Deploy to Server

- 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해줍니다

Dockerfile

Dokerfile은 container 실행 시 빌드된 jar파일을 실행하도록 작성했습니다

FROM bellsoft/liberica-openjdk-alpine:21

WORKDIR /app

COPY ./build/libs/*.jar app.jar

EXPOSE 3000

ENTRYPOINT ["java","-jar","app.jar"]

트러블 슈팅

gradle build 위치

spring boot 환경에서 CI/CD를 구성하는게 처음이라 다양한 글들을 참고하며 구축했습니다
그런데 gradle build를 Dockerfile에서(서버에서) 하는 글을 보기도 해서, github actions에서 build하는 것과 어느 쪽이 맞는 방향인 지 헷갈려서 조금 헤멨던 것 같습니다
결론은 github-hosted runner에서 build하는 것이 CI의 장점을 활용한다고 볼 수 있습니다
변동이 생길 수 있는 서버 환경과 달리 Runner에서는 build환경을 일관성 있게 구축할 수 있고, build가 성공한 경우만 image를 push함으로서 안정적인 배포 또한 가능합니다
그리고 서버 환경에 불필요한 베이스 이미지들과 Layer를 제외하고 아래와 같이 jar파일만 갖고 실행하면 되도록 구성함으로써 이미지의 크기를 줄이고 서버의 부담을 줄일 수 있습니다

plain jar 생성 방지

Dockerfile에서 COPY명령문 실행 시 executable jar를 복사해야합니다
plain.jar 파일이 생성되지 않도록 build.gradle에 옵션을 추가했습니다

jar {
	enabled = false
}

느낀 점

최대한 github문서들을 참고하며 개발하다보니 github actions의 동작 과정에 대해 깊이 이해할 수 있었습니다
추가로 Gradle Caching이나 bootbuildimage, qemu 등 공부해보고 적용해볼 것들이 남았습니다
이후에 다른 프로젝트는 Jenkins+Docker Hub로 CI/CD를 구축해 볼 생각입니다
감사합니다

0개의 댓글

관련 채용 정보