[2パターン] CFn で Fargate の Blue/Green Deployment の CodePipeline を構築する
はじめに
おはようございます、もきゅりんです。
皆さん、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を使うかどうか、そして、どのパターンを選択するかなどの選択肢が広がったとしてご検討の上、ご活用下さい。
以上です。
どこかのどなたかのお役に立てば幸いです。