🙆

Laravel + ECS Fargate開発サイクル - CodeBuildを使ったイメージビルドをやってみよう

2025/02/16に公開

ここまでで、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

各ファイル


https://github.com/catatsumuri/pingcrm-deploy/blob/main/.dockerignore

https://github.com/catatsumuri/pingcrm-deploy/blob/main/docker-compose.yml

https://github.com/catatsumuri/pingcrm-deploy/blob/main/srv/Dockerfile

https://github.com/catatsumuri/pingcrm-deploy/blob/main/srv/default.conf

ソースコードの呼び込み

ここでは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を借りてリリースするので、このような事になるわけだ。これを回避するためには何度か押すか、あるいはイメージのありかを変更しないといけない

srv/Dockerfile
--- 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 --from=composer /usr/bin/composer /usr/bin/composer
+COPY --from=public.ecr.aws/docker/library/composer:latest /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を更新続けるのは勿体ない

というわけで

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"
      - 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
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