🔌

AWS Lambda Web AdapterでConnect RPCサービスをAWS Lambdaにもデプロイする

に公開

はじめに

Connect RPCは、gRPC互換かつHTTP/1.1でも動作するRPCプロトコルです。バクラク事業部では、Connect RPCをサービス間通信の標準プロトコルとして採用し、connect-goconnect-esを利用してサービスを実装しています。

https://tech.layerx.co.jp/entry/decoupling-a-service-from-monolith-with-Protocol-buffers-and-connect-go

Connect RPCを実装したサービスは、AWS Fargate (ECS) にデプロイしています。しかし、サービスのワークロードによっては、弾力性やコスト最適化の観点からAWS Lambda (以降Lambda) にデプロイしたいモチベーションがありました。Lambdaにデプロイする場合、通常はLambdaが期待するハンドラを用意する必要があります。もし、アプリケーションコードを変更することなく、Connect RPCサービスとしてそのままLambdaにデプロイできると便利です。

AWS Lambda Web Adapter (以降LWA) はこの願いを叶えてくれます。LWAはLambda extensionsとして動作し、LambdaのイベントをHTTP/1.1に変換してアプリケーションとやりとりしてくれます。Dockerイメージにしておくことで、ECSとLambdaの両方に(ほぼ)同じイメージを使ってデプロイできます。この記事では、Lambda extensionsとLWAの説明をしつつ、サービス定義によるデプロイメント先の切り替えを紹介します。

Lambdaに関する前提知識

ここでは、LWAの動作を理解するために前提となる、Lambdaの知識を説明します。

Lambda external extensions

Lambda extensionsにはInternal extensionsとExternal extensionsの2種類がありますが、LWAはExternal extensionsとして動作するため、以降ではExternal extensionsのみを扱います。

External extensionは /opt/extensions 以下に配置された実行ファイルとして認識されます。また、Extensionが余分に消費した実行時間は課金対象となります[1]

https://docs.aws.amazon.com/lambda/latest/dg/lambda-extensions.html

Lambdaの実行環境とライフサイクル

Lambdaの実行環境にはRuntimeプロセスとExtensionプロセスの2系統があります。

  • Runtimeプロセス: Lambdaが起動する言語ランタイムとハンドラ (アプリケーションコード) を含む、本体となるプロセス
  • Extensionプロセス: Runtimeとは独立して起動・終了する補助プロセス

また、基本的に3つのフェーズからなるライフサイクルを持ちます[2]。ざっくりとした説明は次の通りです。

  1. Init: コールドスタート時。各プロセスを初期化
  2. Invoke: 各リクエスト処理。ウォーム状態ではこのフェーズのみループ
  3. Shutdown: 実行環境破棄前の後処理。スケールインや関数の更新などで発生

https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html

Lambda Runtime Interface

LambdaにデプロイしたRuntimeプロセスは、Runtime APIを通してLambdaサービスとやりとりをすることでLambda関数として動作します。Runtime APIを実装したソフトウェアはRuntime Interface Client (以降RIC) と呼ばれます。AWS-managed Runtime Dockerイメージでは、ENTRYPOINT (/lambda-entrypoint.sh) がRICを起動する
ため、ハンドラを指定するだけでLambda関数として動作します。各言語のRICはOSSとして公開されており、例えばNode.jsは https://github.com/aws/aws-lambda-nodejs-runtime-interface-client です。独自のRICを実装して使用することも可能です。

Lambda Web Adapterの動作

LWAの実体は、Rustで実装されたバイナリです。これはExtensionでありながら、Runtime APIを実装したRICの役割を果たします。なお、LWAはZip/Layerの場合とDockerイメージの場合で挙動が異なります。本記事ではDockerイメージを前提にしているため、ここではDockerイメージの場合のみ説明します。ざっくり挙動を表したものが次の図です。

Extensions APIではInvokeイベントを購読せず[3]、代わりにRuntime APIでイベントを受け取ることがポイントです。これにより、アプリケーションコードを変更することなく、Extensionを配置するだけで、Lambda関数がHTTP/1.1を喋れるようになります。

https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html

サービス定義によるデプロイメント先の切り替え

ここでは、実際にLWAを使ってLambdaにデプロイする様子を紹介します。社内の運用をベースにしていますが、運用のイメージを多少でも伝えられればと思いながら書いています。

バクラク事業部では、各サービスを独自のJsonnetスキーマにより管理しつつ、その設定からアプリケーションとインフラのコードを自動生成をするようにしています。例えば、connect-goのサービスは次のイメージです。

// カードサービス
{
	name: 'CardService', 
	owners: ['card']
	lang: 'go',
	features: {
		connect: true,
	},
	deployment: {
		destination: 'ecs',
	},
}

この定義を入力とし、アプリケーションとインフラのコードを自動生成しています。具体的には以下が含まれます。

  • Go: connect-goをベースとしたサーバーのテンプレート
  • Dockerfile
  • GitHub Actions (YAML)
    • Dockerイメージをビルド・プッシュするワークフロー
    • ECSにデプロイするワークフロー
  • Terraform
    • Dockerイメージのプッシュ先となるECRリポジトリ
    • ECSサービスに必要なIAMロールやセキュリティグループ、ターゲットグループ、リスナールールなど
  • ecspressoの設定ファイル

次のように deployment.destination を変更すると、

@@ -6,6 +6,6 @@
                connect: true,
        },
        deployment: {
-               destination: 'ecs',
+               destination: 'lambda',
        },
 }

アプリケーションコード以外の生成物は次のように変化します。

  • Dockerfile: LWAを配置
    • COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
  • GitHub Actions (YAML): Lambdaにデプロイするワークフロー
  • Terraform: Lambda関数に必要なIAMロールやセキュリティグループ、ターゲットグループ、リスナールールなど
  • lambrollの設定ファイル

このように、サービス定義の変更だけでConnect RPCサービスをLambdaにデプロイでき、かつECSサービスと同じインターフェースを維持できます

まとめ

Lambda Web Adapterを使うことで、既存のHTTPサーバをアプリケーションコードの変更なしでLambdaで動かせます。特にConnect RPCをサービス間通信の基盤プロトコルとしている場合、HTTP/1.1互換であることのメリットが活きます。サービス定義と自動生成コードで紹介したように、デプロイ先を柔軟に選択できるプラットフォームの構築にも役立っています。

脚注
  1. 正確には、Init/ShutdownフェーズでExtensionが長く残った分が上乗せされます ↩︎

  2. Lambda SnapStartが有効な場合はRestoreフェーズが追加されます ↩︎

  3. events配列を空にして登録しているためSHUTDOWN以外のイベントは届きません https://github.com/awslabs/aws-lambda-web-adapter/blob/92b4993f4b7668b8cd3aa93faf0d0e34e6f0c0e9/src/lib.rs#L229 ↩︎

LayerX

Discussion