👏

【チュートリアル】 AmazonEC2で作業してnginxとphp-fpmのイメージをECSで動作させる

2025/01/24に公開

https://zenn.dev/catatsumuri/articles/43aaca6a75d9d1

前回の続き

前回までで、nginxphp-fpmの環境をローカルに構築し、docker-composeにてそれぞれ起動してnginx側からphp-fpmと通信し、phpinfo()を出力させる事に成功している。

今回はこの構成をどうやってECSで動作させるかを徹底解説していく。

ECRにローカルリポジトリをプッシュ

現在、記事通りに行ってくると、ECRには最初の記事で作成したnginxでシンプルなhtmlを表示するためのイメージリポジトリが残っているはずだ。今回はこれに加えてnginxphp-fpm用の2つのリポジトリを追加でECR上に作成する。

ローカルの状態確認

現在のローカルの状態は以下の通りである。

$ docker image ls
REPOSITORY       TAG       IMAGE ID       CREATED       SIZE
my-ecs-php-fpm   latest    f06015aafd46   7 hours ago   504MB
my-ecs-nginx     latest    610939fb6090   7 hours ago   50.9MB
...

そうした時に

  • my-ecs-nginx
  • my-ecs-php-fpm

という2つの名前のイメージがあるのでそれぞれECRにリポジトリを作成する

ECR上に2つのリポジトリを作成する

ここでの手順は最初の記事に書いてあるので、必要であれば参照

  • practice/my-ecs-nginx
  • practice/my-ecs-php-fpm

という2つのリポジトリを追加する

https://zenn.dev/catatsumuri/articles/9d1cd203776402

設定項目がほとんど無いため、この手順は難しくないはず。

nginxのconfigを変更

docker-composeではphp-fpm:9000で通信が可能であったが、構成にもよるが、ECSで1タスクの中で2コンテナを動作させる事を目指すとした場合、これは双方localhostで通信しなくてはならない

構築した事が無いと何の話かわからないかもしれないのだが、すなわち今現在nginxのconfigでphp-fpmへの通信はTCPのphp-fpm:9000 を指定しているが、これ「ではなくlocalhost:9000 に変更しないといけないという事になる。

nginx/default.conf
    location ~ \.php$ {
        root           /usr/share/nginx/html;
        # fastcgi_pass   php-fpm:9000; # <----------------- これは無効化
        fastcgi_pass   localhost:9000; # <----------------- PHP-FPMサービス名とポート
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;       
    }

このように変更してビルドする

tag付け後push

ここでも作業自体は前回の記事に書いてあるので詳細は割愛するが

コマンドが複雑になってきているため、以下のようなスクリプトを用意した

build.sh
#!/bin/bash

# 設定
AWS_REGION="ap-northeast-1"
AWS_ACCOUNT_ID="***************" # 置き換える事
ECR_REPOSITORY_PREFIX="practice"
NGINX_TAG="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY_PREFIX/my-ecs-nginx:latest"
PHP_FPM_TAG="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY_PREFIX/my-ecs-php-fpm:latest"

# AWS ECRにログイン
aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"

# Nginx
docker build -t my-ecs-nginx -f ./nginx/Dockerfile . --no-cache
echo "Tagging and pushing my-ecs-nginx as $NGINX_TAG..."
# tag
docker tag my-ecs-nginx:latest "$NGINX_TAG"
# push
docker push "$NGINX_TAG"

# PHP-FPM
# build
docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile . --no-cache
echo "Tagging and pushing my-ecs-php-fpm as $PHP_FPM_TAG..."
# tag
docker tag my-ecs-php-fpm:latest "$PHP_FPM_TAG"
# push
docker push "$PHP_FPM_TAG"

echo "All images have been pushed successfully!"

このスクリプトを使うにせよ、使わないにせよ、ECRにイメージが2つpushされている事が重要である。

タスク定義の修正

再度タスク定義の画面に戻ってみよう

するとここでは

既にmy-ecs-project-task-defというタスク定義を作成しているはずで、今回はこのタスク定義を変更していく。これは主にイメージの指定の変更になる。

新しいリビジョンを作成する


新しいリビジョンの作成

コンテナ1の変更

このように、コンテナ1は/practice/my-ecs-nginxに変更している

ECSサービスの更新

ここで順々に手繰っていき「サービスの更新」を押す

ここで最新のリビジョンにセットして必要なタスクを1にする

新たに割り当てられたIPアドレスを確認する

タスクが正常に起動してきたら、当該タスクIDをクリックして

パブリックIPを確認する

そこにアクセスすると

新しく画像付きのwebページが表示されるはずだ

しかし

phpは何も設定していないため、localhost:9000と通信が出来ずエラーとなる。これを解消する

php-fpmコンテナを追加する

ここでECSタスクは1つのElastic Network Interface (ENI) を持つため、タスク内の全てのコンテナが 同じネットワーク名前空間 を共有するという特性がある。これを利用し、同一コンテナ上にphp-fpmを組むとlocalhostで通信できるようになる。以降この設定を行う

コンテナ1の下にある「コンテナの追加」を押す


php-fpmのECRへのURIを記述し、必須コンテナを「はい」にする


以上を行い更新する。ここではドキュメントをごちゃごちゃ書いたり消したりしたせいでrev6になった

サービスの再更新

タスク定義の新リビジョンを追加したら常にサービスを再度更新する必要がある


筆者の環境ではrev6が最新だったので、そうしている


リビジョンを更新するとまたタスクが自動的に新しいものと置き換わる


自動更新された、これには結構時間かかる

新しいIPアドレスでphpinfoを確認する

このデプロイメントで新しいIPアドレスが割り当てられるから

このトップページとか

phpの起動を確認しておく

まとめ

駆け足で行ってきたが、ここまでで3記事あるけど結構複雑化してきたはずだ。手順をしっかりまとめる必要がある

  • ローカルでnginxとphp-fpmのイメージを作成
  • docker-composeで確認
  • 2つのECRリポジトリを作成
  • php-fpmの通信をlocalhostへと変更
    • localhostに設定を変更したイメージを再度作成
  • ローカルで2つのイメージをタグ付け
  • ECRにプッシュ
  • タスク定義の更新
  • サービスの更新(nginxのみ)
  • php-fpmを含むタスク定義の更新
  • サービスの更新(nginx+php-fpm)
  • 確認

ざっと箇条書きにしただけでも、こんなにもある。

本記事ではphp-fpmを含まないタスク定義を作って最初に失敗してみせているが、そこを一発でうまくやったとしても手順の複雑みはそれほど解消しないので、やはり1つ1つ丁寧にメモを残しつつ設定していく以外手が無い。

しかもこれはまだdevelopment環境で行っている話なのでproduction環境だとVPCの作成から何から諸々あるので、何度も実行して馴染ませていく以外無いかもしれない。

ただし

慣れてくるとwebコンソールからほとんど作業しなくなる。awsコマンドを使ったりだとか設定情報をコードに落としていくいわゆるIaCで管理するのでもう少し楽になるが、それでも結局手でおこなっている事の置き換えなので手で作れないと当然、Iacを完全に理解するのは難しいだろう。

次回

これを踏まえてもう少し環境を整理しつつ、laravelのデプロイメントを行っていく事にする。

ただし、まだローテクでの更新になるかと思う。

補足

この記事では、1タスクに NginxPHP-FPM の 2つのコンテナを詰め込んでいるが、プロダクション環境ではこの構成は分離のメリットを活かせない場合が多い。

実際のところ、Nginx は静的コンテンツのみを配信している。このケースでは index.htmllogo.png などが対象で、処理が非常に軽いことが想像できるはずだ。

一方で、動的コンテンツの生成を担当する PHP-FPM は、PHP による動的処理を行うため、ヘビーな処理が集中する。その結果、Nginx 側の負荷が低くても、PHP-FPM 側の負荷が高まった場合には PHP-FPM タスクのみを増加させる(これをスケールアウトと言う)構成が合理的である。

しかし、この2つをまとめてしまうと、タスク全体としてのスケーリングが必要となり、Nginx のリソースを無駄に増やすことになる。これではコストパフォーマンスの悪化に繋がる。

高負荷なサービスを運用する場合、NginxPHP-FPM を分離し、PHP-FPM 側のみを柔軟にスケーリングできる構成を常に意識することが重要である。

このような事をapache2でやるには、やれるんだけどApache2自体が比較的重厚で多機能なため、近年nginxが積極的に採用されている理由がここにあったりもする。(ただし開発用途等でオールインワンのサーバーを用意するとかいう場合は便利なので積極的に使っていい)

Discussion