Laravel + PPM をAmazon ECS(AWS Fargate)にデプロイしよう

こんにちは。プラットフォーム技術戦略室の青木です。

今までLaravelのアプリケーションを nginx + PHP-FPM でデプロイしていて、ある人に「PPMの方がダントツに早いで!」と聞きましたので今回初めて触ってみようかと思います。*1

PPMって何?

PPM - PHP Process Manager

簡単に言うと、ReactPHPというものに基づきPHPのプロセスへのロードバランサの役割をするものです。

もっと簡単に言うと、"とってもはやくなりうる*2PHP"です。希望が持てますね。*3

PHP-FPM

PHP - FastCGI Process Manager

PHPに付随する技術に FastCGI というものがあります。 こちらは、初回リクエスト時に起動したプロセスをメモリ上へ保持を行い、次回リクエストに対してはそのメモリに保持したプロセスの実行を行うことでプログラムの動作速度を向上し、サーバ負荷を低減させることが出来ます。 PHPのFastCGI実装のひとつに PHP-FPM があり、以下のドキュメントのような機能が備わっています。

https://www.php.net/manual/ja/install.fpm.php

通常、nginxをリバースプロキシとし、PHP-FPMに対してTCPやUNIXドメインソケット通信で通信することが多いです。

改めてPPMについて

PPMは、 PHP-FPM より高速に動作が出来るような構造を持つもので、 ReactPHP というものに基づいて作られているものとなります。 ReactPHP は以下の記事でこのように表現されています。

web.archive.org

Wait, we have asynchronous tools in PHP? Yes, we have. ReactPHP is one of the most promising libraries to me. It brings the powerful concept of event-driven, non-blocking I/O (Hi NodeJS) to PHP. With this technology in mind we are able to write our own HTTP stack in PHP and have control back over the memory without destroying it at the end of each request.

つまり、これまで非同期が扱えなかった(扱いづらかった)PHPにおいて非同期で実装出来る方法を簡素に利用出来るライブラリと言う事になります。また、全てのオブジェクトのインスタンス化、キャッシュの読み取りが必要無くなることでアプリケーションのBootstrapを最小限にすることが出来、パフォーマンスが飛躍的に向上します。

こういった概念を基に、ロードバランサとして機能させたものが PPM ということになります。

PPMはマルチコアをサポートしており、複数のワーカーによって構成されます。その複数のワーカーに対してロードバランシグを行い、適切に処理を行う事でパフォーマンスを向上させます。 また、ロードバランサの機能をnginxhhvmに持たせ、ワーカーに対して直接対話出来るような仕組みも実現可能です。

As we can see, the react server with nginx as load balancer is over 15 times faster than old school PHP-FPM + APC.
引用: https://web.archive.org/web/20190103202024/http://marcjschmidt.de/2014/02/08/php-high-performance/

PHP-FPM よりも、15倍ものパフォーマンスとなっていることがわかります。 現状の懸念点とすれば、メモリリークが確認されていることです。これはPHPの実装時にReactPHPの思想の基に構築されていないので、メモリ管理が適切に行われていません。*4 解決方法としては、ワーカーの再起動を推奨しています。今後の発展を期待してメモリリークしないような設計が出来ると良いですね。

Memory leaks, memory leaks and memory leaks. You will also find leaks in your application. :) But no big issue since workers restart automatically. 引用: https://github.com/php-pm/php-pm

PPMの公式リポジトリですが、こちらの記載によるとワーカーは自動的に再起動されるそうなので、クリティカルな問題とされていません。ですが、こういった事が起こるということを利用者はしっかり覚えておく必要があるかと思います。そもそも、メモリリークを許した状態で本番運用するかどうかも導入前に検討が必要なので、 長期運用が可能なのかどうか、正確にテストを行う事が重要かと思います。*5

作ったアプリをDockerImage化する

さて、ちゃんとした解説は他の記事に任せるとして、早速PPMを使ってみましょう。今回も以前から利用しているLaravelで実装してみます。 ここではDockerが利用されているため、ローカル環境にDockerをインストールしてください。

  1. Laravelを構築する
  2. Dockerfile作成 & Build
  3. ローカルで起動し、動作確認を行う

1. Laravelの構築

tech-blog.optim.co.jp

こちらの記事の HelloWorld(プロジェクトの作成) を目標にコマンドを羅列していきます。 PHPの環境構築などは各自で行ってください。

$ composer create-project laravel/laravel Techblog_sample
$ cd Techblog_sample

構築出来たかを確認します。

$ php artisan serve

ブラウザにて、 http://localhost:8000 にアクセスをし、Laravelを表示されれば完了です。

2. Dockerfile作成 & Build

phppm/nginx:2.0.3 リポジトリからPullしてアプリケーションをCOPYしたイメージを生成します。 必要に応じて、dockerignoreなどを行うことをオススメします。

FROM phppm/nginx:2.0.3
RUN apk update && apk add --no-cache \
      tzdata curl \
      && apk del tzdata \
      && rm -rf /var/cache/apk/*
COPY . /var/www
CMD ["--bootstrap=laravel","--static-directory=public/"]

CMD ["--bootstrap=laravel","--static-directory=public/"] は、Laravelを利用する際につけるコマンドです。こちらはppm公式リポジトリのREADMEに記載されています。

また、今回はソースコードをまるごとイメージに封入し、コンテナ実行だけすればサーバが起動するような構成になっています。

こちらをビルドします。タグ名は任意です。

$ docker build -t techblog_sample:sample .
...

Successfully tagged techblog_sample:sample

Successfullyが出ると完了です。

3. ローカルで起動し、動作確認を行う

nginxによるロードバランシングがdefaultでなされているので、80番ポートでWebサーバが立ちます。 以下のコマンドでサーバを立ててみましょう。

$ docker run --rm -p 8000:80 techblog_sample:sample
...
Starting PHP-PM with 8 workers, using StreamSelectLoop ...
8 workers (starting at 5501) up and ready. Application is ready at http://127.0.0.1:8080/

この時、http://127.0.0.1:8080/ に目が行くと思いますが、こちらはnginxppmを見る際に接続するアドレスになりますので、無視して構いません。 default設定でワーカーが8台立ち上がりますが、こちらは起動時のコマンドで設定変更が可能です。

起動が確認できましたら、あとはどこへでもデプロイ出来る状態です。

AWS,GCP,Azureなどの仮想マシンにDockerをインストールし、コンテナを実行してもいいですし、kubernetesのpodとして定義してデプロイを行っても構いません。

今回はAWS Fargateを利用してデプロイを行いたいと思います。

Amazon ECS(AWS Fargate)にデプロイする前に

出来たイメージをAmazon ECSにデプロイします。 無料枠を超えてしまうことがありますので、利用する際は課金額に注意してください。

Amazon ECS,AWS Fargateを知らない方に、簡単に説明いたします。

[ざっくりと解説] Amazon ECSとは

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/Welcome.html

Amazon Elastic Container Service (Amazon ECS) は、クラスターで Docker コンテナを簡単に実行、停止、管理できる非常にスケーラブルで高速なコンテナ管理サービスです。

簡単に言うと、Dockerイメージを用意すればコンテナを生成し実行してくれるサービスです。 EC2インスタンスを生成してからコンテナを実行するとなると、Dockerエンジンをインストールしたり、管理する際にはインスタンスにアクセスしてコマンドを叩かなければいけません。 Amazon ECSではそういった作業をWebUIでポチポチと簡単に操作することが出来ます。また、APIも用意されており、API経由でECSを操作することも可能になっています。

その他にもたくさんの機能がありますが、今回の説明は省きます。

[ざっくりと解説] AWS Fargateとは

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/AWS_Fargate.html

AWS Fargate は、Amazon ECS とともに使用して Amazon EC2 インスタンスでサーバーまたはクラスターを管理する必要なくコンテナを実行できるテクノロジーです。

Amazon ECSを利用する際に、コンテナを実行する環境であるAmazon EC2インスタンスが必要になります。EC2インスタンスはいわば仮想マシンで、複数のインスタンスを利用する場合はそれらのインスタンスの設定などを行う必要があります。 また、スケールイン・アウトする場合もそれなりの設定が必要になります。

AWS Fargateを利用することにより上記の手間を減らし、Amazon ECS利用者はコンテナ管理に集中することが出来る上に、運用時の仮想マシン管理がかなり楽になります。

でぷろい!

解説は程々にして、、ようやくですがデプロイをしていこうと思います。サクッとデプロイしたいので、手軽にAWSの環境構築が出来るTerraformを利用します。 細かく解説すると日が暮れてしまいますので、TerraformVPCなどの知識はある前提でお話したいと思います。

この作業には以下が用意できることを前提に進めています。

  • AWSのアカウントにそれなり*6の権限がある
  • ドメインを取得済み
  • 月額2.5万円以上の運用資金(起動時間分課金されます)
  • ツールがインストール済み AWS CLI,docker

構成

よくあるAWS VPCのベストプラクティスです。基本的にTerraformに全部記述しますので、そちらを見ていただけるとしっかりと内容がわかるかと思います。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20200310/20200310112736.png

手順

デプロイ手順は以下のとおりです。

  1. イメージをECRにPush
  2. 利用するドメインのSSL証明書を発行
  3. Terraformで環境構築 & デプロイ

1. イメージをECRにPush

まずは、AWSのECRマネジメントコンソールに接続し新しいリポジトリを作成します。 複数のイメージを管理する場合はリポジトリ名に / を入れることが出来るので、各チームや利用者と命名規則を決めると良いでしょう。

参考ドキュメント: https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/repository-create.html

次に、DockerイメージをPushします。ECRにPushする際は docker tag コマンドでPrefixにECRのURIを付ける必要があります。詳しくはドキュメントを御覧ください。

参考ドキュメント: https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-push-ecr-image.html

2. 利用するドメインのSSL証明書を発行

自前の証明書をアップロードする方法や、AWSのSSL証明書を利用しない事も出来ますが、今回はALBに簡単にSSL証明書を紐付けられる方法を用いることとします。

AWS Certificate Managerにアクセスし、Public証明書を発行します。発行手順は以下のドキュメントを参考にポチポチとクリックで進んでください。DNSによる検証を行う場合は取得したドメインの管理者に発行されたCNAMEレコードを追加してもらう必要があります。

参考ドキュメント: https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/gs-acm-request-public.html

3. Terraformで環境構築 & デプロイ

Terraformについてはこちらを参照ください https://www.terraform.io/

github.com

Terraformでの構築に関しては、かなり説明するところが多いのでわかりやすいようGithubにソースコードを置きました。

内部的な構成は先程の 構成 の画像と同等のものとなっていますが、一部違う部分がありますので後ほど説明いたします。

Terraformでの構築手順

  1. リポジトリをCloneする
  2. コードを少し書き換える
  3. Terraformコマンドでデプロイ

a. リポジトリをCloneする

github.com

こちらのリポジトリをCloneしてください。

b. コードを少し書き換える

まずは、どこからでもアクセス出来る状態を無くさなくてはいけません。 不用意にパブリックに公開してしまっては、セキュリティインシデントに繋がりますので、アクセスするグローバルIP等に変更してください。

techblog-ppm-terraform/aws_alb.tf at master · optim-corp/techblog-ppm-terraform · GitHub

cidr_blocks = ["*.*.*.*/32"] 

また、このサンプルリポジトリでは東京リージョンをデフォルトで利用するようになっていますので、各所にある ap-northeast-1 を任意のリージョンに変更しても構いません。ただし、AWS Fargateが対応しているリージョンでなければ動作しませんので、ご注意ください。

次に、先程発行したSSL証明書のARNをこちらに記入してください。

https://github.com/optim-corp/techblog-ppm-terraform/blob/master/modules/aws_alb.tf#L59

次に、先程PushしたECRリポジトリのイメージURIをこちらに記入してください。

https://github.com/optim-corp/techblog-ppm-terraform/blob/master/modules/aws_ecs.tf#L96

最後に、Laravelに読み込ませる環境変数群を任意のものに変更してください。

https://github.com/optim-corp/techblog-ppm-terraform/blob/master/modules/aws_ecs.tf#L120-L135

以上で大まかなコードの書き換えは終了になります。

c. Terraformコマンドでデプロイ

Terraformコマンドが使える状態で、以下のコマンドを実行します。

$ terraform init
$ terraform plan

Planでは、AWSに何を作成・削除・変更するかが一覧で出力されます。 ここでしっかりと内容を確認して間違いが無いようにしてください。

内容が合っていれば以下のコマンドでデプロイを行ってください。

$ terraform apply

このコマンドでアプリケーションがデプロイされます。

構成図と異なっている部分

https://github.com/optim-corp/techblog-ppm-terraform/blob/master/modules/aws_ecs.tf#L29

こちらを見るとわかるかと思いますが、2つ作成したPrivateSubnetに対してサービスが割り当てられていない事がわかります。 サブネット等のネットワーク・インフラなどはマルチAZに対応させた形で実装していますが、料金等が跳ね上がらないよう1つのAZにしかデプロイしないような設定になっています。 こちらは単純にサブネットを追加で指定することで完全なマルチAZに対応します。

デプロイ後

お疲れさまでした。

以上で、アプリケーションのデプロイが終了しました。 以後は、Terraformのタスク定義内のイメージタグ等を変更することでデプロイするアプリケーションを変更出来たり、負荷に応じてオートスケールすることも可能になります。

また、CloudWatchにLogも出しているので、エラーなどがあればそちらで確認することも可能です。必要に応じてアラート等を出すことも出来ますので、是非お使いください。

おまけ:クリーンアップ方法

デプロイは終わりましたが、このままでは課金が続いてしまいますので terraform destroy コマンドで全て削除できます。 この場合全てがキレイに無くなってしまいますので、一時的に停止したい場合などはAWSマネジメントコンソールからサービスの停止などを行ってください。

Terraform(ローカル)とAWSの整合性が取れなくなった場合は再度 terraform plan コマンドで同期することが可能です。*7

さいごに

Webアプリ開発だけを知っていても、インフラを知らなければ思わぬインシデントに出くわす事があります。 こうした幅広い知識を付けていくことで、小さなミス...のちに大きなミスとなるものを減らしつつユーザにとってより良いサービスを提供出来るように日々精進しようかと思います。

最後に一言。 Laravelはいいぞ(今回Laravelのことあんまり書いてない人がry)


OPTiMでは様々な分野で活躍できるエンジニアを募集しております。

興味がある方は是非弊社採用ページにてご覧ください!

www.optim.co.jp

謝辞

こちらの記事を大変参考にさせていただきました。 ありがとうございました。

web.archive.org

*1:今回も初心者向けの記事となります

*2:早くならないケースもあるのでこういった言い回しになっています

*3:数年ほど前からあるものなので、そろそろ枯れた技術となっているのでは?

*4:Synfonyなどのフレームワークを使っていても同様です

*5:こういう事も有り、大規模なアプリケーションをPPMに移行するのはかなりリスクが高く、筆者はオススメしません。新規開発やより高速に動作させる事が重要な場面では利用出来るのではと思います。

*6:ECS,ECR,IAM,ALB,VPC全般,EC2,ACMなど

*7:場合によっては復元できない可能性もありますので、削除や変更は慎重に行ってください