

# 教程：构建自定义运行时系统
<a name="runtimes-walkthrough"></a>

在本教程中，您将创建一个具有自定义运行时的 Lambda 函数。首先，您在函数的部署程序包中包含运行时。然后，您将其迁移到一个您独立于函数管理的层。最后，您通过更新运行时层的基于资源的权限策略来将运行时层与全球共享。

## 先决条件
<a name="runtimes-walkthrough-prereqs"></a>

本教程假设您对 Lambda 基本操作和 Lambda 控制台有一定了解。如果您还没有了解，请按照 [使用控制台创建 Lambda 函数](getting-started.md#getting-started-create-function) 中的说明创建您的第一个 Lambda 函数。

要完成以下步骤，您需要 [Amazon CLI 版本 2](https://docs.amazonaws.cn/cli/latest/userguide/getting-started-install.html)。在单独的数据块中列出了命令和预期输出：

```
aws --version
```

您应看到以下输出：

```
aws-cli/2.13.27 Python/3.11.6 Linux/4.14.328-248.540.amzn2.x86_64 exe/x86_64.amzn.2
```

对于长命令，使用转义字符 (`\`) 将命令拆分为多行。

在 Linux 和 macOS 中，可使用您首选的 shell 和程序包管理器。

**注意**  
在 Windows 中，操作系统的内置终端不支持您经常与 Lambda 一起使用的某些 Bash CLI 命令（例如 `zip`）。[安装 Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10)，获取 Ubuntu 和 Bash 与 Windows 集成的版本。本指南中的示例 CLI 命令使用 Linux 格式。如果您使用的是 Windows CLI，则必须重新格式化包含内联 JSON 文档的命令。

您需要一个 IAM 角色来创建 Lambda 函数。该角色需要权限方可将日志发送到 CloudWatch Logs 并访问您的函数使用的 Amazon Web Services 服务。如果您没有函数开发的角色，请立即创建一个。

**创建执行角色**

1. 在 IAM 控制台中，打开 [Roles（角色）页面](https://console.amazonaws.cn/iam/home#/roles)。

1. 选择**创建角色**。

1. 创建具有以下属性的角色。
   + **Trusted entity**（可信任的实体）– **Lambda**。
   + **Permissions**（权限）– **AWSLambdaBasicExecutionRole**。
   + **Role name**（角色名称）– **lambda-role**。

   **AWSLambdaBasicExecutionRole** 策略具有函数将日志写入 CloudWatch Logs 所需的权限。

## 创建函数
<a name="runtimes-walkthrough-function"></a>

使用自定义运行时创建 Lambda 函数。此示例包含两个文件：一个运行时系统 `bootstrap` 文件和一个函数处理程序。两个文件都在 Bash 中实施。

1. 为项目创建一个目录，然后切换到该目录。

   ```
   mkdir runtime-tutorial
   cd runtime-tutorial
   ```

1. 创建名为 `bootstrap` 的新文件。这是自定义运行时系统。  
**Example bootstrap**  

   ```
   #!/bin/sh
   
   set -euo pipefail
   
   # Initialization - load function handler
   source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
   
   # Processing
   while true
   do
     HEADERS="$(mktemp)"
     # Get an event. The HTTP request will block until one is received
     EVENT_DATA=$(curl -sS -LD "$HEADERS" "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
   
     # Extract request ID by scraping response headers received above
     REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
   
     # Run the handler function from the script
     RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
   
     # Send the response
     curl "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
   done
   ```

   运行时将从部署程序包加载函数脚本。它使用两个变量来查找脚本。`LAMBDA_TASK_ROOT` 向它告知在何处提取程序包，`_HANDLER` 包含脚本的名称。

   在运行时系统加载函数脚本之后，它会使用运行时系统 API 从 Lambda 检索调用事件，并将事件传递到处理程序，然后将响应发送回 Lambda。为了获取请求 ID，运行时会将来自 API 响应的标头保存到临时文件，并从该文件读取 `Lambda-Runtime-Aws-Request-Id` 标头。
**注意**  
运行时还具有其他职责（包括错误处理），并向处理程序提供上下文信息。有关详细信息，请参阅[要求](runtimes-custom.md#runtimes-custom-build)。

1. 为函数创建脚本。以下示例脚本将定义一个处理程序函数，该函数将选取事件数据，将该数据记录到 `stderr`，然后返回它。  
**Example function.sh**  

   ```
   function handler () {
     EVENT_DATA=$1
     echo "$EVENT_DATA" 1>&2;
     RESPONSE="Echoing request: '$EVENT_DATA'"
   
     echo $RESPONSE
   }
   ```

   `runtime-tutorial` 目录现在应如下所示：

   ```
   runtime-tutorial
   ├ bootstrap
   └ function.sh
   ```

1. 使文件可执行并将其添加到 .zip 文件存档。这就是部署包。

   ```
   chmod 755 function.sh bootstrap
   zip function.zip function.sh bootstrap
   ```

1. 创建名为 `bash-runtime` 的函数。对于 `--role`，请输入您的 Lambda [执行角色](lambda-intro-execution-role.md)的 ARN。

   ```
   aws lambda create-function --function-name bash-runtime \
   --zip-file fileb://function.zip --handler function.handler --runtime provided.al2023 \
   --role arn:aws:iam::123456789012:role/lambda-role
   ```

1. 调用函数。

   ```
   aws lambda invoke --function-name bash-runtime --payload '{"text":"Hello"}' response.txt --cli-binary-format raw-in-base64-out
   ```

   如果使用 **cli-binary-format** 版本 2，则 Amazon CLI 选项是必需的。要将其设为默认设置，请运行 `aws configure set cli-binary-format raw-in-base64-out`。有关更多信息，请参阅*版本 2 的 Amazon Command Line Interface 用户指南*中的 [Amazon CLI 支持的全局命令行选项](https://docs.amazonaws.cn/cli/latest/userguide/cli-configure-options.html#cli-configure-options-list)。

   应出现如下响应：

   ```
   {
       "StatusCode": 200,
       "ExecutedVersion": "$LATEST"
   }
   ```

1. 验证响应。

   ```
   cat response.txt
   ```

   应出现如下响应：

   ```
   Echoing request: '{"text":"Hello"}'
   ```

## 创建层
<a name="runtimes-walkthrough-layer"></a>

要将运行时代码与函数代码分开，请创建一个仅包含运行时的层。层可让您单独开发函数的各个依赖项，而且，通过对多个函数使用相同的层，还可以减少存储使用。有关更多信息，请参阅 [使用层管理 Lambda 依赖项](chapter-layers.md)。

1. 创建包含 `bootstrap` 文件的 .zip 文件。

   ```
   zip runtime.zip bootstrap
   ```

1. 使用 [publish-layer-version](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/publish-layer-version.html?highlight=nodejs16%20x) 命令创建层。

   ```
   aws lambda publish-layer-version --layer-name bash-runtime --zip-file fileb://runtime.zip
   ```

   这将创建第一个版本的层。

## 更新函数
<a name="runtimes-walkthrough-update"></a>

要在函数中使用运行时系统层，请将函数配置为使用该层，并从函数中删除运行时代码。

1. 更新函数配置以拉入到层。

   ```
   aws lambda update-function-configuration --function-name bash-runtime \
   --layers arn:aws:lambda:us-east-1:123456789012:layer:bash-runtime:1
   ```

   这会将运行时添加到 `/opt` 目录中的函数中。为确保 Lambda 使用层中的运行时系统，您必须从函数的部署包中移除 `boostrap`，如接下来的两个步骤所示。

1. 创建包含函数代码的 .zip 文件。

   ```
   zip function-only.zip function.sh
   ```

1. 更新函数代码以仅包含处理程序脚本。

   ```
   aws lambda update-function-code --function-name bash-runtime --zip-file fileb://function-only.zip
   ```

1. 调用函数以确认它适用于运行时系统层。

   ```
   aws lambda invoke --function-name bash-runtime --payload '{"text":"Hello"}' response.txt --cli-binary-format raw-in-base64-out
   ```

   如果使用 **cli-binary-format** 版本 2，则 Amazon CLI 选项是必需的。要将其设为默认设置，请运行 `aws configure set cli-binary-format raw-in-base64-out`。有关更多信息，请参阅*版本 2 的 Amazon Command Line Interface 用户指南*中的 [Amazon CLI 支持的全局命令行选项](https://docs.amazonaws.cn/cli/latest/userguide/cli-configure-options.html#cli-configure-options-list)。

   应出现如下响应：

   ```
   {
       "StatusCode": 200,
       "ExecutedVersion": "$LATEST"
   }
   ```

1. 验证响应。

   ```
   cat response.txt
   ```

   应出现如下响应：

   ```
   Echoing request: '{"text":"Hello"}'
   ```

## 更新运行时
<a name="runtimes-walkthrough-runtime"></a>

1. 要记录有关执行环境的信息，请更新运行时脚本以输出环境变量。  
**Example bootstrap**  

   ```
   #!/bin/sh
   
   set -euo pipefail
   
   # Configure runtime to output environment variables
   echo "##  Environment variables:"
   env
   
   # Load function handler
   source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
   
   # Processing
   while true
   do
     HEADERS="$(mktemp)"
     # Get an event. The HTTP request will block until one is received
     EVENT_DATA=$(curl -sS -LD "$HEADERS" "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
   
     # Extract request ID by scraping response headers received above
     REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
   
     # Run the handler function from the script
     RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
   
     # Send the response
     curl "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
   done
   ```

1. 创建包含新版本 `bootstrap` 文件的 .zip 文件。

   ```
   zip runtime.zip bootstrap
   ```

1. 创建新版本的 `bash-runtime` 层。

   ```
   aws lambda publish-layer-version --layer-name bash-runtime --zip-file fileb://runtime.zip
   ```

1. 配置函数以使用新版本的层。

   ```
   aws lambda update-function-configuration --function-name bash-runtime \
   --layers arn:aws:lambda:us-east-1:123456789012:layer:bash-runtime:2
   ```

## 共享层
<a name="runtimes-walkthrough-share"></a>

要与其他 Amazon Web Services 账户 共享层，请在该层的[基于资源的策略](access-control-resource-based.md)中添加跨账户权限语句。执行 [add-layer-version-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/add-layer-version-permission.html) 命令，并将账户 ID 指定为 `principal`。在每个语句中，您可以向 [Amazon Organizations](https://docs.amazonaws.cn/organizations/latest/userguide/orgs_introduction.html) 中的单个账户、所有账户或组织授予权限。

以下示例向账户 111122223333 授予访问 `bash-runtime` 层版本 2 的权限。

```
aws lambda add-layer-version-permission \
  --layer-name bash-runtime \
  --version-number 2 \  
  --statement-id xaccount \
  --action lambda:GetLayerVersion \
  --principal 111122223333 \
  --output text
```

您应该可以看到类似于如下所示的输出内容：

```
{"Sid":"xaccount","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::111122223333:root"},"Action":"lambda:GetLayerVersion","Resource":"arn:aws:lambda:us-east-1:123456789012:layer:bash-runtime:2"}
```

权限仅适用于单个层版本。每次创建新的层版本时都需重复此过程。

## 清理
<a name="runtimes-walkthrough-cleanup"></a>

删除每个版本的层。

```
aws lambda delete-layer-version --layer-name bash-runtime --version-number 1
aws lambda delete-layer-version --layer-name bash-runtime --version-number 2
```

由于函数包含对版本 2 的层的引用，因此该层仍然存在于 Lambda 中。函数可继续工作，但无法再被配置为使用删除的版本。如果您修改了函数的层的列表，则必须指定新版本或忽略已删除的层。

使用 [delete-function](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/delete-function.html) 命令删除函数。

```
aws lambda delete-function --function-name bash-runtime
```