AWS Lambda 用の Java11 コードを docker-lambda 環境でローカル開発するメモ


Docker に慣れちゃうと、別にインストールしても問題ないものまでコンテナ上で動かしたくなりません?特に更新が激しいものはアップデートの導入が面倒なので、つい Docker image を探しがち。

というわけで、AWS Lambda で実行する Java 11 コードを、docker-lambda を用いてローカル環境で開発・テスト実行させてみました。Docker 以外はインストール不要なので、気軽に試せて良い感じです。

ググってみても python や nodejs の説明ばかりヒットする気がしたので、簡単なメモとしてまとめておきます。

docker-lambda とは

docker-lambda は AWS Lambda 開発環境を Docker ベースで提供するものです。以下、概要部分を簡単に訳しておきますね。

インストールされているソフトウェアとライブラリ、ファイル構造と権限、環境変数、コンテキストオブジェクトと動作を含む「ライブ AWS Lambda 環境」をほぼ同じように複製するサンドボックス化されたローカル環境。ユーザーと実行中のプロセスも同じです。

docker-lambda を使用することで、同じ厳密な Lambda 環境で関数を実行し、ライブでデプロイしたときに同じ動作を示せます。AWS Lambda に存在するライブラリバージョンと同様にネイティブ依存関係をコンパイルし、AWS CLI を使用してデプロイすることもできます。

サンプルコードの導入

テスト用のフォルダ(ディレクトリ)を作成し、docker-lambda/examples/java/ にあるファイルを配置します。該当リポジトリ を丸ごと clone もしくは zip 形式でダウンロードして、該当部分だけをコピーすると楽でしょう。

サンプルコードのビルド

ダウンロードしたサンプルは Java のソースコード です。テスト用のフォルダに移動し、Docker コマンドでビルド(コードのコンパイル)を実施します。

docker run --rm -v $PWD:/var/task lambci/lambda:build-java11 gradle build

なお私は Windows 環境なので $PWD が使えないため、実際のフォルダ (今回は c:/work/java/docker-lambda) を指定しました。

docker run --rm -v c:/work/java/docker-lambda:/var/task lambci/lambda:build-java11 gradle build

問題無く実行されれば build フォルダが作成され、その中に幾つかのフォルダが生成されるはずです。この中の docker フォルダに、コンパイルされたクラスのバイトコードと、実行に必要なライブラリが格納されています。

サンプルコードの実行

作成された実行クラスは、build/docker/ フォルダ内にあります。以下のコマンドで実行してみます。

docker run --rm -v $PWD/build/docker:/var/task \
  lambci/lambda:java11 org.lambci.lambda.ExampleHandler \
  '{"some": "event"}'

Windows 環境では改行を使わず、JSON の指定方法も修正して、実際には以下のように実施しています。

docker run --rm -v c:/work/java/docker-lambda/build/docker:/var/task lambci/lambda:java11 org.lambci.lambda.ExampleHandler "{\"some\": \"event\"}"

AWS SDK API を利用してみる

サンプルの実行だけで終わるのも寂しいですので、S3 のバケット一覧を取得してみます。

サンプルコードの修正

AWSのガイド を参考に、まずは import 文を追加します。

src/main/java/org/lambci/lambda/ExampleHandler.java
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.Bucket;
import java.util.List;

そして handleRequest 関数の終わり (Return文の前) に以下のコードを追加します。

src/main/java/org/lambci/lambda/ExampleHandler.java
        logger.log("Your Amazon S3 buckets are:\n");
        final AmazonS3 s3 = AmazonS3ClientBuilder.standard().withRegion(Regions.DEFAULT_REGION).build();
        List<Bucket> buckets = s3.listBuckets();
        for (Bucket b : buckets) {
            logger.log("* " + b.getName() + "\n");
        }

この段階でビルドを実施すると、S3 関連の SDK ライブラリが見つからずエラーになります。テスト用フォルダにある build.gradle にビルド用の設定(2行)を追加します。

build.gradle
dependencies {
    implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.99') // この行を追加
    implementation (
        'com.amazonaws:aws-lambda-java-core:1.2.0',
        'com.amazonaws:aws-lambda-java-events:2.2.7', // カンマ追加
        'com.amazonaws:aws-java-sdk-s3' // この行を追加
    )
}

これで先ほどと同じコマンドで、ビルドできるようになりました。

修正したサンプルコードの実行

さきほどと同様にコードを実行しようとすると、権限エラーになります。以下のように -e オプションで AWS_ACCESS_KEYAWS_SECRET_ACCESS_KEY の値を渡してあげましょう。

docker run --rm -v $PWD/build/docker:/var/task \
  -e AWS_ACCESS_KEY_ID=XXX -e AWS_SECRET_ACCESS_KEY=XXXXXX \
  lambci/lambda:java11 org.lambci.lambda.ExampleHandler \
  '{"some": "event"}'

Windows の場合は1行にまとめて、JSON 表記など修正して以下のように実行します。

docker run --rm -v c:/work/java/docker-lambda/build/docker:/var/task -e AWS_ACCESS_KEY_ID=XXX -e AWS_SECRET_ACCESS_KEY=XXXXXX lambci/lambda:java11 org.lambci.lambda.ExampleHandler "{\"some\": \"event\"}"

こんな感じで s3 バケットのリストが得られれば成功です。

AWS_ACCESS_KEYAWS_SECRET_ACCESS_KEY は、AWS でユーザーを作成して、その「認証情報」タブから作成・取得ができます。なんの権限もない新規ユーザーを作成し、開発対象である Lambda 関数と同じアクセス権限(ロールやポリシー)を与えて利用するのが良さそうです。

stay-open モードで開発する

4つのオプションを追加して、更に便利に開発していきましょう。

  • 環境変数 DOCKER_LAMBDA_STAY_OPEN=1 で API 待ち受けモードになる
  • -p 9001:9001 オプションで待ち受けするポートを指定する
  • 環境変数 DOCKER_LAMBDA_WATCH=1 でファイルの変更を監視し、サービスの再スタートを実施する
  • --restart on-failure Dockerオプションでエラー時に自動的に再起動する (Java 11では必須ではない)

以下のようなコマンド実行になります。

docker run -v $PWD/build/docker:/var/task \
  --restart on-failure \
  -e DOCKER_LAMBDA_WATCH=1 -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 \
  -e AWS_ACCESS_KEY_ID=XXX -e AWS_SECRET_ACCESS_KEY=XXXXXX \
  lambci/lambda:java11 org.lambci.lambda.ExampleHandler \
  '{"some": "event"}'

Windows の場合は以下のように実行します。

docker run -v c:/work/java/docker-lambda/build/docker:/var/task --restart on-failure -e DOCKER_LAMBDA_WATCH=1 -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 -e AWS_ACCESS_KEY_ID=XXX -e AWS_SECRET_ACCESS_KEY=XXXXXX lambci/lambda:java11 org.lambci.lambda.ExampleHandler "{\"some\": \"event\"}"

別なターミナル画面を開き、以下のように curl などでアクセスしてみましょう。

curl -d "{}" http://localhost:9001/2015-03-31/functions/myfunction/invocations

以下のように Return 文の内容 "Hello World!" が返ってくれば動作は成功です。ログは Docker を実行したほうのターミナルのほうに表示されています。

試しにソースコードを修正してビルドを実行すると、/build/docker フォルダの変更を感知して、サービスが自動的に再始動します。

おわりに

Docker 環境されあれば docker-lambda を用いてローカルで AWS Lambda 関数の開発を実施できます。好みのエディタでサクサク開発でき、テストも容易です。いろんなコードを書いて、試してみましょう!

それではまた。