この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
福岡オフィスの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言語のプログラムを作成します。
main.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のプロジェクトを作成します。
CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(custom_runtime_c C)
set(CMAKE_C_STANDARD 99)
add_executable(custom_runtime_c main.c)
ビルド環境を作る
次にビルド環境を構築します。まず、ビルド用のシェルを作ります。
build.sh
#!/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を使用します。
Dockerfile
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を用意します。
docker-compose.yaml
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++用のカスタムランタイムを参考に実装します。
artifacts/bootstrap
#!/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言語実装のライブラリは数多く存在しています。それらを活用して面白いことができるんじゃないかなーと思うので、引き続き色々試していきたいと思います。