AWSの色んなサービスを意図的に使って、サンプルWebアプリを構築してみているのですが、今回は part3 ということで、「CodeBuild・CodePipelineを使ってデリバリーパイプラインを導入」していきたいと思います。
- part1: RDS 環境構築
- part2: EKSでRDSにアクセスするWEB APIサーバを構築
- part3: CodeBuild・CodePipelineを使ってデリバリーパイプラインを導入(今回)
今回実現すること
- ビルド
- CodeBuild でDocker イメージを作成して ECR にプッシュする
- デプロイ
- CodeBuild で ECR 上の Docker イメージを EKS クラスタに、デプロイする
- パイプライン
-
v*.*.*
形式のタグが GitHub リポジトリにプッシュされたら、ビルドを実行する- CodePipeline で、GitHub の変更を検知する
- タグプッシュは検知されないので、branch プッシュをトリガーに、ビルドスクリプトにて
git describe --tags
でタグ名を取得して、タグを取得できなかったり、v*.*.*
形式じゃない場合はエラーとする
- ビルド後、「手動承認」をしたら、デプロイが実行される
-
ビルド
CodeBuild で Docker イメージを作成して ECR にプッシュする設定を行います。
ビルドプロジェクトの作成
CodeBuild コンソール > ビルドプロジェクトを作成する
から以下の設定でビルドプロジェクトを作成します。
- プロジェクトの設定
- プロジェクト名:
sample-api-build
- プロジェクト名:
- ソース
- ソースプロバイダ: GitHub
- リポジトリ: GitHub アカウントのリポジトリ, OAuth を利用して使用して接続
- リポジトリのURL:
https://github.com/rinoguchi/aws_fullstack_sample_application
- プライマリリソースのウェブフックイベント: なし
- 環境
- 環境イメージ: マネージド型イメージ
- オペレーティングシステム: Amazon Linux 2
- ランタイム: Standard
- 特権付与:
Docker イメージを構築するか、ビルドで昇格されたアクセス権限を取得するには、このフラグを有効にします
にチェック
- 特権付与:
- イメージ: aws/codebuild/amazonlinux2-x86_64-standard:3.0
- 環境タイプ: Linux
- サービスロール: 新しいサービスロール
- BuildSpec
- ビルド仕様: buildspec ファイルを使用する
- BuildSpecファイル名:
api/buildspec-build.yml
// あとでもう一つ buildspec ファイルを作るので名前を変えてます
- アーティファクト
- アーティファクトなし(Docker イメージを作成して、ECR にアップロードするため)
- バッチ設定
- 設定なし
- ログ
- 設定なし
環境変数設定
ECR にプッシュするために、IAM ユーザ(sample-admin
※ part2にて作成)のクレデンシャル情報をパラメータストアに、その他の変数を環境変数に設定します。
CodeBuild コンソール > ビルドプロジェクト > sample-api-build
> 編集 > 環境 > 追加設定
- パラメータの作成
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
- 環境変数
ECR_URL
buildspec-build.yml を作成
こちら を参考に、buildspec-build.yml
を作成します。
version: 0.2
phases:
pre_build:
commands:
- IMAGE_TAG=$(git describe --tags)
- |
if [[ ! "${IMAGE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "tag name not matched. tag: ${IMAGE_TAG}";
exit 1;
fi
- aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin ${ECR_URL}
build:
commands:
- cd api # 対象フォルダに移動
- docker build . -t sample-api:${IMAGE_TAG}
- docker tag sample-api:${IMAGE_TAG} ${ECR_URL}/sample-api:${IMAGE_TAG}
- docker push ${ECR_URL}/sample-api:${IMAGE_TAG}
- コンソールで設定した環境変数等は、
env
配下にあらためて記載する必要はありません - タグ名を
git describe --tags
で取得して、v*.*.*
の形式にマッチしているかをチェックしています -
aws ecr get-login-password | docker login
の部分で、事前に AWS のログインパスワードを取得して、docker login しています - IAM ユーザは、part2の記事で作成したものを利用してます
動作確認
タグを作成して、プッシュします。
git tag -a v1.0.1 -m "add buildspec-build.yml"
git push origin v1.0.1
次に、CodeBuild コンソール > ビルドプロジェクト > sample-api-build
> ビルド開始 からビルドを実行してみます。
無事に ECR にプッシュされました。
デプロイ
てっきり CodeDeploy が EKS に対応してるのかと思いきや、対応してないみたいなので、こちらも CodeBuild で実行することにしました。
EKS クラスタの作成・ノードグループの作成・初回 Pod への Docker コンテナのデプロイまでは、part2 の記事の通りやってある状態で、Pod に新しい Docker イメージをデプロイします。
ビルドプロジェクトの作成
デプロイ用にもう一つビルドプロジェクトを作成します。
設定内容はビルド用に作ったものとほぼ同じで以下の点のみ変更しました。
- プロジェクト名:
sample-api-deploy
- BuildSpecファイル名:
api/buildspec-deploy.yml
- 特権付与:
Docker イメージを構築するか、ビルドで昇格されたアクセス権限を取得するには、このフラグを有効にします
チェックなし
環境変数設定
ビルド用と同じ環境変数を設定しました。(全て同じなので割愛)
buildspec-deploy.yml の作成
デプロイ用にもう一つ、buildspec-deploy.yml
ファイルを作成します。
version: 0.2
phases:
pre_build:
commands:
- IMAGE_TAG=$(git describe --tags)
build:
commands:
- cd api # 対象フォルダに移動
- aws eks update-kubeconfig --region us-west-2 --name sample-eks-cluster
- sed -i -e "s/__IMAGE_TAG__/${IMAGE_TAG}/" ./api-deployment.yml
- sed -i -e "s/__ECR_URL__/${ECR_URL}/" ./api-deployment.yml
- kubectl apply -f ./api-deployment.yml
- こちらも
git describe --tags
でタグ名を取得します - まず、
kubeconfig
を更新して、kubectl コマンドを実行できるようにしてます - 次に、podのデプロイ定義が書いてある
api-deployment.yml
内に記載されている__ECR_URL__
と__IMAGE_TAG__
部分を置換します- yaml ファイルの中身の抜粋。全文は part2 を参照ください。
// 抜粋 spec: template: spec: containers: - name: sample-api-container image: __ECR_URL__/sample-api:__IMAGE_TAG__
- sed で、変数に置き換えているため、ダブルクォーテーション(
"
)で囲む必要があります
- yaml ファイルの中身の抜粋。全文は part2 を参照ください。
- 最後に、
kubectl apply
してます
動作確認
タグを作成して、プッシュします。
git tag -a v1.0.2 -m "deploy to EKS"
git push origin v1.0.2
次に、CodeBuild コンソール > ビルドプロジェクト > sample-api-build
> ビルド開始 からビルドを実行してみます。
ログを見ると、以下のように kubectl apply
が成功していました。
// 抜粋
[Container] 2021/08/01 08:19:20 Entering phase POST_BUILD
[Container] 2021/08/01 08:19:20 Running command aws eks update-kubeconfig --region us-west-2 --name sample-eks-cluster
Added new context arn:aws:eks:us-west-2:xxx:cluster/sample-eks-cluster to /root/.kube/config
[Container] 2021/08/01 08:19:20 Running command sed -i -e "s/__IMAGE_TAG__/${IMAGE_TAG}/" ./api-deployment.yml
[Container] 2021/08/01 08:19:20 Running command sed -i -e "s/__ECR_URL__/${ECR_URL}/" ./api-deployment.yml
[Container] 2021/08/01 08:19:20 Running command kubectl apply -f ./api-deployment.yml
deployment.apps/sample-api-deployment configured
[Container] 2021/08/01 08:19:25 Phase complete: POST_BUILD State: SUCCEEDED
手元で kubectl describe pods
で確認しても、以下のように、ちゃんと新しいタグバージョンのイメージがデプロイされていることがわかります。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 24s kubelet Pulling image "xxx.dkr.ecr.us-west-2.amazonaws.com/sample-api:v1.0.2"
Normal Pulled 22s kubelet Successfully pulled image "xxx.dkr.ecr.us-west-2.amazonaws.com/sample-api:v1.0.2" in 1.894029857s
Normal Created 21s kubelet Created container sample-api-container
Normal Started 21s kubelet Started container sample-api-container
最後に、この WEB アプリが提供する API にアクセスしてみたところ、今回仕込んでおいた console.log が出力されていたので、問題なくデプロイできていることも確認できました。
kubectl logs -f sample-api-deployment-xxx-xxx
> codebuild installed. // 今回仕込んでおいたログ
パイプライン
最後に、GitHubの変更を検知したら自動でビルド+デプロイを行うようにします。
ただし、勝手にデプロイされるのも怖いので、デプロイの手前に「手動承認」のステージを入れて、人手で最終チェックを行えるようにしたいと思います。
パイプラインの作成
CodePipeline コンソール > パイプラインの作成 から以下の設定でパイプラインを作成します。
- パイプラインの設定
- パイプライン名:
sample-api-pipeline
- サービスロール: 新しいサービスロール
- パイプライン名:
- ソース(Source)
- ソースプロバイダー: GitHub(バージョン2) ※ バージョン1だと Git のクローンを取得できないため、バージョン2必須
- リポジトリ:
rinoguchi/aws_fullstack_sample_application
- ブランチ:
main
- 検出オプション:
ソースコードの変更時にパイプラインを開始する
にチェック - 出力アーティファクト形式: 完全クローン
- 構築する(Build)
- プロバイダー: AWS CodeBuild
- プロジェクト名:
sample-api-build
- 環境変数: なし(CodeBuild側で設定済み)
- ビルドタイプ: 単一ビルド
- デプロイ(Deploy)
- 本当はここで CodeBuild で作成した
sample-api-deploy
を選択したいのですが、できないので「スキップ」。後で個別に追加します
- 本当はここで CodeBuild で作成した
手動承認とデプロイのステージを追加
CodePipeline コンソール > sample-api-pipeline
> 編集 から、編集モードに入り「手動承認」と「デプロイ」の二つのステージを追加します。
- 手動承認(Approve)
-
Build
の下の+ステージを追加する
からステージを追加- ステージ名:
Approve
- ステージ名:
-
+アクショングループを追加する
からアクショングループを追加- アクション名:
Approve
- アクションプロバイダー: 手動承認
- アクション名:
-
- デプロイ(Deploy)
-
Approve
の下の+ステージを追加する
からステージを追加- ステージ名:
Deploy
- ステージ名:
-
+アクショングループを追加する
からアクショングループを追加- アクション名:
Deploy
- 入力アーティファクト: SourceArtifact ※
git describe --tags
を実行するには Source ステージで作成した Git クローンが必要 - アクションプロバイダー: AWS CodeBuild
- プロジェクト名:
sample-api-deploy
- 環境変数: なし(CodeBuild側で設定済み)
- ビルドタイプ: 単一ビルド
- アクション名:
-
CodeBuildに権限付与
ここまでの設定で、CodePipeline から、二つの CodeBuild のプロジェクトを実行する設定になりましたが、以下のコメントの通り、このままだと権限が足りません。
完全なクローンオプションを使用するには、CodeBuild 実行ロールに、パイプラインで選択した GitHub 接続を介してクローンを実行するアクセス許可が必要です。
最小権限の原則により、これらのアクセス許可はデフォルトでは GitHub ポリシーに含まれません。
なので、こちら を参考に、二つの CodeBuild のそれぞれのロールに対して、ポリシーをアタッチしていきます。
まず、CodePipeline コンソール > sample-api-pipeline
> Source のi
アイコンから ConnectionArn をコピーします。
次に、IAM コンソール > ポリシー
> ポリシーの作成
から、以下の設定でポリシーを作成します。
- JSON
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "codestar-connections:UseConnection", "Resource": "arn:aws:codestar-connections:us-west-2:xxx:connection/xxx-xxx-xxx-xxx-xxx" // 先ほどコピーしたConnectionArn } ] }
- ポリシー名:
connect-to-github-policy
最後に、CodeBuild コンソール > ビルドプロジェクト > sample-api-build
及び sample-api-deploy
> ビルドの詳細 > 環境 > サービスロール のリンクから、IAM ロールの画面を開いて、先程作成したconnect-to-github-policy
ロールをアタッチします。
動作確認
設定は完了したので、最終動作確認をやっていきます。
まず、ソースコードを修正して、変更を確認できるようにします。今回は、APIを呼び出した際にログを出力するようにしました。
app.get('/comments/:id', async (req: express.Request, res: express.Response) => {
console.log('codepipelne installed.')
// 省略
}
この内容で、コミットして、タグ作成して、プッシュしました。
git commit -a -m "install codepipeline"
git tag -a v1.0.3 -m "install codepipeline"
git push origin v1.0.3
git push origin main
CodePipeline の画面を確認したところ、無事、Source 及び Build ステージが実行されて、Approve ステージが保留になっていました。
レビューボタンから、手動承認すると後続の Deploy ステージが実行されました。
kubectl describe pods
でログを確認したところ、無事新しいv1.0.3
のイメージが Pod にデプロイされていることを確認できました。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 2m24s kubelet Pulling image "xxx.dkr.ecr.us-west-2.amazonaws.com/sample-api:v1.0.3"
Normal Pulled 2m22s kubelet Successfully pulled image "xxx.dkr.ecr.us-west-2.amazonaws.com/sample-api:v1.0.3" in 2.562507659s
Normal Created 2m21s kubelet Created container sample-api-container
Normal Started 2m21s kubelet Started container sample-api-container
最後に、kubectl logs -f
でログを表示しながら、curl http://xxx-xxx.us-west-2.elb.amazonaws.com/comments/1
で API を呼び出してみたところ、無事、ソース変更後のログが出力されることも確認できました。
kubectl logs -f sample-api-deployment-xxx-xxx
> codepipelne installed. // 今回仕込んでおいたログ
さいごに
今回、CodeBuildとCodePipelineを使って、デリバリーパイプラインを導入してみました。
一応やりたかったことはだいたいできましたが、コミット=>タグ作成=>タグプッシュ=>ブランチプッシュの順に作業をする必要がある点を妥協しました。ブランチブッシュを先に実行すると、タグが存在しない状態でパイプラインが実行されタグ名を取得できずにエラーになってしまいます。トリガー周りは柔軟に設定できるようにしてほしい感じです。
あとは、設定が結構分かれてしまったのが微妙です。
GitLab CI、Bitbucket Pipeline、GitHub Actions なら一箇所で設定できそうなのに、AWS だと CodeBuild × 2 + CodePipeline と3箇所に分かれてしまいました。
上記の書簡の通り、今回は AWS のお勉強ということで、AWS のサービス縛りでこの構成になりましたが、他の選択肢がある状況なら自分としてはおそらく別の選択肢を選ぶ可能性が高いです。
また、全三回に分けて「AWS で Web アプリ構築」をやってきたのですが、その中で以下のようにいろんな AWS サービスに触れてみましたが、なんとなく使い方にも慣れてきました。
- Amazon VPC
- Amazon IAM
- Amazon EKS
- Amazon RDS
- AWS CodeBuild
- AWS CodePipeline
- AWS ECR
- Elastic Load Balancing
今回は大部分をコンソール上で設定したので、CloudFormation なり Terraform なりでコードに落とし込むことも勉強することが次の課題かな、と思いますので、気が向いたらやってみたいと思います。
トラブルシューティング
蛇足になりますが、設定に当たってトラブったところを紹介しておきます。
CodeBuild で Docker Daemon が動いてない
- エラーメッセージ
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
- 対処
- ビルドプロジェクトを作成する際に、
特権付与: Docker イメージを構築するか、ビルドで昇格されたアクセス権限を取得するには、このフラグを有効にします
にチェックを入れる
- ビルドプロジェクトを作成する際に、
ECR に docker push できない
- エラーメッセージ
no basic auth credentials
- 対処
- docker push 前に、AWS クレデンシャル情報をもとに docker login しておく
- 具体的には
-
AWS_ACCESS_KEY_ID
とAWS_SECRET_ACCESS_KEY
の環境変数を設定し -
aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ECR_URL}
を実行する
-
CodePipeline から実行すると、CodeBuild で Git コマンドエラー
- エラーメッセージ
[Container] 2021/08/01 08:40:22 Running command IMAGE_TAG=$(git describe --tags) fatal: not a git repository (or any parent up to mount point /codebuild) Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
- 原因
- CodePipeline のソースステージで、アクションプロバイダーを
GitHub(バージョン1)
にすると、出力アーティファクト形式はCodePipelineのデフォルト
一択となり、Git クローンが作成されないため
- CodePipeline のソースステージで、アクションプロバイダーを
- 対処
-
CODEBUILD_SOURCE_VERSION
のような環境変数を利用することで回避できるような GIT 情報を取得したいだけであればそうする。 - 上記で解決できないケースは、アクションプロバイダーを
GitHub(バージョン2)
にすると、出力アーティファクト形式に完全くローン
の選択肢が出てくるのでそれを選択する
-
CodePipeline から実行すると、CodeBuild で権限エラー
- エラーメッセージ
authorization failed for primary source and source version xxxxxxx
- 対処
- CodeBuild 側に、CodePipeline で作成した GitHub クローンにアクセスする権限がないため
- こちらの手順を実行する