0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

既存ECS環境にCloudFormationでCodePipelineを追加し、CloudFrontキャッシュでハマった話

0
Posted at

はじめに

既存の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リソースでは、StagesRoleArnArtifactStore などを定義します。

参考: AWS::CodePipeline::Pipeline - AWS CloudFormation

なお、ArtifactStoreArtifactStores はどちらか一方のみを指定します。単一リージョン構成では ArtifactStore を使い、クロスリージョンActionを含む場合は ArtifactStores を使います。

CodeBuildは AWS::CodeBuild::Project として定義できます。CodeBuild Projectでは、SourceArtifactsEnvironmentServiceRole などを定義します。

参考: 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"

ここで重要なのは、ProjectNameEnvironmentName は新規作成するリソース名に使うだけで、既存ECSやS3の参照先を決める値ではないという点です。

既存リソースの参照先は以下で決まります。

SourceBucketName
SourceObjectKey
EcsClusterName
AppServiceName
WorkerServiceName
EcrRepositoryName

例えば、ProjectName=myappEnvironmentName=staging と指定すると、作成されるパイプライン名は以下のようになります。

myapp-staging-app-pipeline

一方で、実際にデプロイするECS ClusterやServiceは EcsClusterNameAppServiceNameWorkerServiceName で指定します。

CodeBuild

CodeBuildはCodePipelineからSource Artifactを受け取るため、SourceArtifactsCODEPIPELINE にします。

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.jsonimagedefinitions-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の PollForSourceChangesfalse にしています。CloudWatch Events / EventBridge を使う場合、重複起動を避けるため PollForSourceChangesfalse にします。

参考: 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/bundlepublic/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を入れるところまでをデプロイ手順として扱うのが安全です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?