この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部のうらわです。
最近、C++の勉強の題材としてAWSが提供しているAWS LambdaカスタムランタイムのC++を触っています。今回はDockerを使用したビルド環境を用意して以下のAPI Gatewayのサンプルコードをビルド・デプロイしてみます。
作業環境
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H15
$ docker --version
Docker version 20.10.2, build 2291f61
$ aws --version
aws-cli/2.1.4 Python/3.7.4 Darwin/19.6.0 exe/x86_64
ビルド準備
Lambda関数のコードをLinux環境でビルドする必要があるためDockerを使用します。aws-lambda-cpp-runtime
とaws-sdk-cpp
がインストール済みのDockerfileを用意します。
aws-sdk-cpp
は全サービスのライブラリをビルドするとかなり時間がかかるため、-DBUILD_ONLY="core"
で必要なライブラリを絞っています。
Dockerfile
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
git \
cmake \
make \
g++ \
zip \
libcurl4-openssl-dev \
libssl-dev \
uuid-dev \
zlib1g-dev \
libpulse-dev
WORKDIR /tmp
RUN git clone https://github.com/awslabs/aws-lambda-cpp-runtime.git && \
cd aws-lambda-cpp-runtime && \
mkdir build && \
cd build && \
cmake .. -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_INSTALL_PREFIX=~/install && \
make && make install
RUN git clone https://github.com/aws/aws-sdk-cpp.git && \
cd aws-sdk-cpp && \
mkdir build && \
cd build && \
cmake .. -DBUILD_ONLY="core" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DENABLE_UNITY_BUILD=ON \
-DCUSTOM_MEMORY_MANAGEMENT=OFF \
-DCMAKE_INSTALL_PREFIX=~/install \
-DENABLE_UNITY_BUILD=ON && \
make && make install
COPY ./run.sh /usr/local/bin/run.sh
RUN chmod +x /usr/local/bin/run.sh
WORKDIR /src
ENTRYPOINT ["run.sh"]
任意のタグを付けてイメージをビルドしておきます。
docker build -t cpp-lambda .
Dockerfile
内でCOPY
しているrun.sh
は以下となります。ENTRYPOINT
に指定しているため、上記でビルドしたDockerイメージを使用してコンテナを起動するとrun.sh
の処理が実行されます。
run.sh
#! /usr/bin/env bash
if [ $# != 1 ]; then
echo There is no argument.
exit 1
fi
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/install
make
make $1
Lambda関数の実装
Lambda関数のコードmain.cpp
とビルドするためのCMakeLists.txt
を作成します。とは言っても、GitHubのexamples/api-gatewayのコードそのままです。
mkdir apigw
cd apigw
touch main.cpp
touch CMakeLists.txt
main.cpp
#include <aws/lambda-runtime/runtime.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/core/utils/memory/stl/SimpleStringStream.h>
using namespace aws::lambda_runtime;
invocation_response my_handler(invocation_request const& request)
{
using namespace Aws::Utils::Json;
JsonValue json(request.payload);
if (!json.WasParseSuccessful()) {
return invocation_response::failure("Failed to parse input JSON", "InvalidJSON");
}
auto v = json.View();
Aws::SimpleStringStream ss;
ss << "Good ";
if (v.ValueExists("body") && v.GetObject("body").IsString()) {
auto body = v.GetString("body");
JsonValue body_json(body);
if (body_json.WasParseSuccessful()) {
auto body_v = body_json.View();
ss << (body_v.ValueExists("time") && body_v.GetObject("time").IsString() ? body_v.GetString("time") : "");
}
}
ss << ", ";
if (v.ValueExists("queryStringParameters")) {
auto query_params = v.GetObject("queryStringParameters");
ss << (query_params.ValueExists("name") && query_params.GetObject("name").IsString()
? query_params.GetString("name")
: "")
<< " of ";
ss << (query_params.ValueExists("city") && query_params.GetObject("city").IsString()
? query_params.GetString("city")
: "")
<< ". ";
}
if (v.ValueExists("headers")) {
auto headers = v.GetObject("headers");
ss << "Happy "
<< (headers.ValueExists("day") && headers.GetObject("day").IsString() ? headers.GetString("day") : "")
<< "!";
}
JsonValue resp;
resp.WithString("message", ss.str());
return invocation_response::success(resp.View().WriteCompact(), "application/json");
}
int main()
{
run_handler(my_handler);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 11)
project(api LANGUAGES CXX)
find_package(aws-lambda-runtime REQUIRED)
find_package(AWSSDK COMPONENTS core)
add_executable(${PROJECT_NAME} "main.cpp")
target_link_libraries(${PROJECT_NAME} PUBLIC AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES})
aws_lambda_package_target(${PROJECT_NAME})
Lambda関数のビルド
現在のディレクトリ(main.cpp
とCMakeLists.txt
が存在するapigw
ディレクトリ)をコンテナの/src
ディレクトリにマウントしてDockerコンテナを起動します。実行時の引数に指定しているaws-lambda-package-api
がrun.sh
に渡され、makeコマンドの引数に使用されます。
--rm
オプションをつけているため、このコンテナはビルドが終わったら自動的に削除されます。
docker run --rm -v $(pwd):/src cpp-lambda aws-lambda-package-api
ビルドが成功すると、apigw/build
内にapi.zip
が作成されます。これをLambda関数としてアップロードします。AWS CLIでアップロードする際のコマンド例はGitHubのaws-lambda-cpp-runtime
リポジトリのREADMEに書いてあります。
aws lambda create-function --function-name api \
--role <予めIAMロールを作成しArnを指定する> \
--runtime provided --timeout 15 --memory-size 128 \
--handler api --zip-file fileb://apigw/build/api.zip
API Gatewayの作成とテスト実行
READMEに記載されている通りの手順でAPI Gatewayのリソースを作成します(作成したLambda関数のトリガーからAPI Gatewayを作成します)。
作成が完了したらAPI Gatewayのエンドポイントを確認し、curl
でリクエストを送ってみます。以下のようなメッセージが返ってくれば成功です。
$ curl -X POST \
'<API Gatewayのエンドポイント>/api?name=Bradley&city=Chicago' \
-H 'content-type: application/json' \
-H 'day: Sunday' \
-d '{ "time": "evening" }'
{"message":"Good evening, Bradley of Chicago. Happy Sunday!"}
おわりに
ビルドするための環境構築が少々手間ですが、ベースとなるDockerfileさえ用意できればあとは作成するLambda関数に応じて少しカスタマイズするだけになります。
今回はサンプルコードを使用してビルドする手順が中心でした。今後はローカルマシンにもaws-lambda-runtime-cpp
とaws-sdk-cpp
をインストールして自作のLambda関数を実装する準備を整えようと思います。
なお、本記事のコードは以下のリポジトリに格納してあります。