re:invent2018の発表みなさんご覧になりましたでしょうか。
個人的に面白い事出来そうだなーと思ったのがLambdaに関わる発表でした。
ALBのlambdaサポート
- http(s)リクエストをイベントとして動くわけで、じゃあハンドラーのevent objectに何が入ってくるんじゃという話ですが、Nodeで受け取って単純に表示してみると以下のようになります。
{
"requestContext":{
"elb":{
"targetGroupArn":"arn:aws:elasticloadbalancing:{himitsu}"
}
},
"httpMethod":"GET",
"path":"/{albのpath}",
"queryStringParameters":{},
"headers":{
"accept":"*/*",
"host":"{albのhost名}",
"user-agent":"curl/7.54.0",
"x-amzn-trace-id":"{id}",
"x-forwarded-for":"{ip.ip.ip.ip}",
"x-forwarded-port":"80",
"x-forwarded-proto":"http"
},
"body":"",
"isBase64Encoded":false
}
- なるほどヘッダー情報やquery stringまでいい感じにパースして渡してくれるようです。
- 以下のようにstatusCodeやstatusDescriptionを詰めてobjectを返せばそれがresponseとなります。
{
statusCode: 200,
statusDescription: "200 OK",
isBase64Encoded: false,
headers: {
Content-Type: "application/json; charset=utf-8"
},
body:""
}
lambdaのcustom runtime
- 任意のruntimeを作れるよという話です。シェルスクリプトでの実装例がドキュメントに上がっています(link)。bootstrapという実行可能ファイルが必要なのですね。
- 今回はRustで実装したかったのですが、awslabにもうmacro化したものがリファレンス実装として上がっていました(link)。コードを見て頂ければ分かるようにループでイベントを待ち受け、eventオブジェクトを登録されたコールバックファンクション(これがまさしくlambdaのhandlerにあたる物)に渡して実行しています。こちらを使わせてもらいましょう。
ALBとcustom runtimeのlambdaを組み合わせる
- コード例
#[macro_use]
extern crate lambda_runtime as lambda;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
extern crate simple_logger;
use lambda::error::HandlerError;
use std::error::Error;
#[derive(Deserialize, Clone)]
struct CustomEvent {
#[serde(rename = "requestContext")]
request_context: Elb,
#[serde(rename = "httpMethod")]
http_method: String,
path: String,
#[serde(rename = "queryStringParameters")]
query_string_parameters:QueryStringParameters
}
#[derive(Serialize, Clone)]
struct Response {
#[serde(rename = "statusCode")]
status_code: i64,
#[serde(rename = "statusDescription")]
status_description: String,
#[serde(rename = "isBase64Encoded")]
is_base64encoded: bool,
headers: ContentType,
body: String,
}
#[derive(Deserialize, Clone)]
struct Elb { elb: TargetGroupArn }
#[derive(Deserialize, Clone)]
struct TargetGroupArn {
#[serde(rename = "targetGroupArn")]
target_group_arn: String
}
#[derive(Deserialize, Clone)]
struct QueryStringParameters {
name: String
}
#[derive(Serialize, Clone)]
struct ContentType {
content_type: String
}
fn main() -> Result<(), Box<dyn Error>> {
simple_logger::init_with_level(log::Level::Info)?;
lambda!(my_handler);
Ok(())
}
fn my_handler(e: CustomEvent, c: lambda::Context) -> Result<Response, HandlerError> {
if e.http_method == "" {
error!("Empty http_method in request {}", c.aws_request_id);
return Err(c.new_error("Empty"));
}
Ok(Response {
status_code: 200,
status_description: "200 OK".to_string(),
is_base64encoded: false,
headers: ContentType {
content_type: "application/json".to_string()
},
body: format!("Rust function invoked! Hello, {}\n", e.query_string_parameters.name)
})
}
- requestイベントのeventオブジェクトの形は最初に書いたとおりだろうなーという事で、それをマッピング出来るように構造体を作ってみました。
- 以下をcargo.tomlに追記すればbootstrapという名前で実行可能なバイナリーを作成出来ますので、zipで固めてlambda関数としてアップロードします。
[[bin]]
name = "bootstrap"
path = "src/main.rs"
- あとはALBのターゲット設定でこの関数を指せばOKですね。
リクエストしてみる
\(^o^)/
感想
- custom runtimeでも問題なくALB連携出来ますね!
- 安定したruntimeがライブラリ化されれば一気にユースケースが広がりそうで、面白くなりそうです。