AWS 自 2014 年推出 Lambda 时仅支持 Node.js,而后添加了对 Python, Ruby, Java, C#, F#, PowerShell 的支持,再来到 2018 年可以自定义运行时了,比如用性能较好的 C, C++, Rust, Go 等语言。见 AWS Lambda Now Supports Custom Runtimes and Enables Sharing Common Code Between Functions.
如果使用 Python, Java 写 Lambda 时觉得还不得快,不想要明显的预热过程,也许 1000 毫秒的任务只想要 600 毫秒就能完成,内存还希望再压缩一些,那着实能在每月千百万次 Lambda 调用的情况下节省一笔可观的支出,那么可以试一试 C, C++, Rust, Go 等编译成了机器指令的语言,况且前三者没有 GC, 执行效率会更高。
本日志记录一下如何用 C++ 创建一个 AWS Lambda, 以及可如何应付 Lambda 的复用。本文主要参考自下面两处
自定义运行时可选择 X86_64 或 arm64 的 Amazon Linux 2023 或 Amazon Linux 2。部署时可选择的 runtime 相应有 provided.al2023, provided.al2, 推荐使用 provided.al2023。runtime provided 不被支持了。
C++ 代码可选择用 GCC 或 Clang 来编译,既然 AWS Lambda 实际的运行时会用到 Amazon Linux 2023,那我们就直接选择 Docker 镜像 amazonlinux:2023 作为我们的编译环境。
设定好工作环境
$ docker run -it amazonlinux:2023 bash
进到 Amazon Linux 容器的 Shell, 或者我们方便本地与 Docker 容器之间共享文件,可以这么做
$ mkdir work && cd work
$ docker run -it -v $(pwd):/work -w /work amazonlinux:2023 bash
然后在容器中安装必需的组件(为区主机与容器的 Shell, 容器的 Shell 就不用提示符了, 也方便复制)
1 2 3 4 5 6 7 |
yum install gcc g++ libcurl-devel cmake git git clone https://github.com/awslabs/aws-lambda-cpp.git cd aws-lambda-cpp mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF make make install |
yum 安装的 gcc 版本是 11
注:
- aws-lambda-cpp 项目中有 CMakeLists.txt 文件,所以应用 cmake 来生成 Makefile 来编译
- 只生成 Release 版本的静态库,Debug 版本用 -DCMAKE_BUILD_TYPE=Debug
- cmake 配置命令也可以加上 -DCMAKE_INSTALL_PREFIX 参数,让生成的静态文件和安装的文件不污染系统,完整的 cmake
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=~/lambda-install - make 命令可用 cmake --build . 替代
- make install 相应的 cmake 命令是 cmake --install .
- 无需导出 CC 和 CXX 环境变量(export CC=gcc export CXX=g++)
make(cmake --build .) 和 make install(cmake --install .) 执行的效果是
bash-5.2# cmake --build .
[ 20%] Building CXX object CMakeFiles/aws-lambda-runtime.dir/src/logging.cpp.o
[ 40%] Building CXX object CMakeFiles/aws-lambda-runtime.dir/src/runtime.cpp.o
[ 60%] Building CXX object CMakeFiles/aws-lambda-runtime.dir/src/backward.cpp.o
[ 80%] Building CXX object CMakeFiles/aws-lambda-runtime.dir/version.cpp.o
[100%] Linking CXX static library libaws-lambda-runtime.a
[100%] Built target aws-lambda-runtime
bash-5.2# cmake --install .
-- Install configuration: "Release"
-- Up-to-date: /usr/local/include/aws/http/response.h
-- Up-to-date: /usr/local/include/aws/lambda-runtime/runtime.h
-- Up-to-date: /usr/local/include/aws/lambda-runtime/version.h
-- Up-to-date: /usr/local/include/aws/lambda-runtime/outcome.h
-- Up-to-date: /usr/local/include/aws/logging/logging.h
-- Installing: /usr/local/lib64/libaws-lambda-runtime.a
-- Installing: /usr/local/lib64/aws-lambda-runtime/cmake/aws-lambda-runtime-targets.cmake
-- Installing: /usr/local/lib64/aws-lambda-runtime/cmake/aws-lambda-runtime-targets-release.cmake
-- Installing: /usr/local/lib64/aws-lambda-runtime/cmake/aws-lambda-runtime-config.cmake
-- Installing: /usr/local/lib64/aws-lambda-runtime/cmake/aws-lambda-runtime-config-version.cmake
-- Up-to-date: /usr/local/lib64/aws-lambda-runtime/cmake/packager
我们从中了解了 aws-lambda-runtime 会生成什么,主要的就是静态库 /usr/local/lib64/libaws-lambda-runtime.a,如果 cmake 配置时指定了 -DCMAKE_INSTALL_PREFIX=~/lambda-install, 这个文件就会安装到 ~/lambda-install 目录下。
创建自己的 C++ Lambda 项目
准备好了 aws-lambda-runtime 运行时库后,现在开始创建 cpp-lambda-helloworld 项目,选择自己的工作目录
mkdir cpp-lambda-helloworld && cd cpp-lambda-helloworld
然后在其中创建 main.cpp 文件,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// main.cpp #include <aws/lambda-runtime/runtime.h> using namespace aws::lambda_runtime; invocation_response my_handler(invocation_request const& request) { return invocation_response::success("Hello, World!", /*payload*/ "application/json" /*MIME type*/); } int main() { run_handler(my_handler); return 0; } |
为方便在容器中编辑文件,我们可在容器中安装 vim(yum install -y vim)。
再创建 CMakeLists.txt 文件,内容
1 2 3 4 5 6 7 8 9 10 11 12 13 |
cmake_minimum_required(VERSION 3.9) set(CMAKE_CXX_STANDARD 11) project(cpp-lambda-helloworld LANGUAGES CXX) find_package(aws-lambda-runtime REQUIRED) add_executable(${PROJECT_NAME} "main.cpp") target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime) #target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") #target_compile_options(${PROJECT_NAME} PRIVATE "-Wall" "-Wextra") # this line creates a target that packages your binary and zips it up aws_lambda_package_target(${PROJECT_NAME}) |
编译构建生成 Lambda zip 包
1 2 3 |
mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make # 或用 cmake --build . |
这时在 build 目录中生成了 cpp-lambda-helloworld 可执行文件。如果现在有点好奇,执行一下它看看
bash-5.2# ./cpp-lambda-helloworld
[ERROR] [1717865932497] LAMBDA_RUNTIME Failed to get next invocation. No Response from endpoint "http:///2018-06-01/runtime/invocation/next"
[ERROR] [1717865932505] LAMBDA_RUNTIME Failed to get next invocation. No Response from endpoint "http:///2018-06-01/runtime/invocation/next"
[ERROR] [1717865932510] LAMBDA_RUNTIME Failed to get next invocation. No Response from endpoint "http:///2018-06-01/runtime/invocation/next"
[ERROR] [1717865932510] LAMBDA_RUNTIME Exhausted all retries. This is probably a bug in libcurl v8.5.0 Exiting!
这是正常的,Lambda 本来就不是本地执行的,配置好某些环境变量,本地应该可以进行测试。要部署到 AWS,我们还需要生成一个包含该执行文件 cpp-lambda-hellworld 的 zip 包,用命令
1 |
make aws-lambda-package-cpp-lambda-helloworld |
或者用 cmake 命令
cmake --build . --target aws-lambda-package-wfe_lambda_cpp # 或 make aws-lambda-package-cpp-lambda-hellworld
前面用 cmake .. -DCMAKE_BUILD_TYPE=Release 在 build 目录中生成的 Makefile 中可以找到这个 target aws-lambda-package-cpp-lambda-hellworld, 即 aws-lambda-package-${PROJECT_NAME}
target aws-lambda-package-cpp-lambda-hellworld 会生成一个压缩包 cpp-lambda-helloworld.zip, 我们大概看一下其中的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
bash-5.2# ls -l cpp-lambda* -rwxr-xr-x 1 root root 60456 Jun 8 16:58 cpp-lambda-helloworld -rw-r--r-- 1 root root 7366062 Jun 8 17:01 cpp-lambda-helloworld.zip bash-5.2# unzip -l cpp-lambda-helloworld.zip Archive: cpp-lambda-helloworld.zip Length Date Time Name --------- ---------- ----- ---- 0 06-08-2024 17:01 bin/ 60456 06-08-2024 17:01 bin/cpp-lambda-helloworld 207 06-08-2024 17:01 bootstrap 0 06-08-2024 17:01 lib/ 20192 06-08-2024 17:01 lib/UTF-16.so 24520 06-08-2024 17:01 lib/libcom_err.so.2 .................. |
lib 目录中包含众多的动态库,其中主要的文件就是 bootstrap 和 bin/cpp-lambda-helloworld. bootstrap 是 AWS Lambda 运行时的入口,它将会调用 bin/cpp-lambda-helloworld
部署 C++ AWS Lambda
我们跳过创建 IAM role 的过程,比如可以选择一个现有的可执行 AWS Lambda 的 role, 之后用 aws lambda create-function 命令创建 Lambda
aws lambda create-function \
--function-name cpp-lambda-helloworld \
--role arn:aws:iam::123456789000:role/demo_lambda_role \
--runtime provided.al2023 \
--handler cpp-lambda-helloworld \
--zip-file fileb://~/work/cpp-lambda-helloworld/build/cpp-lambda-helloworld.zip
默认内存为 128M, Timeout 是 3 秒,默认 CPU 架构是 x86_64,handler 为 C++ 可执行文件名
测试 Lambda
也可以在 AWS Lambda Web 控制台进行测试
1 2 3 4 5 6 7 |
$ aws lambda invoke --function-name cpp-lambda-helloworld --payload '{}' output.txt { "StatusCode": 200, "ExecutedVersion": "$LATEST" } $ cat output.txt Hello, World! |
C++ 的 AWS Lambda 工作正常。
测试验证 Lambda 实例的利用
我们知道 Lambda 在短期内,这个时间是不确定的,由调用模式和 AWS 内部资源管理来定。但基本上可认定几秒钟之类的连续调用会重用之前启动的 Lambda 实例,而无需冷启动。前后调用重用 Lambda 实例的最大好处是可以在后续调用使用前面实例的缓存和已生成(比如下载)的临时文件,这对提升 Lambda 的性特别重要,烦重的任务无须每次都做。
下面就来测试 C++ Lambda 用全局变量来缓存数据,声明一个全局计数器来统计当前 Lambda 实例被调用的次数。
在 main.cpp 中加一个全局变量 count, 新的 main.cpp 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// main.cpp #include <aws/lambda-runtime/runtime.h> using namespace aws::lambda_runtime; int count = 0; invocation_response my_handler(invocation_request const& request) { count ++; auto result = "Hello, World!, count: " + std::to_string(count); return invocation_response::success(result, "application/json"); } int main() { run_handler(my_handler); return 0; } |
用 aws lambda delete-function --function-name cpp-lambda-helloworld 删除然后重新创建 cpp-lambda-helloworld, 现在调用多次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ aws lambda invoke --function-name cpp-lambda-helloworld --payload '{}' output.txt { "StatusCode": 200, "ExecutedVersion": "$LATEST" } $ cat output.txt Hello, World!, count: 2% $ aws lambda invoke --function-name cpp-lambda-helloworld --payload '{}' output.txt { "StatusCode": 200, "ExecutedVersion": "$LATEST" } $ cat output.txt Hello, World!, count: 3 |
说明 Lambda 实例在某个时期内也是可以被重用的,如果等待一段时间,比如 20 分钟,再调用的话会使用一个新的 Lambda, count 又会从 0 开始计数。
如果启用 Lambda 的 Function URL, 用 curl 调用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ curl https://7bugr54bdblovk7x7i6vfdocja0huuod.lambda-url.us-east-1.on.aws/ Hello, World!, count: 1% $ curl -i https://7bugr54bdblovk7x7i6vfdocja0huuod.lambda-url.us-east-1.on.aws/ HTTP/1.1 200 OK Date: Sat, 08 Jun 2024 18:00:14 GMT Content-Type: application/octet-stream Content-Length: 23 Connection: keep-alive x-amzn-RequestId: 2f8a49e1-978e-47b1-a7d5-abe1e1b1e500 X-Amzn-Trace-Id: root=1-66649c2e-020d297f6d272a0d544b3607;parent=114c931f9cf39860;sampled=0;lineage=9c1d5f43:0 Hello, World!, count: 2% $ curl https://7bugr54bdblovk7x7i6vfdocja0huuod.lambda-url.us-east-1.on.aws/ Hello, World!, count: 3% |
可选择其他 Linux 发行版
只要能生成 Lambda 需要的 x86_64 架构的二进制代码,可以选择任何 x86_64 的 Linux 发行版来创建 Lambda 发布包。当然如果部署 Lambda 选择的是 arm64, 那么编译的环境也最好是 arm64, 不然就尝试用交叉编译吧(总觉得不那么可靠)。
比如有些人钟爱于 Debian 系的 Ubuntu 来做开发,就是喜欢 apt 胜过于 yum,可选用 Ubuntu 24.04
$ docker run -it ubuntu:24.04
1 2 |
apt update apt install zip gcc g++ make cmake libcurl4-openssl-dev git |
安装后 gcc/g++ 的版本为 13,无需导出 CC=gcc 和 CXX=g++
一个常见问题
如果访问 Lambda 时出现错误
{
"errorType": "Runtime.InvalidEntrypoint",
"errorMessage": "RequestId: cbc69b13-0187-49f2-8e65-ec2e398360b8 Error: Couldn't find valid bootstrap(s): [/var/task/bootstrap /opt/bootstrap]"
}
基本上就是在 Lambda 的 zip 包根目录中没有可执行权限的 bootstrap 文件。
其他延伸
其他剩下的就是如何处理请求数据,如何在 C++ 中使用 AWS 的那路 SDK。比如产生失败的响应可以用
1 2 3 4 |
if (request.payload.length() > 5) { return invocation_response::failure("error message here"/*error_message*/, "error type here" /*error_type*/); } |
针对它的测试与输出
1 2 3 4 5 6 7 8 |
$ aws lambda invoke --function-name cpp-lambda-helloworld --payload 'eyJrZXkiOiJiaWcgcGF5bG9hZCJ9' output.txt { "StatusCode": 200, "FunctionError": "Unhandled", "ExecutedVersion": "$LATEST" } $ cat output.txt {"errorMessage":"error message here","errorType":"error type here", "stackTrace":[]} |
在使用 request.payload 时默认要求 payload 是 base64 的字符串,要直接传入纯文本用 --cli-binary-format raw-in-base64-out
参数。
实际操练时那就是真正考验 C++ 的功底了。
要获得更详细的日志信息,执行 cmake 配置 aws-lambda-cpp 项目时可用 -DCMAKE_BUILD_TYPE=Debug
,或在 -DCMAKE_BUILD_TYPE=Release
时指定 -DLOG_VERBOSITY=3
。
在 Lambda crash 时看到更详细的错误栈信息,要在编译 aws-lambda-cpp 前在系统中安装 libdw 和 binutils
- Debian 系统:apt install libdw-dev binutils-dev
- RedHat 系统:yum install elfutils-devel binutils-devel
CMake 会自动检测到它并进行连接
看到不能在 std::string 和 Aws::String 之间转换,可以关闭 AWS C++ SDK 的自定义内存管理或用 -BUILD_SHARED_LIBS=OFF 选项编译。
我们也可以在 Mac OS X 下进行,在 Intel 芯片的 Mac OS X 下安装了 gcc(Apple clang version 15.0.0 (clang-1500.3.9.4))。以上 aws-lambda-cpp 和 cpp-lambda-helloworld 除了 make aws-lambda-package-cpp-lambda-helloworld 之外可以正常执行,因为 aws-lambda-package-xxx 是要明确生成包含 Linux 下二进制代码的压缩包。
因此我们用 Mac OS X 开发调试 C++ 的 AWS Lambda 代码,最后交给 Linux 完成最终的编译打包。
链接:
- Introducing the Amazon Linux 2023 runtime for AWS Lambda
- Introducing the C++ Lambda Runtime(其中介绍了如何编译和使用 aws-sdk-cpp)
本文链接 https://yanbin.blog/cpp-aws-lambda-hello-world/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
感谢分享!!!!!!!!