Laravel + ECS Fargate開発サイクル - CodeBuildを使ったイメージビルドをやってみよう
ここまでで、laravel + ecs fargetについてぼちぼちチュートリアルを記事を書いてきているが、イメージのビルドをCodeBuild
に任せる事に関してはそれだけ1本記事が出来るのでここで纏める事にした。
ここではInertia.js
のデモアプリケーションとして用意されているpingcrm
(https://github.com/inertiajs/pingcrm )を教材として取り上げてみる。まあアプリケーション自体は何でもいいのであるが簡単そうだし、ライセンスもMITですし。
構築環境
- githubアカウント
- 必須ではないが、folkするなら欲しい。gitlabとかでも出来なくはないが、手順通りにやりたいなら用意する事
プロジェクトのfolk
別にfolkせずにcloneして再度つっこんでもいい
folkすると https://github.com/catatsumuri/pingcrm こんなのが出来る。これを動作させるイメージを作成していく。
ビルド管理用のデプロイリポジトリを作成する
今folkしたリポジトリとは別に、1つ作成しておく
pingcrm-deploy
としてリポジトリを作成している。ここではメモ用にREADME.mdを同時作成する指示をしているがその辺はどうでもいいっちゃどうでもいい。
これを適当な環境でcloneする
git clone https://github.com/catatsumuri/pingcrm-deploy.git
cd pingcrm-deploy/
前回こさえたビルド用ファイル一式を用意する
pingcrm-deploy
├── .dockerignore
├── srv
│ ├── Dockerfile
│ └── default.conf # nginxのconfig
└── docker-compose.yml
各ファイル
ソースコードの呼び込み
ここではgit submodule
でfolkしたソースを呼びこんでいる
git submodule add https://github.com/catatsumuri/pingcrm
git submodule update --init --recursive
ここはもちろん、 https://github.com/catatsumuri/pingcrm ではなく御自身のrepositoryからビルドする事(まあこれ使ってもいいけど、この後の開発サイクルの解説がうまくいかないため)
ビルド
最終的にはCI/CD
にするのであるが、手作業でビルドできないとCICDできるわけがないので初手で確認しておく。
docker build -t pingcrm-php-fpm -f ./srv/Dockerfile --target=php-fpm .
docker build -t pingcrm-nginx -f ./srv/Dockerfile --target=nginx .
さらに
docker-compose up
して起動する事を確認しておく
よさそうであれば、この段階でgit commit && git push
しておく
接続の管理
https://ap-northeast-1.console.aws.amazon.com/codesuite/settings/connections?region=ap-northeast-1 この辺から管理
接続名は変更して
こんな感じで接続を作った。catatsumuri-github-connect
とした
これで接続アダプターが出来た
ここで「接続」ボタンを押さずに 「新しいアプリをインストールする」
こうなるので
謎の数字が埋まった状態で接続する
接続テストをする
CodeBuild
の初歩として今の接続を検査する
CodeBuildのトップより
ここでは完全なるテストを行うため、名前も test
とした。後で消しやすいような名前とする。
こんな風になっていた場合、Manage account credentials.
を押す
一度「切断」
して再度接続しないとうまくいかない事があるっぽい。なんでしょうね。
いずれにせよ、うまくいくとリストされてくる。ここでは https://github.com/catatsumuri/pingcrm-deploy を使っている
ビルドコマンドに find
と入力して保存
これで完了だ。何かここはうまくいったりうまくいかなかったりする事が多いので、一度ここまで確認しておく。完了すると直ちにビルドプロセスが起動し、find
が行われる。ログをみてみよう
...
このように、カレントディレクトリにgithubの内容がcloneされている事がわかる。すなわち、これを利用しawsコマンドにてビルドするように手配するというわけだ。
testを削除
とりあえずうまく動作すれば必要がないので、testを削除する。地味にロールとかも出来ているので気になるなら調査して消す
ビルドを詰めていく
今、単純にfindしただけなのであるが、要するにこの段階でビルドをかけられればokという事になる。findみたいな単純なコマンドでは厳しいので、大抵もう少し複雑なコマンドセットを定義する事になる。
これはbuildspec.ymlという特殊なファイルをcommitする事で達成される。
いずれにせよ、ちゃんとしたビルドプロジェクトを作成する
pingcrm-docker-image
として作り直す
ここは単一のコマンドではなく buildspec ファイルを使用する
を指定している
Phase context status code: YAML_FILE_ERROR Message: YAML file does not exist
から明かなように、buildspec.ymlが見付からないので停止している
buildspec.ymlを作成し、commitする
version: 0.2
phases:
pre_build:
commands:
- env
- find
これでビルドを開始すると
AWS_REGION
のように、いくばくか使うと便利そうな環境変数がセットされているのがわかる。従ってこれを利用し、まずdocker ecr loginしてみよう
Docker Loginをキめる
version: 0.2
phases:
pre_build:
commands:
- echo "Fetching AWS Account ID..."
- "export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query \"Account\" --output text)"
- "export ECR_REPOSITORY=\"${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/pingcrm\""
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REPOSITORY
これは構文はokなのだが失敗する(予想通り?)
これは、EC2ではECRアクセス用の権限をセットアップしたんだけども、こっちのはセットアップしていないからである。
CodeBuildのロールに適切な権限を与える
まずCodeBuild
がどういう権限(ロール)で動作しているのか把握しておこう。これはCodeBuild
の設定と同時に何も考えてないと自動的に作成されるロールがある
これ
リンクになっているので押してみると
Defaultで2つポリシーがくっついてるので、ここにさらにECR操作のためのポリシーをアタッチする
これは前回作ったやつですね
再度ビルドすると
こんな感じでログインできるようになるわけだ
この段階でECRの操作が完全に出来るのでCodeBuild
の中でビルドしてpushすれば達成できる
buildspec.yamlの編集と予想外の失敗
予想外の失敗キタコレ
#3 ERROR: failed to copy: httpReadSeeker: failed open: unexpected status code https://registry-1.docker.io/v2/library/node/manifests/sha256:edd9e2da79890871b7bb5c9a249021383cb5ac8c9c99bad02182bd169ec38bce: 429 Too Many Requests - Server message: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
これはCodeBuild
で使うIPからリクエストの量が大量発生する事でこのようなリミット制限にひっかかる事がある。自分だけじゃなくて世界中の人間がIPを借りてリリースするので、このような事になるわけだ。これを回避するためには何度か押すか、あるいはイメージのありかを変更しないといけない
--- a/srv/Dockerfile
+++ b/srv/Dockerfile
@@ -1,7 +1,7 @@
# --------------------------------------
# Stage 1: frontend build stage
# --------------------------------------
-FROM node:18-alpine AS frontend-builder
+FROM public.ecr.aws/docker/library/node:18-alpine AS frontend-builder
WORKDIR /laravel
@@ -13,7 +13,7 @@ RUN mkdir /public && cp -a public/* /public
# --------------------------------------
# Stage 2: PHP-FPM
# --------------------------------------
-FROM php:8.4-fpm AS php-fpm
+FROM public.ecr.aws/docker/library/php:8.4-fpm AS php-fpm
WORKDIR /srv
@@ -35,7 +35,7 @@ RUN apt-get update && apt-get install -y \
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd exif
-COPY /usr/bin/composer /usr/bin/composer
+COPY /usr/bin/composer /usr/bin/composer
RUN composer install
#RUN touch database/database.sqlite
@@ -46,7 +46,7 @@ EXPOSE 9000
# --------------------------------------
# Stage 3: Nginx
# --------------------------------------
-FROM nginx:alpine AS nginx
+FROM public.ecr.aws/nginx/nginx:latest AS nginx
WORKDIR /usr/share/nginx/html
最終的なbuildspec.yml
buildspec.yml
version: 0.2
phases:
pre_build:
commands:
- echo "Fetching AWS Account ID..."
- export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
- export AWS_REGION="ap-northeast-1"
- export ECR_REPOSITORY="practice"
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
- echo "Initializing git submodules..."
- git submodule update --init --recursive
build:
commands:
- echo "Building Docker images..."
- docker build -t pingcrm-php-fpm -f ./srv/Dockerfile --target=php-fpm .
- docker build -t pingcrm-nginx -f ./srv/Dockerfile --target=nginx .
- docker tag pingcrm-php-fpm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-php-fpm:latest
- docker tag pingcrm-nginx $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-nginx:latest
post_build:
commands:
- echo "Pushing Docker images to ECR..."
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-php-fpm:latest
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-nginx:latest
せっかく自動ビルドしているのでlatestを更新続けるのは勿体ない
というわけで
version: 0.2
phases:
pre_build:
commands:
- echo "Fetching AWS Account ID..."
- export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
- export AWS_REGION="ap-northeast-1"
- export ECR_REPOSITORY="practice"
- export COMMIT_HASH=$(git rev-parse --short HEAD)
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
- echo "Initializing git submodules..."
- git submodule update --init --recursive
build:
commands:
- echo "Building Docker images..."
- docker build -t pingcrm-php-fpm -f ./srv/Dockerfile --target=php-fpm .
- docker build -t pingcrm-nginx -f ./srv/Dockerfile --target=nginx .
- docker tag pingcrm-php-fpm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-php-fpm:latest
- docker tag pingcrm-nginx $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-nginx:latest
- docker tag pingcrm-php-fpm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-php-fpm:$COMMIT_HASH
- docker tag pingcrm-nginx $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-nginx:$COMMIT_HASH
post_build:
commands:
- echo "Pushing Docker images to ECR..."
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-php-fpm:latest
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-nginx:latest
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-php-fpm:$COMMIT_HASH
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY/pingcrm-nginx:$COMMIT_HASH
不要なものは削除した
このように、8bbfe0b
, latest
という2つのポインタが与えられているのがわかる。
サービスの更新
今、latestも更新し、サービスの定義がlatestを参照するようになっているため、「新しいデプロイの強制」を行うとサービスが更新される。ハッシュ付きイメージの指定を正しく与えるには
aws ecs describe-task-definition --task-definition pingcrm --query taskDefinition | jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' > task-definition.json
{
"containerDefinitions": [
{
"name": "pingcrm-nginx",
"image": "****.dkr.ecr.ap-northeast-1.amazonaws.com/practice/pingcrm-nginx:8bbfe0b",
"cpu": 0,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp",
"name": "pingcrm-nginx-80-tcp",
"appProtocol": "http"
}
],
"essential": true,
"environment": [],
"environmentFiles": [],
"mountPoints": [],
"volumesFrom": [],
"ulimits": [],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/pingcrm",
"mode": "non-blocking",
"awslogs-create-group": "true",
"max-buffer-size": "25m",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
},
"secretOptions": []
},
"systemControls": []
},
{
"name": "pingcrm-php-fpm",
"image": "****.dkr.ecr.ap-northeast-1.amazonaws.com/practice/pingcrm-php-fpm:8bbfe0b",
"cpu": 0,
aws ecs register-task-definition --cli-input-json file://task-definition.json
で挿入し、サービスを更新する
おまけ:エラー発生
これはCodeBuild
の設定で
この場合再度ビルドをしかけても当然commit hashは変わっていないのだがイメージは変わっている
イメージだけリロードする場合にしても何にしてもこのような時は「新しいデプロイの強制」
しておくといいかと思います
まとめ
このようにCodeBuiild
を使うと(基本的に)ビルドのためのリソースを一時的に借りれて終了後すぐ消えてくれるというような使い方ができる。lambdaと似ているが一応buildに特化しているので、そのように使うのがよろしいかと思いますよ。
Discussion