[CI/CD] Docker와 GitHub Actions을 활용한 배포 자동화

Yu Seong Kim·2024년 11월 6일
post-thumbnail

깃액션을 선택한 이유

PEANUT 프로젝트를 진행하면서, 프론트엔드와 연결하여 테스트할 목적으로 배포를 계획했습니다. 단순히 테스트용 배포였기에 AWS EC2 서버에 Docker를 활용해 배포를 진행하려 했지만, 추후 수정 사항이 자주 발생할 것으로 예상되어 배포 과정이 번거로워질 수 있었습니다. 이를 해결하기 위해, CI/CD 파이프라인을 구축하여 자동화된 배포 환경을 마련하고자 GitHub Actions를 선택했습니다.

트러블 슈팅 1 (MalformedJsonException)

com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line X column Y path $ ...

처음에는 그냥 scret파일에 Fcm설정 JSON의 내용들을 저장하고, 다음과 같이 워크플로우 스크립트를 작성했습니다.

- name: Prepare Firebase JSON
        run: |
          mkdir src/main/resources/firebase/
          echo -e "${{ secrets.PEANUT_FIREBASE_CREDENTIALS }}" > src/main/resources/firebase/peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json

그런데 위와 같은 에러가 나왔습니다. ${{ secrets.PEANUT_FIREBASE_CREDENTIALS }}에 저장된 내용을 그대로 출력하여 파일에 저장하지만, 만약 PEANUT_FIREBASE_CREDENTIALS에 저장된 값이 잘못된 형식의 JSON이라면, 생성된 파일 역시 잘못된 JSON 형식이 됩니다. 이로 인해 이후 Firebase 설정에서 JSON 파일을 읽을 때 오류가 발생합니다. 즉, 잘못된 JSON 파일이 생성되었고, 다양한 방법으로 수정을 해보아도 동일한 문제가 발생했습니다.

create-json Action을 사용하여 JSON 파일을 생성하기

- name: Create Firebase JSON directory
        run: |
          mkdir -p src/main/resources/firebase/
- name: create-json
    id: create-json
    uses: jsdaniell/create-json@v1.2.2
    with:
      name: "emptyAdminSDK.json"
      json: ${{ secrets.FIREBASE_JSON }}
      dir: 'src/main/resources/firebase/'
  1. jsdaniell/create-json Action은 내부적으로 JSON 문자열을 검증하고, 이를 파일로 저장합니다.따라서, $ { { secrets.FIREBASE_JSON } }에 잘 형식화된 JSON이 저장되어 있다면, 이 Action이 JSON 형식에 맞게 파일을 생성하게 됩니다.

  2. 이 Action은 JSON 형식이 잘못된 경우 오류를 발생시키고, 올바르지 않은 JSON을 처리할 수 있는 내부 로직을 갖추고 있습니다.
    즉, 이전에 사용한 echo 명령어는 단순히 문자열을 파일로 저장하는 것이기 때문에, 형식 검증이 없었습니다. 반면, create-json Action은 JSON을 생성하는 과정에서 형식 문제를 자동으로 처리합니다.

  3. 지정된 dir 경로에 JSON 파일을 생성하여, 프로젝트의 파일 구조에 맞춰 파일이 저장됩니다. 이는 나중에 Firebase 설정에서 해당 파일을 쉽게 찾을 수 있도록 합니다.

트러블 슈팅 2 (IllegalArgumentException)

# 1. Java 11을 베이스 이미지로 사용
FROM openjdk:11-jre-slim

# 2. 애플리케이션 디렉토리 설정
WORKDIR /app

# 3. 빌드된 JAR 파일을 컨테이너로 복사
COPY target/Peanut-0.0.1-SNAPSHOT.jar app.jar

# 4. 설정 파일 복사
COPY config/application.properties /app/config/application.properties
COPY src/main/resources/firebase/peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json /app/src/main/resources/firebase/peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json

# 5. 포트 설정 (Spring Boot의 기본 포트)
EXPOSE 8080

# 6. JAR 파일 실행
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.config.location=/app/config/application.properties"]

에서 Docker 컨테이너 내의 작업 디렉토리를 /app으로 설정하여 /app/firebase/파일이름.json 파일이 잘 저장이 되는 것을 확인했습니다.
그러나 아래와 같은 오류가 발생했습니다.

[void]: Factory method 'initializeFirebaseApp' threw exception; nested exception is java.lang.IllegalArgumentException: 
Firebase JSON file is empty

오류를 확인하면 Firebase JSON 파일이 비어있다는 것입니다. 시크릿파일에 JSON을 넣었지만 왜 JSON파일이 비어있다는 것인가?에 대한 의문을 가지고, 처음부터 작성한 파일을 보았습니다.

해결 - 시크릿 파일 이름 불일치 확인

제가 시크릿 파일을 저장할 땐 아래와 같은 이름으로 저장하지만,

스크립트를 확인하면 ${{ secrets.FIREBASE_JSON }} 이렇게 저장이 되어있었습니다. 수정한다고 한 것을 놓친 실수 였습니다.

          
      - name: Create Firebase JSON
        id: create-json
        uses: jsdaniell/create-json@v1.2.2
        with:
          name: "peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json"
          json: ${{ secrets.FIREBASE_CREDENTIALS }}
          dir: '/src/main/resources/firebase/'

이렇게 수정 후 워크 플로우를 다시 실행하니 오류 없이 배포가 완료 되었습니다.

최종 배포 스크립트 및 도커 파일

dockerfile

# 1. Java 11을 베이스 이미지로 사용
FROM openjdk:11-jre-slim

# 2. 애플리케이션 디렉토리 설정
WORKDIR /app

# 3. 빌드된 JAR 파일을 컨테이너로 복사
COPY target/Peanut-0.0.1-SNAPSHOT.jar app.jar

# 4. 설정 파일 복사
COPY config/application.properties /app/config/application.properties
COPY src/main/resources/firebase/peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json /app/src/main/resources/firebase/peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json

# 5. 포트 설정 (Spring Boot의 기본 포트)
EXPOSE 8080

# 6. JAR 파일 실행
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.config.location=/app/config/application.properties"]

maven.yml

name: Build and Deploy to Ubuntu Server

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: "17"
          distribution: "temurin"
          
      - name: Prepare application.properties
        run: |
          mkdir config
          echo "${{ secrets.PEANUT_APP_PROPERTIES }}" > config/application.properties
      
      - name: Create Firebase JSON directory
        run: |
          mkdir -p src/main/resources/firebase/
          
      - name: Create Firebase JSON
        id: create-json
        uses: jsdaniell/create-json@v1.2.2
        with:
          name: "peanut-bc734-firebase-adminsdk-bsc10-1d0f1c01e4.json"
          json: ${{ secrets.PEANUT_FIREBASE_CREDENTIALS }}
          dir: 'src/main/resources/firebase/'  

      - name: Build with Maven
        run: mvn clean package -DskipTests
          
      - name: Check if jar file exists
        run: |
          echo "Contents of target directory:"
          ls -la target
          if [ ! -f target/Peanut-0.0.1-SNAPSHOT.jar ]; then
            echo "Error: Jar file not found!"
            exit 1
          fi

      - name: Docker build and push
        run: |
          docker login -u ${{ secrets.PEANUT_DOCKER_REPO }} -p ${{ secrets.PEANUT_DOCKER_KEY }}
          docker build -t ${{ secrets.PEANUT_DOCKER_REPO }}/peanut-backend:latest .
          docker push ${{ secrets.PEANUT_DOCKER_REPO }}/peanut-backend

  deploy:
    runs-on: ubuntu-latest
    needs: build

    steps:
      - name: Deploy to Ubuntu Server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.PEANUT_HOST }}
          username: ${{ secrets.PEANUT_USERNAME }}
          key: ${{ secrets.PEANUT_SSH_PRIVATE_KEY }}
          script: |
            sudo docker network create peanut || true
            sudo docker stop peanut-backend || true
            sudo docker rm peanut-backend || true
            sudo docker pull ${{ secrets.PEANUT_DOCKER_REPO }}/peanut-backend:latest
            sudo docker run -d -p 8080:8080 --net peanut --name peanut-backend ${{ secrets.PEANUT_DOCKER_REPO }}/peanut-backend:latest

배포된 스웨거 확인

이렇게 도커와 깃액션으로 배포 자동화를 진행하였습니다. 다음에는 스터디한 GItLab과 ECR,ECS를 활용한 배포로 다시 해볼 예정입니다.

profile
Development Record Page

0개의 댓글