4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PHP-FPM+NginxをCodePipelineを利用してECSにデプロイする

Last updated at Posted at 2022-12-11

概要

DockerでPHP+Nginxの環境を作り、ECSにデプロイしブラウザでこれを表示することがゴールです。
スクリーンショット 2022-12-07 23.05.28.png
備忘録としてECSにデプロイするまでの最低限の手順を辿ることが目的なので、各サービスや用語等の説明はあまりありません。
GitリポジトリにはAWS CodeCommitを、デプロイ時のビルド作業はCodeBuildを利用します。今回はCodeDeployは利用しません。

構成

本記事はデプロイまでの手順に主眼を置いているので、構成は以下の通り最低限のものになっています。一番上の buildspec.ymlはECSにデプロイする頃に作成するので、一旦は無視して大丈夫です。

.
├── buildspec.yml
├── docker-compose.yml
├── infra
│   ├── nginx
│   │   ├── Dockerfile
│   │   └── default.conf
│   └── php
│       └── Dockerfile
└── src
    └── index.php

CodeCommitの設定

GitホスティングサービスはCodeCommitを使います。Githubを使ったことがあれば特に難しいことはないと思います。まずこの設定をしていきます。
AWSにログインし、CodeCommitのページにアクセス、右上の「リポジトリを作成」をクリック。
スクリーンショット 2022-12-08 22.08.41.png

今回miniappという名前のプロジェクトにするので、リポジトリ名をminiappにします。リポジトリ名だけ入力して作成ボタンをクリック。
スクリーンショット 2022-12-08 22.11.06.png

次の画面で接続の仕方が表示されるので、説明通りにやればOKです。
スクリーンショット 2022-12-08 22.24.01.png

HTTPSでもSSHでもお好きな方法でリポジトリをローカルにクローンします。

$ git clone ssh://git-codecommit.{お使いのリージョン}.amazonaws.com/v1/repos/miniapp

Gitの初期操作

ローカルにリポジトリをクローンしてプロジェクトのディレクトリができたら一度リモートにpushしておきましょう。

$ cd miniapp
$ echo '<?php echo "Hello PHP + Nginx";' >> index.php
$ git add .
$ git commit -m 'first commit'
$ git push

index.phpは後ほど正しいディレクトリに移動させます。

ローカルでアプリを完成させる

docker-compose.ymlの作成

最初にdocker-compose.ymlを作成して、全体像を把握します。

docker-compose.yml
version: '3.9'
services:
  app:
    build:
      context: .
      dockerfile: ./infra/php/Dockerfile

  web:
    build:
      context: .
      dockerfile: ./infra/nginx/Dockerfile
      target: local
    ports:
      - 80:80

見ての通りとても簡単な構成になります。appという名でPHPが動くコンテナを、webという名でNginxが動くコンテナを作成します。ローカルで動かす場合とECSにデプロイした場合で使いたいdockerイメージに少し差があるので、webコンテナの方にtarget:localとの記述をしています。詳しくはDockerfileを作成する際に見ていきます。

infra/php以下の作成

このディレクトリで必要なのはDockerfileのみです。今回はとりあえずPHPが動けば良いので、最低限以上のことはしません。

FROM php:8.1-fpm

RUN apt-get update && \
    apt-get -y install --no-install-recommends less git unzip libzip-dev libicu-dev libonig-dev && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    docker-php-ext-install intl pdo_mysql zip bcmath

COPY ./src /data
FROM php:8.1-fpm

ベースとなるイメージは php:8.1-fpmを利用します。

RUN apt-get update && \
    apt-get -y install --no-install-recommends less git unzip libzip-dev libicu-dev libonig-dev && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    docker-php-ext-install intl pdo_mysql zip bcmath

不要なものもありますが、とりあえず色々インストールしています。

COPY ./src /data

コンテナの /data以下を公開用のディレクトリにするので、ローカルの src/dataにコピーします。
後ほど作成しますがsrcディレクトリの中にGitの初期操作時に作ったindex.phpを移動し、それをブラウザで表示させるのがゴールです。

infra/nginx以下の作成

ここではDockerfileと、appコンテナと通信するためのNginxの設定を書いたdefault.confを作成します。

default.conf

nginxのコンテナイメージに元々存在するdefault.confを上書きするためのファイルを作成します。

ここで確認しておくべき箇所はコメントを書いている2ヶ所です。1つ目は公開ディレクトリを/dataにしているroot /dataと、2つ目はPHPを扱うappコンテナとの通信に関するfastcgi_pass localhost:9000の部分です。
fastcgi_pass localhost:9000に関して簡単に説明すると、後ほどECSにデプロイする際にはPHPが動いているappコンテナと通信するために通信先をlocalhostとする必要があるのですが、ローカルで動かす際にはappというコンテナ名で通信先を指定したいので、ローカルで動かす場合のみこの記述を修正する必要があります。これに関しては後ほどDockerfile内でそれに対応する処理を書いていきます。

default.conf
server {
    listen 80;
    server_name example.com;
    root /data;  # 公開するディレクトリを/dataに設定

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass localhost:9000;  # phpを処理する場合はappコンテナにバトンタッチするイメージ
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

src/index.php

index.php
<?php
echo 'Hello PHP + Nginx';

index.phpは最初に作成したので、src/に移動させておきます。

$ mv index.php src/

Dockerfile

FROM nginx:latest AS production
COPY ./infra/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY ./src /data

FROM production AS local
RUN sed -i -e s/localhost:9000/app:9000/ /etc/nginx/conf.d/default.conf
FROM nginx:latest AS production

ベースとなるイメージはnginx:latestです。一番下にローカルでのみ実行するコマンドを記述しているのですが、ECSデプロイの際にはそれを適用したくないので、区別するためにproductionという名前をつけています。

参考: マルチステージ ビルドを使う

COPY ./infra/nginx/default.conf /etc/nginx/conf.d/default.conf

先ほど作成したinfra/nginx/default.conf(nginxの設定ファイル)でコンテナイメージに元々存在するdefault.confを上書きします。

COPY ./src /data

PHPの方のDockerfileと同様です。

FROM production AS local
RUN sed -i -e s/localhost:9000/app:9000/ /etc/nginx/conf.d/default.conf

こちらにはlocalという名前をつけています。ローカルではdocker-compose.ymlでwebコンテナの記述にtarget: localとしてproductionのイメージをビルドしてさらにlocalも適用します。CodePipelineを使ってECSをデプロイする際には、後ほど出てくるbuildspec.ymlにwebコンテナのビルドを記述する際target: productionとしてビルドにproductionしか適用されないようにします。

いったん完成!

ここまでで以下のようなディレクトリ構成になりました。

.
├── docker-compose.yml
├── infra
│   ├── nginx
│   │   ├── Dockerfile
│   │   └── default.conf
│   └── php
│       └── Dockerfile
└── src
    └── index.php

ではイメージをビルドしていきます。コマンドを実行するのはプロジェクトルートのディレクトリです。間違ってinfra/src/で実行しないように気をつけましょう。

$ docker compose build

ビルドが完了したらイメージを確認します。

$ docker images

「REPOSITORY」にminiapp_appminiapp_webが出てきたらOKです。
ビルドしたイメージからコンテナを作ります。

$ docker compose up -d

-dをつけることでデタッチモードにしていますが、アクセスした時のログなどをみたい場合は-dなしで実行します。その場合、コンテナを停止したいときはcontrol+Cでできます。
ブラウザでlocalhostにアクセスするとこちらの画面が表示されます。
image.png

ここまで確認できたらコンテナを削除しておきます。

$ docker compose down

出来上がったコードをリモートにpushします。

$ git add .
$ git commit -m 'create php and nginx app'
$ git push

ECRへのpush

ECRにdockerイメージをpushします。ECRのコンソールへはECSのページの左にある「リポジトリ」からとべます。「リポジトリを作成」をクリック
スクリーンショット 2022-12-10 23.35.56.png

appコンテナ用のリポジトリとwebコンテナ用のリポジトリの2つを作ります。appコンテナ用ののものはリポジトリ名をminiapp_app、他はデフォルトのままで下の「リポジトリを作成」をクリック。webコンテナ用のものはリポジトリ名をmini_webとして作成します。
スクリーンショット 2022-12-10 23.46.33.png

できたリポジトリを確認すると右上に「プッシュコマンドの表示」があります。これを開くとECRにプッシュするための手順が書いてあるので、この通りに進めてappコンテナのイメージとwebコンテナのイメージをそれぞれプッシュします。AWS CLIからECRへアクセスする権限が必要となります。

CodePipelineを使ってECSにデプロイする

ここからはECSにデプロイするための作業をしていきます。

タスク定義

最初にタスク定義を作成します。ECSコンソール画面の左のメニューからタスク定義を選択、タスク定義画面に移ったら「新しいタスク定義の作成」をクリック
スクリーンショット 2022-12-10 20.34.38.png

タスク定義名はminiappとします。オペレーティングシステムにLinuxを選択します。
スクリーンショット 2022-12-10 20.36.11.png

タスク実行ロールは、「ecsTaskExcecutionRole」が存在すればそれを選択、ない場合は「新しいロールの作成」を選択。タスクサイズはメモリもCPUも最小のもの(メモリ0.5GBとCPU0.25vCPU)を選択します。ここまでできたらコンテナの追加をクリック。
スクリーンショット 2022-12-10 20.40.06.png

appコンテナとwebコンテナの2つを作ります。最初にappコンテナです。コンテナ名はapp、イメージはECRで作ったminiapp_appリポジトリの「latest」というイメージタグがついているもののURIを使います。ポートマッピングは9000と入力してあとはデフォルトのままで「追加」をクリック。続いてもう一度「コンテナの追加」を押しwebコンテナを作ります。コンテナ名はweb、イメージはminiapp_appリポジトリの「latest」というイメージタグがついているもののURI、ポートマッピングは80で作成してください。
スクリーンショット 2022-12-11 21.29.38.png
これでタスク定義の設定は完了です。スクロールして一番下にある「作成」をクリックします。

クラスター

次はクラスターを作成します。ECSコンソールのクラスターから、「クラスターの作成」をクリック
スクリーンショット 2022-12-10 18.39.34.png

今回はFargateでデプロイするのでデフォルトのままで「次のステップ」をクリック
スクリーンショット 2022-12-10 18.40.55.png

クラスター名はminiappとします。ここで新しくVPCを作成したい場合はVPCの作成にチェックをつけて設定をしましょう。「作成」をクリック。
スクリーンショット 2022-12-10 18.42.22.png

サービス

次にサービスを作成します。「作成」をクリック。
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3730373138322f33326231343564372d623163302d356265322d653433632d3531353163376239616331622e706e67.png

項目はたくさんありますが入力するところはあまり多くありません。
起動タイプにFARGATE、タスク定義はminiapp、サービス名はminiapp_service、タスク数は1とします。他はデフォルトのままで「次のステップ」へいきます。
スクリーンショット 2022-12-11 21.51.59.png

次はネットワーク構成の設定です。適切なVPC、サブネット、セキュリティグループを選択すれば大丈夫です。httpでアクセス可能なら問題ないと思います。これから作成する場合はこちらに書いているとおり作成すれば良いと思います。Amazon ECS を使用するようにセットアップする
他はデフォルトのままで「次のステップ」。
次はAutoScalingの設定です。今回は必要ないので「次のステップ」。
最後に設定を確認し、「サービスの作成」でサービスの作成は完了です。

CodePipeline

CodePipelineに移ります。CodePipelineのページにアクセスし、右上の「パイプラインを作成する」をクリック。
スクリーンショット 2022-12-08 22.40.17.png

パイプライン名はminiappにします。パイプライン名を入力すると新しいサービスロールのロール名も自動的に入力されます。このロール名のまま使うことにします。「次に」をクリック。
スクリーンショット 2022-12-08 22.47.38.png

ソースプロバイダーとしてCodeCommitを、リポジトリは先ほど作成したminiapp、ブランチ名はmainを選択します。あとは初期値のままにしておきます。今後miniappのmainブランチに変更が生じたときにパイプラインが自動的に起動しデプロイがされることになります。「次に」をクリック。
スクリーンショット 2022-12-08 22.48.06.png

ここではCodeBuildの設定をします。「プロバイダーを構築する」でAWS CodeBuildを選択。リージョンはお使いのリージョンを選択します。
ここでCodeBuildのビルドプロジェクトを作成します。「プロジェクトの作成」をクリック。
スクリーンショット 2022-12-08 22.53.13.png

別のウィンドウでCodeBuildの画面が立ち上がるので設定していきます。プロジェクト名はminiappにします。
スクリーンショット 2022-12-10 18.15.22.png

少し下に行き、環境の設定です。画像の通り選択します。
スクリーンショット 2022-12-10 18.21.34.png
スクリーンショット 2022-12-10 18.21.55.png

これより下はデフォルトのままで大丈夫です。「CodePipelineに進む」をクリック。

これでCodeBuildのプロジェクトができたので、「次に」をクリック。
スクリーンショット 2022-12-10 18.23.45.png

ここでCodePipelineの設定は中断し、ローカルで必要なファイルを作成していきます。プロジェクトルートにbuildspec.ymlを作成します。こちらに書いているものを雛形に使っています。Tutorial: Amazon ECS Standard Deployment with CodePipeline
日本語で記載している部分は適宜修正してください。

buildspec.yml
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin {12桁のアカウントID}.dkr.ecr.{ap-northeast-1などのリージョン名}.amazonaws.com
      - REPOSITORY_URI_APP={ECRのminiapp_appリポジトリのURI}
      - REPOSITORY_URI_WEB={ECRのminiapp_webリポジトリのURI}
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_URI_APP:latest -f infra/php/Dockerfile .
      - docker build -t $REPOSITORY_URI_WEB:latest -f infra/nginx/Dockerfile --target production .
      - docker tag $REPOSITORY_URI_APP:latest $REPOSITORY_URI_APP:$IMAGE_TAG
      - docker tag $REPOSITORY_URI_WEB:latest $REPOSITORY_URI_WEB:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI_APP:latest
      - docker push $REPOSITORY_URI_WEB:latest
      - docker push $REPOSITORY_URI_APP:$IMAGE_TAG
      - docker push $REPOSITORY_URI_WEB:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"app","imageUri":"%s"}]' $REPOSITORY_URI_APP:$IMAGE_TAG > imagedefinitions.json
      - printf '[{"name":"web","imageUri":"%s"}]' $REPOSITORY_URI_WEB:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files: imagedefinitions.json

mainブランチの変更でCodePipelineは発火します。CodeCommitからソースコードの情報がCodeBuildに渡され、それを元にCodeBuildのビルド環境でbuildspec.ymlに書かれたコマンドを実行しコンテナイメージをビルド、ECRにプッシュされます。
ここではwebコンテナのdocker buildtarget productionを指定することで、webコンテナのDockerfileから適切なイメージがビルドできます。

ここで改めてCodeCommitにプッシュします。

$ git add .
$ git commit -m 'created buildspec.yml'
$ git push

ここでCodePipelineコンソールに戻ります。
デプロイプロバイダーにECSを選択、クラスター名、サービス名は先ほど作成したものを選択し、「次に」をクリック。
スクリーンショット 2022-12-11 11.26.18.png

「パイプラインを作成する」をクリックするとパイプラインが出来上がり、一連の流れが実行されます。
するとCodeBuildでパイプラインが失敗します。CodeBuildのビルドプロジェクトminiappから入り、ビルド履歴で最新の「ビルドの実行」をクリック、「フェーズ詳細」タブを確認すると「PRE_BUILD」でECRにログインするコマンドが失敗しています。
スクリーンショット 2022-12-11 20.50.17.png
これはCodePipelineからECRにアクセスする権限がないためです。「ビルド詳細」タブの「環境」にサービスロールを見つけクリックします。
スクリーンショット 2022-12-11 20.52.54.png

許可を追加から「ポリシーをアタッチ」をクリック
スクリーンショット 2022-12-11 20.54.08.png

「AmazonEC2ContainerRegistryPowerUser」を探してポリシーをアタッチします。
スクリーンショット 2022-12-11 20.56.55.png

これでビルドがうまくいくはずなのでCodePipelineに戻り再試行します。
スクリーンショット 2022-12-11 20.58.25.png

これで完了です。この通りにやってうまく行かない場合は、権限周りに問題がある可能性が高いと思います。こけた所に関係するIAMロールなどの権限を確認してみましょう。
全て完了したら適宜ECSのサービスやECRリポジトリなど削除しておきましょう。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?