DevOps

K3s 기반 멀티 노드 클러스터 구축기 (6) - GitHub Actions와 AWS ECR로 경량화 배포

zyin 2025. 5. 11. 00:00

1. 시작하면서

Spring Boot 멀티모듈 프로젝트를 기반으로, 자동화된 CI 파이프라인을 구성하고 AWS ECR에 최적화된 Docker 이미지를 푸시하는 것을 목표로 한다. 단순한 배포가 아니라, 가능한 최소한의 메모리, 스토리지, 네트워크 리소스를 사용하는 이미지를 만드는 데 집중하였다. 이를 위해 빌드 도구나 소스코드를 최종 이미지에 포함시키지 않고, 필요한 실행 파일만을 남기는 Multi-stage Build를 적용하고, eclipse-temurin:21-jre-alpine과 같은 경량화된 베이스 이미지를 선택했다.

2. 인프라 기본 구성

먼저, GitHub Actions가 AWS에 직접 접근할 수 있도록 OpenID Connect(OIDC)를 이용해 인증을 구성하였다. IAM Role을 생성하여 OIDC Federation 방식을 통해 GitHub Actions가 STS:AssumeRoleWithWebIdentity를 통해 Role을 Assume할 수 있도록 설정하였다. Trust Policy는 다음과 같이 작성하였다.

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:TeamFair/illsang-infra:*"
    }
  }
}

이와 함께 AWS ECR 리포지터리(app-backend)를 생성하여, 빌드된 이미지를 저장하고 운영에 활용할 준비를 완료하였다.

3. GitHub Actions 워크플로우 구성

워크플로우는 수동 트리거(workflow_dispatch) 방식으로 구성하여, 필요할 때마다 GitHub Actions에서 수동으로 실행할 수 있도록 하였다. 전체 플로우는 infra 레포지토리와 backend 레포지토리를 각각 checkout한 뒤, AWS Credentials를 설정하고 Amazon ECR에 로그인한 후 Docker 이미지를 빌드하여 ECR에 푸시하는 단계로 구성되었다.

워크플로우는 다음과 같다.

name: Build and Push to ECR

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout infra repository
        uses: actions/checkout@v4

      - name: Checkout backend repository
        uses: actions/checkout@v4
        with:
          repository: backend
          ref: feat-LayerHex
          token: ${{ secrets.GITHUB_TOKEN }}
          path: app-source

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubDeployRole
          aws-region: ap-northeast-2

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, Tag, and Push Docker image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: app-backend
          IMAGE_TAG: latest
        run: |
          cd app-source
          docker build -f module-bootstrap/Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

 

해당 파일이 실행되고 나면 AWS 의 해당 ECR에 이미지가 저장된것을 확인할수 있다. 

 

4. Dockerfile 최적화

Dockerfile은 멀티스테이지 빌드를 적용하여 최적화하였다. 1단계에서는 gradle:8.7-jdk21-alpine 이미지를 사용하여 소스코드로부터 bootJar를 빌드하고, 2단계에서는 eclipse-temurin:21-jre-alpine 이미지를 베이스로 사용하여 빌드된 JAR 파일만을 복사하여 실행 환경을 구성하였다. 이로써 최종 이미지는 오직 필요한 런타임 파일만 포함하게 되어 크기를 줄일 수 있었다.

# 1단계: 빌드 스테이지
FROM gradle:8.7-jdk21-alpine AS builder

WORKDIR /workspace
COPY . .
RUN ./gradlew :module-bootstrap:bootJar

# 2단계: 런타임 스테이지
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app
COPY --from=builder /workspace/module-bootstrap/build/libs/*.jar app.jar

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

이 구조 덕분에 GitHub Actions에서는 별도로 Gradle을 설치하거나 bootJar 빌드를 실행할 필요 없이, Docker 빌드 과정 안에서 모든 과정을 처리할 수 있게 되었다.

5. 최적화 결과

최적화된 결과는 상당히 만족스러웠다. 기존에는 Docker 이미지 크기가 약 299MB 정도였으나, 최적화 후에는 약 161.65MB까지 줄어들었다. 이 과정에서 Distroless 베이스 이미지도 테스트했으나, Alpine 기반의 eclipse-temurin:21-jre-alpine이 훨씬 더 경량화에 적합했다. Distroless는 보안 강화에는 유리하지만, 절대적인 크기에서는 Alpine보다 무거운 편이었기 때문이다.

최종적으로, 최소한의 런타임 환경만을 포함하는 경량화된 이미지를 구성함으로써, Docker pull 속도, 컨테이너 부트 속도, 메모리 사용량을 모두 최적화하는 데 성공하였다.

항목 최적화 전 최적화 후
이미지 크기 약 299MB 약 161.65MB
런타임 베이스 기본 JDK jre-alpine
구성 방식 소스 및 빌드 도구 포함 빌드된 JAR 파일만 포함