💡

【ソース有】【チュートリアル】ECSでlaravel(11)をSQLiteでとり急ぎ動作させる(改訂版) 〜 運用編

2025/02/14に公開

https://zenn.dev/catatsumuri/articles/c432a0239c164d

ここからproductionレベルで運用可能なようにサイクルを考えてみる。

本稿のまとめ

  • EC2に適切なロールとポリシーをセットし、AWSキーを使わないようにしよう
    • ECRの操作には適切な名前空間に限定したポリシーの割当てにする
    • aws sts get-caller-identityで状態を確認する
  • pingcrmというアプリをcloneし、ビルドしてECS Fargateで動作させてみる練習をしよう
    • 欠損モジュールをビルドする(exif, gd)
  • タスク定義をコマンドからIn/Outしよう
    • 必要な権限の追加
    • aws ecs describe-task-definition --task-definition <定義名> --query taskDefinition --output json
      • jqを用いた不要エントリの削除
    • aws ecs register-task-definition --cli-input-json file://task-definition.json
  • larvelのエラーを意図的に作り出し、エラーが出ないようにしよう

ビルド環境の構築

ここではpingcrmというinertia.jsで使われているデモをECSで動かすという事を目標にしてみよう。まあ、前回でlaravelアプリを動作させられているので、これといって難しさは無いと思うんだがモジュールのビルドなど、動作に少しコツが必要だ。

https://github.com/inertiajs/pingcrm

よりセキュアに

先ず、現在はEC2を使ってECRにイメージをプッシュしている環境である。ここでAWSキーを使って通信しているが、これは実は必要がない

ここではキーを使わないデプロイ(等)の環境を設定していく

ECRリポジトリの作成

以下の2つのリポジトリを作成する

  • practice/pingcrm-nginx
  • practice/pingcrm-php-fpm

作業用EC2にロールを割り当てキーを抹消する

ここで手順通りに来ていると作業にて使うEC2インスタンスが1つ起動していると思うがこのEC2からここのリポジトリにアクセスするためのroleを作る


ロールへ移動


ロールを作成


すると「信頼されたエンティティを選択」となる。信頼されたエンティティて...

これは何かというと、簡単にいえばここで作成するロールをどのサービスで使えるかを決めるという事だ。ここではEC2からこのロールを使うので当然EC2を選ばなくてはいけない

このようになっていれば「次へ」を押す


すると許可を追加になるんだけど、まだ許可を考えていなかったので何もおさずに次へを押す。これは後からでも当てられるので、とりあえずガワを作るイメージ

そしてロール名をECSPracticeManagementRoleとして保存する

とりあえずロールができる


ポリシーを作る


ポリシーの作成


とりあえずビジュアルモードで作っていく。registryみたいに入力するとElastic Container Registryが選択できる


リストと読み取りは全部付けといて、書き込みはこの4点とする


こんな感じで

  • ap-northeast-1
  • pracitice/*

とした。practice名前空間をどうにかして当てたかったのはここに理由がある。


ECRPracticeManagePolicyとして保存した

ロールにポリシーを当てる

さきほど作成したポリシーが空のロールに


許可を追加して、ポリシーをアタッチする

EC2にロールを当てる

そしたら、起動中のEC2の設定に戻りセキュリティー→ IAMロールの変更をする



ECSPracticeManagementRoleを当てた

ここでずらずらと列挙されているロールこそが「信頼されたエンティティ」で結ばれているロールであって、ロールは多数作る事はできてもEC2と信頼関係がないとここに出てこないってこと。リレーションみたいなもんすね。

アクセスキーを無効化

そうするともう practice/ を触る事に関してはキーが必要ない。AmazonEC2ContainerRegistryFullAccessという物騒な権限がついているアカウントのキーを無効にする

まあ無効化後削除しといていいと思う

EC2でロールの確認とECR操作

aws sts get-caller-identity

でまずロールを確認する


よさそう

そしたらログインする

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ****.dkr.ecr.ap-northeast-1.amazonaws.com


ログインできている

権限の詳細はまだ確認していないけどログインできている。まあ前にやった奴を取得チェックしてみてもいいだろう。

aws ecr describe-images --repository-name practice/my-ecs-nginx --region ap-northeast-1

practice/ 以下操作できるようにしてあるので、これは通るはずだ(practiceじゃないやつもやってみた方が万全だけど)

イメージの構築

前回同様さっさか構築していく。workingディレクトリつくってgit initして

git submodule add https://github.com/inertiajs/pingcrm.git pingcrm
git submodule update --init --recursive

みたいな感じでinertiajs/pingcrmを引きこんでくる。少し改変したsrv/Dockerfile

srv/Dockerfile
srv/Dockerfile
# --------------------------------------
# Stage 1: frontend build stage
# --------------------------------------
FROM node:18-alpine AS frontend-builder

WORKDIR /laravel

COPY ./pingcrm /laravel

RUN npm install && npm run build
RUN mkdir /public && cp -a public/* /public

# --------------------------------------
# Stage 2: PHP-FPM
# --------------------------------------
FROM php:8.4-fpm AS php-fpm

WORKDIR /srv

COPY ./pingcrm /srv
COPY --from=frontend-builder /public /srv/public

RUN cp .env.example .env
RUN chown -R www-data:www-data storage database

RUN apt-get update && apt-get install -y \
    git \
    unzip \
    zip

COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

EXPOSE 9000

# --------------------------------------
# Stage 3: Nginx
# --------------------------------------
FROM nginx:alpine AS nginx

WORKDIR /usr/share/nginx/html

COPY ./srv/default.conf /etc/nginx/conf.d/default.conf
COPY --from=php-fpm /srv/public/ /usr/share/nginx/html

EXPOSE 80

これでビルドできるはず、と思い

docker build -t pingcrm-php-fpm -f ./srv/Dockerfile --target=php-fpm .

すると、モジュールのエラーになる

nstalling dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Your lock file does not contain a compatible set of packages. Please run composer update.

  Problem 1
    - Root composer.json requires PHP extension ext-exif * but it is missing from your system. Install or enable PHP's exif extension.
  Problem 2
    - Root composer.json requires PHP extension ext-gd * but it is missing from your system. Install or enable PHP's gd extension.

To enable extensions, verify that they are enabled in your .ini files:
    - /usr/local/etc/php/conf.d/docker-fpm.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini
You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.
Alternatively, you can run Composer with `--ignore-platform-req=ext-exif --ignore-platform-req=ext-gd` to temporarily ignore these required extensions.
The command '/bin/sh -c composer install' returned a non-zero code: 2

これはexifエクステンションが無いといっている。この辺解説が面倒なのでDockerfileをアップデートしておきました。まあ詳細は必要に応じてAIにでも聞いてみて

# --------------------------------------
# Stage 1: frontend build stage
# --------------------------------------
FROM node:18-alpine AS frontend-builder

WORKDIR /laravel

COPY ./pingcrm /laravel

RUN npm install && npm run build
RUN mkdir /public && cp -a public/* /public

# --------------------------------------
# Stage 2: PHP-FPM
# --------------------------------------
FROM php:8.4-fpm AS php-fpm

WORKDIR /srv

COPY ./pingcrm /srv
COPY --from=frontend-builder /public /srv/public

RUN cp .env.example .env
RUN chown -R www-data:www-data storage database

RUN apt-get update && apt-get install -y \
    git \
    unzip \
    zip \
    libjpeg-dev \
    libpng-dev \
    libfreetype6-dev \
    libz-dev

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
RUN composer install

EXPOSE 9000

# --------------------------------------
# Stage 3: Nginx
# --------------------------------------
FROM nginx:alpine AS nginx

WORKDIR /usr/share/nginx/html

COPY ./srv/default.conf /etc/nginx/conf.d/default.conf
COPY --from=php-fpm /srv/public/ /usr/share/nginx/html

EXPOSE 80

aptで必要なビルドライブラリーのインストールとdocker-php-ext-installコマンドでモジュールを作成している。これはdockerイメージ内でphpモジュールのビルドをするのに用意されているコマンド(シェルスクリプト)である。この辺も必要に応じてAIに尋ねるといいだろう。

いずれにせよこれでdocker-compose.ymlで起動する。ここでもイメージの指定は書き換えてある。

一見良さそうではあるが


sqliteデータベースが無いといわれる。まあそうだろう。そもそもsqliteなんてのはましてECSでは絶対使わないからね。

ってわけでイリーガルな対応ではあるけど、ここで作る

# ...
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

RUN touch database/database.sqlite
RUN php artisan migrate:fresh --seed

EXPOSE 9000
# ...

これで起動するはずだ。

あとは前回と同じ手順でECSにデプロイして欲しい

docker tag pingcrm-php-fpm ****.dkr.ecr.ap-northeast-1.amazonaws.com/practice/pingcrm-php-fpm:latest
docker tag pingcrm-nginx ****.dkr.ecr.ap-northeast-1.amazonaws.com/practice/pingcrm-nginx:latest
docker push ****.dkr.ecr.ap-northeast-1.amazonaws.com/practice/pingcrm-php-fpm:latest
docker push ****.dkr.ecr.ap-northeast-1.amazonaws.com/practice/pingcrm-nginx:latest


pushされとる

【必須作業】タスク定義のIn/OutをJSONで行う

タスク定義というのは基本的に初回こそwebインターフェースでセットするのであるが、その後はJSONを書き換えて再投入という流れになるのが普通、というわけでこれをEC2から行う。

基本構文
aws ecs describe-task-definition --task-definition <TASK_DEFINITION_NAME> --query taskDefinition --output json > task-definition.json

ここで

pingcrmというタスク定義をひっぱってくるには

aws ecs describe-task-definition --task-definition pingcrm --query taskDefinition --output json

なのだが

こんな感じでエラーになる。ここまででロールを作成してきているから、ここに新たなポリシーを割り当てて実行する。 https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/policies から作成

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecs:DescribeTaskDefinition"
            ],
            "Resource": "*"
        }
    ]
}

こういうのはもう直接jsonで作成した方が間違いも少ないかと思う。

ECSPracticeServiceManagerPolicyとした。作成したら正しくroleに割り当てること

これで

aws ecs describe-task-definition --task-definition pingcrm --query taskDefinition --output json > task-definition.json

としてtask-definition.jsonを作成した

jsonのimport

このjsonを何の変更もなしにimportしてみると

aws ecs register-task-definition --cli-input-json file://task-definition.json

これで出来る、しかし

このような余計なものが付いているのでjqでカットする。

aws ecs describe-task-definition --task-definition pingcrm --query taskDefinition | jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' > clean-task-definition.json

そうしたら

aws ecs register-task-definition --cli-input-json file://clean-task-definition.json

でimportできそうだが

もちろん権限不足なのでポリシーを変更する

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecs:DescribeTaskDefinition",
                "ecs:RegisterTaskDefinition"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::****:role/ecsTaskExecutionRole"
        }
    ]
}

こんな感じでecs:RegisterTaskDefinitionを追加しあとiam:PassRoleってのも追加している。これは権限を使う権限というか...気になったら調査してみてください

現在の問題点

たとえばlaravelのエラーをここで改めて意図的に引き出してみる。

srv/Dockerfile
# <snip>
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

#RUN touch database/database.sqlite
#RUN php artisan migrate:fresh --seed

EXPOSE 9000
# <snip>

このようにしてビルド&プッシュすると、前回のようにエラー画面が出るはずだ

これを、さっきのタスク定義を変更するとともに考えていく

まずproduction環境でエラー画面が出るのは言語道断

というわけで、ここでタスク定義にデバッグを表示しないようにセットしサービスを更新してみよう

clean-task-definition.json
      "environment": [
        {
          "name": "APP_KEY",
          "value": "base64:slBwwE6jbHjJSLHAlWbVvm9WUeqJrAQO/QaMrbdfcuA="
        },
        {
          "name": "APP_DEBUG",
          "value": "false"
        }
      ],

こんな感じにしてimport

aws ecs register-task-definition --cli-input-json file://clean-task-definition.json

これで新たなリビジョンがセットされる。なお、登録とリビジョンを表示させる場合シェルスクリプトを用意しておくといいかも

#!/bin/bash

if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <task-definition.json>"
    exit 1
fi

TASK_DEFINITION_FILE=$1

# AWS CLI の `pager` を無効化
export AWS_PAGER=""

# タスク定義を登録
TASK_DEF_JSON=$(aws ecs register-task-definition --cli-input-json file://"$TASK_DEFINITION_FILE")

# 最新のリビジョンを取得
LATEST_REVISION=$(echo "$TASK_DEF_JSON" | jq -r '.taskDefinition.taskDefinitionArn')

if [ -z "$LATEST_REVISION" ]; then
    echo "Error: Failed to register task definition."
    exit 1
fi

echo "Latest Task Definition: $LATEST_REVISION"

ここではrev:7が最新である事がわかったりする

サービスの更新

サービスを更新して最新のrevにする

新しいデプロイを強制してもいいけど、基本的にサービスが更新されるとタスクも更新される、はず。


エラー画面が消滅して500になった

今回のまとめと課題

  • laravelのエラーが500なのはいいとして何が起きてるのか全くわからない
  • サービスの更新を手動でしたくない
  • .envに関してファイルを削除する
    • そもそも、.envというファイルを本番環境に置かない方がいい
      • 全ては環境変数からコントロールできるし、それが正解
      • ssm
  • イメージビルドの自動化

など、まだまだネタが書けそうっすね

Discussion