はじめに
既存のECS環境に対して、CodePipeline / CodeBuild を後から追加し、S3にアップロードしたアプリケーションZIPを元にDockerイメージをビルドしてECRへpushし、その後ECS Serviceへデプロイする構成をCloudFormationで作成しました。
既存環境には、すでに以下が存在している前提です。
- ECS Cluster
- ECS Service
- app
- worker
- ECR Repository
- CloudFront
- ALB / ECSまわりのネットワーク構成
今回CloudFormationで新規作成したのは、主に以下です。
- CodePipeline
- CodeBuild Project
- CodePipeline用IAM Role
- CodeBuild用IAM Role
- Artifact用S3 Bucket
- EventBridge Rule
- EventBridgeからCodePipelineを起動するIAM Role
S3 Source Action、CodeBuild、ECS Deploy Actionを組み合わせる構成です。
この記事では、構成、CloudFormationテンプレートの考え方、デプロイ手順、実際にハマったポイントをまとめます。
構成
今回の構成は以下です。
S3 source bucket
├── staging/app.zip
└── prod/app.zip
↓
CodePipeline
↓
CodeBuild
- Docker image build
- ECR push
- imagedefinitions-app.json 出力
- imagedefinitions-worker.json 出力
↓
ECS Deploy
- app service
- worker service
↓
CloudFront cache invalidation
staging と production では、同じCloudFormationテンプレートを使い、パラメータだけ変えて2本のパイプラインを作成します。
<project>-staging-app-pipeline
<project>-prod-app-pipeline
なぜCloudFormationで作るのか
既存リソースに手作業でCodePipelineを追加することもできますが、以下の理由からCloudFormation化しました。
- staging / production で同じ構成を再現したい
- 別環境にも展開しやすくしたい
- IAM Role / Policy の差分を管理したい
- CodePipeline / CodeBuild の設定をレビュー可能にしたい
- 手作業による設定漏れを減らしたい
CodePipelineはCloudFormationでは AWS::CodePipeline::Pipeline として定義できます。CodePipelineのCloudFormationリソースでは、Stages、RoleArn、ArtifactStore などを定義します。
参考: AWS::CodePipeline::Pipeline - AWS CloudFormation
なお、ArtifactStore と ArtifactStores はどちらか一方のみを指定します。単一リージョン構成では ArtifactStore を使い、クロスリージョンActionを含む場合は ArtifactStores を使います。
CodeBuildは AWS::CodeBuild::Project として定義できます。CodeBuild Projectでは、Source、Artifacts、Environment、ServiceRole などを定義します。
参考: AWS::CodeBuild::Project - AWS CloudFormation
前提
この記事では以下を前提にします。
Region: us-west-2
Source bucket: 事前作成
ECS cluster: 既存
ECS service: 既存
ECR repository: 既存
CloudFront: 既存
S3 Source Actionを使う場合、Source用S3バケットはバージョニングを有効化しておく必要があります。また、S3 SourceではZIPファイルをアップロードして下流のBuild Actionへ渡す構成にします。
参考: Amazon S3 source action reference - AWS CodePipeline
Source用S3バケット
Source用S3バケットは事前に作成しました。
staging / production でバケットを分ける方法もありますが、今回は同一バケット内でObjectKeyを分ける構成にしました。
staging/app.zip
prod/app.zip
S3バケット作成例です。
aws s3api create-bucket `
--bucket <source-bucket-name> `
--region us-west-2 `
--create-bucket-configuration LocationConstraint=us-west-2 `
--profile <profile>
バージョニングを有効化します。
aws s3api put-bucket-versioning `
--bucket <source-bucket-name> `
--versioning-configuration Status=Enabled `
--region us-west-2 `
--profile <profile>
パブリックアクセスをブロックします。
aws s3api put-public-access-block `
--bucket <source-bucket-name> `
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true `
--region us-west-2 `
--profile <profile>
必要に応じて、デフォルト暗号化も設定します。
aws s3api put-bucket-encryption `
--bucket <source-bucket-name> `
--server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}' `
--region us-west-2 `
--profile <profile>
CloudFormationテンプレートのパラメータ設計
staging / production で共通テンプレートを使えるように、環境差分はパラメータ化しました。
Parameters:
ProjectName:
Type: String
EnvironmentName:
Type: String
SourceBucketName:
Type: String
SourceObjectKey:
Type: String
EcsClusterName:
Type: String
AppServiceName:
Type: String
Default: app
WorkerServiceName:
Type: String
Default: worker
EcrRepositoryName:
Type: String
EcrCacheRepositoryName:
Type: String
ImageTag:
Type: String
Default: latest
RailsEnv:
Type: String
Default: production
ServerEnv:
Type: String
Default: production
DockerfilePath:
Type: String
Default: Dockerfile.production
EcsTaskExecutionRoleArn:
Type: String
Default: "*"
EcsTaskRoleArn:
Type: String
Default: "*"
WorkerEcsTaskExecutionRoleArn:
Type: String
Default: "*"
WorkerEcsTaskRoleArn:
Type: String
Default: "*"
EnableSourceChangeRule:
Type: String
Default: "false"
AllowedValues:
- "true"
- "false"
ここで重要なのは、ProjectName と EnvironmentName は新規作成するリソース名に使うだけで、既存ECSやS3の参照先を決める値ではないという点です。
既存リソースの参照先は以下で決まります。
SourceBucketName
SourceObjectKey
EcsClusterName
AppServiceName
WorkerServiceName
EcrRepositoryName
例えば、ProjectName=myapp、EnvironmentName=staging と指定すると、作成されるパイプライン名は以下のようになります。
myapp-staging-app-pipeline
一方で、実際にデプロイするECS ClusterやServiceは EcsClusterName、AppServiceName、WorkerServiceName で指定します。
CodeBuild
CodeBuildはCodePipelineからSource Artifactを受け取るため、Source と Artifacts は CODEPIPELINE にします。
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${ProjectName}-${EnvironmentName}-build-app"
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
Source:
Type: CODEPIPELINE
BuildSpec: buildspec.yml
Artifacts:
Type: CODEPIPELINE
Environment:
Type: ARM_CONTAINER
Image: aws/codebuild/amazonlinux-aarch64-standard:3.0
ComputeType: BUILD_GENERAL1_SMALL
PrivilegedMode: true
ImagePullCredentialsType: CODEBUILD
EnvironmentVariables:
- Name: ECR_REPOSITORY_NAME
Type: PLAINTEXT
Value: !Ref EcrRepositoryName
- Name: ECR_CACHE_REPOSITORY_NAME
Type: PLAINTEXT
Value: !Ref EcrCacheRepositoryName
- Name: IMAGE_TAG
Type: PLAINTEXT
Value: !Ref ImageTag
- Name: RAILS_ENV
Type: PLAINTEXT
Value: !Ref RailsEnv
- Name: SERVER_ENV
Type: PLAINTEXT
Value: !Ref ServerEnv
- Name: DOCKERFILE_PATH
Type: PLAINTEXT
Value: !Ref DockerfilePath
Docker buildを行うため、PrivilegedMode: true にしています。
今回の構成ではARM環境向けにビルドするため、CodeBuildのEnvironmentは ARM_CONTAINER を使いました。環境に応じて LINUX_CONTAINER に変更するケースもあります。
buildspec.yml
CodeBuildでは以下のような buildspec.yml を使いました。
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
- REGION=${AWS_DEFAULT_REGION}
- REPOSITORY_NAME=${ECR_REPOSITORY_NAME}
- CACHE_REPOSITORY_NAME=${ECR_CACHE_REPOSITORY_NAME}
- IMAGE_TAG=${IMAGE_TAG:-latest}
- RAILS_ENV_VALUE=${RAILS_ENV:-production}
- SERVER_ENV_VALUE=${SERVER_ENV:-production}
- DOCKERFILE=${DOCKERFILE_PATH:-Dockerfile.production}
- APP_IMAGE_DEFINITIONS=${APP_IMAGE_DEFINITIONS_FILE:-imagedefinitions-app.json}
- WORKER_IMAGE_DEFINITIONS=${WORKER_IMAGE_DEFINITIONS_FILE:-imagedefinitions-worker.json}
- REPOSITORY_URI=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$REPOSITORY_NAME
- CACHE_REPOSITORY_URI=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$CACHE_REPOSITORY_NAME
- aws ecr describe-repositories --repository-names $REPOSITORY_NAME --region $REGION || aws ecr create-repository --repository-name $REPOSITORY_NAME --region $REGION
- aws ecr describe-repositories --repository-names $CACHE_REPOSITORY_NAME --region $REGION || aws ecr create-repository --repository-name $CACHE_REPOSITORY_NAME --region $REGION
- aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
build:
commands:
- echo Building the Docker image...
- docker buildx build --platform linux/arm64 --build-arg rails_env=$RAILS_ENV_VALUE --build-arg server=$SERVER_ENV_VALUE -t $REPOSITORY_URI:$IMAGE_TAG -f $DOCKERFILE .
post_build:
commands:
- echo Pushing the Docker image...
- docker push $REPOSITORY_URI:$IMAGE_TAG
- printf '[{"name":"app","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > $APP_IMAGE_DEFINITIONS
- printf '[{"name":"worker","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > $WORKER_IMAGE_DEFINITIONS
artifacts:
files:
- $APP_IMAGE_DEFINITIONS
- $WORKER_IMAGE_DEFINITIONS
ここで imagedefinitions-app.json と imagedefinitions-worker.json を出力しています。ECS Deploy Actionはこのファイルを使って、対象サービスのコンテナイメージを更新します。
なお、今回は検証しやすいように、ECRリポジトリが存在しない場合はCodeBuild内で作成するようにしています。
aws ecr describe-repositories --repository-names $REPOSITORY_NAME --region $REGION || aws ecr create-repository --repository-name $REPOSITORY_NAME --region $REGION
ただし、本番運用ではECRリポジトリを事前作成し、CodeBuildロールから ecr:CreateRepository を外す方が権限を絞りやすいです。
CodePipeline
CodePipelineは以下の3ステージ構成です。
Source
Build
Deploy
Source
SourceはS3です。
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: S3
Version: "1"
Configuration:
S3Bucket: !Ref SourceBucketName
S3ObjectKey: !Ref SourceObjectKey
PollForSourceChanges: "false"
OutputArtifacts:
- Name: SourceArtifact
RunOrder: 1
S3 Sourceの PollForSourceChanges は false にしています。CloudWatch Events / EventBridge を使う場合、重複起動を避けるため PollForSourceChanges を false にします。
参考: Amazon S3 source action reference - AWS CodePipeline
Build
BuildはCodeBuildです。
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: "1"
Configuration:
ProjectName: !Ref CodeBuildProject
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
RunOrder: 1
Deploy
DeployはECSのapp / workerに対して2アクション用意しました。
- Name: Deploy
Actions:
- Name: Deploy-app
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: "1"
Configuration:
ClusterName: !Ref EcsClusterName
ServiceName: !Ref AppServiceName
FileName: imagedefinitions-app.json
InputArtifacts:
- Name: BuildArtifact
RunOrder: 1
- Name: Deploy-worker
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: "1"
Configuration:
ClusterName: !Ref EcsClusterName
ServiceName: !Ref WorkerServiceName
FileName: imagedefinitions-worker.json
InputArtifacts:
- Name: BuildArtifact
RunOrder: 1
EventBridgeトリガー
S3にZIPをアップロードしたタイミングで自動起動したい場合は、EventBridge Ruleを作成します。
最初は安全のため、EnableSourceChangeRule=false で作成し、手動実行で動作確認してから true にするのがおすすめです。
SourceChangeEventRule:
Type: AWS::Events::Rule
Condition: CreateSourceChangeRule
Properties:
State: ENABLED
EventPattern:
source:
- aws.s3
detail-type:
- AWS API Call via CloudTrail
detail:
eventSource:
- s3.amazonaws.com
eventName:
- PutObject
- CompleteMultipartUpload
- CopyObject
requestParameters:
bucketName:
- !Ref SourceBucketName
key:
- !Ref SourceObjectKey
Targets:
- Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}"
Id: !Sub "${ProjectName}-${EnvironmentName}-pipeline"
RoleArn: !GetAtt EventBridgeInvokePipelineRole.Arn
このEventBridgeルールは detail-type: AWS API Call via CloudTrail を前提にしています。対象S3バケットのPutObject等がCloudTrailイベントとして取得できる状態になっていないと、ルールを作成してもPipelineは自動起動しません。
まずは EnableSourceChangeRule=false で手動実行確認し、その後に自動起動を有効化するのが安全です。
デプロイコマンド
staging用です。
aws cloudformation deploy `
--template-file .\codepipeline_cloudformation.yaml `
--stack-name <project>-staging-cicd `
--region us-west-2 `
--profile <profile> `
--capabilities CAPABILITY_NAMED_IAM `
--parameter-overrides `
ProjectName=<project> `
EnvironmentName=staging `
SourceBucketName=<source-bucket-name> `
SourceObjectKey=staging/app.zip `
EcsClusterName=<staging-ecs-cluster> `
AppServiceName=app `
WorkerServiceName=worker `
EcrRepositoryName=<staging-ecr-repository> `
EcrCacheRepositoryName=<staging-ecr-cache-repository> `
ImageTag=<image-tag> `
RailsEnv=production `
ServerEnv=staging `
DockerfilePath=Dockerfile.production `
EcsTaskExecutionRoleArn=<app-task-execution-role-arn> `
EcsTaskRoleArn=<app-task-role-arn> `
WorkerEcsTaskExecutionRoleArn=<worker-task-execution-role-arn> `
WorkerEcsTaskRoleArn=<worker-task-role-arn> `
EnableSourceChangeRule=false
production用です。
aws cloudformation deploy `
--template-file .\codepipeline_cloudformation.yaml `
--stack-name <project>-prod-cicd `
--region us-west-2 `
--profile <profile> `
--capabilities CAPABILITY_NAMED_IAM `
--parameter-overrides `
ProjectName=<project> `
EnvironmentName=prod `
SourceBucketName=<source-bucket-name> `
SourceObjectKey=prod/app.zip `
EcsClusterName=<prod-ecs-cluster> `
AppServiceName=app `
WorkerServiceName=worker `
EcrRepositoryName=<prod-ecr-repository> `
EcrCacheRepositoryName=<prod-ecr-cache-repository> `
ImageTag=<image-tag> `
RailsEnv=production `
ServerEnv=production `
DockerfilePath=Dockerfile.production `
EcsTaskExecutionRoleArn=<app-task-execution-role-arn> `
EcsTaskRoleArn=<app-task-role-arn> `
WorkerEcsTaskExecutionRoleArn=<worker-task-execution-role-arn> `
WorkerEcsTaskRoleArn=<worker-task-role-arn> `
EnableSourceChangeRule=false
Source ZIPの作成
今回一番ハマったのが、ZIPに不要なファイルを含めてしまったことです。
特にRailsアプリの場合、以下はZIPに含めないようにします。
.git
build
app.zip
public/assets
public/packs
tmp/cache
vendor/bundle
node_modules
vendor/bundle を含めると、ローカル環境のgemや壊れたassetがDocker build contextに混ざる可能性があります。
PowerShellでは以下のようにZIPを作成しました。
cd <project-root>
Remove-Item -Force .\app.zip -ErrorAction SilentlyContinue
tar.exe `
--exclude=.git `
--exclude=build `
--exclude=app.zip `
--exclude=public/assets `
--exclude=public/packs `
--exclude=tmp/cache `
--exclude=vendor/bundle `
--exclude=node_modules `
-a -cf app.zip .
ZIP内に不要なassetが混入していないか確認します。
Remove-Item -Recurse -Force .\build\zipcheck -ErrorAction SilentlyContinue
Expand-Archive .\app.zip .\build\zipcheck -Force
Get-ChildItem -Recurse .\build\zipcheck -Filter "turbo.min*.js"
何も出ないことを確認します。
また、Dockerfileで参照するcredentialsファイルが含まれているかも確認します。
Test-Path .\build\zipcheck\config\credentials.yml.enc.staging
production用であれば、production用のcredentialsファイルが含まれていることも確認します。
Test-Path .\build\zipcheck\config\credentials.yml.enc.production
S3へアップロード
staging用です。
aws s3 cp .\app.zip s3://<source-bucket-name>/staging/app.zip `
--region us-west-2 `
--profile <profile>
production用です。
aws s3 cp .\app.zip s3://<source-bucket-name>/prod/app.zip `
--region us-west-2 `
--profile <profile>
Pipelineを手動実行
最初はEventBridgeトリガーを無効にして、手動実行で確認しました。
aws codepipeline start-pipeline-execution `
--name <project>-staging-app-pipeline `
--region us-west-2 `
--profile <profile>
実行状態を確認します。
aws codepipeline get-pipeline-state `
--name <project>-staging-app-pipeline `
--region us-west-2 `
--profile <profile>
CodeBuildの最新実行も確認します。
$BuildId = aws codebuild list-builds-for-project `
--project-name <project>-staging-build-app `
--region us-west-2 `
--profile <profile> `
--sort-order DESCENDING `
--query "ids[0]" `
--output text
aws codebuild batch-get-builds `
--ids $BuildId `
--region us-west-2 `
--profile <profile>
CloudFront Invalidation
ECSへのデプロイが完了したら、CloudFrontのキャッシュ削除を実行します。
CloudFrontでは、エッジキャッシュに残っているファイルを期限前に削除したい場合、Invalidationを実行します。Invalidation後、次回リクエスト時にCloudFrontはOriginから最新ファイルを取得します。
参考: Invalidate files to remove content - Amazon CloudFront
今回のようにassetやimportmapが絡む場合は、デプロイ後に以下を実行する運用にしました。
aws cloudfront create-invalidation `
--distribution-id <distribution-id> `
--paths "/*" `
--profile <profile>
検証時や原因切り分けでは /* のInvalidationが確実です。一方、本番運用で範囲を絞る場合は /assets/* などに限定する選択肢もあります。
ただし、Rails importmapやHTML側の参照も絡む場合は、HTMLも含めて更新されるよう /* の方が安全なケースがあります。
AWSとしては、頻繁に更新するファイルについてはInvalidationだけでなく、ファイル名にバージョンを含める運用も推奨しています。
参考: Invalidate files to remove content - Amazon CloudFront
ハマりどころ1: app / worker でTask Execution Roleが違った
ECS Deploy Actionでは、タスク定義に設定されているTask Role / Task Execution Roleに対して iam:PassRole が必要です。
appとworkerでExecution Roleが違う場合、appだけ成功してworkerだけ失敗することがあります。
そのため、CloudFormationでは app / worker の両方のRole ARNを渡せるようにしました。
Parameters:
EcsTaskExecutionRoleArn:
Type: String
Default: "*"
EcsTaskRoleArn:
Type: String
Default: "*"
WorkerEcsTaskExecutionRoleArn:
Type: String
Default: "*"
WorkerEcsTaskRoleArn:
Type: String
Default: "*"
IAM Policy側では以下のように指定します。
- Sid: IamPassRoleForEcs
Effect: Allow
Action:
- iam:PassRole
Resource:
- !Ref EcsTaskExecutionRoleArn
- !Ref EcsTaskRoleArn
- !Ref WorkerEcsTaskExecutionRoleArn
- !Ref WorkerEcsTaskRoleArn
Condition:
StringEquals:
iam:PassedToService:
- ecs.amazonaws.com
- ecs-tasks.amazonaws.com
最初は Resource: "*" にして動作確認することもできますが、本番運用では対象のTask Role / Task Execution Roleに絞る方が安全です。
ハマりどころ2: vendor/bundleを含めてassetが壊れた
最初、ローカルの vendor/bundle や public/assets がZIPに含まれていました。
その結果、Docker build時に壊れたassetが混入し、ブラウザで以下のようなエラーが出ました。
Uncaught SyntaxError: Unexpected token '{'
対応として、以下をZIPから除外しました。
vendor/bundle
public/assets
public/packs
tmp/cache
node_modules
Dockerfile内で bundle install を行っている場合、vendor/bundle は含めない方が安全です。
ハマりどころ3: credentialsファイルがZIPに含まれていなかった
Dockerfile内で、server の値に応じてcredentialsファイルをコピーしていました。
RUN case "$server" in \
"production" ) cp config/credentials.yml.enc.production config/credentials.yml.enc ;; \
"staging" ) cp config/credentials.yml.enc.staging config/credentials.yml.enc ;; \
esac
このため、Source ZIP内に対象環境のcredentialsファイルが含まれていないと、Docker buildが失敗します。
cp: cannot stat 'config/credentials.yml.enc.staging': No such file or directory
ZIP作成後は、対象のcredentialsファイルが含まれているか確認するようにしました。
Test-Path .\build\zipcheck\config\credentials.yml.enc.staging
ハマりどころ4: buildspecのcache.registryはCodeBuildに認識されなかった
当初、buildspecに以下のような設定を書いていました。
cache:
registry: $CACHE_REPOSITORY_URI
しかし、CodeBuildではこの registry キーが認識されず、以下のような警告が出ました。
Found possible syntax errors in buildspec:
In the section cache
The following keys cannot be identified:
registry
そのため、今回の記事のbuildspecではこの設定を削除しています。
Docker layer cacheを使いたい場合は、CodeBuildのローカルキャッシュやDocker側のcache-to/cache-fromなど、別の方式を検討する必要があります。
ハマりどころ5: CloudFrontキャッシュ
最終的に、CodeBuild / ECS deploy は成功しているのに、ブラウザでは古いJavaScript assetが配信され続ける事象がありました。
原因はCloudFrontのキャッシュでした。
CodeBuild / ECSのデプロイが成功していても、CloudFrontが古いassetを返していると、ブラウザ上では古いJavaScriptが動き続けます。
今回はCloudFront Invalidationを実行することで解消しました。
aws cloudfront create-invalidation `
--distribution-id <distribution-id> `
--paths "/*" `
--profile <profile>
最終的なデプロイ手順
最終的には、以下の流れにしました。
1. Source用S3バケットを作成し、versioningを有効化
2. CloudFormationでCodePipeline / CodeBuild / IAM / Artifact Bucketを作成
3. app.zipを作成
4. app.zipに不要なassetsやvendor/bundleが含まれていないことを確認
5. app.zipに対象環境のcredentialsファイルが含まれていることを確認
6. app.zipをS3へアップロード
7. CodePipelineを手動実行
8. CodeBuild / ECS Deployの成功を確認
9. CloudFront Invalidationを実行
10. ブラウザで動作確認
11. 問題なければEventBridgeトリガーを有効化
チェックリスト
最後に、今回のような構成で確認すべき項目をまとめます。
[ ] Source用S3 Bucketのversioningは有効か
[ ] staging / production で SourceObjectKey は分かれているか
[ ] PollForSourceChanges は false になっているか
[ ] EventBridgeを使う場合、CloudTrailイベントを拾える状態か
[ ] app.zipに public/assets が入っていないか
[ ] app.zipに vendor/bundle が入っていないか
[ ] Dockerfileで参照するcredentialsファイルがZIPに含まれているか
[ ] app / worker両方のTask Role / Execution Roleに iam:PassRole できるか
[ ] CodeBuildでDocker buildするために PrivilegedMode が有効か
[ ] ECS serviceが新しいTask Definitionに更新されたか
[ ] CloudFront Invalidationを実行したか
まとめ
既存ECS環境に後からCodePipeline / CodeBuildを追加する場合、CloudFormation化しておくとstaging / productionで同じ構成を再現しやすくなります。
一方で、実際に運用してみると、CodePipelineそのものよりも以下のような周辺でハマりやすいです。
- S3 SourceのObjectKey
- ZIPに含めるファイル
- vendor/bundleやpublic/assetsの混入
- credentialsファイルの有無
- app / workerのIAM PassRole
- CloudFrontキャッシュ
- staging / production のbuild-arg差分
特にCloudFrontを挟んでいる場合、ECSには新しいイメージがデプロイされていても、ブラウザには古いassetが返ってくることがあります。
そのため、ECS deploy完了後にCloudFront Invalidationを入れるところまでをデプロイ手順として扱うのが安全です。