Lambdaで環境変数っぽいことを実現するためにエイリアス名を使う方法があります。
http://qiita.com/mashiro/items/ab4cab5e623397d6b29d
しかし、ハマりどころがあるので書いておきます。
サンプル
devステージとproステージでDynamoDBのテーブルを切り替える例です。
- configモジュールを使用
- 環境変数
NODE_ENV
にdev,proという値を設定して切りかえ- Lambdaのコンテキスト変数からエイリアス名を取り出す
envtest/
├── config
│ ├── dev.js
│ └── pro.js
├── db.js
├── index.js
├── node_modules
│ ├── co
│ └── config
└── package.json
"use strict";
const co = require("co");
const db = require("./db");
exports.handle = function(e, ctx, cb) {
const aliaseName = ctx.invokedFunctionArn.match(/([^:]+)$/)[1];
process.env.NODE_ENV = aliaseName;
console.log("NODE_ENV: ", process.env.NODE_ENV);
co(function*(){
return yield db.getFromDB();
}).then(result => {
cb(null, result);
}).catch(err => {
cb(err);
});
};
const config = require("config");
const AWS = require("aws-sdk");
AWS.config.setPromisesDependency(Promise);
const DynamoDB = new AWS.DynamoDB.DocumentClient();
module.exports.getFromDB = function*() {
console.log(`get from table ${config.tablePrefix}.Table`);
return yield DynamoDB.get({
TableName: `${config.tablePrefix}.Table`,
Key: {
key: "hogehoge"
}
}).promise();
};
動作
一見動きそうですが、このようなエラーになります。
$ apex invoke envtest -L -a dev
START RequestId: d7c859e0-7284-11e6-92b7-4568188a1126 Version: $LATEST
2016-09-04T09:49:16.384Z d7c859e0-7284-11e6-92b7-4568188a1126 NODE_ENV: dev
2016-09-04T09:49:16.384Z d7c859e0-7284-11e6-92b7-4568188a1126 get from table undefined.Table
2016-09-04T09:49:16.488Z d7c859e0-7284-11e6-92b7-4568188a1126 {"errorMessage":"Requested resource not found","errorType":"ResourceNotFo
undException"
なぜでしょう? これはrequire("config")
されるタイミングにあります。
const db = require("./db");
という部分がありますが、これにより芋づる式に、
const config = require("config");
ココが呼ばれます。
これはhandle関数の下記の部分よりも先に呼ばれてしまうのです。
exports.handle = function(e, ctx, cb) {
const aliaseName = ctx.invokedFunctionArn.match(/([^:]+)$/)[1];
process.env.NODE_ENV = aliaseName;
console.log("NODE_ENV: ", process.env.NODE_ENV);
つまり、NODE_ENVにdevだとか、proだとかがセットされる前です。
修正案
これを解消するには、
"use strict";
const co = require("co");
exports.handle = function(e, ctx, cb) {
const aliaseName = ctx.invokedFunctionArn.match(/([^:]+)$/)[1];
process.env.NODE_ENV = aliaseName;
console.log("NODE_ENV: ", process.env.NODE_ENV);
co(function*(){
const db = require("./db"); //ココに持ってくる
return yield db.getFromDB();
}).then(result => {
cb(null, result);
}).catch(err => {
cb(err);
});
};
こうなります。
$ apex invoke envtest -L -a dev
START RequestId: 8a0fbd26-7286-11e6-8438-af4d75d76e02 Version: $LATEST
2016-09-04T10:01:24.967Z 8a0fbd26-7286-11e6-8438-af4d75d76e02 NODE_ENV: dev
2016-09-04T10:01:24.967Z 8a0fbd26-7286-11e6-8438-af4d75d76e02 get from table Sample.DEV.Table
END RequestId: 8a0fbd26-7286-11e6-8438-af4d75d76e02
REPORT RequestId: 8a0fbd26-7286-11e6-8438-af4d75d76e02 Duration: 67.24 ms Billed Duration: 100 ms Memory Size: 128 MBMax Memory Used: 26 MB
{"Item":{"value":"DEV環境","key":"hogehoge"}}
できた気がしますね。
しかし、Lambdaファンクションの関数インスタンスは再利用されるのです
話はまだ終わりません。ここからが本当に書きたかったことです。
devもproもバージョン9を向ける
下記のようにdevもproもバージョン9を向けるようにしてみましょう。
aws lambda list-aliases --function-name sample_envtest
{
"Aliases": [
{
"FunctionVersion": "9",
"Name": "dev",
},
{
"FunctionVersion": "9",
"Name": "pro",
}
]
}
実行結果
proを実行してみます。ふむ、期待通りですね
$ apex invoke envtest -L -a pro
START RequestId: b45a8038-7287-11e6-91a6-e3f26108a1b5 Version: 9
2016-09-04T10:09:45.389Z b45a8038-7287-11e6-91a6-e3f26108a1b5 NODE_ENV: pro
2016-09-04T10:09:45.390Z b45a8038-7287-11e6-91a6-e3f26108a1b5 get from table Sample.PRO.Table
END RequestId: b45a8038-7287-11e6-91a6-e3f26108a1b5
REPORT RequestId: b45a8038-7287-11e6-91a6-e3f26108a1b5 Duration: 77.82 ms Billed Duration: 100 ms Memory Size: 128 MBMax Memory Used: 26 MB
{"Item":{"value":"PRO環境","key":"hogehoge"}}
続いてdevを実行すると・・・
$ apex invoke envtest -L -a dev
START RequestId: dad8792f-7287-11e6-8961-2d198a82fe02 Version: 9
2016-09-04T10:10:49.968Z dad8792f-7287-11e6-8961-2d198a82fe02 NODE_ENV: dev
2016-09-04T10:10:49.969Z dad8792f-7287-11e6-8961-2d198a82fe02 get from table Sample.PRO.Table
END RequestId: dad8792f-7287-11e6-8961-2d198a82fe02
REPORT RequestId: dad8792f-7287-11e6-8961-2d198a82fe02 Duration: 134.01 ms Billed Duration: 200 ms Memory Size: 128 MBMax Memory Used: 26 MB
{"Item":{"value":"PRO環境","key":"hogehoge"}}
おやおや? Sample.PRO.Table
を読んでしまっている!
NODE_ENV: dev
と出ているのに!
関数インスタンスの再利用問題
一度requireされたモジュールはキャッシュされるのはNode.jsを使う方なら周知の事実でしょう。
だからdb.jsでrequire("config")された内容はキャッシュされます。
私はLambdaはCGIのように実行されるたびにプロセス実行をするものだと思っていました。
しかし、この結果を見るに1回目にproとして実行されたプロセスが再利用されているように見えます。
これが「関数インスタンスが再利用される」というやつですね
devを$LATESTに向けてみる
aws lambda list-aliases --function-name sample_envtest
{
"Aliases": [
{
"FunctionVersion": "$LATEST",
"Name": "dev",
},
{
"FunctionVersion": "9",
"Name": "pro",
}
]
}
実はこの環境では$LATEST==9
なのですが、ともかくやってみます。
$ apex invoke envtest -L -a dev
START RequestId: b5ad53ae-7289-11e6-99c6-e397f9b62998 Version: $LATEST
2016-09-04T10:24:06.605Z b5ad53ae-7289-11e6-99c6-e397f9b62998 NODE_ENV: dev
2016-09-04T10:24:06.605Z b5ad53ae-7289-11e6-99c6-e397f9b62998 get from table Sample.DEV.Table
END RequestId: b5ad53ae-7289-11e6-99c6-e397f9b62998
REPORT RequestId: b5ad53ae-7289-11e6-99c6-e397f9b62998 Duration: 101.35 ms Billed Duration: 200 ms Memory Size: 128 MBMax Memory Used: 27 MB
{"Item":{"value":"DEV環境","key":"hogehoge"}}
$ apex invoke envtest -L -a pro
START RequestId: b7771774-7289-11e6-a3ca-55d16d901553 Version: 9
2016-09-04T10:24:09.589Z b7771774-7289-11e6-a3ca-55d16d901553 NODE_ENV: pro
2016-09-04T10:24:09.589Z b7771774-7289-11e6-a3ca-55d16d901553 get from table Sample.PRO.Table
END RequestId: b7771774-7289-11e6-a3ca-55d16d901553
REPORT RequestId: b7771774-7289-11e6-a3ca-55d16d901553 Duration: 46.22 ms Billed Duration: 100 ms Memory Size: 128 MBMax Memory Used: 26 MB
{"Item":{"value":"PRO環境","key":"hogehoge"}}
なるほど。どうやらLambdaファンクションの関数インスタンスはバージョンごとに再利用されるようです。
ログに、
START RequestId: b5ad53ae-7289-11e6-99c6-e397f9b62998 Version: $LATEST
START RequestId: b7771774-7289-11e6-a3ca-55d16d901553 Version: 9
という出方をしているので、$LATESTと最新バージョは違うものとして扱われるようです。
まとめ
この挙動は注意しておかないとハマりそうです。Lambdaファンクションの内部実装がコンテナで、コンテナ起動時間の問題を軽減するためにある程度の単位で再利用されるというのは小耳に挟んでいました。
Q: AWS Lambda は関数インスタンスを再利用しますか?
パフォーマンス向上のため、AWS Lambda は新しく関数のインスタンスを作成するのではなく、関数のインスタンスを保持してその後のリクエストに対応することがあります。ただし、常にインスタンスを再利用するわけではありません。
が、こうなるというところまで頭が回っていませんでした。
回避するためにはconfigの内容がキャッシュされない方法で、configを毎回評価する必要があるでしょう。
大域変数であるprocess.env.NODE_ENV
にdev,proをセットするのも怪しくなってきました。
同時invokeとLambdaファンクションのプロセスの関係がどうなるのかがまだわかりませんが、スレッドセーフでは無いかもしれません。
雑惑
apexには環境変数っぽいことをする機能があります。
apex deploy
するとこっそりとこんなファクションがかまされています。
try {
var config = require('./.env.json')
for (var key in config) {
process.env[key] = config[key]
}
} catch (err) {
// ignore
}
exports.handle = require('./index').handle
これを素直に使ったほうが良いのかなと思うのですが、この機能を使うと、
バージョン番号 | |
---|---|
10 | proの最新 |
9 | devの最新 |
となってしまうのが嫌なのですよね。。。