以下のQuarkusを利用したプロジェクトのソースコードを読んでいてなるほど、と思ったので調べたことをまとめておく。
[Javaのシステムプロパティ]
Javaにはシステムプロパティと呼ばれるJavaの動作環境に関する情報がある。
// 利用例 System.out.println(System.getProperty("java.version")); //1.8.0_272
System.setProperty("myKey", "hoge");
System.out.println(System.getProperty("myKey")); //hoge
デフォルトでJavaのバージョン情報を取得したりできるのに加えて、上記のように利用者が独自にプロパティを設定することもできる。
通常はソースコード上でプロパティを上書きして設定することは少なく、Javaアプリケーションを起動する際に、javaコマンドのオプションとして-Dkey=value
の形式で設定する。たとえばこんな風に。
[AWS Lambdaと環境変数]
AWS Lambdaでは、ランタイムとしてJava 8またはJava 11を選択することができる。しかし、AWS Lambdaでは利用者が変更できる設定値として用意されているのは環境変数であって、システムプロパティではない。
環境変数の場合はSystem.getenv("ENV_VAR_KEY")
のように取得することができるが、System.setenv()メソッドは用意されていないため、環境変数はJavaのソースコード上では変更することができない。一方、システムプロパティの場合は、ソースコード上で値を変更することができるので、設定を変更した場合のテストコードを書くのがそれほど難しくはない。
環境変数に依存するテストコードを書く際は、環境変数の取得部分を外に出して、テスト対象のメソッドに引数として環境変数を渡すか、staticメソッドであるSystem.getenv(String)
をモックするためにPowerMockなどのライブラリに頼る、といった方法が必要であった。
テストが書きづらいという問題はあるものの、AWS Lambda + Javaで開発環境と本番環境の動作を変更する、といった場合にはAWS Lambdaの環境変数を利用することになる。そう思っていたのだが……。
[環境変数JAVA_TOOL_OPTIONS]
QuarkusをServerless Frameworkと組み合わせてLambdaに乗せるのにいい感じの例がないかな、と思ってGitHubを漁っていたときにこちらのリポジトリを見つけた。
serverless.yml
の環境変数の設定箇所をみると次のように書かれていた。
environment: JAVA_TOOL_OPTIONS: -Dquarkus.profile=${opt:stage, self:provider.stage} -Dquarkus.lambda.handler=${self:custom.function.addHandler} -Ddynamo.table.name=${self:custom.dynamodb.tableName} -Dquarkus.dynamodb.aws.region=${self:provider.region}
-Dkey=value
の形式で設定しているからにはこれはやはりシステムプロパティなのだろう、と推測できたが、JAVA_TOOL_OPTIONS
という環境変数名には馴染みがなかった。このリポジトリで追加されている何か特有のライブラリでしか使えないのだろうか、と懸念しつつGoogle先生に尋ねると、Oracleのドキュメントがヒットした。
どうやら、本来はJavaエージェント*1の設定などのために利用する環境変数のようだ。
環境変数以外にも、システムプロパティを設定するのにも使える、ということらしい。
複数のシステムプロパティを設定できるようだが、この環境変数の上限は1024文字までらしいので、その点は注意が必要そうだ。AWS Lambdaの環境変数自体は関数1つにつき合計で4KBまで許容されているが、JVMの(正確にはAWS LambdaのJava 8ランタイムであるOpenJDKの)側で制約がある、ということらしい。
ただし、AWS LambdaのJava 11ランタイムはOpenJDKではなくAmazon Correttoを使用している。
こちらのソースコードを確認したところ、JAVA_TOOL_OPTIONS
環境変数をパースする際のバッファの指定がなくなっていたので、もしかしたらJava 11の場合は1024文字以上指定できるのかもしれない(未検証)*2。
[AWS Lambda (Java 8)の動作確認]
念のため、簡単なソースを書いて動作確認をしておく。作業ディレクトリで以下のコマンドを実行して、Serverless FrameworkのJavaプロジェクトを作成する。
sls create -t aws-java-maven -p java-envvar
つぎに、serverless.yml
に以下のように環境変数の設定を追加する。
service: java-envvar provider: name: aws runtime: java8 region: ap-northeast-1 memorySize: 256 package: artifact: target/hello-dev.jar functions: hello: handler: com.serverless.Handler # ここから環境変数の設定を追加 environment: STAGE: dev JAVA_TOOL_OPTIONS: -Dapp.name=exampleApp
そして、Lambda関数のコードにシステムプロパティ(この場合はapp.name
)を取得するコードを追加する。
@Override public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) { input.put("System::STAGE", System.getenv("STAGE")); // 環境変数を取得してMapに詰める input.put("System::appName", System.getProperty("app.name")); // システムプロパティを取得してMapに詰める Response responseBody = new Response("success", input); return ApiGatewayResponse.builder() .setStatusCode(200) .setObjectBody(responseBody) .build(); }
mvn install
コマンドでjarファイルをビルドしたあとは、sls deploy
コマンドでLambdaにjarファイルをデプロイする。sls invoke
コマンドでLambda関数を呼び出して動作確認をすると、下のようにserverless.ymlで環境変数JAVA_TOOL_OPTIONS
に指定したシステムプロパティを取得することができた。
$ sls invoke -f hello { "statusCode": 200, "body": "{\"message\":\"success\",\"input\":{\"System::STAGE\":\"dev\",\"System::appName\":\"exampleApp\"}}", "isBase64Encoded": false }
serverless.yml
にさらにシステムプロパティを追加して試してみる。
functions: hello: handler: com.serverless.Handler environment: STAGE: dev JAVA_TOOL_OPTIONS: -Dapp.name=exampleApp2 -Dapp.version=3
追加したシステムプロパティを出力できるように関数コードを以下のように修正する。
@Override public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) { input.put("System::STAGE", System.getenv("STAGE")); input.put("System::appName", System.getProperty("app.name")); input.put("System::appVersion", System.getProperty("app.version")); // この行を追加 Response responseBody = new Response("success", input); return ApiGatewayResponse.builder() .setStatusCode(200) .setObjectBody(responseBody) .build(); }
ビルド、デプロイ、そしてLambda関数を呼び出すと以下の結果が得られた。問題なく複数のシステムプロパティの値を取得することができた。
$ sls invoke -f hello { "statusCode": 200, "body": "{\"message\":\"success\",\"input\":{\"System::STAGE\":\"dev\",\"System::appName\":\"exampleApp2\",\"System::appVersion\":\"3\"}}", "isBase64Encoded": false }
[まとめ]
- OracleのJVMにはJAVA_TOOL_OPTIONSという環境変数がある
- JAVA_TOOL_OPTIONSにはJavaのシステムプロパティを設定することもできる
- AWS Lambdaでは環境変数しか直接は設定できないが、JAVA_TOOL_OPTIONS環境変数を利用するとシステムプロパティを複数指定できる
- サンプルコードは以下のリポジトリにまとめた
*1:監視のためのAppDynamicsとかコードカバレッジ測定のためのjacocoあたりが有名だろう。
*2:そもそもCorrettoはOpenJDKのAmazon版ディストリビューションなので、OpenJDKでも同じはず、と思ったらその通りだった。http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/arguments.cpp#l3284