Amazon Web Services ブログ

AWS LambdaサポートのコンテナイメージとAWS CDKを利用したマイクロサービス開発

AWS Cloud Development Kit (AWS CDK)はオープンソースソフトウェア開発フレームワークで、使い慣れたプログラミング言語でクラウドアプリケーションリソースを定義する事が可能です。AWS CDKはローカル環境でのコンテナイメージのビルドが可能で、コンテナイメージの Amazon Elastic Container Registry (Amazon ECR)へのデプロイや、コンテナイメージをLambda関数として実行する設定も可能です。AWS CDKは少しの学習だけでAWSへのオンボーディングを加速できます。AWS CDKは既存のスキルやツールの利用が可能であり、クラウドインフラストラクチャの構築タスクに活用できます。
AWS LambdaはLambda関数を最大10GBのコンテナイメージとしてパッケージ化し、デプロイ可能です。このことは、コンテナツールの柔軟性や親和性と、Lambdaの持つアジリティと操作のシンプルさを組み合わせる事を意味します。よって、カスタマーはコンテナツールの柔軟性や親和性を活用でき、AWS Lambdaの持つアジリティと操作のシンプルさ利用してアプリケーションのデプロイが可能です。多くのカスタマーは、コンテナツール、開発ワークフローや専門知識の習得に投資してきました。AWS CDKなしでは、コンテナツールやパッケージ化を利用するカスタマーは、AWS Lambdaの最大限の恩恵を享受できません。また、好みのコミュニティやプライベート、エンタープライズ向けのコンテナイメージを活用できません。
このブログでは、一つのコンテナイメージとしてパッケージ化した複数のLambda関数を実行する、サーバーレスなHTTP APIをデプロイする方法を紹介します。この関数は AWS SDK for JavaScriptを利用して Amazon DynamoDBのテーブルからデータを取得します。インフラストラクチャは、AWS CDK for TypeScriptで作成、管理しています。

前提条件

このソリューションのデプロイに以下が必要です:

ソリューション

ローカルの開発マシンを利用して環境をセットアップするか、AWS Cloud9利用できます。 このブログでは、AWS Cloud9を利用した例を紹介します。AWS Cloud9環境を作成するために、GitHubの手順に従います。

AWS Cloud9で新しいターミナルを開いて、jq のインストールを実行してください:

sudo yum install jq -y

サンプルコードの入ったGitHubリポジトリをクローンします:

git clone https://github.com/aws-samples/aws-cdk-lambda-container.git

サンプルデータセットの作成

このソリューションは、データセットとしてサンプルのmoviesテーブルを利用します。手順については、AWSドキュメントのCreate a DynamoDB Table with the AWS SDK for JavaScriptを参照してください。

テーブルをセットアップするために、コードリポジトリのDynamoDBディレクトリに移動します。create-MoviesTable.sh と load-MovieTable.sh のスクリプトを実行します。

cd ~/environment/aws-cdk-lambda-container/DynamoDB 
./create-MoviesTable.sh 
./load-MoviesTable.sh

screenshot of command-line showing script running

Lambda関数の作成

2つのLambda関数、list.js と get.jsを作成します。このコードはGitHubリポジトリに含まれています。

list.js 関数: list関数はmoviesテーブルから全てのmovieデータを取得します。コードはGitHubを参照してください。

get.js 関数: get関数は2つのパラメータ、yearとtitleを条件にmoviesテーブルから項目を取得します。これらのパラメータは、Amazon API Gateway経由のHTTP APIを通して後ほど関数に渡されます。get.js関数のコードはGitHubを参照してください。

Dockerfileの作成

Dockerfileはテキストドキュメントで、コンテナイメージをアセンブルするためにユーザーが実行するコマンドラインコマンドを含める事ができます。Dockerfileは現在のワークスペースの ~/environment/aws-cdk-lambda-container/src/movie-service/Dockerfileに置いてあります。

FROM public.ecr.aws/lambda/nodejs:12 
# Alternatively, you can pull the base image from Docker Hub: amazon/aws-lambda-nodejs:12 
# Copy the Lambda functions 
COPY list.js get.js package.json package-lock.json ${LAMBDA_TASK_ROOT}/ 
# Install NPM dependencies for functions 
RUN npm install

このDockerfileは、 Node.js12のLambda用に公開されているAWSベースイメージ、public.ecr.aws/lambda/nodejs:12を指定しています。list.js, get.js, package.jsonpackage-lock.json ファイルを${LAMBDA_TASK_ROOT} ディレクトリにコピーしています。その後、Lambda関数の依存関係をインストールするため、npm install を実行しています。 ${LAMBDA_TASK_ROOT} は、AWSドキュメントのUsing AWS Lambda environment variablesに記載されているLambda関数のパスを表しています。

コンテナイメージのビルド

Dockerfileと2つのLambda関数を利用して、コンテナイメージをビルドします。 コンテナイメージはLambda関数のハンドラー実行に必要な以下の全てが必要です: 関数コードと依存関係、ランタイムやベースイメージから継承されたオペレーティングシステム。

ターミナルで以下のコマンドを実行してください:

cd ~/environment/aws-cdk-lambda-container/src/movie-service docker 
build -t movie-service . 
docker images | grep movie-service

以下がアウトプットです:

screenshot of output from running cd ~/environment/aws-cdk-lambda-container/src/movie-service docker build -t movie-service . docker images | grep movie-service

ローカルでのLambda関数のテスト

パッケージングしたLambda関数をローカルでテストするため、AWS Lambda Runtime Interface Emulator (RIE)を利用します。Lambda Runtime Interface Emulatorは軽量なウェブサーバで、HTTPリクエストをJSONイベントに変換し、コンテナイメージのLambda関数に渡してくれます。

コンテナイメージでは、以下の環境変数を設定する必要があります:

  • AWS SDK認証用のAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKENと AWS_REGION 。AWS CLIを利用して現在の環境のクレデンシャルを取得し、aws configure getを利用してローカルのコンテナにこのクレデンシャル情報を渡します。
  • DYNAMODB_TABLE を使用して Moviesテーブルの情報をLambda関数に渡します。

1. list.js 関数を実行します。このコマンドはイメージをmovie-serviceコンテナとして起動し、ローカルでlist.js 関数用のエンドポイント、 http://localhost:9080/2015-03-31/functions/function/invocationsを起動します:

docker run \ 
 --env DYNAMODB_TABLE=Movies \ 
 --env AWS_ACCESS_KEY_ID="$(aws configure get default.aws_access_key_id)" \ 
 --env AWS_SECRET_ACCESS_KEY="$(aws configure get default.aws_secret_access_key)" \ 
 --env AWS_SESSION_TOKEN="$(aws configure get default.aws_session_token)" \ 
 --env AWS_REGION="$(aws configure get default.region)" \ 
 -p 9080:8080 \ 
 movie-service list.list

2. 新しいターミナルを開いて後続のコマンドを実行します:
3. list.js 関数を呼び出します:

curl -s "http://localhost:9080/2015-03-31/functions/function/invocations" -d '{}' | jq

4. get.js 関数を実行します。以下のコマンドはmovie-serviceイメージをコンテナとして起動し、ローカルでget.js 関数用のエンドポイント、 http://localhost:9080/2015-03-31/functions/function/invocationsを起動します:

docker rm -f $(docker ps -q)
docker run \
	--env DYNAMODB_TABLE=Movies \
	--env AWS_ACCESS_KEY_ID="$(aws configure get default.aws_access_key_id)" \
	--env AWS_SECRET_ACCESS_KEY="$(aws configure get default.aws_secret_access_key)" \
	--env AWS_SESSION_TOKEN="$(aws configure get default.aws_session_token)" \
	--env AWS_REGION="$(aws configure get default.region)" \
	-p 9080:8080 \
	movie-service get.get

5. get.js 関数をテストします。このコマンドは2つの環境変数をyear=”2013” と title=”Rush”として指定して get.js 関数を呼び出し、API Gatewayのリクエストをシミューレートしています:

curl -s "http://localhost:9080/2015-03-31/functions/function/invocations" -d '{"pathParameters": {"year": "2013", "title": "Rush"} }' | jq

output screenshot

アプリケーションのデプロイ

新しいターミナルを開いて、以下を実行します:

cd ~/environment/aws-cdk-lambda-container/cdk
npm install

このコマンドにより、最新のAWS CDKモジュールをnode_modules ディレクトリにインストールします。

AWS CDKでのAWSリソースの作成

AWS CDKはTypeScriptで記述された1つのCDKスタックからアーキテクチャをデプロイします。cdk/lib ディレクトリにある、http-api-aws-lambda-container-stack.tsファイルを開き、次に示すCDKコンストラクトを見ていきましょう。

DynamoDBテーブル

AWS CDKを利用して(ドキュメントに記載されているように)既存のDynamoDBテーブルをインポートします:

const table = dynamodb.Table.fromTableName(this, 'MoviesTable', 'Movies');

Lambda関数

AWS CDKのDockerImageFunctionクラスを利用してLambda関数を作成します。コードはDockerImageCodeクラスのメソッドのstatic fromImageAsset(directory, props?)を利用しています。これは、src/movie-serviceディレクトリのDockerfileで使用します。

listMovieFunction:

const listMovieFunction = new lambda.DockerImageFunction(this, 'listMovieFunction', {
    functionName: 'listMovieFunction',
    code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../src/movie-service'), {
    cmd: [ "list.list" ],
    entrypoint: ["/lambda-entrypoint.sh"],
    }),
    environment: {
            DYNAMODB_TABLE: this.table.tableName
    },
});

getMovieFunction:

const getMovieFunction = new lambda.DockerImageFunction(this, 'getMovieFunction',{
    functionName: 'getMovieFunction',
    code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../src/movie-service'), {
    cmd: [ "get.get" ],
    entrypoint: ["/lambda-entrypoint.sh"],
    }),
    environment: {
            DYNAMODB_TABLE: this.table.tableName
    },
});

Lambdaプロキシ統合

Amazon API Gateway Lambdaプロキシ統合により、クライアントがLambda関数を呼び出す事が可能です。クライアントがAPIリクエストを送信すると、API Gatewayはrawリクエストを統合されたLambda関数に渡します。

LambdaProxyIntegration クラスを利用して2つのLambda関数用の2つのLambdaプロキシ統合を作成します。このクラスは引数として、LambdaProxyIntegrationPropsを受け取ります。

listMovieFunctionIntegration:

const listMovieFunctionIntegration = new apigintegration.LambdaProxyIntegration({
        handler: listMovieFunction,
});

getMovieFunctionIntegration:

const getMovieFunctionIntegration =  new apigintegration.LambdaProxyIntegration({
  	handler: getMovieFunction,
});

HTTP API

Amazon API GatewayのHTTP APIにより開発者はREST APIよりも低遅延で低コストなRESTful APIを作成できます(HTTP APIの詳細は、Building faster, lower cost, better APIs – HTTP APIs now generally availableを参照してください)。HTTP APIを利用してLambda関数にリクエストを送る事が可能です。

バックエンドの2つLambda関数と統合するHTTP APIを作成します。クライアントはこのAPIを呼び出す際に、API GatewayがリクエストをLambda関数に送信し、関数のレスポンスをクライアントに返します。以下がdefault stageのHTTP API のコードです。

const httpApi = new apig.HttpApi(this, "httpApi", { 
	apiName: "httpApi", 
	createDefaultStage: true, 
});

HTTP API ルート

HTTP APIのルートは2つの要素、HTTPメソッドとリソースパスで構成されています。ルートは、例えばAWS Lambda関数のようなバックエンドに直接APIリクエストをルーティングします。

listMovieFunction Lambda関数と統合したGET /list ルートと、getMovieFunction Lambda関数と統合したGET /{year}/{title} ルートを作成します。詳細は、HttpRouteクラスのドキュメントを参照してください。

httpApi.addRoutes({
  integration: listMovieFunctionIntegration, 
  methods: [apig.HttpMethod.GET], 
  path: '/list',
});
httpApi.addRoutes({
  integration: getMovieFunctionIntegration,
  methods: [apig.HttpMethod.GET],
  path: '/get/{year}/{title}',
});

AWS CDKでのAWSリソースのプロビジョニング

1. TypeScriptのコンパイル:

cd ~/environment/aws-cdk-lambda-container/cdk
npm run build

2. 特定のリージョン (今回の例では、us-east-1 )で最初のCDKのインフラストラクチャを作成するために、cdk bootstrapコマンドを実行します:

export AWS_REGION=us-east-1
cdk bootstrap

AWS CDKはリージョン内で、全てのプロジェクトのCDKデプロイ管理用の環境(S3バケット)を利用します。 bootstrapコマンドはCDKスタックを作成するリージョンで、1回だけ実行する必要があります。

3. 以下のコマンドでスタックをデプロイします:

cdk deploy

(Do you wish to deploy all these changes (y/n)?にはyと応答してください)

注意: エラーが出た場合、package.json をチェックし、全てのCDKライブラリが同じバージョン番号か確認してください(先頭のキャレット ^以外)。多くのCDKプロジェクトのエラーは、バージョンの不一致が原因です。必要に応じて、バージョン番号を修正し、package-lock.json ファイルとnode_modules ツリーを削除して、npm installを実行してください。
これらのコマンドの構文や詳細はドキュメントに記載してあります。

output with question "Do you wish to deploy all these changes (y/n)"

HTTP APIのテスト

Outputs セクションのlistLambda関数のHTTP APIエンドポイントをメモしておいてください:

HttpApiAwsLambdaContainerStack.HttpApiendpointlistMovieFunction.

APIエンドポイントのテスト:

curl -s https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/list | jq
curl -s https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/get/2013/Rush | jq

The output shows the integration of the HTTP API with the Lambda function in the API Gateway console.

API GatewayコンソールでLambda関数と統合されたHTTP APIを確認できます。

The API Gateway console shows the HTTP API integration with the Lambda function.

Cleanup

AWS CDKで作成したリソースをクリーンアップするため、以下を実行します:

cd ~/environment/aws-cdk-lambda-container/cdk
cdk destroy

(Are you sure you want to delete (y/n)?には、y と応答してください)

DynamoDB のMovies テーブルを削除するため、以下を実行してください:

aws dynamodb --region us-east-1 delete-table --table-name Movies

まとめ

このブログでは、AWS CDKとAWS Lambdaを利用したサーバーレスアプリケーションのデプロイ方法を紹介しました。Infrastructure as Code (IaC) を利用したLambda関数の開発とデプロイにより、AWSマネージメントコンソールやAWS CLIでのマニュアルステップを削減する事ができます。

AWS CDKを更に学びたい場合は、CDKWorkshop siteを参照ください。

 

翻訳はPublic Sector Prototyping Solutions Architect 小泉 秀徳が担当しました。原文はこちらです。