AWS LambdaのCustom RuntimesでC言語のLambda Functionを動かしてみた! #reinvent
福岡オフィスのyoshihitohです。 本記事はAWS Lambda Custom Runtimes芸人 Advent Calendar 2018の15日目の記事です。
はじめに
Advent Calendarを見るとわかるように、 Custom Runtimesを活用すると色々な言語でLambda Functionを実装できます。 今回はC言語を使ってLambda Functionを実装してみました。
やること
Custom Runtimesのチュートリアルを参考に、bootstrap
のシェルスクリプトからC言語のプログラムを実行します。C言語のプログラムは、受け取ったイベントをシーザー暗号にするだけの単純なものにしてみます。
プログラムとCMakeプロジェクトを作る
まず、C言語のプログラムを作成します。
#include <stdio.h> #include <ctype.h> static const char* const ORIGINAL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char* const CAESAR_CHARS = "XYZABCDEFGHIJKLMNOPQRSTUVW"; static int encode_caesar_cipher(int ch) { if (isupper(c)) return CAESAR_CHARS[ch - 'A']; if (islower(c)) return tolower(CAESAR_CHARS[ch - 'a']); return c; } int main() { int c; while ((c = getchar()) != EOF) { if (!isprint(c)) continue; putchar(encode_caesar_cipher(c)); } return 0; }
次に、CMakeのプロジェクトを作成します。
cmake_minimum_required(VERSION 3.8) project(custom_runtime_c C) set(CMAKE_C_STANDARD 99) add_executable(custom_runtime_c main.c)
ビルド環境を作る
次にビルド環境を構築します。まず、ビルド用のシェルを作ります。
#!/bin/sh set -e set -o pipefail mkdir -p build cd build CC=gcc cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. make mkdir -p /artifacts/bin cp custom_runtime_c /artifacts/bin mkdir -p /artifacts/lib cp /lib/ld-musl-x86_64.so.1 /artifacts/lib
次に、Dockerコンテナを利用してビルド環境を構築します。C++で試したときと同様に、Alpine Linuxを使用します。
FROM alpine:latest RUN apk update && apk add cmake make gcc bash zip musl-dev WORKDIR /opt/src COPY ./CMakeLists.txt . COPY ./main.c . COPY ./bootstrap . COPY ./build.sh . VOLUME /artifacts
また、簡単にビルドできるようにdocker-composeを用意します。
version: "3" services: custom_runtime_c: build: context: . image: custom_runtime_c command: bash ./build.sh volumes: - ./artifacts:/artifacts
ここまでで準備完了です。ビルドしてみます。
$ mkdir -p ./artifacts $ docker-compose up -d --build $ ls -lah ./artifacts/bin -rwxr-xr-x 1 yoshihitoh staff 11K 12 15 18:17 custom_runtime_c $ ls -lah ./artifacts/lib -rwxr-xr-x 1 yoshihitoh staff 550K 12 15 18:17 ld-musl-x86_64.so.1
ちゃんとビルドできていますね!
Lambda Functionを作る
最後にLambda Functionを作ります。チュートリアルとC++用のカスタムランタイムを参考に実装します。
#!/bin/sh set -euo pipefail EXEC="$LAMBDA_TASK_ROOT/bin/$_HANDLER" MUSL="$LAMBDA_TASK_ROOT/lib/ld-musl-x86_64.so.1" # Processing while true do HEADERS="$(mktemp)" # Get an event EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Execute the handler function from the script RESPONSE=$(echo "$EVENT_DATA" | exec $MUSL --library-path $LAMBDA_TASK_ROOT/lib $EXEC) # Send the response curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" done
基本的にはサンプルと同様ですが、実行ファイルの呼び出し方を変更しています。
ZIPファイルに固めてLambda Functionを作れば完了です。
$ cd artifacts $ zip -r custome-runtime-c.zip bootstrap bin lib $ aws lambda create-function \ --function-name "custom-runtime-c" \ --zip-file "fileb://custome-runtime-c.zip" \ --handler "custom_runtime_c" \ --runtime provided \ --role arn:aws:iam::xxxxxxxxxxxx:role/lambda_basic_execution
動かしてみる
例えば、暗号結果を以下のデータにしたい場合を考えます。
{ "hello": "c-runtime!", "method": "caesar" }
アルファベットを左に3個ずつずらした結果を上記の文字列にしたいので、右側に3個ずつずらしてみます。
{ "khoor": "f-uxqwlph!", "phwkrg": "fdhvdu" }
上記のテストデータを使ってLambda Functionを動かしてみます。
ちゃんと動いていますね!
おわりに
今回はC言語でLambda Functionを実装してみました。本当はbootstrapで行っているLambdaのAPIを叩く部分もC言語のプログラムに寄せたいなーと思っていたんですが、結構面倒くさそうな感じでした。 C言語でハンドラーを実装する場合、C++ or Rustのカスタムランタイムからハンドラ関数を呼び出すのがよさそうです。
C言語は長いこと使われている言語なこともあって、C言語実装のライブラリは数多く存在しています。それらを活用して面白いことができるんじゃないかなーと思うので、引き続き色々試していきたいと思います。