この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
おはようございます、もきゅりんです。
皆さん、Blue/Green デプロイメントしてますか?
Blue/Green デプロイメントとは、はすでに理解されていることを前提として話を進めます。
本稿では、Fargate の Blue/Green デプロイする CodePipelineを構築します。
CodePipeline を使った Fargate のBlue/Greenには下図の通り、プレースホルダを利用したものと、CodeDeploy::BlueGreen フックを利用したものと、2パターンがあるという認識です。(他にもいろいろあるかもしれませんが)
プレースホルダ
CodeDeploy::BlueGreen フック
2つのデプロイパターンの違い
プレースホルダとCodeDeploy::BlueGreenフックを使ったパターンにおける差異を簡単にまとめておきます。
プレースホルダ版でやってること
あなたの組織に最適なECSデプロイ手法の考察 | DevelopersIO で丁寧に解説がされていますが、要点は下記です。
CodeDeployの機能によって、バージョン管理ツール (CodeCommitやGitHub) に格納された taskdef.json
のイメージURI および appspec.yml
のタスク定義のプレースホルダのみを更新してECS Blue/Greenデプロイを実行します。
したがって、タスク定義のURI以外の部分の更新は、Blue/Greenデプロイの管理外となります。
CodeDeploy::BlueGreen フック版(以下フック版)でやってること
詳細は、Create an Amazon ECS blue/green deployment through AWS CloudFormation - AWS CodeDeploy をご確認いただきたいのですが、Transform
というAWSから提供されるマクロの機能を使って ECS の Blue/Green の環境を作成します。
マクロを使用すると、検索して置換操作のような単純なアクションからテンプレート全体の広範な変換 (Transform) まで、テンプレートに対してカスタム処理を実行できるようになります。
マクロの詳細は、AWS CloudFormation マクロを使用したテンプレートのカスタム処理の実行 - AWS CloudFormation をご参照下さい。
Transform
は、AWS CloudFormation によってホストされているマクロで使用するための特別なアクセス許可は必要ありません。
よく認知されているのは、Serverless Application Model (AWS SAM) で使われている Transform かと思います。
Transform の詳細は、変換のリファレンス - AWS CloudFormationをご参照下さい。
こちらの要点としては、下記になります。
- AWSCodeDeploy アプリケーションおよびデプロイメントグループを作成しない
- アプリケーション仕様ファイル (AppSpecファイル)は指定せず、通常 AppSpec ファイルで管理される情報は、AWS::CodeDeploy::BlueGreen フックによって管理される
- CFn スタックのタスク定義およびサービス定義が変更されたタイミングで Blue/Green デプロイメントを実行する
プレースホルダ版とは異なり、イメージURI以外のタスク定義の更新やサービスの設定更新も、Blue/Greenデプロイの管理範囲となります。
以下は通常のCodeDeployと異なり、注意が必要です。
- トラフィックの再ルーティングの設定をできないため、トラフィックを再ルーティングするタイミング指定できない
- 待機中に正式リリースを実行することはできないため、設定した終了待機まで旧環境は残り続ける
- ロールバックはAWSCloudFormationでスタックの更新をキャンセルする
- タスク定義およびサービス定義リソースに対する更新と、同じスタック更新内の他のリソースに対する更新を含めることはできないため、別々のスタック更新操作を実行するか、テンプレートから Transform および Hook セクションを削除(コメントアウト)して、スタックの更新を実行する
CloudFormation のスタックでリソース一式を管理することになります。
仕様の詳細は、AWSCloudFormationを使用してCodeDeployを介してECSブルー/グリーンデプロイを実行します-AWSCloudFormation をご確認下さい。
以下でプレースホルダ版とフック版をそれぞれ紹介します。
プレースホルダ版 Blue/Green デプロイ
前提
CloudFormation 一撃で Fargate のBlue/Green Deployment 環境を構築する を参考に下記リソースを構築します。
- ECR
- CodeDeployのアプリケーション/デプロイメントグループの作成
- ECSの構築
準備にえらい時間がかかりますが、ようやく本題です。
やること
CodePipelineからECSにBlue/Greenデプロイする | DevelopersIO の内容になります。
図を再掲しますが、以下です。
対応することは、以下になります。
- CodeCommitにAppspec, Dockerfile(+その他) , taskdef.jsonをプッシュする
- CloudFormation を実行する
本番で利用する際は、“Too Many Requests.” でビルドが失敗する…。AWS CodeBuild で IP ガチャを回避するために Docker Hub ログインしよう!という話 | DevelopersIO を考慮してテンプレートに追記するのを推奨します。
手動承認ステージはコメントアウトしていますので、利用する場合は必要な設定を確認の上、ご利用下さい。
placeholder-codepipeline.yml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For ECS Fargate Blue/Green Deploy with PlaceHolder
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
NameTagPrefix:
Type: String
Default: test
Description: Prefix of Name tags.
ServiceName:
Type: String
Default: myapp
Description: Prefix of Service tags.
CodeCommitRepositoryName:
Type: String
CodeDeployAppName:
Type: String
CodeDeployDGName:
Type: String
ContainerName:
Type: String
ECRName:
Type: String
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# IAM Roles
# ------------------------------------------------------------#
# CodeWatchEventを実行できるIAMRole
CloudwatchEventRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${ServiceName}-CloudWatchEventRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: CloudWatchEventsPipelineExecution
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: codepipeline:StartPipelineExecution
Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
# CodeBuildに適用するIAMRole
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${ServiceName}-CodeBuildServiceNameRole
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SampleCodeBuildAccess
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource: '*'
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Effect: Allow
Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages
Resource: '*'
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:PutImage
Resource: '*'
# CodePipelineに適用するIAMRole
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${ServiceName}-CodePipelineServiceRole
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SamplePipeline
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- iam:PassRole
Resource: '*'
Effect: Allow
Condition:
StringEqualsIfExists:
iam:PassedToService:
- ecs-tasks.amazonaws.com
- Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetRepository
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Resource: '*'
Effect: Allow
- Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
- codedeploy:*
Resource: '*'
Effect: Allow
- Action:
- elasticbeanstalk:*
- ec2:*
- elasticloadbalancing:*
- autoscaling:*
- cloudwatch:*
- sns:*
- cloudformation:*
- rds:*
- sqs:*
- ecs:*
Resource: '*'
Effect: Allow
- Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
- codebuild:BatchGetBuildBatches
- codebuild:StartBuildBatch
Resource: '*'
Effect: Allow
# S3Bucket
ArtifactBucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
# CloudWatchEventの実行ルール
AmazonCloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.codecommit
detail-type:
- CodeCommit Repository State Change
resources:
- Fn::Join:
- ''
- - 'arn:aws:codecommit:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref CodeCommitRepositoryName
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- master
Targets:
- Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
RoleArn: !GetAtt CloudwatchEventRole.Arn
Id: codepipeline-AppPipeline
# CodeBuild
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
ServiceRole: !Ref CodeBuildServiceRole
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --no-include-email)
- IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:$IMAGE_TAG .
- docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Writing imageDetail json...
- echo "{\"name\":\"${ContainerName}\",\"ImageURI\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}" > imageDetail.json
artifacts:
files: imageDetail.json
Environment:
PrivilegedMode: true
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:4.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: REPOSITORY_URI
Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
- Name: ContainerName
Value: !Ref ContainerName
- Name: DOCKER_BUILDKIT
Value: '1'
Name: !Ref AWS::StackName
# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------#
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: !Sub ${NameTagPrefix}-${ServiceName}-pipeline
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Version: '1'
Provider: CodeCommit
Configuration:
RepositoryName: !Ref CodeCommitRepositoryName
PollForSourceChanges: false
BranchName: master
RunOrder: 1
OutputArtifacts:
- Name: App
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: '1'
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1
InputArtifacts:
- Name: App
OutputArtifacts:
- Name: BuildOutput
# - Name: Approval
# Actions:
# - Name: Manual_Approval
# ActionTypeId:
# Category: Approval
# Owner: AWS
# Version: '1'
# Provider: Manual
# Configuration:
# CustomData: !Sub '${ServiceName} will be updated. Do you want to deploy it?'
# NotificationArn: arn:aws:sns:ap-northeast-1:xxxxxxxx:hogehoge
- Name: Deploy
Actions:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: '1'
Provider: CodeDeployToECS
Configuration:
AppSpecTemplateArtifact: App
AppSpecTemplatePath: appspec.yaml
TaskDefinitionTemplateArtifact: App
TaskDefinitionTemplatePath: taskdef.json
ApplicationName: !Ref CodeDeployAppName
DeploymentGroupName: !Ref CodeDeployDGName
Image1ArtifactName: BuildOutput
Image1ContainerName: IMAGE1_NAME
RunOrder: 1
InputArtifacts:
- Name: App
- Name: BuildOutput
Region: !Ref AWS::Region
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
PipelinelogicalID:
Description: logical ID.
Value: !Ref Pipeline
フック版 Blue/Green デプロイメント
前提
- VPC、ALB Security Group、ECS Task Security Groupが構築済み
- Fargateを配置するサブネットは、NATゲートウェイ等経由でインターネット通信可能であること
- ECRが構築済み
やること
図を再掲しますが、以下です。
CodeBuildが新しくビルドされたイメージURI をCloudFormationのテンプレートに反映して、CodePipeline のデプロイステージで CloudFormation を実行します。
対応することは、以下になります。
- CodeCommitにDockerfile (+その他)、CloudFormationテンプレート をプッシュする
- CloudFormation を実行する
繰り返しますが、本番で利用する際は、“Too Many Requests.” でビルドが失敗する…。AWS CodeBuild で IP ガチャを回避するために Docker Hub ログインしよう!という話 | DevelopersIO を考慮してテンプレートに追記するのを推奨します。
デプロイメントグループの設定についてはテンプレートを確認の上、適宜変更下さい。
現状の設定は下記です。
- デプロイ設定は、5分後ごとに20%ずつグリーンにルーティングします。
- 元のリビジョンの終了は30分後です。
手動承認ステージはコメントアウトしていますので、利用する場合は必要な設定を確認の上、ご利用下さい。
AWSCloudFormationを使用してCodeDeployを介してECSブルー/グリーンデプロイを実行します-AWSCloudFormation に注記がありますが、使用してみてテンプレートの注意として下記があります。
注記
- ALBおよびALB配下はすべてテンプレートで管理する必要があります
- ServiceRole は必須と記載されているのですが、なくても動きます(なお、サンプルテンプレートには入っていません)
- VPCやサブネットグループ、セキュリティグループ、クラスターなどはパラメータ指定できますが、イメージURIはパラメータ指定するとCodeDeployはうまく動きません
- 必ず変更セットを確認してリソースの何がどう変わるかを確認してから実行しましょう
このパイプラインでは、コミットハッシュを元にイメージURIが更新されるため、CloudFormation のテンプレートの変更だけでもタスク定義が更新され、 フック対象のリソースだけのスタック更新ができません。
Transform および Hook セクションを削除(コメントアウト)して、ALBの設定変更をしたところ、問題なくスタック更新は成功しました。
その後に、再度コメントアウトを外して、コンテンツを更新して CodeCommit にプッシュしたところ、無事 Blue/Green デプロイは実行できます。
なお、仮にロールバックした場合、テンプレートの設定を現環境と合わせるために再度 Blue/Greenデプロイをし直す必要があるかと思います。
cfn-ecs-bg-deploy.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Fargate Blue/Green template with CodeDeployBlueGreenHook
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
NameTagPrefix:
Type: String
Default: demo
Description: Prefix of Name tags.
Env:
Type: String
Default: dev
Description: Prefix of Env tags.
App:
Type: String
Default: apps
Description: Prefix of App tags.
Vpc:
Type: AWS::EC2::VPC::Id
Default: 'vpc-xxxxxx'
Subnet1a:
Type: AWS::EC2::Subnet::Id
Default: 'subnet-xxxxxx'
Subnet1c:
Type: AWS::EC2::Subnet::Id
Default: 'subnet-xxxxxx'
LBSecurityGroup:
Type: AWS::EC2::SecurityGroup::Id
Default: 'sg-xxxxxxxxx'
S3LogBucketName:
Type: String
Default: demo-alb-logs
ECSCluster:
Type: String
Default: 'arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:cluster/demo-cluster'
FamilyName:
Type: String
Default: 'demo-container'
TaskContainerName:
Type: String
Default: 'demo-container'
TaskExecutionRoleArn:
Type: String
Default: 'arn:aws:iam::xxxxxxxxxxxxx:role/ecsTaskExecutionRole'
# ------------------------------------------------------------#
# Transform
# ------------------------------------------------------------#
Transform:
- 'AWS::CodeDeployBlueGreen'
Hooks:
CodeDeployBlueGreenHook:
Properties:
TrafficRoutingConfig:
Type: TimeBasedLinear
TimeBasedLinear:
StepPercentage: 20
BakeTimeMins: 5
AdditionalOptions:
TerminationWaitTimeInMinutes: 30
Applications:
- Target:
Type: 'AWS::ECS::Service'
LogicalID: ECSService
ECSAttributes:
TaskDefinitions:
- BlueTaskDefinition
- GreenTaskDefinition
TaskSets:
- BlueTaskSet
- GreenTaskSet
TrafficRouting:
ProdTrafficRoute:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
LogicalID: listenerProdTraffic
TargetGroups:
- lbTargetGroupBlue
- lbTargetGroupGreen
Type: 'AWS::CodeDeploy::BlueGreen'
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
lbTargetGroupBlue:
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckPort: '80'
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
Matcher:
HttpCode: '200'
Port: 80
Protocol: HTTP
TargetType: ip
UnhealthyThresholdCount: 5
VpcId: !Ref Vpc
lbTargetGroupGreen:
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckPort: '80'
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
Matcher:
HttpCode: '200'
Port: 80
Protocol: HTTP
TargetType: ip
UnhealthyThresholdCount: 5
VpcId: !Ref Vpc
loadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Name: !Sub ${NameTagPrefix}-${Env}-${App}-alb
LoadBalancerAttributes:
- Key: deletion_protection.enabled
Value: 'false'
- Key: access_logs.s3.enabled
Value: 'true'
- Key: routing.http2.enabled
Value: 'true'
- Key: routing.http.drop_invalid_header_fields.enabled
Value: 'false'
- Key: idle_timeout.timeout_seconds
Value: '60'
- Key: access_logs.s3.bucket
Value: !Ref S3LogBucketName
- Key: access_logs.s3.prefix
Value: 'prod'
Scheme: internet-facing
SecurityGroups:
- !Ref LBSecurityGroup
Subnets:
- !Ref Subnet1a
- !Ref Subnet1c
Type: application
listenerProdTraffic:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref lbTargetGroupBlue
LoadBalancerArn: !Ref loadBalancer
Port: 80
Protocol: HTTP
listenerTestTraffic:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref lbTargetGroupGreen
LoadBalancerArn: !Ref loadBalancer
Port: 8080
Protocol: HTTP
BlueTaskDefinition:
Type: 'AWS::ECS::TaskDefinition'
Properties:
ExecutionRoleArn: !Ref TaskExecutionRoleArn
ContainerDefinitions:
- Name: !Ref TaskContainerName
Image: xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hogehoge:1.0.0
Essential: true
PortMappings:
- HostPort: 80
Protocol: tcp
ContainerPort: 80
RequiresCompatibilities:
- FARGATE
NetworkMode: awsvpc
Cpu: '256'
Memory: '512'
Family: !Ref FamilyName
ECSService:
Type: 'AWS::ECS::Service'
Properties:
Cluster: !Ref ECSCluster
DesiredCount: 1
DeploymentController:
Type: EXTERNAL
BlueTaskSet:
Type: 'AWS::ECS::TaskSet'
Properties:
Cluster: !Ref ECSCluster
LaunchType: FARGATE
NetworkConfiguration:
AwsVpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref LBSecurityGroup
Subnets:
- !Ref Subnet1a
- !Ref Subnet1c
PlatformVersion: LATEST
Scale:
Unit: PERCENT
Value: 1
Service: !Ref ECSService
TaskDefinition: !Ref BlueTaskDefinition
LoadBalancers:
- ContainerName: !Ref TaskContainerName
ContainerPort: 80
TargetGroupArn: !Ref lbTargetGroupBlue
PrimaryTaskSet:
Type: 'AWS::ECS::PrimaryTaskSet'
Properties:
Cluster: !Ref ECSCluster
Service: !Ref ECSService
TaskSetId: !GetAtt
- BlueTaskSet
- Id
hook-codepipeline.yml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For ECS Fargate Blue/Green Deploy with CodeDeploy Hook
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
NameTagPrefix:
Type: String
Default: demo
Description: Prefix of Name tags.
Env:
Type: String
Default: stg
Description: Prefix of Env tags.
ServiceName:
Type: String
Default: myapp
Description: Prefix of Service tags.
CodeCommitRepoName:
Type: String
ECRName:
Type: String
TemplateName:
Type: String
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# IAM Roles
# ------------------------------------------------------------#
# CodeWatchEventを実行できるIAMRole
CloudwatchEventRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${Env}-${ServiceName}-CloudWatchEventRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: CloudWatchEventsPipelineExecution
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: codepipeline:StartPipelineExecution
Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
# CodeBuildに適用するIAMRole
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${Env}-${ServiceName}-CodeBuildServiceNameRole
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SampleCodeBuildAccess
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource: '*'
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Effect: Allow
Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages
Resource: '*'
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:PutImage
Resource: '*'
# CloudFormationに適用するIAMRole
CFnServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${Env}-${ServiceName}-CloudFormationRole
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SampleCloudFormationPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- iam:PassRole
Resource: '*'
Effect: Allow
Condition:
StringEqualsIfExists:
iam:PassedToService:
- ecs-tasks.amazonaws.com
- Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
- codedeploy:*
Resource: '*'
Effect: Allow
- Action:
- ec2:*
- elasticloadbalancing:*
- autoscaling:*
- cloudwatch:*
- sns:*
- cloudformation:*
- ecs:*
Resource: '*'
Effect: Allow
# CodePipelineに適用するIAMRole
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NameTagPrefix}-${Env}-${ServiceName}-CodePipelineServiceRole
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SamplePipeline
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- iam:PassRole
Resource: '*'
Effect: Allow
Condition:
StringEqualsIfExists:
iam:PassedToService:
- ecs-tasks.amazonaws.com
- Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Resource: '*'
Effect: Allow
Action:
- ecr:DescribeImages
- Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetRepository
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Resource: '*'
Effect: Allow
- Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
- codedeploy:*
Resource: '*'
Effect: Allow
- Action:
- elasticbeanstalk:*
- ec2:*
- elasticloadbalancing:*
- autoscaling:*
- cloudwatch:*
- sns:*
- cloudformation:*
- rds:*
- sqs:*
- ecs:*
Resource: '*'
Effect: Allow
- Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
- codebuild:BatchGetBuildBatches
- codebuild:StartBuildBatch
Resource: '*'
Effect: Allow
# S3Bucket
ArtifactBucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
# CloudWatchEventの実行ルール
AmazonCloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.codecommit
detail-type:
- CodeCommit Repository State Change
resources:
- Fn::Join:
- ''
- - 'arn:aws:codecommit:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref CodeCommitRepoName
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- master
Targets:
- Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
RoleArn: !GetAtt CloudwatchEventRole.Arn
Id: codepipeline-AppPipeline
# CodeBuild
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
ServiceRole: !Ref CodeBuildServiceRole
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --no-include-email)
- IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:$IMAGE_TAG .
- docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Updating CloudFormation Template...
- sed -i -e "s|$REPOSITORY_URI:.*$|$REPOSITORY_URI:$IMAGE_TAG|" $TEMPLATE_NAME.yml
artifacts:
files: $TEMPLATE_NAME.yml
Environment:
PrivilegedMode: true
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:4.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: REPOSITORY_URI
Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
- Name: TEMPLATE_NAME
Value: !Ref TemplateName
- Name: DOCKER_BUILDKIT
Value: '1'
Name: !Ref AWS::StackName
# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------#
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: !Sub ${NameTagPrefix}-${Env}-${ServiceName}-pipeline
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Version: '1'
Provider: CodeCommit
Configuration:
RepositoryName: !Ref CodeCommitRepoName
PollForSourceChanges: false
BranchName: master
RunOrder: 1
OutputArtifacts:
- Name: App
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: '1'
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1
InputArtifacts:
- Name: App
OutputArtifacts:
- Name: BuildOutput
# - Name: Approval
# Actions:
# - Name: Manual_Approval
# ActionTypeId:
# Category: Approval
# Owner: AWS
# Version: '1'
# Provider: Manual
# Configuration:
# CustomData: !Sub '${ServiceName} will be updated. Do you want to deploy it?'
# NotificationArn: arn:aws:sns:ap-northeast-1:xxxxxxx:hogehoge
- Name: Deploy
Actions:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: '1'
Provider: CloudFormation
InputArtifacts:
- Name: BuildOutput
Configuration:
ActionMode: CREATE_UPDATE
Capabilities: CAPABILITY_AUTO_EXPAND
RoleArn: !GetAtt CFnServiceRole.Arn
StackName: !Sub ${NameTagPrefix}-${Env}-${TemplateName}
TemplatePath: !Sub 'BuildOutput::${TemplateName}.yml'
RunOrder: 1
Region: !Ref AWS::Region
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
PipelinelogicalID:
Description: logical ID.
Value: !Ref Pipeline
最後に
本稿では、プレースホルダおよびCodeDeploy::BlueGreenフックを活用する CodePipeline の構築をする CFn を紹介しました。
これまで Fargate のBlue/Green デプロイで使われていたのは、プレースホルダ版で情報も多そうなので無難だと思いますが、若干クセはありつつもフック版もまずは開発環境から利用してみても良いのではないでしょうか。
そもそも Blue/Green デプロイをするかどうか、CodePipelineを使うかどうか、そして、どのパターンを選択するかなどの選択肢が広がったとしてご検討の上、ご活用下さい。
以上です。
どこかのどなたかのお役に立てば幸いです。