summary
Docker compose 로 만든 어플리케이션을 AWS 환경에 지속적으로 통합, 배포하는 방법에 대해 알아본다.
AWS ECS에 docker 컨테이너를 배포 및 관리 두 번째 포스팅이다.
만약 이전 포스팅을 확인하지 못했다면 포스팅 링크를 통해 확인하고 본 포스팅을 읽기 바란다.
*이전 포스팅에서 만들었던 guest-book 어플리케이션을 가지고 진행한다.
*전체 소스코드는 링크에서 확인할 수 있다.
*reference
concept
AWS 환경에 docker 어플리케이션을 배포하는 컨셉은 다음과 같다.
- 기본 인프라를 구축한다.
- VPC 등 인터넷 관련 인프라
- ECS를 활용한 컨테이너 오케스트레이션 인프라
- Code Pipeline을 구축한다.
- github 레포지토리의 master 브랜치에 변동사항이 생긴다.
- Code Build가 docker의 각 서비스의 이미지를 이미지화 하고, 빌드된 이미지를 ECR에 업로드한다.
- compose 파일을 CloudFormation 파일로 변환한다.
- 기존 어플리케이션 인프라와 비교해 Change Set을 생성한다.
- 수동 승인 과정을 거쳐 ECS에 배포된다.
본 포스팅에서는 두 번째 항목인 Code Pipeline 구축에 대해서 다룬다.
사전 준비물
파이프라인을 클라우드 포메이션으로 생성하기 위해 아래와 같은 사전 준비물이 필요하다.
- github personal access token
- 각 도커 서비스의 베이스 이미지를 저장하는 ECR
- infra cloudformation을 통해 생성한 리소스
- vpc id
- ELB arn
- ECS cluster arn
각 준비물을 어떻게 준비하는지 확인해보자.
github personal access token
github personal access token은 파이프라인에서 브랜치의 변동사항을 감지하기 위해 필요하다.
github의 공식문서를 통해 발급하고 값을 따로 기억해두자.
(code pipeline 에서는 repo 항목의 권한만 있으면 된다)

설정 후 generate버튼을 눌러 토큰을 발급하고, 값을 따로 저장해 둔다.
베이스 이미지 ECR
프로젝트의 파이프라인 중 build 섹션에서는 아래와 같은 작업을 수행한다.
- 각 도커 서비스 디렉토리에 접근한다.
- 디렉토리의 Dockerfile을 통해 이미지를 생성한다.
- 생성된 이미지를 ECR에 저장한다.
이 때 Docker 파일에 저장된 베이스 이미지를 pull 한 뒤 레이어를 쌓아 서비스의 이미지를 생성하게 되는데, 이 베이스 이미지를 dockerhub가 아닌 ECR에서 땡겨온다.
이는 pipeline에서 사용하는 docker context의 타입이 ecs 타입이기 때문이다.
ecs 타입의 docker context를 사용하는 경우 docker pull
실행시 dockerhub가 아닌 aws ECR 에서 이미지를 떙겨온다.
이를 위해 기존에 베이스 이미지로 사용하던 이미지를 dockerhub로 부터 pull 받아 ECR로 업로드 해야한다.
guest-book-app에서 사용하는 redis 이미지를 pull 받은 뒤, ECR에 업로드 해 보자.

먼저 docker pull redis:latest
명령어로 redis의 최신 이미지를 pull 받자.
pull 을 받은 뒤, aws 콘솔에 접속하여 ECR 으로 접속하여 redis라는 이름으로 private 레포지토리를 생성한다.
생성 한 뒤 콘솔에서 레포지토리의 url을 복사헤두자.

다시 터미널로 돌아와 이전에 pull 받은 redis:latest 이미지를 docker tag 커맨드를 통해 ECR 형식으로 다시 태깅해주자.
docker tag redis:latest ${ECR 레포지토리 url}/redis:latest
태깅이 완료되면 프라이빗 레지스트리 인증을 통해 ECR 에 대해 도커 클라이언트 인증을 진행한다.
본 인증은 12시간 동안 유효하다는 점도 참고하자.

로그인이 완료되면 docker push ${ECR 레포지토리 url}/redis:latest
명령어를 통해 ECR에 redis:latest 이미지를 푸쉬하자.

성공적으로 push가 완료되면 콘솔에서 위와 같이 확인 할 수 있다.
(frontend, backend 서비스에서 사용하는 node 이미지도 같은 방식으로 업로드 하면된다)
생성된 인프라 리소스 id, arn 확인
우리가 만들 code pipeline에서 기존에 만든 infra resource를 사용할 것이기 때문에 아래의 리소스 정보가 필요하다.
- vpc id
- ELB arn
- ECS cluster arn
리소싀 정보는 aws cloudformation 콘솔에서 확인할 수 있다.
이전에 생성한 guest-book-compose-infra 에 들어가서 출력항목을 확인하자.

(ECS cluster arn은 콘솔에서 해당 리소스에 접근하여 확인하도록 한다)
프로젝트의 루트 디렉토리에 /pipeline/cloudformation.yaml 파일을 생성한다.
AWSTemplateFormatVersion: "2010-09-09"
Description: Change Set CodeDeploy Pipeline
Parameters:
GitHubOAuthToken:
Type: String
NoEcho: true
GitHubOwner:
Type: String
AllowedPattern: "[A-Za-z0-9-]+"
GitHubRepo:
Type: String
AllowedPattern: "[A-Za-z0-9-]+"
GitHubBranch:
Type: String
Default: master
AllowedPattern: "[A-Za-z0-9-]+"
ApplicationStackName:
Description: Name of the not yet created Application CFN Stack
Type: String
Default: guest-book-app
AllowedPattern: (^[a-z][a-z0-9-]+$)
ChangeSetName:
Description: Name of Cfn Change Set
Type: String
Default: guest-book-app-change-set
AllowedPattern: (^[a-z][a-z0-9-]+$)
ExistingAwsVpc:
Description: Name of existing AWS Vpc
Type: AWS::EC2::VPC::Id
ExistingLoadbalancer:
Description: ARN of existing Application Loadbalancer
Type: String
ExistingEcsCluster:
Description: Name of existing Ecs Cluster
Type: String
NodeECRArn:
Description: Node ECR arn
Type: String
NginxECRArn:
Description: Nginx ECR arn
Type: String
RedisECRArn:
Description: Redis ECR arn
Type: String
Resources:
GuestBookEcr:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Join
- "-"
- - !Sub '${AWS::StackName}'
- 'ecr'
PipelineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Version: "2012-10-17"
PipelineRoleDefaultPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: PipelineRoleDefaultPolicy
Roles:
- Ref: PipelineRole
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:DeleteObject*
- s3:PutObject*
- s3:Abort*
Effect: Allow
Resource:
- Fn::GetAtt:
- PipelineArtifactsBucket
- Arn
- Fn::Join:
- ""
- - Fn::GetAtt:
- PipelineArtifactsBucket
- Arn
- /*
- Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
Effect: Allow
Resource:
- Fn::GetAtt:
- ExtractBuild
- Arn
- Fn::GetAtt:
- ImageBuild
- Arn
- Action:
- cloudformation:CreateChangeSet
- cloudformation:DescribeStacks
- cloudformation:DescribeChangeSet
- cloudformation:ExecuteChangeSet
Effect: Allow
Resource:
- !Join
- ""
- - "arn:aws:cloudformation:"
- Ref: AWS::Region
- ":"
- Ref: AWS::AccountId
- ":stack/"
- Ref: ApplicationStackName
- "/*"
- Action:
- iam:PassRole
Effect: Allow
Resource:
- !GetAtt [ExtractBuildRole, Arn]
ImageBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Version: "2012-10-17"
ImageBuildRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: ImageBuildRoleDefaultPolicy
Roles:
- Ref: ImageBuildRole
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:PutObject*
Effect: Allow
Resource:
- Fn::GetAtt:
- PipelineArtifactsBucket
- Arn
- Fn::Join:
- ""
- - Fn::GetAtt:
- PipelineArtifactsBucket
- Arn
- /*
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource:
- Fn::Join:
- ""
- - "arn:"
- Ref: AWS::Partition
- ":logs:"
- Ref: AWS::Region
- ":"
- Ref: AWS::AccountId
- :log-group:/aws/codebuild/
- Ref: ImageBuild
- :*
- Action:
- ecr:GetAuthorizationToken
Effect: Allow
Resource:
- "*"
- Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
- ecr:BatchCheckLayerAvailability
Effect: Allow
Resource:
- !Ref NodeECRArn
- !Ref NginxECRArn
- !Ref RedisECRArn
- Action:
- ecr:BatchCheckLayerAvailability
- ecr:CompleteLayerUpload
- ecr:InitiateLayerUpload
- ecr:PutImage
- ecr:UploadLayerPart
Effect: Allow
Resource:
- !GetAtt GuestBookEcr.Arn
ExtractBuildRole:
Type: AWS::IAM::Role
Properties:
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: codebuild.amazonaws.com
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Version: "2012-10-17"
ExtractBuildRoleDefaultPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: ExtractBuildRoleDefaultPolicy
Roles:
- Ref: ExtractBuildRole
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource:
- Fn::Join:
- ""
- - "arn:"
- Ref: AWS::Partition
- ":logs:"
- Ref: AWS::Region
- ":"
- Ref: AWS::AccountId
- :log-group:/aws/codebuild/
- Ref: ExtractBuild
- :*
- Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:PutObject*
Effect: Allow
Resource:
- Fn::GetAtt:
- PipelineArtifactsBucket
- Arn
- Fn::Join:
- ""
- - Fn::GetAtt:
- PipelineArtifactsBucket
- Arn
- /*
- Action:
- ecr:GetAuthorizationToken
Effect: Allow
Resource:
- "*"
- Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
- ecr:BatchCheckLayerAvailability
Effect: Allow
Resource:
- !GetAtt GuestBookEcr.Arn
ComposeRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: ComposeRolePolicy
Roles:
- Ref: ExtractBuildRole
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- cloudformation:*
- ecs:ListAccountSettings
- ecs:CreateCluster
- ecs:CreateService
- ecs:DeleteCluster
- ecs:DeleteService
- ecs:DeregisterTaskDefinition
- ecs:DescribeClusters
- ecs:DescribeServices
- ecs:DescribeTasks
- ecs:ListTasks
- ecs:RegisterTaskDefinition
- ecs:UpdateService
- ec2:AuthorizeSecurityGroupIngress
- ec2:DescribeVpcs
- ec2:DescribeVpcAttribute
- ec2:DescribeSubnets
- ec2:DescribeRouteTables
- ec2:CreateSecurityGroup
- ec2:CreateTags
- ec2:DescribeSecurityGroups
- ec2:DeleteSecurityGroup
- ec2:RevokeSecurityGroupIngress
- elasticfilesystem:CreateAccessPoint
- elasticfilesystem:CreateFileSystem
- elasticfilesystem:CreateMountTarget
- elasticfilesystem:DeleteAccessPoint
- elasticfilesystem:DeleteFileSystem
- elasticfilesystem:DeleteMountTarget
- elasticfilesystem:DescribeAccessPoints
- elasticfilesystem:DescribeBackupPolicy
- elasticfilesystem:DescribeFileSystems
- elasticfilesystem:DescribeFileSystemPolicy
- elasticfilesystem:DescribeMountTargets
- elasticfilesystem:ModifyMountTarget
- elasticfilesystem:ModifyMountTargetSecurityGroups
- iam:AttachRolePolicy
- iam:CreateRole
- iam:DeleteRole
- iam:DeleteRolePolicy
- iam:DetachRolePolicy
- iam:PassRole
- iam:PutRolePolicy
- elasticloadbalancing:*
- application-autoscaling:*
- servicediscovery:*
- logs:CreateLogGroup
- logs:DescribeLogGroups
- logs:FilterLogEvents
- logs:DeleteLogGroup
- route53:CreateHostedZone
- route53:DeleteHostedZone
- route53:GetHealthCheck
- route53:GetHostedZone
- route53:ListHostedZonesByName
Effect: Allow
Resource:
- "*"
# CodeBuild to Build the Container Image
ImageBuild:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join
- "-"
- - !Sub ${AWS::StackName}
- "ImageBuild"
Artifacts:
Type: CODEPIPELINE
EncryptionDisabled: false
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:5.0
PrivilegedMode: true
Type: LINUX_CONTAINER
ServiceRole: !Ref ImageBuildRole
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Building the Docker image...
- cd application/frontend/
- docker build -t ${IMAGE_URI}:frontend.${IMAGE_TAG} .
- cd ../backend/
- docker build -t ${IMAGE_URI}:backend.${IMAGE_TAG} .
- cd ../nginx/
- docker build -t ${IMAGE_URI}:nginx.${IMAGE_TAG} .
post_build:
commands:
- echo Pushing the Docker image...
- docker push ${IMAGE_URI}:frontend.${IMAGE_TAG}
- docker push ${IMAGE_URI}:backend.${IMAGE_TAG}
- docker push ${IMAGE_URI}:nginx.${IMAGE_TAG}
# Code Build to Extract Cfn
ExtractBuild:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join
- "-"
- - !Sub ${AWS::StackName}
- "ExtractBuild"
Artifacts:
Type: CODEPIPELINE
EncryptionDisabled: false
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
PrivilegedMode: true
Type: LINUX_CONTAINER
ServiceRole: !Ref ExtractBuildRole
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
phases:
install:
commands:
- mv /usr/local/bin/docker /usr/bin/docker
- curl -L https://raw.githubusercontent.com/docker/compose-cli/main/scripts/install/install_linux.sh | sh
- cd ./application
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin ${IMAGE_URI}
- echo Creating Docker Compose Context
- curl "http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}" > creds.json
- cat creds.json
- export AWS_ACCESS_KEY_ID=$(cat creds.json | jq -r .AccessKeyId)
- export AWS_SECRET_ACCESS_KEY=$(cat creds.json | jq -r .SecretAccessKey)
- export AWS_SESSION_TOKEN=$(cat creds.json | jq -r .Token)
- docker context create ecs GuestBookecs --from-env
- docker context use GuestBookecs
- docker context ls
build:
commands:
- echo Convert Compose File
- docker --debug compose -p guest-book-app -f docker-compose.yml -f docker-compose.prod.yml convert > cloudformation.yml
artifacts:
base-directory: ./application
files:
- cloudformation.yml
PipelineArtifactsBucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled
# Code Pipeline
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: "guest-book-pipeline"
ArtifactStore:
Type: S3
Location: !Ref "PipelineArtifactsBucket"
RoleArn: !GetAtt PipelineRole.Arn
Stages:
- Name: Source
Actions:
- Name: Source
InputArtifacts: []
ActionTypeId:
Category: Source
Owner: ThirdParty
Version: "1"
Provider: GitHub
OutputArtifacts:
- Name: SourceCode
Configuration:
Owner: !Ref GitHubOwner
Repo: !Ref GitHubRepo
Branch: !Ref GitHubBranch
PollForSourceChanges: false
OAuthToken: !Ref GitHubOAuthToken
- Name: Build
Actions:
- Name: BuildContainerImage
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: "1"
Configuration:
ProjectName: !Ref ImageBuild
EnvironmentVariables: !Sub |
[
{
"name": "AWS_ACCOUNT_ID",
"value": "${AWS::AccountId}",
"type": "PLAINTEXT"
},
{
"name": "IMAGE_URI",
"value": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${GuestBookEcr}",
"type": "PLAINTEXT"
},
{
"name": "IMAGE_TAG",
"value": "#{codepipeline.PipelineExecutionId}",
"type": "PLAINTEXT"
}
]
InputArtifacts:
- Name: SourceCode
OutputArtifacts:
- Name: ImageBuild
- Name: Compose2Cloudformation
Actions:
- Name: ExtractCFN
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: "1"
Configuration:
ProjectName: !Ref ExtractBuild
EnvironmentVariables: !Sub |
[
{
"name": "AWS_ACCOUNT_ID",
"value": "${AWS::AccountId}",
"type": "PLAINTEXT"
},
{
"name": "IMAGE_URI",
"value": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${GuestBookEcr}",
"type": "PLAINTEXT"
},
{
"name": "IMAGE_TAG",
"value": "#{codepipeline.PipelineExecutionId}",
"type": "PLAINTEXT"
},
{
"name": "AWS_ECS_CLUSTER",
"value": "${ExistingEcsCluster}",
"type": "PLAINTEXT"
},
{
"name": "AWS_VPC",
"value": "${ExistingAwsVpc}",
"type": "PLAINTEXT"
},
{
"name": "AWS_ELB",
"value": "${ExistingLoadbalancer}",
"type": "PLAINTEXT"
}
]
InputArtifacts:
- Name: SourceCode
OutputArtifacts:
- Name: ExtractedCfn
- Name: DeployStage
Actions:
- Name: CreateChangeSet
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: CloudFormation
Version: "1"
InputArtifacts:
- Name: ExtractedCfn
Configuration:
ActionMode: CHANGE_SET_REPLACE
RoleArn: !GetAtt [ExtractBuildRole, Arn]
StackName: !Ref ApplicationStackName
ChangeSetName: !Ref ChangeSetName
TemplatePath: "ExtractedCfn::cloudformation.yml"
Capabilities: CAPABILITY_IAM
RunOrder: 1
- Name: ApproveChangeSet
ActionTypeId:
Category: Approval
Owner: AWS
Provider: Manual
Version: "1"
RunOrder: 2
- Name: ExecuteChangeSet
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: CloudFormation
Version: "1"
Configuration:
ActionMode: CHANGE_SET_EXECUTE
StackName: !Ref ApplicationStackName
ChangeSetName: !Ref ChangeSetName
RoleArn: !GetAtt [ExtractBuildRole, Arn]
RunOrder: 3
GithubWebhook:
Type: 'AWS::CodePipeline::Webhook'
Properties:
Authentication: GITHUB_HMAC
AuthenticationConfiguration:
SecretToken: !Ref GitHubOAuthToken
RegisterWithThirdParty: 'true'
Filters:
- JsonPath: "$.ref"
MatchEquals: refs/heads/{Branch}
TargetPipeline: !Ref Pipeline
TargetAction: Source
TargetPipelineVersion: !GetAtt Pipeline.Version
Outputs:
GuestBookEcr:
Description: ECR Repository to store the guest book App Image
Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${GuestBookEcr}"
GuestBookEcrName:
Description: ECR Repository to store the guest book App Image
Value: !Ref GuestBookEcr
논리적인 순서에 따라 cloudformation 템플릿을 살펴보자.
1) github 레포지토리의 master 브랜치에 변동사항이 생긴다
Resources-GithubWebhook 에서 깃헙 웹훅을 설정하여 pipeline 이 트리거 되도록 했다. (Target: Source)
Resources-Pipeline-Stages의 첫 번째 아이템(Name: Source)에서 확인 할 수 있다.
Parameters에서 설정한 GitHubOwner, GitHubRepo, GitHubOAuthToken을 Configuration으로 사용한다.
InputArtifact를 SourceCode로 설정해 이후 단계에서 InputArtifacts로 사용할 수 있게 한다.
2) Code Build가 docker의 각 서비스의 이미지를 이미지화 하고, 빌드된 이미지를 ECR에 업로드한다.
Resources-Pipeline-Stages의 두 번째 아이템(Name: Build)에서 확인 할 수 있다.
Action은 Resources-ImageBuild 에 정의하였으며, 필요한 정책과 권한을 연결하였다.
이 단계는 변경된 소스코드를 기반으로 새로운 docker 이미지를 생성하고, ECR에 push 하는 과정을 포함한다.
Resources-Pipeline-Stages의 세 번째 아이템(Name: Compose2Cloudformation)에서 확인 할 수 있다.
Action은 Resources-Compose2Cloudformation 에 정의하였으며, 필요한 정책과 권한을 연결하였다.
docker context를 ecs로 생성하여 사용하기 위해 AWS 관련 정보를 불러와 환경변수로 지정한다.
compose 파일을 cloudformation 템플릿으로 변환하여 change set을 생성한다.
(docker compose 시 -p 옵셥으로 어플리케이션의 이름을 지정할 수 있다 - logGroup 등 자동으로 생성되는 AWS 리소스가 컴포즈 어플리케이션의 이름을 참조한다)
(참고로 logGroup의 경우 docker-compose/${어플리케이션 이름}
의 규칙으로 생성된다)
4) 기존 어플리케이션 인프라와 비교해 Change Set을 생성한다
5) 수동 승인 과정을 거쳐 ECS에 배포된다
Resources-Pipeline-Stages의 네 번째 아이템(Name: DeployStage)에서 확인 할 수 있다.
총 세 가지 단계를 거쳐 배포된다.
- 기존 어플리케이션 cloudformation 템플릿과 비교하여 Change set 생성
- Change set 적용을 수동승인
- 수동승인 완료되면 cloudformation 업데이트(배포)
Pipeline c AWS-cli로 생성
위의 과정을 이해 했다면 실제로 파이프라인을 생성해보자.
pipeline 디렉토리로 이동 한 뒤, 아래 명령어를 실행해보자.
aws cloudformation create-stack \
--stack-name guest-book-pipeline \
--template-body file://cloudformation.yaml \
--capabilities CAPABILITY_IAM \
--parameters \
ParameterKey=GitHubOAuthToken,ParameterValue=${깃헙에서 발급한 토큰} \
ParameterKey=GitHubOwner,ParameterValue=${깃헙 오너 이름} \
ParameterKey=GitHubRepo,ParameterValue=${깃헙 레포지코리 이름} \
ParameterKey=NodeECRArn,ParameterValue=${AWS에 생성한 Node ECR arn} \
ParameterKey=NginxECRArn,ParameterValue=${AWS에 생성한 Nginx ECR arn} \
ParameterKey=RedisECRArn,ParameterValue=${AWS에 생성한 Redis ECR arn} \
ParameterKey=ExistingAwsVpc,ParameterValue=${AWS에 생성한 vpc id} \
ParameterKey=ExistingEcsCluster,ParameterValue=${AWS에 생성한 ECS cluseter arn} \
ParameterKey=ExistingLoadbalancer,ParameterValue=${AWS에 생성한 ELB arn}
ParameterValue 값에 본인이 생성한 리소스의 값들을 올바르게 넣고 실행시키면 정상적으로 스택이 생성되고, 리소스를 생성한다.
Pipeline 실행
리소스가 처음 생성된 이후에 pipeline은 최초 실패할 것이다. 이는 전체 리소스가 생성되기 이전에 pipeline이 실행됐기 때문이므로 낙심할 필요는 없다.
레포지토리에 변동사항을 만든 뒤, origin이 변동되면 Pipeline이 자동으로 실행될 것이다.
(수동 승인 과정을 포함하였으므로, 파이프라인 콘솔에서 승인대기중인 과정애서 승인을 눌러야 최종 배포되는 점 참고하자)

Pipeline이 성공적으로 모두 완료되면, 생성했던 ELB 주소로 접속해보자.

guest-book 어플리케이션이 정상적으로 실행되는 것을 알 수 있다.
end
이번 포스팅과 이전 포스팅을 통해 AWS 환경에 다중 도커 컴포넌트 서비스를 CI/CD 하는 과정에 대해서 알아보았다.
실제로는 CI 과정만을 다뤘다고 볼 수 있는데, CD 는 파이프라인에 테스트과정을 포함하면 되므로 직접 구현해 보자.
또한, docker compose ecs auto-scaling문서를 통해 auto-scaling 설정도 가능하다는 점을 참고하자.