Amazon Web Services ブログ

サーバーレス LAMP スタック – Part 3: Webサーバーの置き換え

本投稿は AWS サーバーレス アプリケーションのシニアデベロッパーアドボケートである Benjamin Smith による寄稿です。

本シリーズの他のパートは以下のリンクからアクセスできます。また、関連するサンプルコードはこちらの GitHub リポジトリにあります。


この投稿では、Web サーバーを使用せずにサーバーレス PHP アプリケーションを構築する方法を学びます。

この投稿の後半で、bref および Serverless Visually Explained の作成者である Matthieu Napoli が FastCGI Process Manager の実装を Lambda 関数内で使うことでそれを可能にする方法を説明しています。Bref は、PHP 用のオープンソースのランタイム Lambda レイヤーです。

また、プライベート Amazon S3 バケットから静的アセットを安全に提供およびキャッシュするように Amazon CloudFront を構成する方法を示します。動的リクエストは、その後 Amazon API Gateway にルーティングされ、単一の AWS Lambda 関数にルーティングされます。
これらのサービスを組み合わせることで、PHP アプリケーション用の従来の Web サーバーを置き換えることができます。

サンプルコードについては、この GitHubリポジトリ にアクセスしてください。

サーバーレス LAMP スタックアーキテクチャについては、この投稿で説明しています。Web アプリケーションは2つのコンポーネント(静的アセットと動的コンテンツを生成するバックエンドアプリケーション)に分割されます。Lambda 関数には、アプリケーションのビジネスロジックと MySQL データベースとの対話が含まれています。各応答は、API Gateway を介して同期的に返されます。

API Gateway を使用したルーティング

サーバーレス LAMP スタックでは、httpサーバーを使用していません。代わりに、API Gateway が Apache または NGINX のルーティングメカニズムを置き換えます。AWSサーバーレスアプリケーションモデル(AWS SAM)を使って、 API Gateway のルーティングルールを構成します。

      Events:
        DynamicRequestsRoot:
          Type: HttpApi
          Properties:
            Path: /
            Method: ANY
        DynamicRequestsProxy:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY

すべてのインバウンドリクエストを HTTP API から単一の Lambda 関数にルーティングする AWS SAM テンプレート

上記のテンプレートは、インバウンド要求に対して “catch-all” ルールを適用した HTTP API を作成します。リクエストコンテキストはダウンストリームで単一の Lambda 関数に送信されます。これは、リクエストを index.php ファイルに転送する PHP MVC フレームワークの動作と似ています。以下は、これが Web サーバーと .htaccess 構成の組み合わせを使用して、従来の LAMP スタックでどのように実現されていたかを示しています。

Alias /yourdir /var/www/html/yourdir/public/ 
<Directory “/var/www/html/yourdir/public”> 
AllowOverride All 
Order allow,deny 
Allow from all 
</Directory>

apache2.conf ファイル構成

<IfModule mod_rewrite.c> 
RewriteEngine On 
RewriteBase / 
RewriteRule ^index\.php$ - [L] 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteRule . /index.php [L] 
</IfModule>

Public/.htaccess構成

bref を使用した従来の PHP フレームワークのホスティング

bref は Lambda のオープンソースの PHP ランタイムレイヤーです。bref-fpm レイヤーを使用すると、Symfony や Laravel などの従来の PHP フレームワークを使ってアプリケーションを構築できます。フレームワークは単一の Lambda 関数内にあり、前述のサービスアーキテクチャとルーティングルールを使用して呼び出されます。これは、bref の FastCGI Process Manager の実装により可能になりました。Bref の作成者である Matthieu Napoli がその方法を説明しています。

brefの「FPMランタイム」は php-fpm バイナリを実行します。PHP-FPM は、PHPコアチームによって開発された FastCGI プロトコルを実装するサーバーです。従来、Apache や NGINX などの HTTP サーバーで使用されています。

bref の PHP-FPM の実装により、PHP アプリケーションは次のような使い慣れた環境で実行できます。

  • 新しいプロセスでの各 HTTP リクエストの実行(PHPの「シェアードナッシング」実行モデルの基礎)
  • HTTP要求データへのアクセスに使用されるグローバル変数($_GET、$_POST…)の設定
  • PHPスクリプトがHTTP応答を返すためのメカニズム(header 関数、stdout…)
  • OPcache(opcodeキャッシュ)、APCu(共有メモリキャッシュ)、データベースの永続的な接続などのパフォーマンスの最適化機能

ほとんどの PHP フレームワークはこれらの PHP-FPM 機能を中心に構築されているため、この機能を含むランタイムは「サーバーホスティング型」からサーバーレスへの優れた移行となりえます。

ランタイムの仕組みの概要は次のとおりです。

bref-fpm 処理サイクル

起動

新しい Lambda 環境の最初の呼び出し時に、bref のブートストラップが実行され、PHP-FPM プロセスがバックグラウンドで開始されます。この PHP-FPM サーバーが FastCGI プロトコルで新しい接続を待ちます。

要求/応答サイクル

新しい HTTP リクエストがアプリケーションに送信されると、次のことが起こります。

  1. API Gateway は HTTP リクエストを受信し、AWS Lambda を呼び出します。
  2. Lambda 関数環境は、Bref ベースのランタイムのブートストラップを実行します。
  3. bref は、HTTPリクエストを API Gateway 形式から FastCGI 形式に変換します。
  4. bref は、FastCGI プロトコルを介して PHP-FPM を呼び出します。
  5. PHP-FPM は PHP ハンドラーを実行し、その応答を返します。
  6. bref は、FastCGI 応答を API Gateway 形式に変換します。
  7. bref は API Gateway に応答を返し、API Gateway は HTTP 応答をクライアントに返します。

複数のプロセスが関連しますが、これは即座に起動します。

bref ランタイムは、Apache または NGINX(FastCGIプロトコルを介してHTTPリクエストを転送)と同様のジョブを実行します。PHP-FPM は何十年にもわたって最適化されています。リクエスト間で、PHP-FPM は新しい PHP プロセスを強制終了したり作成したりしません。同じプロセスを維持し、メモリをリセットするという動作をしています(OPcache や APCu などのメモリ内キャッシュを保持します)。

Lambda 向けの PHP の構成

bref は AWS Lambda に向けて PHP-FPM の構成を最適化しています。

  • Lambda インスタンスは一度に1つの HTTP リクエストを処理するため、PHP-FPM は単一の「ワーカー」を実行します。
  • PHP-FPM の標準エラー出力は CloudWatch に転送されます。これにより、PHP からのロギングが stderr への書き込みと同じくらい簡単になります。
  • すべての PHP エラー、警告、および通知は、HTTP 応答の外部でログに記録され、デフォルトで CloudWatch に転送されます。
  • PHP コードベースが Lambda で読み取り専用としてマウントされているため、PHP の OPcache は、ディスクからの読み取りを回避するように最適化されています。

さらに、bref は、Apache/NGINX から API Gateway および Lambda への簡単な移行を支援するための動作を提供してくれています。

  • アップロードされたファイルは、PHP-FPMのアップロードされたファイルメカニズムでブリッジされます。
  • バイナリコンテンツを含む HTTP リクエストは、API Gateway の base64 形式から自動的にデコードされます。
  • バイナリHTTP 応答は、Bref によって自動的に base64 にエンコードすることもできます。
  • Cookieは、PHP-FPMのメカニズムで動作するように調整されています。

また、brefは、API Gateway リクエストの v1 と v2 の両方のペイロード形式をサポートしています。

Amazon CloudFront による静的コンテンツのルーティングとキャッシング

Lambda の価格モデル では、割り当てられたメモリの GB に応じて、リクエスト数と実行所要時間で課金されます。これは、動的な計算処理のリクエストに理想的です。

Amazon CloudFront は、静的コンテンツのリクエストをサーバーよりも効率的に処理します。これは、大規模でグローバルなコンテンツ配信ネットワーク(CDN)であり、コンテンツの安全でスケーラブルな配信を提供します。これは、世界中に分散された拠点のデータをキャッシュすることによって行われます。これにより、コンテンツのローカルコピーが配信されるため、アプリケーションオリジンの負荷が軽減され、要求者のエクスペリエンスが向上します。

CloudFront による Webディストリビューションは、複数のオリジンからさまざまなタイプのデータを提供できます。このテンプレートは、静的アセットのリクエストを直接 S3 バケットにルーティングするように CloudFront を構成しています。他のすべてのリクエストを直接 API Gateway にルーティングします。

Origins:
   -   Id: Website  
   DomainName: !Join ['.', [!Ref ServerlessHttpApi, 'execute-api', !Ref AWS::Region, 'amazonaws.com']]
   # This is the stage
   OriginPath: "/dev"
   CustomOriginConfig:
   	   OriginProtocolPolicy: 'https-only' # API Gateway only supports HTTPS
   # The assets (S3)
   -   Id: Assets
   DomainName: !GetAtt Assets.RegionalDomainName
   S3OriginConfig: {}

前に示したように、API Gateway ルーティングは HTTP API を使用して構成され、すべての受信リクエストを単一の Lambda 関数にダウンストリームでルーティングします。

オリジンアクセスアイデンティティ(OAI)を使用した Amazon S3 アセットへのアクセス制限

各リソースに最小限のアクセス許可を付与することがベストプラクティスです。これにより、セキュリティリスクと、エラーや悪意による影響を軽減できます。このベストプラクティスに従うと、S3 バケットにもセキュリティ制限が適用されるべきです。バケットはプライベートになり、内部のオブジェクトは CloudFront ディストリビューション経由でのみ利用可能になります。
これは、オリジンアクセスアイデンティティ(OAI)を使用して実現されます。OAI は CloudFormation テンプレート内で定義されます。

  S3OriginIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: Cloudfront OAI

次に、S3バケットのポリシー内でプリンシパルとして設定します。

AssetsBucketPolicy: 
    Type: AWS::S3::BucketPolicy
    Properties: 
      Bucket:
        Ref: Assets # References the bucket we defined above
      PolicyDocument: 
        Statement:
          Effect: Allow  
          Action: s3:GetObject # to read
          Principal: 
            CanonicalUser: 
              Fn::GetAtt: S3OriginIdentity.S3CanonicalUserId
          Resource: # things in the bucket 'arn:aws:s3:::<bucket-name>/*'
            Fn::Join: 
                - ""
                - 
                  - "arn:aws:s3:::"
                  - 
                    Ref: Assets
                  - "/*"

インフラストラクチャの展開

GitHubリポジトリには、このインフラストラクチャをデプロイする手順が記載された AWS SAM テンプレートが含まれています。ここには、Bref の php-73-fpm:25ランタイムレイヤーを使用する単一の Lambda 関数(index.php)があります。

Layers:
        - 'arn:aws:lambda:<<region>>:123456789012:layer:php-73-fpm:25'

bref にはランタイムの依存関係を保持する/vendors ディレクトリも含まれています。index.php 内のハンドラーは、HTML コンテンツを API Gateway のリクエストに返します。Lambda 関数ハンドラー内には、静的イメージと静的 CSS ファイルへの参照があります。

<link href="/assets/style.css" rel="stylesheet">
…
<img src="/assets/serverless-lamp-stack.png">

これらのファイルは、Webサイトの動的部分と同じ CloudFront ドメインの下で提供されるため、相対的に(絶対ではなく)参照されます。生成された CloudFront ドメインに移動すると、参照されている静的イメージとともに動的 Web ページが表示されます。この PHP Lambda 関数では、FastCGI Process Manager によって使用可能になったグローバル $_GET 変数を使用しています。

index.php を各自のカスタマイズ版として構築または置き換えることにより、機能豊富なサーバーレス Web アプリケーションをPHP でデプロイできます。bref-fpm カスタムランタイムを使用した一般的な PHP フレームワークでのビルドの詳細については、bref のドキュメントを参照してください。

まとめ

この投稿では、Apache や NGINX などの HTTP サーバーの代わりに Lambda および API Gateway を使用して PHP アプリケーションを構築する方法について説明しました。アプリケーションを静的リクエストと動的リクエストに分離する方法について説明しました。すべての動的 HTTP リクエストは、bref の FPM カスタムランタイムレイヤーを使用して単一の Lambda 関数にルーティングされます。FastCGI Process Manager のカスタムランタイムの実装により、従来のフレームワークで PHP アプリケーションを構築できます。

HTTP サーバーを置き換えることで、開発者は Webサーバーの保守、構成、同期、スケーリングの責任から解放されます。PHP開発チームは、ビルド方法を変更することなく、コードの配布に集中できます。

さあ、PHP を使用してサーバーレスアプリケーションの構築を開始しましょう。

原文はこちらです。