CodePipelineを利用してECS Fargateでブルーグリーンデプロイメントする
はじめに
今回の記事の目的
CodePipelineを利用してECS Fargateでブルーグリーンデプロイメント(Blue-Green Deployment)する。
まず、ビルド、デプロイについて単体で理解し、それらの設定をCodePipelineに組み込んでいった。かなり苦労したので、エラーの内容も合わせて紹介する。
順々にやっていったことで20000字の超大作になってしまったが、チュートリアルの通りにやるだけだとなかなか理解できないので、本記事の流れで理解していくのがおすすめ。
主に参考にしたチュートリアル:
https://pages.awscloud.com/rs/112-TZM-766/images/AWS_CICD_ECS_Handson.pdf
前提条件
以下の記事で作成したFargateを利用する。 ALB経由でインターネットからアクセス可能なコンテナを立ち上げた。 tomiko0404.hatenablog.com
また、コードコミットとの連携は以下の記事の通り完了している。 tomiko0404.hatenablog.com
【参考】CodePipelineを使用せずイメージだけ作り直した場合の動作確認
dockerイメージをECRにプッシュ
アプリを変更し、dockerイメージをビルドし直したら、ECRにpushする。 ECR上でもlatestが最新となった。
この状態でALBにHTTP接続してみてもアプリは変更前(旧バージョン)の状態であった。
最終的に用意する必要があるファイル
元々作っていたプロジェクトフォルダ直下に、以下3ファイルを追加する必要がある。詳細は実施内容の中で記述する。
- buildspec.yml
- appspec.yml
- taskdef.json
これらのファイルはCodeCommit上のリポジトリに存在しないと意味が無いので注意。 作成したり修正したりするたびに、pushする必要がある。
buildspec.yml
buildspec.ymlとは、CodeBuildがビルド時に実行する処理を記載するファイル。
チュートリアルを通してCodeBuildを理解する #reinvent | DevelopersIO
appspec.yml
appspec.ymlとは、CodeDeployを使ってデプロイする際の条件を記載したもの。
taskdef.json
taskdef.jsonとは、「タスク定義」を記載したもの。
- はじめに
- 最終的に用意する必要があるファイル
- CodeBuildとCodeDeployの理解
- CodePipelineを使った自動デプロイ(この記事の本番)
- 動作確認
- エラー対処のご紹介
- おわりに
- その他参考資料
CodeBuildとCodeDeployの理解
CodePipelineは、ソースコードのコミット→ビルド→デプロイの3ステップを自動で行ってくれるものである。
そもそもビルド、デプロイの概念を理解したいため、ビルド(Code Build)、デプロイ(Code Deploy)のステップをそれぞれ手動実行で利用してみる。
CodeBuildを利用してみる(手動)
IAMポリシー作成
「CodeBuildが」「ECRに」アクセスするためのIAMポリシーを作成する。
公式サイト( CodeBuild のDocker サンプル - AWS CodeBuild ) からのコピペで実施しようとしたがうまくいかなかった(ECRログインで権限エラー)ので、とりあえずフルアクセス権限にした。
フルアクセス権限作成
IAMのマネジメントコンソール→「ロールを作成」→「AWSサービス」→「CodeBuild」
Administrator Accessのポリシーをアタッチする。
最終的に以下のようになっていた。CodeBuildでIAMロールを設定すると自動で必要なポリシーもつくと思われる。
【参考】公式サイトの内容
公式サイトでは、ポリシーに以下JSONを追加するようにとのことだった。うまくいかなかった原因は不明。
{ "Action": [ "ecr:BatchCheckLayerAvailability", "ecr:CompleteLayerUpload", "ecr:GetAuthorizationToken", "ecr:InitiateLayerUpload", "ecr:PutImage", "ecr:UploadLayerPart" ], "Resource": "*", "Effect": "Allow" },
buildspec.yml 作成
プロジェクトフォルダ直下(Dockerfileと同じ場所)に作成した。 公式サイト(CodeBuild のDocker サンプル - AWS CodeBuild)からのコピペ。
これを見ると、実行しているコマンドは手動でECRにプッシュしていた時と同じことをしているとわかる。(一部環境変数になっているが、後で設定する。)
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 Build started on `date` - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
CodeBuildプロジェクトを作成する
マネジメントコンソールの「CodeBuild」→ 「ビルドプロジェクトを作成する」。
公式サイト:
ビルドプロジェクトの作成 (コンソール) - AWS CodeBuild
に従って実施。
- 環境イメージは「マネージド型イメージ」。
- オペレーティングシステムは「Ubuntu」、ランタイムは「Standard」、イメージは「aws/codebuild/standard:4.0」、イメージのバージョンは最新のものとした。(公式サイト( CodeBuild のDocker サンプル - AWS CodeBuild ) より)
- 特権付与(Privileged)にチェックする
- ソースの設定
- ビルドバッジは無効にした。
- サービスロールは、先ほど作成した「ECRにアクセスできるロール(adminをつけたロール)」を設定する。
- ビルド仕様は「buildspecファイルを使用する」を選択。オプションのBuildspec名は記載不要。デフォルトでbuildspec.ymlを利用するようになっているため。
- アーティファクトは「なし」とした。
- 環境変数の設定。buildspecファイルに記載した環境変数を、ECRに手動でpushするときのコマンドと見比べながら以下のように設定。
パラメータ | 値 |
---|---|
AWS_DEFAULT_REGION |
ap-northeast-1 |
AWS_ACCOUNT_ID |
[AWSアカウント(数字12桁)] |
IMAGE_TAG |
latest |
IMAGE_REPO_NAME |
ay-s-react-practice-6 (ECRのリポジトリ名) |
ビルドの実行
「ビルドを開始」で手動ビルド(=CodeCommitからECRへのpush)を実施する。 何とか成功。
失敗するときはビルドプロジェクトに設定したIAMロールに、ECRへのアクセス権限があるか要確認。
ECR上に新たなイメージがビルドされていることを確認した。
これでビルドは完了。
CodeDeployを利用してみる(手動)
以下記事で作成したサービスを、CodeDeployを使ってデプロイする。
ECS(Fargate)でコンテナを起動その2(ALBを利用したルーティング&CodeDeployの下準備) - エンジニアを目指す日常ブログ
前提として必要な設定
前回の記事で完了しているが、事前に必要な設定として以下のようなものがある。
- Fargateサービスの作成時にALBが設定されていること。
- CodeDeploy用のIAMロールが作成されていること。
- Fargateサービスの作成時に、デプロイ方法をBlue/Greenデプロイメントと設定していること。
- Fargateサービスの作成時に、「CodeDeploy のサービスロール」にCodeDeploy用IAMロールを設定していること。
公式サイト「CodeDeploy のBlue/Greenデプロイメントを実行する」以降の手順を実施する。
AWS CodeDeploy による AWS Fargate と Amazon ECS でのBlue/Greenデプロイメントの実装 | Amazon Web Services ブログ
タスク定義の新バージョンを作成する
タスク定義から、現行バージョンのタスク定義にチェックを入れ「新しいリビジョンの作成」をクリック。Nameタグのみ、値を少し変えて作成完了する。
サービスの更新を手動で実行
ECSコンソールの「サービス」→立ち上げているサービスにチェック→「更新」ボタンをクリック。
サービスの設定画面
- リビジョンは2(latest)に変更する。
デプロイメントの設定は変更なし。
ネットワークの設定も変更なし。
オートスケーリングも設定なしのまま。
- サービスの更新を実行する。
デプロイ開始
CodeDeployによるBlue/Greenデプロイメントが開始した。
デプロイに失敗した・・・・・・・
失敗したので、ロールバックしてくれている模様。つまり、今リリースしている資材には影響を与えていない。
デプロイ失敗のログ確認
CodeDeployのコンソール画面にはログが出てこなかったが、ECSコンソール画面の「サービス」→「ログ」のタブからログを確認することができた。
また、「タスク」タブで「Stopped」を選ぶと、起動しようとして停止を繰り返すタスクがたくさん存在していることがわかる。停止を繰り返して60分たったので、デプロイがタイムアウトし、失敗となった。
デプロイ失敗の原因
Dockerfileの中身にミスがあった。CodeBuildの失敗時に色々試していた時に、コンテナのWORKDIRを変更してしまっていた。その結果/app
フォルダ内にあるpackage.json
が見つからずエラーとなっていた。
FROM node:14.15.4-slim RUN apt-get update && apt-get install -y locales \ && locale-gen en_US.UTF-8 \ && localedef -i en_US -f UTF-8 en_US.UTF-8 \ && echo "export LC_ALL=en_US.UTF-8" >> ~/.bashrc \ && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime COPY ./app /app WORKDIR /app RUN npm install ENV PORT 80 CMD ["npm", "start" ] WORKDIR / ←←これが原因だったため、削除 RUN apt-get install -y \ wget \ curl \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*
Dockerfileを修正し、CodeCommitにpush→CodeBuildを手動実行→サービスの更新を再実行した。
サイトを参考にした。
CodeDeployを使用したデプロイがタイムアウトとなり失敗する - あとらすの備忘録
デプロイに成功
デプロイに成功した。このまま1時間待機して問題が無ければ元のタスクが終了する。
手動で「元のタスクセットを終了」することもできた。
タスクの起動状況を見ると以下のように遷移。一時的に2つのタスクが起動し、デプロイ終了すると1つになる。
ALBにアクセスすると、修正版のWebアプリが表示された。Deploy画面のStep2まで完了した時点で、ALBの向き先は新バージョンのタスクに変更されていた(新バージョンのアプリを閲覧できた)。
CodePipelineを使った自動デプロイ(この記事の本番)
本構成のCodePipelineの役割
- CodeCommit上のソースコードの変更を検知して、パイプラインを開始してくれる。
- パイプラインが開始すると、CodeBuildでdockerイメージ化&ECRへのイメージプッシュを実施(そのようにbuildspec.ymlを記載している)してくれる。
- 続いて、パイプラインでCodeDeployを動かしてFargateのサービスを更新してくれる。
パイプラインの作成
パイプラインを作成する。AWSマネジメントコンソールの「CodePipeline」→「新規のパイプラインを作成する」。
サービスロール
- サービスロールは新規作成する。
ソースプロバイダ
- ソースプロバイダーは「CodeCommit」
- プロジェクトのリポジトリとブランチ名を設定する。
ビルドステージ
- ビルドステージを追加する。
- プロバイダは「AWS CodeBuild」を選択する。
- プロジェクト名は先ほど作ったBuildプロジェクトを指定した。
デプロイステージ
- デプロイステージを追加する。
- デプロイプロバイダーは「Amazon ECS(ブルー/グリーン)」を選択。
- AWS CodeDeployアプリケーション名を選択する。アプリケーションとは、CodeDeployでのデプロイの設定をまとめたもの。
※【利用中のECSサービスの頭に「AppECS-[クラスタ名]」が追記されたアプリケーション名】が選べるようになっていた。これは先ほど手動でデプロイしたときに作成されたもの。 - AWS CodeDeployデプロイグループを選択する。デプロイグループとは、デプロイする単位をグループ化したもの。
※【利用中のECSサービスの頭に「DgpECS-[クラスタ名]」が追記されたデプロイグループ名】が選べるようになっていた。これも先ほど手動でデプロイしたときに作成されたもの。以下のような内容が設定されていた。
ひとまずパイプラインの作成を完了した。
デプロイステージの編集
taskdef.jsonファイルを作成する
先ほど(タスク定義の新バージョンを作成する)で作成したタスク定義をJSON形式でコピーしてきて、プロジェクトフォルダ直下に新規作成したtaskdef.json
ファイルに貼り付けた。
その後、
"image": "イメージのタグ名",
となっている部分を
"image": "<IMAGE1_NAME>",
と書き換える。
appspec.ymlファイルを作成する
プロジェクトフォルダ直下にappspec.yml
を作成し、公式ドキュメントに従い、以下の通り記載する。
公式ドキュメント:
チュートリアル: Amazon ECR ソースと、ECS と CodeDeploy 間のデプロイを含むパイプラインを作成する - AWS CodePipeline
コンテナ名は、taskdef.jsonで定義したタスクの中で作成しているコンテナ名である必要がある。
appspec.yml
version: 0.0 Resources: - TargetService: Type: AWS::ECS::Service Properties: TaskDefinition: <TASK_DEFINITION> LoadBalancerInfo: ContainerName: "ay-s-ReactPrc6-forALB-vpc16-container" ContainerPort: 80
デプロイステージのアーティファクトを設定する
アーティファクトとは、CodePipeline上で前のステージから次のステージに引き渡すもの。(例:SorceからBuildeへ)
デプロイステージのアクションを編集する。
pipelineの「編集」→「ステージを編集する」→「Deploy」の編集ボタンをクリックする。
- 入力アーティファクトを「Source Artifact」と「Build Artifact」とする。
- Amazon ECS タスク定義を「SourceArtifact」の「taskdef.json」とする。
- AWS CodeDeploy App Spec ファイルを「SourceArtifact」の「appspec.yml」とする。
- 入力アーティファクトを持つイメージの詳細を「Build Artifact」とする。
- タスク定義のプレースホルダー文字を「IMAGE1_NAME」とする。
imageDetail.jsonを作成する
イメージファイルのURLを指定するためのimageDetail.jsonを作成する。 imageDetail.jsonは、ビルドステージで自動作成させるため、buildspec.ymlにファイル作成のための処理を追記する。
※今回はBlue/Green デプロイのためimageDetail.json
だが、ローリングデプロイの場合は
imagedefinitions.json
となり、フォーマットも異なるので注意。(原因②artifactのファイル名も参照)
まず、buildspec.ymlのpost_build:
の処理の中に以下を追記。
- printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json
イメージURLをimageDetail.jsonに書き込んでいる。
また、ファイル末尾に以下を追記。
artifacts: files: imageDetail.json
全文は以下となる。
buildspec.yml
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 Build started on `date` - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - echo Writing image definitions file.... - printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json artifacts: files: imageDetail.json
Build後、ArtifactファイルはS3に保存されているため、ダウンロード可能。
中身は以下のように記載されている。
imageDetail.json
{"ImageURI":"XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/ay-s-react-practice-6:latest"} ※XXXXXXXはアカウントID
設定ファイルをコードコミットにpush
appspec.yml
、buildspec.yml
、taskdef.json
を下記の通り配置できたので、コードコミットにpushする。
$ git add . $ git commit -m "設定ファイルを作成" $ git push origin_codecommit master
コードコミットの設定:
tomiko0404.hatenablog.com
動作確認
ソースコードを変更して、CodeCommitにプッシュ。
※実際は、下記のとおり問題にぶつかり続けたので、各種設定ファイルの変更→コミットをかなりの回数実施しています。
$ git add . $ git commit -m "ソースコードを変更" $ git push origin_codecommit master
やっとできた!
Deployが進行中になっているのは、1時間の切り戻し待機中(Blue/Greenデプロイ)のため。
置き換えタスクセットがトラフィックを処理していればOK。
ブラウザから確認
ALBのDNS名にアクセスすると、変更が反映されていた。
DNS名の確認方法:
ECS(Fargate)でコンテナを起動その2(ALBを利用したルーティング&CodeDeployの下準備) - エンジニアを目指す日常ブログ
エラー対処のご紹介
ハマったエラーの紹介。
※ここまでの章は、ハマったエラー原因は解消した状態での結果を載せている。
エラー#1:BuildArtifactへのアクセス拒否
デプロイステージで以下のエラーが発生。
You are missing permissions to access input artifact: BuildArtifact.
原因と解決策
buildspec.yml内でBuildステージのアーティファクトを作成できていなかったことが問題。imageDetail.jsonを作成するの手順を実施することで解消。
エラー#2 BuildArtifactの読み込み時にエラー
なんとかBuildArtifactを作れたが、以下エラーが連発。
Exception while trying to read the image artifact file from: BuildArtifact.
原因①イメージ名の環境変数間違い
環境変数を$IMAGE_REPO_NAME
と自分でCodeBuild上で設定していたが、チュートリアルに沿って$REPOSITORY_URI
にしてしまっていた。
原因②artifactのファイル名
imagedefinition.jsonではなく、imageDetail.jsonでないとだめ。
原因③artifactファイルの中身のパラメータ名違い
チュートリアルがECS標準デプロイ前提だったので、中身の書き方もそのまま間違えていた。
imagedefinitions.json
の場合(誤)
[ { "name": "sample-app", "imageUri": "11111EXAMPLE.dkr.ecr.us-west-2.amazonaws.com/ecs-repo:latest" } ]
imageDetail.json
の場合(正)
{ "ImageURI": "ACCOUNTID.dkr.ecr.us-west-2.amazonaws.com/dk-image-repo@sha256:example3" }
artifactファイルの書き方
イメージ定義ファイルのリファレンス - AWS CodePipeline
エラー#3:コンテナ名まちがい
デプロイステージで以下のエラーが発生。
The ECS service cannot be updated due to an unexpected error: The container ay-s-ReactPrc6-forALB-vpc16-container-codepipeline does not exist in the task definition. (Service: AmazonECS; Status Code: 400; Error Code: InvalidParameterException; Request ID: XXXXXXXX; Proxy: null). Check your ECS service status.
原因と解決策
appspec.yml
ファイルの中でContainerName:
を設定する際、勝手に名前を付けてしまっていた。エラーが出たため、タスク定義の中で設定されているコンテナ名に修正した。
エラー#4
デプロイを開始したが、その後タスクが起動と停止を繰り返す現象。
以下の状態で止まり、60分でタイムアウトする。
さらに、今回はログが出る前にタスクがStopしている模様。
ただし、タスクのステータス画面に怪しげな文言が出ていたので確認。
STOPPED (CannotPullContainerError: inspect image has been retried 1 time(s): failed to resolve ref "docker.io/library/ay-s-react-practice-6:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: ...)
原因と解決策
ECSのタスク定義画面から、コンテナの設定を見てみると、「イメージ」にちゃんとしたURLが入っていない。
imageDetail.jsonの中身が
{"ImageURI":"ay-s-react-practice-6:latest"}
となっており、"imageUri"がURIになっていないことが原因と思われた。
そこで、imageDetail.jsonを作っているbuildspec.yml
を修正した。
誤:元の記載) - printf '{"ImageURI":"%s"}' $IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json 正:修正後) - printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json
おわりに
土日をつぶして2週間かかったが、CodePipelineでソースコードをデプロイすることができた。