AWS의 ECS(Elastic Container Service)와 ECR(Elastic Container Registry)를 활용해 CI/CD를 구성하면, Docker 컨테이너 기반의 애플리케이션을 효율적으로 빌드하고 배포할 수 있습니다. 이번 글에서 Spring Boot 애플리케이션을 Docker 이미지로 빌드하여 ECR에 저장하고 저장된 이미지를 ECS를 통해 배포하는 방법을 다루었습니다. ECR과 ECS를 이용한 CI/CD 파이프라인 워크플로의 전체 흐름은 다음과 같습니다.
ECR-ECS 전체 파이프라인 워크플로 흐름
- 코드 변경 → GitHub에 푸시
- GitHub Action 실행
- GitHub 저장소에 코드가 푸시되면 GitHub Actions 워크플로가 실행
- Docker 이미지 빌드 : 빌드된 애플리케이션 JAR 파일을 기반으로 Docker 이미지 생성
- Docker 이미지 ECR에 푸시
- ECR 로그인 : GitHub Actions가 AWS IAM 자격 증명을 사용해 ECR 로그인
- 이미지 태그 지정 및 푸시 : Docker 이미지를 ECR에 푸시
- ECS 태스크 정의 업데이트 :
- ECR에 저장된 새 이미지를 사용하도록 ECS 태스크 정의를 업데이트
- ECR에 저장된 새 이미지를 사용하도록 ECS 태스크 정의를 업데이트
- ECS 서비스 업데이트
- ECS 서비스에 새 태스크 배포 : 최신 태스크 정의로 업데이트하여 최신 애플리케이션 버전이 배포되도록 구성
이때, ECS는 기존 태스크를 안전하게 중단한 후, 새로운 태스크를 실행하여 애플리케이션의 업데이트된 버전을 배포
- ECS 서비스에 새 태스크 배포 : 최신 태스크 정의로 업데이트하여 최신 애플리케이션 버전이 배포되도록 구성
- 애플리케이션 배포 완료
- ECS 클러스터에서 최신 이미지를 기반으로 애플리케이션 실행
1. Docker 이미지 빌드 및 실행
1.1 Dockerfile 작성
FROM amazoncorretto:17
CMD ["./mvnw", "clean", "package"]
COPY ./build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
1.2 Docker 이미지 빌드 및 실행
// Gradle 빌드
./gradlew clean build
// 현재 디렉토리에 있는 Dockerfile을 기반으로 Docker 이미지를 생성
docker build -t <이미지이름> .
// 이미지 생성 여부 확인
docker images
// 생성된 Docker 이미지를 기반으로 컨테이너를 실행
docker run --name cicd -d -p 80:80 cide
2. GitHub Actions를 활용한 CI 구성
2.1 GitHub Secrets 설정
GitHub Repository Settings > Secrets and variables > Actions > New Repository Secret
DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, DOCKERHUB_REPOSITORY 추가
2.2 GitHub Actions 워크플로 파일 작성
.github/workflows/cicd.yaml 작성
name: Java and AWS ECS CICD
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
build-docker-image:
runs-on: ubuntu-latest
steps:
# 소스 코드 가져오기
- uses: actions/checkout@v3
# JDK 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Spring Boot Application Build
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: clean bootJar
# Docker Hub 로그인
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 이미지 빌드 및 푸시
- name: Build and Push Docker Image
run: |
docker build -t ${{ secrets.DOCKERHUB_REPOSITORY }} .
docker push ${{ secrets.DOCKERHUB_REPOSITORY }}
3. AWS ECR 설정
3.1 프라이빗 리포지토리 생성
Amazon ECR 서비스에 접속해서 Private Repository 를 생성합니다.
3.2 IAM 정책 생성
AWS Management Console에서 IAM 서비스로 이동합니다. 정책 메뉴를 선택하고 정책 생성 버튼을 클릭합니다.
정책을 생성하여 GitHub Actions가 ECR에 접근하여 이미지를 업로드할 수 있는 권한을 부여합니다.
<AWS_ACCOUNT_ID>: 자신의 AWS 계정 ID로 변경합니다.
<ECR_REPOSITORY_NAME>: 생성한 ECR 리포지토리 이름으로 변경합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:ap-northeast-2:<AWS_ACCOUNT_ID>:repository/<ECR_REPOSITORY_NAME>"
},
{
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}
정책 이름을 private_image_push로 설정하고 생성 버튼을 클릭합니다.
3.3 IAM 역할에 정책 연결
GitHub Actions가 AWS 리소스에 접근하려면 IAM 역할에 위에서 만든 정책을 연결합니다.
공급자 유형은 OpenID Connect 를 선택합니다.
- OIDC 공급자 URL : https://token.actions.githubusercontent.com 입력
- Audience : sts.amazonaws.com
권한 추가에서 ecs 를 검색해서 AmazonECS_FullAccess 권한을 추가해줍니다.
cicd 이름으로 역할이 생성된 것을 확인할 수 있습니다.
4. AWS ECR로 푸시하도록 GitHub Actions 파일 수정
GitHub Actions를 이용해 이미지를 빌드하고 AWS ECR로 푸시할 수 있도록 cicd.yaml 파일을 수정합니다.
name: Java and AWS ECS CICD
# main 브랜치에 push 발생 시 실행
on:
push:
branches: [ "main" ]
permissions:
id-token: write # OIDC를 통한 AWS 접근 권한 부여
contents: read # 레포지토리 읽기 권한
env:
AWS_REGION: ap-northeast-2 # AWS 리전 설정
ECR_REPOSITORY: cicd # ECR 리포지토리 이름
jobs:
build-and-deploy-to-ecr:
runs-on: ubuntu-latest
steps:
# 1. 소스 코드 가져오기
- name: Checkout code
uses: actions/checkout@v3
# 2. Java 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# 3. Spring Boot 애플리케이션 빌드
- name: Build with Gradle
run: ./gradlew clean build
# 4. AWS 자격 증명 구성
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::<AWS_ACCOUNT_ID>:role/cicd # 생성한 IAM 역할 ARN
aws-region: ${{ env.AWS_REGION }} # AWS 리전 설정
# 5. Amazon ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# 6. Docker 이미지 빌드 및 ECR 푸시
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} # 로그인된 ECR 레지스트리
IMAGE_TAG: ${{ github.sha }} # GitHub SHA 값으로 이미지 태그 생성
run: |
# Docker 이미지 빌드
docker build -t $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG .
# ECR에 이미지 푸시
docker push $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG
# 푸시된 이미지 출력
echo "Image pushed to: $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG"
푸시된 이미지는 ECR 콘솔에서 확인할 수 있습니다.
5. ECS 클러스터 생성
ECS(Elastic Container Service) 클러스터는 컨테이너 오케스트레이션을 제공하며, ECR에서 이미지를 가져와 컨테이너를 실행하는 데 사용됩니다. AWS Management Console에서 ECS를 검색하여 이동합니다.
클러스터 생성
- 클러스터 템플릿 선택: “Networking only (Fargate)” 옵션을 선택
- Fargate는 AWS가 관리하는 서버리스 방식의 컴퓨팅 옵션으로, 서버 관리를 생략할 수 있습니다.
- 클러스터 이름 입력
Amazon CloudWatch Container Insights는 ECS 클러스터에서 실행되는 컨테이너의 성능 데이터를 수집하고 시각화합니다.
Grafana , Prometheus 와 같은 기능을 하며 간단한 설정만으로 ECS 환경의 성능 상태를 실시간으로 모니터링할 수 있습니다.
하지만 더 높은 성능의 컴퓨팅 리소스가 할당되어 비용이 증가하여 우선 모니터링에 대한 설정을 하지 않고 지나갑니다.
생성 완료 후 클러스터 상세 페이지로 이동하면 생성된 클러스터 정보를 확인할 수 있습니다.
6. Task 정의 생성
Task 는 ECS에서 컨테이너를 실행하기 위한 구성 정보를 담고 있습니다. 여기에는 컨테이너 이미지, 리소스 설정(CPU/메모리), 네트워크 설정 등이 포함됩니다.
6.1 Task 정의
- Task 정의 패밀리 이름 설정
- Launch type 선택 : AWS Fargate
컨테이너 정의
- 컨테이너 이름: nginx
- 이미지: 도커 허브( Docker Hub) 에 있는 공식 nginx 이미지 사용
7. 서비스 생성
Service는 ECS에서 Task를 원하는 수만큼 실행하고 이를 지속적으로 유지(자동 복구)할 수 있도록 관리하는 데 사용됩니다. 또한, 로드 밸런서를 통해 인터넷과 연결합니다.
7.1 Service 생성
ECS 콘솔 > 클러스터 선택(ECS-CICD-Cluster) > Create Service 클릭
Service 유형
- 컴퓨팅 옵션 : 시작 유형
- Launch type: “Fargate”.
- Task 정의: 이전에 생성한 ECS-CICD-Task의 최신 버전 선택.
Service 세부 설정
- Number of tasks: 3.
최소 두 개 이상의 컨테이너를 실행하여 가용성을 높입니다. 여기서는 3개로 설정하였습니다.
네트워크 설정
- Cluster VPC: 기본 VPC 선택.
- Subnet: 퍼블릭 서브넷 선택.
- 보안 그룹 선택 : default 기본 보안 그룹을 사용하고, 해당 보안 그룹에는 인바운드 규칙에 HTTPS(443), HTTP(80) 포트가 열려있어야 합니다.
AWS ECS(Elastic Container Service)를 사용하여 애플리케이션을 배포할 때, 애플리케이션의 성격과 사용 목적에 따라 서비스(Service)와 태스크(Task) 중 하나를 선택할 수 있습니다.
사용자가 애플리케이션을 지속적으로 이용할 필요가 있는 상황에서는 서비스를 선택하고
서비스처럼 지속적으로 실행할 필요가 없는 경우 태스크를 선택합니다.
7.2 로드밸런서 구성
로드 밸런서 연결
- Load balancer type: “Application Load Balancer”. 선택
- 새 로드밸런서 생성
- 로드밸런서 이름
Tasks 탭에서 실행 중인 태스크를 확인합니다.
태스크의 구성을 클릭합니다. 브라우저에서 http://<퍼블릭 IP>를 입력하여 애플리케이션 확인합니다.
Welcome to Nginx! 메시지가 나타나면 성공적으로 컨테이너가 실행 중임을 확인할 수 있습니다.
로드 밸런서를 사용하여 하나의 고정된 주소로 트래픽을 처리할 수 있습니다. Load Balancers 메뉴에서 생성된 로드 밸런서를 선택하고
Description 탭에서 DNS Name 확인합니다.
Welcome to Nginx! 페이지가 표시되면 성공적으로 연결된 것입니다.
8. 태스크 업데이트
AWS ECS에서 태스크(Task)는 컨테이너의 정의를 포함하는 실행 단위입니다. 새로운 애플리케이션 버전이나 환경 설정이 필요할 때, 태스크 정의(Task Definition)를 업데이트하고 이를 서비스에 적용하여 다운타임 없이 최신 버전으로 교체합니다.
아래 과정은 ECS 태스크 정의를 업데이트하고, 이를 서비스에 적용하여 기존 태스크를 새 버전으로 교체하는 과정입니다.
8.1 새 계정 생성
8.2 업데이트
클러스터에서 Services를 선택하고 업데이트할 서비스를 선택한 뒤 Update 버튼을 클릭합니다.
Task Definition 섹션에서 새로 생성한 최신 개정을 선택합니다.
8.3 태스크 업데이트 후 결과 확인
실행 중인 태스크의 개수가 원하는 대로 늘어나고, 새 태스크 정의를 사용하고 있는지 확인합니다.
기존 태스크를 순차적으로 종료하며 리소스를 회수하고 최신 태스크 정의로 새로운 태스크를 생성하고 실행합니다.
AWS ECS의 태스크 업데이트는 애플리케이션의 새로운 버전 배포 또는 설정 변경 시 필수적인 작업입니다. 롤링 업데이트 방식으로 다운타임 없이 서비스를 유지할 수 있으며, 로드 밸런서와 퍼블릭 IP를 통해 쉽게 결과를 확인할 수 있습니다. 이를 CI/CD 파이프라인과 통합하면 자동화된 배포 환경을 구축할 수 있어 더욱 효율적인 운영이 가능합니다.
9. 태스크 정의 업데이트
위에서는 AWS 콘솔 UI를 통해서 수동으로 태스크 정의를 업데이트하였습니다. 이제 위에서 진행한 태스크 정의 업데이트를 GitHub Actions 워크플로를 사용하여 자동화된 방식으로 코드 변경 시 태스크 정의를 업데이트하고 ECS 서비스에 배포하도록 해보겠습니다.
- ECS 서비스: Docker 컨테이너를 안정적으로 실행하고 관리하는 역할
- 태스크 정의(Task Definition): ECS에서 실행할 컨테이너의 설정(이미지, CPU/메모리, 네트워크 정책 등) 정의
이미지가 변경되면 이를 ECS 서비스에서 반영하기 위해 태스크 정의를 수정해야 합니다. 새 태스크 정의를 적용하면 ECS 서비스는 기존 태스크를 중지하고 새로운 태스크를 실행합니다.
9.1 워크플로 개요
- 빌드 단계: Spring Boot 애플리케이션을 빌드하고 Docker 이미지를 생성합니다.
- 푸시 단계: 이미지를 ECR에 푸시합니다.
- 배포 단계: ECR 이미지를 기반으로 태스크 정의를 업데이트하고, ECS 서비스에 새로운 태스크를 배포합니다.
9.2 GitHub Actions 워크플로 파일 수정
태스크 정의는 ECS가 실행할 컨테이너 설정입니다. 업데이트된 이미지를 반영하려면 새로운 태스크 정의를 생성해야 합니다. JSON 파일을 다운로드 받아서 spring boot 프로젝트에 해당 JSON 파일을 넣어줍니다.
- 기존 JSON 태스크 정의 파일에서 image 부분은 비워둡니다. (GitHub Actions에서 자동으로 채워줍니다.
cicd.yaml 파일 수정
# 7. 태스크 정의 업데이트
- name: Update ECS Task Definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.TASK_DEFINITION_FILE }}
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ env.image }}
10. ECS 서비스에 태스크 배포
지금까지 태스크 정의를 업데이트하는 단계까지 진행하였습니다. 이제 마지막으로 ECS 서비스에 새 태스크를 배포하는 단계인 ECS 서비스 배포를 추가해야 합니다. CI/CD 파이프라인의 마지막 단계로, 배포된 이미지를 기반으로 서비스가 자동으로 갱신됩니다.
10.1 GitHub Actions 워크플로 파일 수정
GitHub Actions 워크플로 파일에 ECS 서비스에 태스크를 배포하는 코드를 추가해줍니다.
cicd.yaml 파일 수정
# ECS 서비스에 태스크 배포
- name: Deploy Amazon ECS Task Definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.SERVICE_NAME }}
cluster: ${{ env.CLUSTER_NAME }}
wait-for-service-stability: true
전체 cicd.yaml 파일 코드
name: Java and AWS ECS CICD
on:
push:
branches: [ "main" ]
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-northeast-2
ECR_REPOSITORY: <REPOSITORY_NAME>
CLUSTER_NAME: <ECS_CLUSTER_NAME>
SERVICE_NAME: <SERVICE_NAME>
TASK_FAMILY: <TASK_FAMILY>
CONTAINER_NAME: <CONTAINER_NAME>
TASK_DEFINITION_FILE: ./<TASK_DEFINITION_FILE>
jobs:
build-and-deploy-to-ecr:
runs-on: ubuntu-latest
steps:
# 소스 코드 가져오기
- name: Checkout code
uses: actions/checkout@v3
# JDK 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Spring Boot Application Build
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: clean bootJar
# AWS 자격 증명 구성
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::<AWS_ACCOUNT_ID>:role/<ROLE_NAME>
role-session-name: cicdSession
aws-region: ${{ env.AWS_REGION }}
# ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# 이미지 빌드 및 푸시
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
# Docker 이미지 빌드
docker build -t $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG .
# ECR에 이미지 푸시
docker push $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG
# 푸시된 이미지 정보 출력
echo "image=$ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG" >> $GITHUB_ENV
# 태스크 정의 수정
- name: Update ECS Task Definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.TASK_DEFINITION_FILE }}
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ env.image }}
# ECS 서비스에 태스크 배포
- name: Deploy Amazon ECS Task Definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.SERVICE_NAME }}
cluster: ${{ env.CLUSTER_NAME }}
wait-for-service-stability: true
'BackEnd > DEVOPS' 카테고리의 다른 글
[ArgoCD] ArgoCD 설치 (1) | 2024.11.15 |
---|---|
[Kubernetes] Master Node, Worker Node 연결 (0) | 2024.11.15 |
[Kubernetes] Spring Boot 애플리케이션 배포: CNI, SDN, NodePort 서비스 설정 (1) | 2024.10.29 |
[Kubernetes] 쿠버네티스 클러스터 구성 및 관리 (0) | 2024.10.29 |
[Docker] Dockerfile 작성 및 이미지 빌드 (5) | 2024.10.16 |