ECS를 이용한 Blue/Green 무중단 배포 구성하기 (7) - Github Actions를 이용한 배포 자동화 구축
작성 일자 : 2024년 8월 22일
시리즈 순서
- 프로젝트 생성과 ECR 리포지토리
- VPC 생성 및 NAT Instance를 이용한 인터넷 연결
- Route 53 Hosted Zone 생성 및 ACM SSL 인증서 발급
- 애플리케이션 로드 밸런서(ALB) 생성
- ECS를 이용한 컨테이너 배포
- CodeDepoly를 이용한 Blue/Green 무중단 배포 테스트
- Github Actions를 이용한 배포 자동화 구축 (👈 지금 보고 있는 포스트)
1. Github Actions의 전체적인 흐름
Github Actions를 통한 무중단 배포의 전체적인 흐름은 아래와 같습니다:
2. GithubActions를 통해 배포할 새로운 User 생성
먼저, AWS의 IAM에서 새로운 사용자를 생성합니다.
Github Actions가 트리거 되면 배포 스크립트에서는 핵심적으로 도커 이미지를 빌드해서 ECR에 푸시하고, CodeDeploy를 통해 ECS에 배포하는 과정을 거치게 됩니다.
때문에, ECR에 대한 권한과 CodeDeploy에서 ECS에 배포하는 권한을 가진 사용자를 생성하겠습니다.
2-1. GithubActionCodeDeployPolicy 생성
생성될 사용자에게 부여할 CodeDeploy 정책을 생성하겠습니다.
- AWS Management Console에서 IAM > Policies로 이동합니다.
Create policy
버튼을 클릭합니다.Policy editor
에서 우측에 있는JSON
버튼을 클릭한 후, 아래와 같이 작성합니다. 주의해야 할 점은 괄호("<>")로 명시된 부분은 자신의 환경에 맞게 수정해주어야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DescribeTaskDefinition",
"Effect": "Allow",
"Action": [
"ecs:DescribeTaskDefinition"
],
"Resource": "*"
},
{
"Sid": "RegisterTaskDefinition",
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition"
],
"Resource": "*"
},
{
"Sid": "PassRolesInTaskDefinition",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::<자신의 Account ID>:role/ecsTaskExecutionRole"
]
},
{
"Sid": "DeployService",
"Effect": "Allow",
"Action": [
"ecs:DescribeServices",
"codedeploy:GetDeploymentGroup",
"codedeploy:CreateDeployment",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
],
"Resource": [
"arn:aws:ecs:ap-northeast-2:<자신의 Account ID>:service/<자신의 ECS 클러스터 명>/<자신의 ECS 서비스 명>",
"arn:aws:codedeploy:ap-northeast-2:<자신의 Account ID>:deploymentgroup:<자신의 CodeDeploy Application 명>/<자신의 CodeDeploy Deployment group 명>",
"arn:aws:codedeploy:ap-northeast-2:<자신의 Account ID>:deploymentconfig:*",
"arn:aws:codedeploy:ap-northeast-2:<자신의 Account ID>:application:<자신의 CodeDeploy Application 명>"
]
}
]
}
<자신의 Account ID>
: AWS Console에서 우측 상단의 사용자 이름을 클릭하면 나오는 드롭다운에서 확인 가능합니다.<자신의 ECS 클러스터 명>
: ECS > Clusters에서 자신이 생성한 클러스터의 이름<자신의 ECS 서비스 명>
: ECS > Clusters > 자신이 생성한 클러스터 > Services에서 자신이 생성한 서비스의 이름<자신의 CodeDeploy Application 명>
: CodeDeploy > Applications에서 자동으로 생성된 Application의 이름<자신의 CodeDeploy Deployment group 명>
: CodeDeploy > Applications > 생성된 Application > Deployment group의 이름
치환된 json 예시
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DescribeTaskDefinition",
"Effect": "Allow",
"Action": [
"ecs:DescribeTaskDefinition"
],
"Resource": "*"
},
{
"Sid": "RegisterTaskDefinition",
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition"
],
"Resource": "*"
},
{
"Sid": "PassRolesInTaskDefinition",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::730335408691:role/ecsTaskExecutionRole"
]
},
{
"Sid": "DeployService",
"Effect": "Allow",
"Action": [
"ecs:DescribeServices",
"codedeploy:GetDeploymentGroup",
"codedeploy:CreateDeployment",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
],
"Resource": [
"arn:aws:ecs:ap-northeast-2:730335408691:service/ecsdeploy-ECS-Cluster/ecsdeploy-ECS-Service",
"arn:aws:codedeploy:ap-northeast-2:730335408691:deploymentgroup:AppECS-ecsdeploy-ECS-Cluster-ecsdeploy-ECS-Service/DgpECS-ecsdeploy-ECS-Cluster-ecsdeploy-ECS-Service",
"arn:aws:codedeploy:ap-northeast-2:730335408691:deploymentconfig:*",
"arn:aws:codedeploy:ap-northeast-2:730335408691:application:AppECS-ecsdeploy-ECS-Cluster-ecsdeploy-ECS-Service"
]
}
]
}
Next
버튼을 클릭하고Policy name
에GithubActionCodeDeployPolicy
를 입력한 후Create policy
버튼을 클릭합니다.
2-2. 새로운 User 생성
- AWS Management Console에서 IAM > Users로 이동합니다.
Create user
버튼을 클릭합니다.User details
>User name
에ecsdeploy-github-action-deploy-user
를 입력합니다.
Next
를 클릭하고Permissions options
에서Attach policies directly
를 선택합니다.- 정책으로는
AmazonEC2ContainerRegistryFullAccess
와 위에서 생성한GithubActionCodeDeployPolicy
를 선택합니다.
Next
를 클릭한 후,Permissions summary
에 두 정책이 모두 추가되어 있는지 확인합니다.Create user
를 클릭합니다.
2-3. Access Key 생성
이번 단계의 마지막으로 새로 생성한 사용자의 Access Key를 생성합니다.
- 생성한 사용자를 클릭한 후,
Security credentials
탭으로 이동합니다. Access keys
>Create access key
를 클릭합니다.Use case
에서Third-party service
를 선택하고 하단의Confirmation
을 체크한 후Next
를 클릭합니다.
Create Access key
를 클릭한 후, 생성된 Access Key ID와 Secret Access Key를 잘 기록해둡니다.
3. application.yml
값을 잘 불러오는지 테스트 준비
보통 application.yml
에 저장되어 있는 중요한 값들은 리포지토리에 올리지 않고 사용합니다.
때문에 Github Actions에서 이미지를 빌드할 때, application.yml
생성하고 빌드할 수 있도록 저희는 Repository secrets
를 사용할 것입니다.
배포가 완료되고, application.yml
값들이 잘 불러와지는지 확인하기 위해 application.yml
을 생성하고, 이를 메인페이지에서 출력하는 간단한 코드를 작성해보겠습니다.
src/main/resources
디렉토리에application.yml
파일을 생성합니다.application.yml
에 아래와 같이 값을 입력합니다.
test-value: ThisIsTestValue
IndexController.java
파일에서는 아래와 같이 코드를 수정합니다. 간단하게 Value 어노테이션을 사용하여application.yml
에 저장된 값을 불러와서 출력하는 코드입니다.
@Controller
public class IndexController {
@Value("${test-value}")
private String testValue;
@GetMapping("/")
@ResponseBody
public String index() {
return "This is Green Deployment! " + testValue;
}
}
- 애플리케이션을 실행하고,
localhost:8080
으로 접속하여This is Green Deployment! ThisIsTestValue
가 출력되는지 확인합니다.
4. Github Actions를 배포 스크립트 작성
다음으로는 리포지토리의 main
브랜치에 새로운 코드가 푸시되면 트리거되는 Github Actions 배포 스크립트를 작성해보겠습니다.
4-1. deploy.yml
파일 작성
- 스프링부트 프로젝트
.github/workflows
디렉토리에deploy.yml
파일을 생성합니다. deploy.yml
파일에 아래와 같이 코드를 작성합니다.
name: ECS Blue/Green Deployment
on:
push:
branches: [ "main" ]
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
ECS_TASK_DEFINITION: ${{ secrets.ECS_TASK_DEFINITION }}
CONTAINER_NAME: ${{ secrets.CONTAINER_NAME }}
CODEDEPLOY_APPLICATION: ${{ secrets.CODEDEPLOY_APPLICATION }}
CODEDEPLOY_DEPLOYMENT_GROUP: ${{ secrets.CODEDEPLOY_DEPLOYMENT_GROUP }}
jobs:
ecs-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Make application.yml
run: |
mkdir -p ./src/main/resources
touch ./src/main/resources/application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Test with Gradle
run: ./gradlew clean test
- name: Build with Gradle
run: ./gradlew clean build -x test
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- 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 build --build-arg JAR_FILE=build/libs/\*.jar -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Download Amazon ECS task definition
run: |
aws ecs describe-task-definition --task-definition $ECS_TASK_DEFINITION --query taskDefinition > task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- 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.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
codedeploy-appspec: appspec.yml
codedeploy-application: ${{ env.CODEDEPLOY_APPLICATION }}
codedeploy-deployment-group: ${{ env.CODEDEPLOY_DEPLOYMENT_GROUP }}
각 부분 설명
name: ECS Blue/Green Deployment
on:
push:
branches: [ "main" ]
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
ECS_TASK_DEFINITION: ${{ secrets.ECS_TASK_DEFINITION }}
CONTAINER_NAME: ${{ secrets.CONTAINER_NAME }}
CODEDEPLOY_APPLICATION: ${{ secrets.CODEDEPLOY_APPLICATION }}
CODEDEPLOY_DEPLOYMENT_GROUP: ${{ secrets.CODEDEPLOY_DEPLOYMENT_GROUP }}
name
: Github Actions의 이름을 설정합니다.on
: 트리거 이벤트를 설정합니다. 여기서는main
브랜치에 푸시되었을 때만 트리거됩니다.env
: 환경 변수를 설정합니다. 환경 변수는Repository secrets
에 저장된 값을 사용합니다.
jobs:
ecs-deploy:
runs-on: ubuntu-latest
jobs
: 작업을 설정합니다. 여기서는ecs-deploy
라는 작업을 설정합니다.runs-on
: 작업을 실행할 환경을 설정합니다. 여기서는ubuntu-latest
를 사용합니다.
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
steps
: 작업의 단계를 설정합니다.Checkout
: 코드를 Github Actions가 트리거된 시점의 코드로 Checkout합니다.Set up JDK 17
: JDK 17을 Github Actions 환경에 설치합니다.
- name: Make application.yml
run: |
mkdir -p ./src/main/resources
touch ./src/main/resources/application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml
Make application.yml
:APPLICATION_YML
에 저장된 값을application.yml
파일로 생성합니다.
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Test with Gradle
run: ./gradlew clean test
- name: Build with Gradle
run: ./gradlew clean build -x test
Grant execute permission for gradlew
:gradlew
파일에 실행 권한을 부여합니다.Test with Gradle
: Gradle을 사용하여 테스트를 실행합니다. (테스트 코드가 없다면 생략 가능)Build with Gradle
: Gradle을 사용하여 JAR 파일 빌드를 실행합니다.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
Configure AWS credentials
: AWS 계정 정보를 설정합니다. (위에서 생성한 User의 Access Key ID, Secret Access Key와 Region)Login to Amazon ECR
: Amazon ECR에 로그인합니다.
- 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 build --build-arg JAR_FILE=build/libs/\*.jar -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
Build, tag, and push image to Amazon ECR
: Docker 이미지를 빌드하고 Amazon ECR에 푸시합니다.IMAGE_TAG
: Github Actions가 실행된 시점의 SHA 값을 사용합니다.echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
: Github Actions의 출력에 이미지 정보를 추가합니다. (다음 단계에서 사용)
- name: Download Amazon ECS task definition
run: |
aws ecs describe-task-definition --task-definition $ECS_TASK_DEFINITION --query taskDefinition > task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- 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.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
codedeploy-appspec: appspec.yml
codedeploy-application: ${{ env.CODEDEPLOY_APPLICATION }}
codedeploy-deployment-group: ${{ env.CODEDEPLOY_DEPLOYMENT_GROUP }}
Download Amazon ECS task definition
: 기존 ECS의 Task Definition을 다운로드합니다.Fill in the new image ID in the Amazon ECS task definition
: 새로운 이미지 ID를 Task Definition에 적용해 수정합니다. (이미지 정보는 이전 단계에서 Github Actions의 출력에 저장되었습니다.)Deploy Amazon ECS task definition
: CodeDeploy를 사용하여 ECS에 새로운 Task Definition을 배포합니다.
4-2. appspec.yml
작성
appspec.yml
은 CodeDeploy에서 사용하는 파일로, 배포할 애플리케이션의 정보를 정의합니다.
스프링부트 프로젝트 루트 경로에 appspec.yml
파일을 생성한 후, 아래 내용을 추가합니다.
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "<TASK_DEFINITION>"
LoadBalancerInfo:
ContainerName: "<자신의 컨테이너 이름>"
ContainerPort: 8080
<TASK_DEFINITION>
: 해당 부분은 변경할 필요가 없습니다. CodeDeploy에서 task definition의 arn을 자동으로 채웁니다.<자신의 컨테이너 이름>
:Repository secrets
의CONTAINER_NAME
과 동일한 값으로, 배포할 컨테이너의 이름을 입력합니다. 저는 시리즈 5편의ECS Task Definition 생성
파트에서 설정한 기존의 컨테이너 이름인ecsdeploy-ECS-Container
를 사용했습니다. (ECS > Task definitions > 자신의 Task Definition > JSON 탭에서 기존에 설정한 컨테이너 이름을 확인 가능합니다.)
치환된 appspec.yml
예시
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "<TASK_DEFINITION>"
LoadBalancerInfo:
ContainerName: "ecsdeploy-ECS-Container"
ContainerPort: 8080
5. Repository Secrets 설정
작성한 deploy.yml
과 appspec.yml
파일을 리포지토리에 푸시하기 이전에, Github Actions에서 사용할 환경 변수를 설정해야 합니다.
- 자신의 Github Repository로 이동합니다.
- 해당 리포지토리에서 Settings > 좌측 Secrets and variables > Actions 탭으로 이동합니다.
- Repository secrets에서
New repository secret
을 클릭합니다. - 총 11개의 Secrets를 아래와 같이 추가합니다.
AWS_REGION
: 배포할 AWS 리전(가동중인 ECS Blue 배포 리전)을 입력합니다. (예: ap-northeast-2)ECR_REPOSITORY
: ECR > Repositories > 자신의 Repository nameECS_CLUSTER
: ECS > Clusters > 자신의 Cluster nameECS_SERVICE
: ECS > Clusters > 자신의 Cluster > Services > 자신의 Service nameECS_TASK_DEFINITION
: ECS > Task definitions > 자신의 Task Definition 명CONTAINER_NAME
: ECS > Task definitions > 자신의 Task Definition > JSON 탭에서 기존에 설정한 컨테이너 이름 (또는 새로운 컨테이너 이름)CODEDEPLOY_APPLICATION
: CodeDeploy > Applications에서 자동으로 생성된 Application의 이름CODEDEPLOY_DEPLOYMENT_GROUP
: CodeDeploy > Applications > 생성된 Application > Deployment group의 이름AWS_ACCESS_KEY_ID
: 새로 생성한 IAM User의 Access key IDAWS_SECRET_ACCESS_KEY
: 새로 생성한 IAM User의 Secret access keyAPPLICATION_YML
:application.yml
파일 내용 입력 (테스트를 위해서 이전과 다르게 수정하겠습니다.)test-value: ThisIsTestValue1234
- 추가된 모습은 아래와 같습니다.
AWS_REGION
:ap-northeast-2
ECR_REPOSITORY
:gerrymandering-ecsdeploy
ECS_CLUSTER
:ecsdeploy-ECS-Cluster
ECS_SERVICE
:ecsdeploy-ECS-Service
ECS_TASK_DEFINITION
:ecsdeploy-ECS-TD
CONTAINER_NAME
:ecsdeploy-ECS-Container
CODEDEPLOY_APPLICATION
:AppECS-ecsdeploy-ECS-Cluster-ecsdeploy-ECS-Service
CODEDEPLOY_DEPLOYMENT_GROUP
:DgpECS-ecsdeploy-ECS-Cluster-ecsdeploy-ECS-Service
AWS_ACCESS_KEY_ID
:ThisIsAccessKeyID1234
AWS_SECRET_ACCESS_KEY
:ThisIsSecretAccessKey1234
APPLICATION_YML
:test-value: ThisIsTestValue1234
6. Github Actions 실행
모든 설정이 완료되었으면, main
브랜치에 푸시하여 Github Actions를 실행합니다.
- 푸시한 뒤 Github 리포지토리로 이동하여 아래와 같이 Github Actions가 실행되는 것을 확인합니다.
- 갈색 원을 클릭하거나, Actions 탭으로 이동하여 실행 중인 Workflow를 확인합니다.
- 아래와 같이 Workflow가 성공적으로 완료되었음을 확인합니다.
- AWS Management Console > CodeDeploy > Deployments로 이동하여, 새로운 배포가 생성되었음을 확인합니다.
- 배포가 완료되면, 자신의 도메인에 접속하여 배포된 애플리케이션을 확인합니다.
이전의 배포 모습
새로운 배포 모습
Repository sercrets
의APPLICATION_YML
에 입력한 내용인test-value: ThisIsTestValue1234
가 정상적으로 반영되었음을 확인할 수 있습니다.
이번 시리즈를 작성하고 aws-cdk와 Terraform을 찾아보기 시작했습니다 🤣
감사합니다!