Bitbucket Pipelines から CD の一部を CodeBuild へ移行した
某プロジェクトにて、それまで Bitbucket Pipelines にて CI/CD を運用して約半年、小問題は何度か発生したものの CI/CD としては致命的に止まる・詰まることなくやっていました。しかし、とうとう exceed memory limit
で Fail する [1] 頻度が高くなり、ECR イメージの build・push に関しては AWS CodeBuild へ移行することにしました。
この記事では、その CodeBuild 移行を実現する際のポイントを共有します。
なお、過程で呟いてきたことは sogaoh monthly 2021-09 (2) の Bitbucket Pipelines から 一部 CD を CodeBuild へ
に畳んで置いてあります。(リンクへは 新しいタブを開く
or 新しいウィンドウで開く
で進むのが良さそうです)
Bitbucket Pipelines での3処理
ECR イメージの build・push を、Bitbucket Pipelines では以下の3処理にて行っていました。
- CI/CD用リポジトリ (以降
adminリポジトリ
と言います) の submodule として配置してした、コードリポジトリの clone - コードリポジトリのフロントエンド Vue.js を
npm run production
相当のスクリプトでビルド(少しカスタマイズしたnpm run pack
が実行コマンド) - フロントエンド・バックエンドの docker イメージを build して Amazon ECR に push する
exceed memory limit の解決
Bitbucket Pipelines では 2. の処理で最大に使える 7128 MB のメモリを越えてしまい Fail するようになってしまいました(逆に厄介なことにたま〜に成功する。。)。
CodeBuild への移行先ではこれを上回る 15 GB のメモリを積んでいる Linux large を選択しました。[2]
メモリ不足問題つまり 2. に関してはこれだけで課題解決できましたが、むしろ 1. 3. を完遂するのに苦戦しました。。
カスタムイメージの使用とコードリポジトリの clone
上述3処理のうち 1. のところに関するポイントを書きます。
AWS CodeBuild には標準でマネージド型イメージが用意されてもいますが、Bitbucket Pipelines で動かしていたことができれば良かった、というか フロントエンド Vue.js のビルドができれば良くて、マネージド型イメージへの nvm インストール等を新たにしたくないと思ったので、これまで使用していた ECR にある docker イメージ(以降、amzn2-builder
と書きます)を使うことにしました。
ECR の Permissions 調整
CodeBuild のプロジェクトをマネコンで新規作成し、ひとまずビルドしてみたのですが、予想通り一発で成功はせず、どういうエラーかを見ると amzn2-builder
を pull できなかったようでした。
エラーメッセージで検索した情報から、ECR 側に Permissions を設定する必要があるとわかり[3]、以下のような IAM ポリシーを設定しました。
CodeBuildAccess : 最初はUIで作り、サービスプリンシパルも足しておこうと json を調整
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "CodeBuildAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root",
"Service": "codebuild.amazonaws.com"
},
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
}
Bitbucket Cloud へのSSH接続設定
これで amzn2-builder
は pull できたものの、次のコケポイントは
submodule として配置してした、コードリポジトリの clone
でした。submodule add しようとしたのですがコードリポジトリ側のブランチを自由に選択したいこともあり、そうなるとなかなかうまくいかない[4] ので、普通に git clone してくるようにしました。
そこで当然といえば当然なのですが認証が必要になり、個人的には避けたかったのですが自分のアプリパスワードで接続するようにしました[5]。
この点、将来的には人のアカウントに紐づいていない形に直す等していかないと自分がいなくなったときにコケるので追い追い考えないと、です。
ともあれ、接続設定しないと先に進まないので、CodeBuild にはパラメータストアに書き込んだ自分のアプリパスワードを参照させました。ただこれだけでは git clone は成功せず、CodeBuild 稼働コンテナに SSH 接続設定を埋め込んでおく必要があり、事例を探した結果 AWS CodePipelineでサブモジュールを使う | きいちログ に「おおこれは」と思えた buildspec.yml サンプルを見つけ、以下のような設定を入れた結果、git clone に成功しました。
Bitbucket への SSH鍵接続設定埋め込み buildspec.yml サンプル
${BRANCH}
は後述しますが CodeBuild環境変数 の1つ
version: 0.2
env:
git-credential-helper: yes
variables:
remote_origin: "bitbucket.org"
remote_user: "[ore-no-user-id]"
node_version: "v14.17.5" # このバージョンでビルドする
parameter-store:
ssh_key: "[hoge-git_ssh-key]"
aws_access_key: "[powerful_user_access_key]" # あとで使う
aws_secret_key: "[powerful_user_secret_key]" # あとで使う
phases:
install:
commands:
- mkdir -p ~/.ssh
- echo "$ssh_key" > ~/.ssh/ssh_key
- chmod 600 ~/.ssh/ssh_key
- ssh-keygen -F "$remote_origin" || ssh-keyscan "$remote_origin" >> ~/.ssh/known_hosts
- eval "$(ssh-agent -s)"
- ssh-add ~/.ssh/ssh_key
- aws configure set aws_access_key_id "$aws_access_key" # あとで活きる
- aws configure set aws_secret_access_key "$aws_secret_key" # あとで活きる
pre_build:
commands:
# - cd [ソースを集めたディレクトリ] && git clone -b ${BRANCH} "$remote_user"@"$remote_origin":[organization]/[repository].git
# ・・・
CodeBuild環境で dockerd を動かし続ける
話は 2. のVue.js ビルドを飛ばして、おそらくこの記事で一番伝わってほしい、3. フロントエンド・バックエンドの docker イメージを build して Amazon ECR に push する
で躓いていたのをクリアーするのに必要なポイントを書いていきます。
↓になったときは「なにぃ〜」と思いましたが、
トラブルシューティング[6]含めよくよく探したところ、2つほど有効そうな対策事例を発見し[7][8]、buildspec.yml に以下のように記述を加えてみたものの・・・
# ・・・
post_build:
commands:
- nohup /usr/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2 &>/var/log/docker.log &
- timeout 60 sh -c "until docker info; do echo .; sleep 1; done"
# ・・・
artifacts:
files:
- /var/log/docker.log
まだダメで、一時はカスタムイメージでやるのを諦めかけたのですが、そうしてる間に AWSサポート に問い合わせていた Case へ回答が来ました。そのヒントでやりたいことを実現できたので、感謝の意を込めて明記しておきます。回答内容としては次項の 2. と 3. が主旨でした。
Docker-in-Docker で動かすポイント3点
ここまでで以下3つのうち 1.2. までは設定できていたのですが、 3. が欠けていました。
- 特権付与する
- CodeBuild のUIでも
Docker イメージを構築するか、ビルドで昇格されたアクセス権限を取得するには、このフラグを有効にします。
と説明書きがあり、必要そうだと思っていたので最初からONにしました。
- CodeBuild のUIでも
- Dockerデーモンが動いている状態にする
- Amazon Linux 2 (x86_64) standard 3.0 のマネージド型イメージでは
ENTRYPOINT ["dockerd-entrypoint.sh"]
で起動させているようでした。 refs https://github.com/aws/aws-codebuild-docker-images/blob/master/al2/x86_64/standard/3.0/dockerd-entrypoint.sh
ここのディレクトリの Dockerfile をみた感想: マネージド型イメージ、盛り盛りすぎる。。
- Amazon Linux 2 (x86_64) standard 3.0 のマネージド型イメージでは
-
VOLUME /var/lib/docker
する- Docker in Docker するときはやっておかないといけないことらしいです(理解不十分のためちゃんと説明できませんすみません)。 refs https://www.docker.com/blog/docker-can-now-run-within-docker/ , すぐ上に挙げた Dockerfile の 375 行目
この 3. をさっそくカスタムイメージに適用しビルドし直し、ECR へ push して CodeBuild を再実行したところついに build・push 成功。
なお push の前には ECR へのログインが必要で、以下の aws ecr
コマンドが実行できるように、上述の SSH鍵接続設定埋め込み buildspec.yml サンプル
で aws configure
してありました。
Makefile に整備した ECR ログイン自動実行ワンライナー
ecr-login:
aws ecr get-login-password --region $(AWS_REGION) | docker login --username AWS --password-stdin $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com
codebuild-[ECR-Update]-service-role (default+α)
書き忘れていたなと思って後で追加した節なのですが、UI で CodeBuild プロジェクトを作ったときに AWS 側で作ってくれる IAM ロールに、ポリシー json をいくらか足していたと思います。最終的にだいたい以下のような内容となりました。
codebuild-[ECR-Update]-service-role
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Resource": [
"arn:aws:logs:ap-southeast-1:[123456789012]:log-group:/aws/codebuild/[ECR-Update]",
"arn:aws:logs:ap-southeast-1:[123456789012]:log-group:/aws/codebuild/[ECR-Update]:*"
],
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
},
{
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::codepipeline-[AWS_REGION]-*",
"arn:aws:s3:::[adminリポジトリ名]-codebuild-artifacts/*"
],
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketAcl",
"s3:GetBucketLocation"
]
},
{
"Effect": "Allow",
"Action": [
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:UpdateReport",
"codebuild:BatchPutTestCases",
"codebuild:BatchPutCodeCoverages"
],
"Resource": [
"arn:aws:codebuild:[AWS_REGION]:[123456789012]:report-group/[ECR-Update]-*"
]
},
{
"Effect": "Allow",
"Resource": [
"arn:aws:ssm:[AWS_REGION]:[123456789012]:parameter/*"
],
"Action": [
"ssm:GetParameters",
"ssm:GetParameter",
"secretsmanager:GetSecretValue",
"kms:Decrypt"
]
}
]
}
Bitbucket Pipelines から CodeBuild を起動する
ここまででだいたいやりたいことはできたのですが、「誰でもデプロイできる」を狙いに Bitbucket Pipelines を仕込んでいたので、この「CodeBuild 版 ECR イメージ build・push」もこれまでと変わらないインターフェースで使えるように、aws codebuild
CLI から kick する方法を探りました。
すると程なく aws codebuild start-build
で起動できそうとわかり、試してみたところ成功。しかし起動したらすぐレスポンスが返って来てしまい、どこを見れば状況が分かるのかが確認しずらかったので、
- CodeBuild の完了を待つようにしたい
- CodeBuild のURLリンクを出すようにしたい
に挑みました。結果を言うと両方とも実現できて、最終的に以下のような bitbucket pipelines の step となりました。
CodeBuild完了を待つ阪 bitbucket-pipelines.yml
variables は CodeBuild の環境変数に設定しておく必要があります。
# ・・・
pipelines:
custom:
# ・・・
ecr-update:
- variables:
- name: ENVIRONMENT # 環境
- name: BRANCH # コードリポジトリのブランチ
- name: IDENTIFIER # 識別子:ECRイメージのタグに使用する
- name: SIDE # Blue/Green Deployment のどちら側か
- name: SOURCE # adminリポジトリのブランチ
- step: &code-build-run
name: "Call CodeBuild [ECR-Update] project"
size: 2x
script:
- aws codebuild start-build
--project-name "[ECR-Update]"
--source-version "$SOURCE"
--privileged-mode-override
--environment-variables-override
"[{\"name\":\"ENVIRONMENT\",\"value\":\"$ENVIRONMENT\",\"type\":\"PLAINTEXT\"},
{\"name\":\"BRANCH\",\"value\":\"$BRANCH\",\"type\":\"PLAINTEXT\"},
{\"name\":\"IDENTIFIER\",\"value\":\"$IDENTIFIER\",\"type\":\"PLAINTEXT\"},
{\"name\":\"SIDE\",\"value\":\"$SIDE\",\"type\":\"PLAINTEXT\"}]" > start-build.json
- CODEBUILD_BUILD_ID=$(cat start-build.json| jq -cr '.[].id')
# ↓で CodeBuild のURLを Bitbucket Pipelines のコンソールに出すことに成功
- echo "https://[AWS_REGION].console.aws.amazon.com/codesuite/codebuild/[AWS_ACCOUNT_ID]/projects/[ECR-Update]/build/$CODEBUILD_BUILD_ID/?region=[AWS_REGION]"
# ↓2行の1行シェル書き出しと実行については次に説明
- echo "CODEBUILD_BUILD_ID=\$1; SLEEP_NUM=1; while true; do BUILD_STATUS=\$(aws codebuild batch-get-builds --ids \${CODEBUILD_BUILD_ID} | jq -cr '.[][].buildStatus'); echo \"\${CODEBUILD_BUILD_ID} \${BUILD_STATUS} \${SLEEP_NUM}\"; if [ \${BUILD_STATUS} != \"IN_PROGRESS\" ]; then echo \"\${CODEBUILD_BUILD_ID} \${BUILD_STATUS}\"; if [ \${BUILD_STATUS} != \"SUCCEEDED\" ]; then exit 1; fi; break; fi; let SLEEP_NUM=\${SLEEP_NUM}+1; sleep 10; done" > while_in_progress.sh
- bash ./while_in_progress.sh $CODEBUILD_BUILD_ID
- pipe: atlassian/slack-notify:2.0.0
variables:
WEBHOOK_URL: $WEBHOOK_URL_DEV
MESSAGE: "**FINISH ecr-update** branch=[$BRANCH], identifier=[$IDENTIFIER], side=[$SIDE], environment=[$ENVIRONMENT], source-version=[$SOURCE]"
services:
- docker
artifacts:
- start-build.json
- while_in_progress.sh
# ・・・
CodeBuild の完了を待つ1行シェル書き出しと実行
CodeBuildを待つやつ - sasasin/aws-codebuild-wait-build.sh を大いに参考にさせてもらい、aws codebuild batch-get-builds
を利用したシェルを作り検証しました。
だいたい良さそうな感じだったのでこれを一度 bitbucket-pipelines.yml の step に突っ込んだのですが何度かシンタックスエラーとなっては直し、そしてビルドが失敗しているのに Bitbucket Pipelines の結果としては成功になってしまっているのを手直しして、以下のようなシェルとなりました。
改行を入れるなどして見やすくした1行シェル
CODEBUILD_BUILD_ID=\$1;
SLEEP_NUM=1;
while true;
do
BUILD_STATUS=\$(aws codebuild batch-get-builds --ids \${CODEBUILD_BUILD_ID} | jq -cr '.[][].buildStatus');
echo \"\${CODEBUILD_BUILD_ID} \${BUILD_STATUS} \${SLEEP_NUM}\";
if [ \${BUILD_STATUS} != \"IN_PROGRESS\" ]; then
echo \"\${CODEBUILD_BUILD_ID} \${BUILD_STATUS}\";
if [ \${BUILD_STATUS} != \"SUCCEEDED\" ]; then
exit 1;
fi;
break;
fi;
let SLEEP_NUM=\${SLEEP_NUM}+1;
sleep 10;
done
工夫したり調整したところを列挙しておくと、
-
$
と"
は全てエスケープが必要 - 改行を表現するのは
;
- 変数の定義は
${〜}
で囲まず、参照箇所では${〜}
で囲む
といったところでしょうか。
これを1行で表現し echo コマンドで while_in_progress.sh
に書き出し、それを bash ./while_in_progress.sh $CODEBUILD_BUILD_ID
で呼び出すと、いい感じに完了を待ってくれて、CodeBuild側のビルドが失敗だったらちゃんと失敗したと通知してくれます。
補足
実はこの機構、Production デビューはまだで、諸事情あってカスタムイメージを差し替えて急遽実施しようとしたら VOLUME /var/lib/docker
を入れ忘れてたり特権付与のチェックを入れてなかったりをやってしまいました。
という失敗を経て整理し直したので、いい完成度になっているはずです。
-
Read more about service memory limits
-> Atlassian Support / Bitbucket / Resources / Build, test, and deploy with Pipelines / Databases and service containers ↩︎ -
AWS / ドキュメント / AWS CodeBuild / ユーザーガイド / ビルド環境のコンピューティングタイプ ↩︎
-
AWS / ドキュメント / AWS CodeBuild / ユーザーガイド / CodeBuild の Amazon ECR サンプル に
以下のいずれかに該当する場合は、AWS CodeBuild が Docker イメージをビルド環境にプルできるように、Amazon ECR のイメージリポジトリにアクセス許可を割り当てる必要があります。
と書いてあるのを見つけた ↩︎ -
単に自分の git submodule 力が足りなかったのかもしれません ↩︎
-
AWS / ドキュメント / AWS CodeBuild / ユーザーガイド / CodeBuild でソースプロバイダにアクセスする #Bitbucket アプリのパスワード ↩︎
-
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/troubleshooting.html ↩︎
-
AWS CodeBuildカスタムDockerイメージを使ってビルドする - Speaker Deck (slide=14) ↩︎
-
「CodeBuild に用意されている Docker イメージ」で AWS CodeBuild 上での docker-compose のインストール作業を省略する - Beeeat’s log ↩︎
Discussion