ECS, Fargate, ECR 조합으로 서버 배포하기 (w. Spring boot)

🔥Log·2025년 12월 31일

☕️ 개요


이번 글에서는 제목에 작성한 것처럼 ECS, Fargate, ECR을 사용하여 컨테이너화된 서버를 구동하는 방법에 대해서 빠르게 알아보도록 하겠다.
각 기술에 대한 설명은 최대한 생략하고, AWS 콘솔에서 컨테이너 운영 환경 구축하는 방법을 집중적으로 이야기하겠다.

📌 글에서 사용되는 코드들은 깃헙에 있다.



1. Github workflow로 ECR에 배포


1) ECR Repository 생성

먼저, ECR Repository를 생성해준다.

나는 이렇게 2개의 Repository를 생성했다.

2) IAM Role 생성

EC2InstanceProfileForImageBuilderECRContainerBuilds를 갖고 있는 IAM 계정을 하나 생성하고, 이 계정의 키 페어 또한 하나 발급한다.

키 페어는 Github secret 변수에 등록한다.

3) Workflow 작성

name: Build order service

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

concurrency:
  group: ${{ github.workflow }}

jobs:
  build:
    runs-on: ubuntu-latest

    outputs:
      img-tag: ${{ steps.tag.outputs.this }}

    steps:
      - name: 컨테이너 tag 를 생성한다
        id: tag
        run: >-
          echo "this=$(date +'%y%m%d%H%M')-${GITHUB_SHA:0:4}"
          >> "$GITHUB_OUTPUT"

      - name: 코드를 받아온다
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: 25
          distribution: corretto
          cache: gradle

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4

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

      - name: Execute Gradle build
        run: ./gradlew app-order:build -x test --stacktrace

      - name: AWS credentials 를 구성한다
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
          aws-region: ap-northeast-2

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

      - name: buildx를 설정한다
        uses: docker/setup-buildx-action@v3

      - name: 컨테이너 이미지를 빌드하고 푸시한다
        env:
          img-uri: ${{ steps.login-ecr.outputs.registry }}/app-order-service
          img-tag: ${{ steps.tag.outputs.this }}
        uses: docker/build-push-action@v4
        with:
          tags: ${{ env.img-uri }}:${{ env.img-tag }}
          push: true
          context: app-order
          file: app-order/Dockerfile
      - name: 이미지 태그를 출력한다
        env:
          img-tag: ${{ steps.tag.outputs.this }}
        run: |
          echo "# 이미지 태그" >> $GITHUB_STEP_SUMMARY
          echo "tag: \`${{ env.img-tag }}\`" >> $GITHUB_STEP_SUMMARY

물론 AWS CLI를 통해서 ECR 배포도 가능하지만, 귀찮으니까 Github workflow로 작성해보았다.
이 Workflow를 실행하면 아래와 같이 도커 이미지가 ECR에 배포될 것이다.



2. ECS 세팅


1) 클러스터 생성

1-1) 클러스터 기본 값 입력

클러스터의 이름을 기입해주고, 실행 환경은 Fargate를 선택해준다.

1-2) 컨테이너 Insight 설정

이 부분은 기호(?)에 맞게 설정하면 되는데, 가능하면 Container insight를 활성화하는 것이 서비스를 운영하는 데에 있어서 정신 건강에 이롭다.
테스트 또는 개발용 Task를 정의하는 것이라면 굳이 활성화할 필요는 없다.

1-3) 클러스터 생성 확인

간단하게 클러스터가 생성된 것을 확인할 수 있다.


2) Task 정의 생성

2-1) Task 정의 이름 입력

2-2) 인스턴스 스펙 설정

OS는 Arm 리눅스를 추천하며, CPU와 메모리는 적당한 값을 입력해준다.
Spring boot 서버를 구동하는 것이라면 개발 환경은 최소 0.5vCPU, 1GB 메모리를 선택해주면 좋고, 운영 환경은 최소 1vCPU, 2GB 메모리를 선택해주면 좋다.
(최.소. 스펙이 이런 것이고, 사실 운영 환경은 여유를 줘서 1.5~2vCPU, 4GB 메모리 정도를 할당해주는 것이 정신 건강에 좋다.)

💡 Docker 이미지를 빌드할 때부터 OS 아키텍처에 맞게 빌드해줘야한다.

2-3) 컨테이너 정보 설정

컨테이너명과 ECR의 URI를 입력해준다. 특정 태그를 지정할 것이라면 해당 태그까지 입력해주면 된다. 태그를 지정하지 않을 경우 latest태그가 자동으로 선택된다.
Spring boot의 기본 포트를 그대로 사용할 것이므로 8080을 포트 매핑해준다.

2-4) 환경 변수 설정

애플리케이션에서 사용하는 환경 변수가 있다면 이 부분에 입력해준다.

2-5) 로그 설정

사실상 필수로 로그 수집 옵션은 활성화해준다.

2-6) Task 정의 생성 확인

Task 정의가 정상적으로 설정된 것을 확인할 수 있다.


3) Service 생성 전, 보안 그룹 생성

3-1) ALB 보안 그룹 생성

3-2) ECS Service가 사용할 보안 그룹 생성

방금 만든 ALB에서 사용할 보안 그룹을 8080포트에 대해서 허용해준다.


4) Service 생성

4-1) Task 정의 지정과 Service 이름 입력

4-2) 용량 공급자 설정

운영 환경에서는 물론 일반 Fargate를 선택하고, 개발 환경에서는 비용 절감을 위해서 Fargate 스팟을 선택할 것을 권장한다.
(그런데 생각보다 Fargate 스팟은 자주 자원 회수가 발생한다. 😅)

4-3) 배포 구성

한 번에 구동할 Task(인스턴스)의 수를 지정하고, 서버 워밍업 시간도 적절히 설정해준다.

4-4) 배포 옵션

배포 전략은 상황에 맞게 정해주면 되고, 배포 실패 감지 옵션은 꼭 활성화해준다.

4-5) 네트워크 설정 ⭐️

정석적으로는 이미지처럼 Private 서브넷에 위치 시키는 게 좋다. 단, Private 서브넷이 외부 솔루션과 통신이 가능하도록 NAT Gateway 또는 VPC 엔드포인트가 생성되어 있어야한다.
만약, Private 서브넷을 사용할 것이 아니라면, Public 서브넷에 위치시키고, 퍼블릭 IP를 활성화해주면 된다.
그리고, 보안 그룹은 위에서 만든 ECS Service용 보안 그룹을 선택해준다.

4-6) 로드 밸런서 설정

로드 밸런서 설정은 ECS Service를 만들 때, 함께 해줘도 되지만 나는 이후에 따로 진행하도록 하겠다.

4-7) 생성 확인

위 과정들을 통해서 Service를 생성하면 이렇게 Task가 정상적으로 실행된 것을 확인할 수 있다.



3. 로드 밸런서 생성


1) Target group 생성

2) Load balancer 생성

로드 밸런서는 ALB로 생성해주고, Internet-facing, Public 서브넷으로 설정해준다.
그리고 방금 만든 Target group을 연동해준다.

3) ECS Service에 ALB 연동

ECS Service 콘솔로 이동한 후, 'Service 업데이트'버튼을 눌러서 로드 밸런서를 연동해주고, 저장해준다.

4) ALB 연동 확인

4-1) 타겟 그룹 확인

잠시 후에 Target group에 ECS Service를 바라보는 대상 데이터가 하나 생성된 것을 확인할 수 있다.

4-2) 로드밸런서 URL 접속

그리고, 당연하게도 로드 밸런서의 URL로 들어가봐도 정상적으로 서버 애플리케이션에 접속되는 것을 확인할 수 있다.



4. 새롭게 배포하기


지금까지는 단순히 세팅하는 것에 대해서 알아보았는데, 이번에는 코드를 새롭게 빌드하여 새로운 이미지를 ECS에 배포하는 방법에 대해서 알아보도록 하겠다.

1) IAM Role 추가

AmazonECS_FullAccess

초반에 생성한 IAM 계정에 위 Role을 추가해준다. (물론, FullAccess 권한보다는 필요한 것들만 각각 할당해주는 것이 좋다. 😅)

2) Github workflow 작성

name: Deploy order service

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

concurrency:
  group: ${{ github.workflow }}

jobs:
  build:
    runs-on: ubuntu-latest

    outputs:
      img-tag: ${{ steps.tag.outputs.this }}
      img-uri: ${{ steps.login-ecr.outputs.registry }}/app-order-service

    steps:
      - name: 컨테이너 tag 를 생성한다
        id: tag
        run: >-
          echo "this=$(date +'%y%m%d%H%M')-${GITHUB_SHA:0:4}"
          >> "$GITHUB_OUTPUT"

      - name: 코드를 받아온다
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: 25
          distribution: corretto
          cache: gradle

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4

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

      - name: Execute Gradle build
        run: ./gradlew app-order:build -x test --stacktrace

      - name: AWS credentials 를 구성한다
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
          aws-region: ap-northeast-2

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

      - name: buildx를 설정한다
        uses: docker/setup-buildx-action@v3

      - name: 컨테이너 이미지를 빌드하고 푸시한다
        env:
          img-uri: ${{ steps.login-ecr.outputs.registry }}/app-order-service
          img-tag: ${{ steps.tag.outputs.this }}
        uses: docker/build-push-action@v4
        with:
          tags: ${{ env.img-uri }}:${{ env.img-tag }}
          push: true
          context: app-order
          file: app-order/Dockerfile
      - name: 이미지 태그를 출력한다
        env:
          img-uri: ${{ steps.login-ecr.outputs.registry }}/app-order-service
          img-tag: ${{ steps.tag.outputs.this }}
        run: |
          echo "# 이미지 태그" >> $GITHUB_STEP_SUMMARY
          echo "tag: \`${{ env.img-tag }}\`" >> $GITHUB_STEP_SUMMARY
          echo "uri: \`${{ env.img-uri }}\`" >> $GITHUB_STEP_SUMMARY

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

    steps:
      - name: AWS credentials 를 구성한다
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
          aws-region: ap-northeast-2

      - name: ECS 태스크 정의를 다운로드한다
        run: |
          aws ecs describe-task-definition \
            --task-definition app-order-service-task \
            --query taskDefinition > task-definition.json

      - name: 새로운 이미지로 태스크 정의를 업데이트한다
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: app-order-service
          image: ${{ needs.build.outputs.img-uri }}:${{ needs.build.outputs.img-tag }}

      - name: ECS 서비스를 배포한다
        uses: aws-actions/amazon-ecs-deploy-task-definition@v2
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: app-order-service
          cluster: app-ecs-fargate-cluster
          wait-for-service-stability: true

      - name: 배포 완료
        run: |
          echo "# ECS 배포 완료" >> $GITHUB_STEP_SUMMARY
          echo "서비스: app-order-service" >> $GITHUB_STEP_SUMMARY
          echo "이미지: ${{ needs.build.outputs.img-uri }}:${{ needs.build.outputs.img-tag }}" >> $GITHUB_STEP_SUMMARY

위에서 작성한 ECR 배포용 Workflow에서 deploy 스텝이 추가된 것이다.

3) 배포 확인

워크플로우를 통해서 ECR에 이미지를 배포하고, 해당 이미지로 ECS Task가 정상적으로 교체된 것을 확인할 수 있다.



0개의 댓글