AWSECS

CodePipelineとCodeBuildのCI/CDでGitHubからECSへデプロイする

アジャイル開発やDevOpsが流行りだして久しいですが、それらを支える手法にCI(継続的インテグレーション) CD(継続的デリバリー) というものがあります。

AWSにはこのCI/CD環境を構築するのためのサービスがいくつか用意されています。

今回はGitHubにリソース(ソースコードやDockerfile)がプッシュされたことをトリガーとして、自動的にDockerイメージを作成し、ECSにデプロイするCI/CD環境を構築してみます。

スポンサーリンク

前提

AWSのCI/CD用サービス群の概要

AWSには巷でCode4兄弟と言われているCI/CD用のサービスがあります。

CI/CD周りは関連するリソースが多く初めて構築するときは大変ですが、これらのサービス群はフルマネージドで管理不要であるため、一旦構築してしまえば楽に運用することができます。

またCI/CDのためのJenkinsサーバ(経験上、CPUやメモリ、ディスク容量を食うので、それなりに高スペックサーバになりがちです)などを常時起動させておく必要がなくなるため、コスト削減にもつながります。

CodeCommit

AWSが提供しているプライベート用のGitリポジトリです。

Gitの有名なホスティングサービスにGitHubがありますが、そのAWS版みたいなものです。

ただ僕が職場でGitHubを使用していることもあり、今回はCodeCommitの代わりにGitHubを使用します。

CodePipeline

CI/CDの中核となるサービスです。CI/CDの全体フローを管理します。

Code兄弟の長男、まとめ役といった立ち位置です。

CodeBuild

CI部分を担当します。ビルドやテストを行います。

ビルドはMavenでのJavaのビルドや、Dockerのコンテナイメージの作成などの用途で利用します。

テストはJunitをはじめとしたXUnit系のテストツールなど、いくつかのテストフレームワークに対応しています。

CodeDeploy

CD部分を担当します。アプリケーションのデプロイ(リリース)を行います。

EC2、オンプレ、Lambda、ECSへデプロイする仕組みを提供します。

ですが、今回はCodeDeployを使用しません

ECSのデプロイ方式には、CodePipelineの機能による標準デプロイ(ローリングデプロイ)と、CodeDeployの機能による Blue/Green の2種類があり、今回は前者の標準デプロイを使用するためです。

構築するインフライメージ

今回構築するインフラは以下のとおりです。特に指定がない限り、各AWSサービスは東京リージョン(ap-northeast-1)で構築します。

あらかじめ必要な関連リソース

本題のCodePipeline、CodeBuildの構築の前に、以下のリソースを準備します。

Githubリポジトリ

GitHubでアカウントを登録し、リポジトリを作成します。プライベートリポジトリのほうが安心して練習できるかと思います。

今回は demo-app というプライベートリポジトリを用意します。

GitHub: Let’s build from here
GitHub is where over 100 million developers shape the future of software, together. Contribute to the open source community, manage your Git repositories, revie

ECRリポジトリ

ECSで動かすDockerイメージを保管するため、ECRのリポジトリを用意します。まだ作成していない方は以下の記事を参考にしてみて下さい。

今回は demo-image というプライベートリポジトリを用意します。

ECS Fargate

Dockerコンテナが稼働するECSを構築しておきます。まだ構築していない方は以下の記事を参考にしてみて下さい。

今回はECS周りの設定を以下のとおりに設定します。以下に明記していないものは参考記事どおりの内容でOKです。

タスクとコンテナの定義の設定

タスク定義名を demo-task とします。

コンテナの定義
項目名
コンテナ名demo-container
イメージ作成したECRのURIを入力
AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/demo-image
ポートマッピング80 (tcp)
クラスターの作成

クラスター名を demo-cluster とします。

サービスの作成

サービス名を demo-service とします。



CI/CD環境の構築手順

それでは本題のCodePipeline、CodeBuildの構築に入ります。AWSマネジメントコンソールを使用します。

以降の説明で特に明記していない設定項目はデフォルトのままでOKです。

GitHubにbuildspec.ymlなどをコミット

まずはじめにGitHubリポジトリに必要なファイルをコミットします。今回は以下の3種類を用意します。

実物はこちらのGitHubにコミットしました。

buildspec.yml

CodeBuild設定ファイルである(デフォルト名では)buildspec.ymlをコミットします。今回は公式ドキュメントにあるサンプルを参考に少し修正したものになります。

AWS_ACCOUNT_ID, REPOSITORY_URI, CONTAINER_NAME は環境に合わせて変更してください。

version: 0.2

phases:
  pre_build:
    commands:
      - aws --version
      # 環境変数の設定
      - AWS_ACCOUNT_ID=12346789012
      - ECR_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
      - REPOSITORY_URI=${ECR_URI}/demo-image
      - CONTAINER_NAME=demo-container
      # コミットハッシュの先頭7桁をタグに利用する
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
      # ECRログイン
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_URI

  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG

  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files: imagedefinitions.json
version

記事執筆時点では 0.2 が最新です。こちらのドキュメントによると、0.1のときは各コマンドが異なるインスタンスで実行されてコマンドの結果を引き継げなかったようです。

phases

ビルド処理をいくつかのフェーズに分けて記述します。

フェーズの分け方ですが、(少なくとも前述のcommandsの内容であれば)この処理は必ずこのフェーズに書かなければならない という強制的なものではなく、感覚的に分けても大丈夫です。

runtime-versions はinstallフェーズでしか指定できないなどの制約があるにはありますが、例えば前述のbuildspec.ymlのcommands を全部buildに書いても問題なく動作はします。

ただ以下のようにCodeBuildの実行ログ画面で、各フェーズのごとに終了ステータスや実行時間の確認ができるので、フェーズを分けておいたほうがチューニングやエラー時の解析がやりやすくなると思います。

pre_build フェーズ

必要な環境変数の設定やECRへのログインなど、ビルド本処理前に必要な準備を行います。

なお、あらかじめCodeBuild側で定義される環境変数もあるため、適宜利用します。

build フェーズ

Dockerイメージのビルドとタグ付けを行います。

post_build フェーズ

ECRへDockerイメージをプッシュします。latest とGitHubコミットハッシュの先頭7桁の文字列の2つをイメージタグとして登録します。

最終的に、imagedefinitions.jsonというファイルにコンテナ名とECRのURIを書き込んでいます。

artifacts

ビルドの成果物を出力します。ECSの標準デプロイ(のデフォルト)では、デプロイ処理に必要なimagedefinitions.jsonを出力します。

Dockerfile

CodeBuildでDockerイメージを作成するため、元になるDockerファイルもコミットします。

アプリケーションソースコード

今回はindex.htmlをアプリケーションソースに見立てて、イメージ内にCOPYします。

以上の3つのファイルをGitHubにコミットします。GitHubコマンドでコミット、プッシュする場合は以下のようになります。

## ローカル環境へクローンしたディレクトリ配下
$ vi buildspec.yml
...
前述の内容を記載します
...
$
$ cat <<EOF > Dockerfile
FROM nginx
COPY index.html /usr/share/nginx/html
EOF
$
$ echo 'Hello CI/CD!' > index.html
$
$ ls
Dockerfile	README.md	buildspec.yml	index.html
$
$ git add .
$ git commit -m 'Add demo files' .
$ git push

Codestar Connectionsの作成

AWS CodeStarというCI/CD環境の構築を簡単にできるサービスがありますが、その名を冠した Codestar Connections という、サードパーティリポジトリへの接続機能を提供するリソースを作成します。

Codestar Connectionsは、CodePipeline構築の途中でも作成可能ですが、手順の流れをわかりやすくするため先に作成しておきます。

AWSマネジメントコンソールの画面上部検索窓 [ codepipeline ] で検索 → 当該サービスをクリック → デベロッパー用ツール画面の左ペイン [ 設定 ] → [ 接続 ] → 画面右の [ 接続を作成 ] を押下し、次画面から以下のとおり設定を行います。(AWSとGitHubの画面を行ったり来たりします)

接続を作成する

プロバイダーで GitHub を選択、接続名に任意の名前を入力し [ GitHub に接続する ] を押下します。

GitHubでGitHub Apps を認可

AWSマネジメントコンソールを操作しているブラウザで、まだGitHubにログインしていなければ自動的にGitHubのログイン画面に遷移します。

GitHubログイン後、もしくは既に当該ブラウザでGitHubにログインしている場合は、以下のような画面になります。

[ Authorize AWS Connector for GitHub ] を押下し、AWS Connector for GitHub というアプリケーションを認可します。

GitHub に接続する

AWSの画面へ自動的に戻った後、 [ 新しいアプリをインストールする ] を押下すると、再び以下のようなGitHub画面に遷移します。

[ Only select repositories ] を選択し、[ Select repositories ] で、demo-app を選択し、[ Install ] を押下します。

AWS画面に戻ると、GitHubアプリ が自動的に入力されています。そのまま [ 接続 ] を押下します。

以上でCodestar Connections の作成は完了となります。

スポンサーリンク

CodePipelineの作成

ソースステージ、ビルドステージ、デプロイステージの3つのステージからなるパイプラインを作成します。

デベロッパー用ツール画面の左ペイン [ パイプライン ] → 画面右の [ パイプラインを作成する ] を押下し、次画面からパイプラインの設定を行います。

パイプラインの設定を選択する

パイプラインの設定

以下のとおり設定し [ 次に ] を押下します。

項目名
パイプライン名任意の名前を入力
サービスロール新しいサービスロール を選択
ロール名任意の名前を入力

ソースステージを追加する

ソース

以下のとおり設定し [ 次に ] を押下します。

項目名
ソースプロバイダーGitHub (バージョン 2)
接続作成済みの Codestar Connections を選択
リポジトリ名GitHubアカウント名/demo-app
ブランチ名main
検出オプションを変更するソースコードの変更時にパイプラインを開始する にチェックを入れる
出力アーティファクト形式CodePipeline のデフォルト

ビルドステージを追加する

構築する – オプショナル
項目名
プロバイダーを構築するAWS CodeBuild
リージョンアジアパシフィック(東京)
(画面一番下の)ビルドタイプ単一ビルド

上記の通り設定し、プロジェクト名の [ プロジェクトを作成する ] を押下します。

ポップアップ画面が新しく開くので、そこでCodeBuildの設定を行います。

ビルドプロジェクトを作成する(CodeBuildの作成)

プロジェクトの設定

プロジェクト名に任意の名前を入力します。

環境
項目名
環境イメージマネージド型イメージ
オペレーティングシステムAmazon Linux 2
ランタイムStandard
イメージaws/codebuild/amazonlinux2-x86_64-standard:最新バージョン
イメージのバージョンこのランタイムバージョンには常に最新のイメージを使用してください
環境タイプLinux
特権付与チェックを入れる
サービスロール新しいサービスロール
ロール名任意の名前を入力
Buildspec

ビルド仕様で [ buildspec ファイルを使用する ] を選択します。Buildspec名は、入力なしでOKです。

バッチ設定

バッチ設定を定義は、チェックなしのままでOKです。

ログ

CloudWatch Logs のチェックを入れたままにします。ログを出力することで、CodeBuild画面からビルドログを確認することができるようになりビルドエラーの解決に役立ちます。

グループ名を入力しない場合は、/aws/codebuild/ビルドプロジェクト名 というロググループに出力されます。

以上を入力したら、[ CodePipeline に進む ] を押下します。ポップアップ画面が閉じられ、CodePipeline画面のプロジェクト名に当該プロジェクト名が自動入力されます。

[ 次に ] を押下します。

デプロイステージを追加する

デプロイ – オプショナル

以下のとおり設定し [ 次に ] を押下します。

項目名
デプロイプロバイダーAmazon ECS
リージョンアジアパシフィック(東京)
クラスター名demo-cluster
サービス名demo-service

レビュー

内容を確認し [ パイプラインを作成する ] を押下します。

以上でCodePipelineの作成は完了となります。

作成ボタン押下後、自動的にパイプラインが実行されます。が、以下のエラーでビルドに失敗します。

ECRへのアクセス権限不足のエラーとなります。

[Container] 2022/03/29 14:43:11 Running command aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_URI
An error occurred (AccessDeniedException) when calling the GetAuthorizationToken operation: User: arn:aws:sts::123456789012:assumed-role/codebuild-demo-build-service-role/AWSCodeBuild-23b4xxx is not authorized to perform: ecr:GetAuthorizationToken on resource: * because no identity-based policy allows the ecr:GetAuthorizationToken action
Error: Cannot perform an interactive login from a non TTY device
[Container] 2022/03/29 14:43:12 Command did not exit successfully aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_URI exit status 1
[Container] 2022/03/29 14:43:12 Phase complete: PRE_BUILD State: FAILED

CodeBuildのIAMロール修正

CodeBuildで使用しているIAMロールへ、ECRのアクセス権限を付与します。

直接IAM画面で当該ロールを検索する他に、CodeBuild画面からも当該ロールへ遷移できます。

デベロッパー用ツール画面の左ペイン [ ビルド ] → [ ビルドプロジェクト ] → 当該ビルドプロジェクトをクリック → 画面中央あたりの [ ビルドの詳細 ] タブ → 環境 のサービスロール をクリックします。

IAMの当該ロール画面に遷移後、[ アクセス許可を追加 ] → [ ポリシーをアタッチ ] → 検索ボックスに [ AmazonEC2ContainerRegistryPowerUser ] と入力し Enterキー押下→ 検索結果の右のチェックボックスにチェックを入れる → [ ポリシーをアタッチ ] を押下します。

以上でCI/CD環境構築の全行程が完了となります。

再度CodePipipeline画面に戻り、画面右上の[ 変更をリリースする ] を押下します。しばらく待つとデプロイステージまで正常に終了します。

こちらの手順を参考にしてECSで稼働しているコンテナのIPアドレスを確認してください。そのIPにアクセスすると以下のように表示されると思います。

$ curl 10.0.0.1
Hello CI/CD!
$

CI/CDの動作確認

GitHubにソースコードをコミットしたことをトリガーとして、自動でDockerビルドが行われECSへデプロイされるかの確認を行います。

以下のようにindex.htmlを変更しGitHubへプッシュすると、CodePipelineが変更を検知しビルド&デプロイが行われます。

$ echo 'Hello CI/CD! Version 2' > index.html
$
$ git add .
$ git commit -m 'Fix index.html' .
$ git push
$
...
パイプラインの完了まで待ちます
...
$
## デプロイによりコンテナの実行インスタンスが変わるため、IPも変わる可能性があります
$ curl 10.0.0.2
Hello CI/CD! Version 2
$

今回は以上です〜ノシ

参考

アリガト━━━ヾ(´∀`)ノ━━━━♪

チュートリアル: CodePipeline を使用した Amazon ECS 標準デプロイ
What are connections?
GitHub バージョン 1 のソースアクションを GitHub バージョン 2 のソースアクションに更新する
GitHubとの連携手段(OAuth Apps, GitHub Apps)を整理する