스프링부트 GitHub Actions + EC2 SSH CI/CD 구축하기

2025. 4. 3. 13:54AWS

이번에는 스프링부트의 배포를 자동화해볼 예정이다!

여러 방법이 있지만 나는 이미 리액트 앱을 GibHub Actions를 이용해서 배포해봤기 때문에 익숙한 이 방법으로 백엔드도 구축해볼거다.

하지만 나중에 젠킨스를 이용한 CI/CD 구축하는 방법도 꼭 공부해볼거다.

 

기존에 백엔드 배포를 하면서 EC2와 ECR은 세팅해두었기 때문에 이 부분은 건너뛰겠다.

모르는 분들은 제 이전 글들에 자세히 나와있으니 참고하면 좋을 듯 하다.

 

1. deploy.yml 파일 작성

 

.github/workflows 경로에 deploy.yml 파일을 생성한다. 리액트 앱 배포할 때 봐서 익숙하겠지만 해당 경로의 파일을 실행해서 자동화를 진행한다. 

반드시 해당 경로에 deploy.yml 파일이 존재해야한다.

 

name: CI/CD to EC2 # 워크 플로우 이름, GitHub Actions에서 보여지는 이름

on:
  push:
    branches:
      - dev  # main 브랜치에 push될 때 실행

jobs:
  spring-build:
    runs-on: ubuntu-latest # ubuntu-latest 환경에서 실행

    env:
      IMAGE_URI: ${{ secrets.AWS_ECR_URI }}
      REGION: ap-northeast-2

    steps:
      - name: Checkout source
        uses: actions/checkout@v3 # GitHub 저장소 코드를 현재 실행 환경에 복사

      - name: Set up Docker
        uses: docker/setup-buildx-action@v3 # 고급 빌드 기능을 활성화 - 멀티 플랫폼 빌드나 캐시 최적화

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v3 # AWS 사용자 인증
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          region: ${{ env.REGION }}

      - name: Login to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2 # ECR 로그인

      - name: Build and Push Docker image # 빌드하고 ECR에 도커 이미지 푸시
        run: |
          echo "Using image URI: $IMAGE_URI"
          docker build --platform linux/amd64 -t $IMAGE_URI .
          docker push $IMAGE_URI

      - name: SSH to EC2 and Deploy
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ec2-user
          key: ${{ secrets.EC2_KEY }}
          script: |
            IMAGE_URI=${{ secrets.AWS_ECR_URI }}
            REGION=${{ env.REGION }}
            aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $IMAGE_URI
            docker stop spring-app || true
            docker rm spring-app || true
            docker pull $IMAGE_URI
            docker run -d --name petlog -p 8080:8080 $IMAGE_URI

 

이렇게 deploy.yml 이 작성됐으면 중간에 환경변수들로 설정된 값들을 GitHub Secrets에 해준다.

 

만약 EC2에 해당 권한이 없다면 권한을 부여해준다.

 

 

에러 발생 application-dev.yml 파일을 찾을 수가 없다고..

나는 엥 분명 내 application-dev.yml을 COPY 하라고 했는데 왜 없다고 하는거지 의문이 들어 찾아보았다.

 

문제는 바로 GitHub Actions가 실행될 때 application-dev.yml이 존재하지 않기 때문이다.

GitHub Actions는 GitHub에 있는 파일들만 접근가능한데 나는 .gitignore 에 추가해놨기 때문에 올라가지 않는 것.

그래서 application.yml 파일이 작성되도록 추가해줘야한다.

 

name: CI/CD to EC2 # 워크 플로우 이름, GitHub Actions에서 보여지는 이름

on:
  push:
    branches:
      - temp  # dev 브랜치에 push될 때 실행

jobs:
  spring-build:
    runs-on: ubuntu-latest # ubuntu-latest 환경에서 실행

    env:
      IMAGE_URI: ${{ secrets.AWS_ECR_URI }}
      REGION: ap-northeast-2

    steps:
      - name: Checkout source
        uses: actions/checkout@v3 # GitHub 저장소 코드를 현재 실행 환경에 복사

      - name: Set up Docker
        uses: docker/setup-buildx-action@v3 # 고급 빌드 기능을 활성화 - 멀티 플랫폼 빌드나 캐시 최적화

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v3 # AWS 사용자 인증
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.REGION }}

      - name: Login to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2 # ECR 로그인

      - name: Create application-dev.yml
        run: |
          echo "spring:
            datasource:
              url: ${{ secrets.SPRING_DATASOURCE_URL }}
              username: ${{ secrets.SPRING_DATASOURCE_USERNAME }}
              password: ${{ secrets.SPRING_DATASOURCE_PASSWORD }}
              driver-class-name: com.mysql.cj.jdbc.Driver
            servlet:
              multipart:
                max-file-size: 10MB
                max-request-size: 30MB
          mybatis:
            configuration:
              map-underscore-to-camel-case: true
            mapper-locations: classpath:mappers/*.xml
            type-aliases-package: com.petlog.userService.domain
          jwt:
            secret:
              key: ${{ secrets.JWT_SECRET_KEY }}
          aws:
            s3:
              bucket: ${{ secrets.AWS_S3_BUCKET }}
            credentials:
              accessKey: ${{ secrets.AWS_ACCESS_KEY_ID }}
              secretKey: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
            region: ap-northeast-2" > src/main/resources/application-dev.yml

      - name: Build and Push Docker image # 빌드하고 ECR에 도커 이미지 푸시
        run: |
          echo "Using image URI: $IMAGE_URI"
          docker build --platform linux/amd64 -t $IMAGE_URI .
          docker push $IMAGE_URI

      - name: SSH to EC2 and Deploy
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_KEY }}
          script: |
            IMAGE_URI=${{ secrets.AWS_ECR_URI }}
            REGION=${{ env.REGION }}
            aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $IMAGE_URI
            docker stop petlog || true
            docker rm petlog || true
            docker pull $IMAGE_URI
            docker run -d --name petlog -p 8080:8080 $IMAGE_URI

 

그래서 이렇게 코드를 바꾸고 깃헙시크릿에 환경변수도 더 추가해주었다.

 

 

그리고 10번만에 성공 🥳

글은 간략하게 썼지만 잔잔한 오류들이 계속 떴었다,, 하지만 해결해나가는 거 너무 재밌고요,,~

 

 

하지만 여기서 끝이 아니였다.

제대로 실행되는지 EC2에 가서 확인해보기로 했다. 원래라면 docker ps 명령어를 입력했을 때 컨터이너가 실행중이여야한다.

 

 

 

실패!

키자마자 바로 꺼져버린 내 컨테이너,, 로그를 봐보자

 

Config data resource 'file [/app/application.yml]' via location 'file:/app/application.yml' does not exist

Action:

Check that the value 'file:/app/application.yml' is correct, or prefix it with 'optional:'

 

이 에러는 application.yml이 Docker 컨테이너 안에 없기 때문에 발생한 오류다.

 

# 빌드 과정
FROM openjdk:17-jdk-slim AS builder

WORKDIR /app

# xargs가 필요하므로 필수 유틸 설치
RUN apt-get update && apt-get install -y findutils

COPY . .

RUN ./gradlew build -x test --no-daemon

# 이미지 생성 과정
FROM eclipse-temurin:17-jre

WORKDIR /app

COPY --from=builder /app/build/libs/*SNAPSHOT.jar app.jar

ENTRYPOINT ["java", "-jar", "app.jar", "--spring.config.location=file:/app/application.yml"]

 

내 도커파일을 보면 ENTRYPOINT ["java", "-jar", "app.jar", "--spring.config.location=file:/app/application.yml"] 이부분에서 컨테이너 내부의 /app/application.yml 파일을 Spring Boot가 찾으려고 하는데 없어서 오류가 나는 것이다.

 

왜그러냐면 deploy.yml에서 echo "spring: ..." > src/main/resources/application-dev.yml 로 application-dev.yml 파일을 생성했지만 이걸 도커파일로 복사를 안해서 그런것

 

ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=dev"]

 

그래서 Spring Boot가 기본으로 application.yml, application-dev.yml을 병합해주도록 위와 같은 코드로 바꿔주었다.

 

 

그리고 다시 EC2 인스턴스를 확인해주었더니 아주 잘 떠있다! 

지난번 리액트앱 CI/CD 구축했을 때랑 같은 Github Actions를 이용해서 그런지 이번에는 나름 수월했다.

이래서 경력직 경력직 하는구나,,~