月曜日から3日間、AWSアーキテクト研修でした。そこではじめてLambdaに接しまして、ひととおり驚いてきたところです。自分なりのまとめです(ご存知の方には釈迦に説法)。
Lambdaとは?
恥ずかしながら私は「サーバーレス」という言葉を聞いてもいまいちピンと来ていませんでした。ですが、これは文字通りなんですよね。プログラムが動作する環境なんてどうだっていいんです。OSがなんだとか、ミドルウェアがなんだとか、メモリがどれくらいでCPUがいくらで、NWがどうで、トポロジーがなんでとか、そんなことはどうでもいいんです(実際はメモリサイズを指定できて、それに応じたCPU能力が割当たりますが)。
とにかく、「トリガー」と呼ばれる"きっかけ"を契機に、コード(プログラム)が動くんです。Java
ならJVMがおもむろに立ち上がって、アップロードしておいたjar
が実行されるんです。「トリガー」はAWSサービスと高度に統合されていて、例えば
- ファイルストレージサービスであるところのS3(もはや単なるストレージの域を超越していますが)にファイルがアップロードされた
- メッセージがキューにputされた
- API Gatewayにリクエストがきた
- EC2インスタンスが起動した
- 3時になった
- おなかがすいた(とAmazon echoに話した)
- e.t.c.
MDBならぬTDB(Trigger Driven Bean)でしょうか。Beanである必要もないので、TDC(Trigger Driven Code)とでも言ったほうがいいのかもしれません。
うごかしてみる
研修の間、実習時間に余裕があったので、研修端末に入っていたEclipseで簡単なコードを書いて動かしてみました。テストはAWS Consoleからキックできるので、特に「トリガー」を定義しなくても動かすだけなら簡単に試せます。
お作法
基本的にどんなJava
プログラムでも必要なライブラリを組み込んでおけば動きますが、コールするメソッドにはお約束があるようです。それは引数です。第一引数にObject
をもらい、第二引数にContext
をもらいます。メソッド名はなんでもいいです。型もなんでもいいです(ただし第一引数と戻り値の型ともにSeriarizable
である必要あり。プリミティブ型もOK)。
第一引数に入るのは、具体的には「トリガー」からの情報です。メッセージがキューにput
されたことをトリガーとするのであれば、そのメッセージ自体を渡してあげたり。戻り値は同期呼び出しであればほぼそのまんまでしょう。インタフェース要件に従って、Serialize
して返してあげればよいだけです。
第二引数のContext
ですが、これはjavax.naming.Context
ではなく、com.amazonaws.services.lambda.runtime.Context
です。というわけで、AWSが提供するjarファイルをビルドパスに追加する必要があります。1
作る
まだ意味のあるコードを書くほどの技量もアイディアもないので、インフラ屋っぽくどんな環境(システムプロパティ、環境変数、渡されたContext
オブジェクト)で動いているのかみてみることにしました。
package net.mognet.aws.lambda;
import java.util.Map;
import java.util.Properties;
import com.amazonaws.services.lambda.runtime.Context;
public class SystemInfo {
public static String printSystemInfo(int i, Context context) {
StringBuilder sb = new StringBuilder();
//ヘッダを追加
sb.append("name,value\n");
//システムプロパティ取得
Properties prop = System.getProperties();
for(Object key : prop.keySet()) {
String name = (String) key;
String value = prop.getProperty(name);
sb.append(name + "," + value + "\n");
}
//環境変数取得
Map<String, String> env = System.getenv();
for(String key : env.keySet()) {
String value = env.get(key);
sb.append(key + "," + value + "\n");
}
//Contextの情報を取得
sb.append("context" + "," + context.toString());
return sb.toString();
}
public static void main(String[] args) {
System.out.println(printSystemInfo(1, null));
}
}
main
はテスト用です。1個目の引数こそ本来は大事なんでしょうけど今回は何もしません。
乗せる
AWSコンソールを開いてLambdaの関数を作ります(関数という単位で動きます。複数の関数をオーケストレーションするサービスもあるようです(詳細未調査))。
適当に名前とランタイム(今回はJava8)を選んで「関数の作成」を押します。
標準出力はCloudWatchLogsへ流れるので、事前にCloudWatchLogsへのWrite権限のあるロールを作って必要に応じてここでアタッチしてください。
本来ならここでトリガーを選んで云々となりますが、とにかくテストしてみたいだけなので、その辺の条件だけいれます。
関数コードのところで、「アップロード」からjar
ファイルをアップロード、大事なのが「ハンドラ」でここに実行するメソッドを入力します。書き方が決まっていて、"."表記でクラスのフルパスの後ろに"::
"をつけてメソッド名です。
今回は"net.mognet.aws.lambda.SystemInfo::printSystemInfo
"となります。ついでに環境変数もつけてみました。一旦「保存」すると実際にファイルがアップロードされます。
次にテストの準備です。テストケース(入力設定=第一引数設定)です。画面上部の「テストイベントの設定」を選びます。
実行するメソッドpublic static String printSystemInfo
の第一引数がint
なので、1とだけ書いて終わりです。下の方にある「保存」を押します。これでテスト準備完了です。
いざ実行!
おもむろに「テスト」を押します。
動きました。今回はログ出力(標準出力)なしなので、ログは見ませんが開始と終了のメッセージが出ていました。String
型のメソッドを実行したので、return
した文字列がそのまま画面上に表示されています(改行コードは改行してほしかったけど実行結果表示コンソールとしてはこれが正しいあり方ですね)。
付録
付録で実行結果を載せておきます。
name | value |
---|---|
java.runtime.name | OpenJDK Runtime Environment |
sun.boot.library.path | /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/amd64 |
java.vm.version | 25.141-b16 |
java.vm.vendor | Oracle Corporation |
java.vendor.url | http://java.oracle.com/ |
path.separator | : |
java.vm.name | OpenJDK 64-Bit Server VM |
file.encoding.pkg | sun.io |
user.country | US |
sun.java.launcher | SUN_STANDARD |
sun.os.patch.level | unknown |
java.vm.specification.name | Java Virtual Machine Specification |
user.dir | / |
java.runtime.version | 1.8.0_141-b16 |
java.awt.graphicsenv | sun.awt.X11GraphicsEnvironment |
java.endorsed.dirs | /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/endorsed |
os.arch | amd64 |
java.io.tmpdir | /tmp |
line.separator | |
java.vm.specification.vendor | Oracle Corporation |
os.name | Linux |
sun.jnu.encoding | UTF-8 |
java.library.path | /lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib |
java.specification.name | Java Platform API Specification |
java.class.version | 52.0 |
sun.management.compiler | HotSpot 64-Bit Tiered Compilers |
os.version | 4.9.77-31.58.amzn1.x86_64 |
user.home | /home/sbx_user1066 |
user.timezone | UTC |
java.awt.printerjob | sun.print.PSPrinterJob |
file.encoding | UTF-8 |
java.specification.version | 1.8 |
java.class.path | /var/runtime/lib/LambdaJavaRTEntry-1.0.jar |
user.name | sbx_user1066 |
java.vm.specification.version | 1.8 |
sun.java.command | /var/runtime/lib/LambdaJavaRTEntry-1.0.jar |
java.home | /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre |
sun.arch.data.model | 64 |
user.language | en |
java.specification.vendor | Oracle Corporation |
awt.toolkit | sun.awt.X11.XToolkit |
java.vm.info | mixed mode, sharing |
java.version | 1.8.0_141 |
java.ext.dirs | /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/ext:/usr/java/packages/lib/ext |
sun.boot.class.path | /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/classes |
java.vendor | Oracle Corporation |
file.separator | / |
java.vendor.url.bug | http://bugreport.sun.com/bugreport/ |
sun.io.unicode.encoding | UnicodeLittle |
sun.cpu.endian | little |
sun.cpu.isalist | |
PATH | /usr/local/bin:/usr/bin/:/bin |
_AWS_XRAY_DAEMON_ADDRESS | 169.254.79.2 |
LAMBDA_TASK_ROOT | /var/task |
AWS_LAMBDA_FUNCTION_MEMORY_SIZE | 128 |
TZ | :UTC |
AWS_SECRET_ACCESS_KEY | secret |
AWS_EXECUTION_ENV | AWS_Lambda_java8 |
AWS_DEFAULT_REGION | ap-northeast-1 |
AWS_LAMBDA_LOG_GROUP_NAME | /aws/lambda/SystemInfo |
XFILESEARCHPATH | /usr/dt/app-defaults/%L/Dt |
_HANDLER | net.mognet.aws.lambda.SystemInfo::printSystemInfo |
LANG | en_US.UTF-8 |
LAMBDA_RUNTIME_DIR | /var/runtime |
AWS_SESSION_TOKEN | tokenString |
AWS_ACCESS_KEY_ID | accessKeyId |
LD_LIBRARY_PATH | /lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib |
_X_AMZN_TRACE_ID | Root=1-5a71b98c-393aaa7b51f5612a348586c0;Parent=3ff8164301e3ccd4;Sampled=0 |
AWS_SECRET_KEY | secretKey |
hogehoge | gehogeho |
AWS_REGION | ap-northeast-1 |
AWS_LAMBDA_LOG_STREAM_NAME | 2018/01/31/[$LATEST]29640ec0ac8e426ab2b0a041b3a1b1f4 |
AWS_XRAY_DAEMON_ADDRESS | 169.254.79.2:2000 |
_AWS_XRAY_DAEMON_PORT | 2000 |
NLSPATH | /usr/dt/lib/nls/msg/%L/%N.cat |
AWS_XRAY_CONTEXT_MISSING | LOG_ERROR |
AWS_LAMBDA_FUNCTION_VERSION | $LATEST |
AWS_ACCESS_KEY | accessKey |
AWS_LAMBDA_FUNCTION_NAME | SystemInfo |
context | lambdainternal.api.LambdaContext@604ed9f0 |
アクセスキー等の情報も環境変数に乗っていましたのでそこはマスクしてます。そういう仕様だということは理解しておくべきかもしれません。この辺のキーを使ってAWS API呼び出したりするのかな?あと、ちゃんと設定した環境変数も出て来てます(あたりまえですが)。
OpenJDK on Amazon Linuxで動かしているみたいですね。こればっかりは実際に本稼働したときにどうなるかわかりませんけれども。あくまでこのテスト実行時はこうでした、というだけです。なんといってもサーバーレスですので、繰り返しになりますが実行環境(HW、OS、MW等々)はどうでもいいです。というか、どうでもいい前提でコードを書いてください、というのがLambda的な使い方と認識しました。
参考
Lambda 関数ハンドラー(Java) - AWS Lambda
-
EclipseにはAWSのツールキットプラグインがあるので、この環境をセットアップしておくだけでも可です。 ↩