Amazon Web Services ブログ

新しいサーバーレス LAMP スタック – Part 1: 概要紹介

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

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


これは、PHP 開発者向けの投稿シリーズの第一弾です。このシリーズでは、PHP でサーバーレステクノロジーを使用する方法を説明します。サーバーレスアプリケーションを構築するために利用できるツール、フレームワーク、戦略や、なぜ今始めるべきかについて説明します。

今後の投稿では、LaravelSymfony などの PHP フレームワークとともに構築された Web アプリケーションに AWS Lambdaを使用する方法を示します。Lambda を Web ホスティング機能の代替として使用することから、分離されたイベント駆動型のアプローチに移行する方法を示します。最小限のスコープの複数の Lambda 関数を他のサーバーレスサービスと組み合わせて、パフォーマンスの高いスケーラブルなマイクロサービスを作成する方法について説明します。

まずは、カスタムランタイム API を使用して Lambda で PHP を使用する方法を学びます。サンプルコードについては、この GitHubリポジトリにアクセスしてください。

サーバーレスLAMPスタック

従来の PHP アプリケーションの課題

スケーラビリティは、従来の LAMP スタックの伝統的な課題です。スケーラブルなアプリケーションとは、非常に多様なレベルのトラフィックを処理できるアプリケーションです。PHP アプリケーションは、多くの場合、必要に応じて Web サーバーを追加することにより、水平方向にスケーリングされます。これは、リクエストをさまざまな Web サーバーに転送するロードバランサーを介して管理されます。サーバーを追加するたびに、ネットワーキング、管理、ストレージ容量、バックアップと復元のシステム、およびアセット管理インベントリの更新に関するオーバーヘッドが増加します。さらに、水平方向にスケーリングされた各サーバーは独立して実行されます。これにより、構成の同期が困難になる可能性があります。

従来の LAMP スタックアプリケーションによる水平スケーリング

各サーバーには独自のディスクとファイルシステムがあり、開発者がユーザーセッションを処理するメカニズムを追加する必要があることが多いため、新しいストレージの課題が発生します。
サーバーレステクノロジーを使用すれば、開発者のためにスケーラビリティが(自動で)管理されます。トラフィックが急増した場合、サービスが拡張され、追加のサーバーを展開する必要なく、需要に応えます。これにより、アプリケーションをプロトタイプから本番環境にすばやく移行できます。

サーバーレス LAMP アーキテクチャ

従来の Web アプリケーションは、次の2つのコンポーネントに分割できます。

  • 静的アセット(メディアファイル、CSS、JS)
  • 動的アプリケーション(PHP、MySQL)

これら2つのコンポーネントを提供するサーバーレスアプローチを以下に示します。

サーバーレス LAMP スタック

動的コンテンツ(/assets/* を除くすべて)に対するすべてのリクエストは、Amazon API Gateway に転送されます。これは、あらゆる規模の API を作成、公開、保護するためのフルマネージドサービスです。これは、PHP アプリケーションへの「フロントドア」として機能し、Lambda 関数にダウンストリームでリクエストをルーティングします。Lambda 関数では、ビジネスロジックと MySQL データベースへの操作処理を保持します。リクエストヘッダー、パス変数、クエリ文字列(クエリストリング)パラメーター、ボディ本文の任意の組み合わせとして入力データを Lambda 関数に渡すことができます。

PHP 開発者にとって注目すべき AWS 機能

Amazon Aurora Serverless
re:Invent 2017 に、従量課金制のコストモデルを備えたオンデマンドのサーバーレスリレーショナルデータベースである Aurora Serverless を発表しました。これは、開発者のために、リレーショナルデータベースのプロビジョニングとスケーリングの責任を担います。

Lambdaレイヤーカスタムランタイム API
re:Invent 2018 で、AWS は 2 つの新しい Lambda 機能を発表しました。これらにより、開発者はカスタムランタイムを構築でき、複数の関数間で共通のコード(ライブラリ)を共有管理できます。

Lambda 関数の VPC ネットワーク改善
2019年9月、AWS は VPC 内の Lambda 関数のコールドスタートの大幅な改善を発表しました 。これにより、関数の起動パフォーマンスが向上し、Elastic Network Interface がより効率的に使用され、VPC コールドスタートを大きく減少させることができます。

Amazon RDS Proxy
re:Invent 2019、Amazon RDS Proxyと呼ばれる新しいサービスの開始を発表しました。アプリケーションとリレーショナルデータベースの間にある完全に管理されたデータベースプロキシ機能です。データベース接続を効率的にプールおよび共有して、アプリケーションのスケーラビリティを向上させます。

サーバーレス LAMP スタックのタイムラインにおける重要な出来事

これらのサービスを組み合わせると、PHP とリレーショナルデータベースを使用して、安全・高性能でスケーラブルなサーバーレスアプリケーションを構築できます。

カスタムランタイム API

カスタムランタイム API は、任意のプログラミング言語または特定の言語バージョンで Lambda 関数を実行できるようにするシンプルなインターフェイスです。カスタムランタイム API には、ブートストラップと呼ばれる実行可能ファイルが必要です。ブートストラップファイルは、コードと Lambda 環境間の通信を担当します。
PHP のカスタムランタイムを作成するには、最初に、Lambda 実行環境と互換性のある Amazon Linux 環境で必要なバージョンの PHPをコンパイルする必要があります。これを行うには、これらのステップバイステップの指示に従います。

ブートストラップファイル

以下のファイルは、基本的な PHP ブートストラップファイルの例です。この例は説明のためのものであり、エラー処理や抽象化は省いてあります。本番環境のカスタムランタイムを構築するときには、例外を適切に処理するようにランタイムAPIドキュメントを参照して実装してください 。

#!/opt/bin/php
<?PHP

// This invokes Composer's autoloader so that we'll be able to use Guzzle and any other 3rd party libraries we need.
require __DIR__ . '/vendor/autoload.php;

// This is the request processing loop. Barring unrecoverable failure, this loop runs until the environment shuts down.
do {
    // Ask the runtime API for a request to handle.
    $request = getNextRequest();

    // Obtain the function name from the _HANDLER environment variable and ensure the function's code is available.
    $handlerFunction = array_slice(explode('.', $_ENV['_HANDLER']), -1)[0];
    require_once $_ENV['LAMBDA_TASK_ROOT'] . '/src/' . $handlerFunction . '.php;

    // Execute the desired function and obtain the response.
    $response = $handlerFunction($request['payload']);

    // Submit the response back to the runtime API.
    sendResponse($request['invocationId'], $response);
} while (true);

function getNextRequest()
{
    $client = new \GuzzleHttp\Client();
    $response = $client->get('http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/next');

    return [
      'invocationId' => $response->getHeader('Lambda-Runtime-Aws-Request-Id')[0],
      'payload' => json_decode((string) $response->getBody(), true)
    ];
}

function sendResponse($invocationId, $response)
{
    $client = new \GuzzleHttp\Client();
    $client->post(
    'http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/' . $invocationId . '/response',
       ['body' => $response]
    );
}

この #!/opt/bin/php 宣言は、Amazon Linux 用にコンパイルされた PHP バイナリを使用するようにプログラムローダーに指示します。
ブートストラップファイルは、操作ループで次のタスクを実行します。

  1. 次に処理すべきリクエストを取得します。
  2. コードを実行してリクエストを処理します。
  3. 応答を返します。

ここに記載の手順に従って、ブートストラップとコンパイルされた PHP バイナリを一緒に ‘runtime.zip’ にパッケージ化します。

ライブラリ、依存ファイル

このランタイムブートストラップでは、HTTP ベースのローカルインターフェイスを使用しています。これにより、各 Lambda 関数呼び出しのためのイベントペイロード(引数)を取得し、最終的に関数から応答を得て戻す、という処理をします。ここでは、人気のある PHP HTTPクライアントである Guzzle を使用して、カスタムランタイム API にリクエストを送信しています。Guzzle パッケージは、Composer パッケージマネージャーを使用してインストールされます。この方法でパッケージをインストールすると、アプリケーションの進化に応じて追加のライブラリと依存関係を組み込むメカニズムが作成されます。

ここに記載の手順に従って、ランタイム依存ファイルを作成して ‘vendors.zip’ バイナリにパッケージ化します。

Lambda レイヤーは、複数の関数で共有されるコードとデータを集中管理するメカニズムを提供します。Lambda 関数がレイヤーとともに構成されている場合、レイヤーのコンテンツは実行環境の /opt ディレクトリに配置されます。カスタムランタイムは、関数のデプロイパッケージ内に含めることもできますし、レイヤーとして配置することもできます。Lambda は、利用可能な場合、デプロイパッケージのブートストラップファイルを実行します。そのようなファイルがない場合、Lambda は関数のレイヤーの中でランタイムを探します。現在利用できるオープンソースの PHP ランタイムレイヤーがいくつかあります。

次の手順は、前述で作成した ‘runtime.zip’ と ‘vendor.zip’ バイナリを Lambda レイヤーとして発行し、それらを使用した PHP ランタイムによる Lambda 関数を構築する方法を示しています。

  1. AWSコマンドラインインターフェイス(CLI)を 使用して、前述のバイナリからレイヤーを公開します

    aws lambda publish-layer-version \
        --layer-name PHP-example-runtime \
        --zip-file fileb://runtime.zip \
        --region <<your region like eu-west-1>>

    aws lambda publish-layer-version \
        --layer-name PHP-example-vendor \
        --zip-file fileb://vendors.zip \
        --region <<your region like eu-west-1>>

  2. 各コマンドの実行後の出力値である LayerVersionArn ( arn:aws:lambda:<<region>>:XXXXXXXXXXXX:layer:PHP-example-runtime:1 の形式)をメモします。これは、後段の手順で必要になります。

PHP Lambda 関数の作成

Lambda 関数は、AWS CLI、AWSサーバーレスアプリケーションモデル(SAM)、または AWSマネジメントコンソールで直接作成できます。コンソールを使用してこれを行うには:

  1. AWSマネジメントコンソールの Lambdaセクションに移動し、[関数の作成] を選択します。
  2. [関数名] フィールドに PHPHello と入力し、[ランタイム] フィールドで [ユーザー独自のブートストラップを提供する] を選択します。次に、[関数の作成] を選択します。
  3. bootstrap.sample を右クリックし、[Delete] を選択します。
  4. レイヤーアイコンを選択し、[レイヤーの追加] を選択します。
  5. [レイヤーバージョン ARN を提供] を選択し、手順1 のカスタムランタイムレイヤーのARNを [レイヤーバージョンARN] フィールドにコピーして貼り付けます。
  6. ベンダーARNに対してステップ6と7を繰り返します。
  7. [関数コード] セクションで、src という新しいフォルダーを作成し、その中に index.php という新しいファイルを作成します。
  8. 次のコードを index.php に貼り付けます。
    //index function
    function index($data)
    {
     return "Hello, ". $data['name'];
    }
  9. [基本設定] セクションの [ハンドラ] 入力フィールドを [index] と設定します。これにより、この関数が呼び出されたときに index.php ファイルを実行するように Lambda に指示します。
  10. ページの右上にある [保存] を選択します。
  11. ページの右上にある [テスト] を選択し、[イベント名] フィールドに [phpTest] と入力します。イベントペイロードフィールドに次のように設定し、[作成] を選択します:   { "name": "world" }
  12. 作成されたテストを選択して [テスト] を実行すると、実行結果 が表示されます。

    引数であるイベントペイロードの name 値(world)が戻り値である「Hello, world」で使用されていることがわかります。これは Lambda 関数から提供される  $data['name']  パラメーターから取得されています。ログ出力は、実行の所要期間、請求期間、およびコードの実行に使用されたメモリ量に関する詳細を提供します。

まとめ

この投稿では、Lambda レイヤーとカスタムランタイム API を使用して、PHP ランタイムで Lambda 関数を作成する方法について説明しました。アプリケーショントラフィックに応じてスケーリングするサーバーレス LAMP スタックのアーキテクチャを紹介しました。

Lambda では、ランタイムが混在する関数が相互に対話することができます。これで、PHP 開発者は他のサーバーレス開発チームに参加でき、コードのリリースに焦点を当てることができます。サーバーレステクノロジーを使用すると、Web ホストの再起動、スケーリング、ホスティングについて考える必要がなくなります。

さあ、Lambda 用の独自のカスタムランタイムの構築を開始しましょう。

原文はこちらです。