

# 使用 Ruby Lambda 函数的层
<a name="ruby-layers"></a>

使用 [Lambda 层](chapter-layers.md)来打包要在多个函数中重复使用的代码和依赖项。层通常包含库依赖项、[自定义运行时系统](runtimes-custom.md)或配置文件。创建层涉及三个常见步骤：

1. 打包层内容。此步骤需要创建 .zip 文件存档，其中包含要在函数中使用的依赖项。

1. 在 Lambda 中创建层。

1. 将层添加到函数。

**Topics**
+ [打包层内容](#ruby-layers-package)
+ [在 Lambda 中创建层](#publishing-layer)
+ [在函数中使用层中的 Gem](#ruby-layers-bundler-limitations)
+ [将层添加到函数](#ruby-layer-adding)
+ [示例应用程序](#ruby-layer-sample-app)

## 打包层内容
<a name="ruby-layers-package"></a>

要创建层，请将您的包捆绑到满足以下要求的 .zip 文件存档中：
+ 使用计划用于 Lambda 函数的相同 Ruby 版本来创建层。例如，如果为 Ruby 3.4 创建层，则函数应使用 Ruby 3.4 运行时。
+ 您层的 .zip 文件必须使用以下目录结构之一：
  + `ruby/gems/x.x.x`（其中 *x.x.x* 为 Ruby 版本，例如 `3.4.0`）
  + `ruby/lib`

  有关更多信息，请参阅 [每个 Lambda 运行时的层路径](packaging-layers.md#packaging-layers-paths)。
+ 您的层中的包必须与 Linux 兼容。Lambda 函数在 Amazon Linux 上运行。

您可以创建包含第三方 Ruby Gem 或自己的 Ruby 模块和类的层。许多主流的 Ruby Gem 包含必须针对 Lambda Linux 环境进行编译的本机扩展（C 代码）。

### 纯 Ruby Gem
<a name="ruby-layers-pure-ruby-gems"></a>

纯 Ruby Gem 仅包含 Ruby 代码，无需编译。这些 Gem 更易于打包和跨平台使用。

**使用纯 Ruby Gem 创建层**

1. 创建一个 `Gemfile`，指定要包含在层中的纯 Ruby Gem：  
**Example Gemfile**  

   ```
   source 'https://rubygems.org'
   
   gem 'tzinfo'
   ```

1. 使用捆绑程序将 Gem 安装到 `vendor/bundle` 目录：

   ```
   bundle config set --local path vendor/bundle
   bundle install
   ```

1. 将已安装的 Gem 复制到 Lambda 需要的目录结构 `ruby/gems/3.4.0`）：

   ```
   mkdir -p ruby/gems/3.4.0
   cp -r vendor/bundle/ruby/3.4.0*/* ruby/gems/3.4.0/
   ```

1. 压缩层内容：

------
#### [ Linux/macOS ]

   ```
   zip -r layer.zip ruby/
   ```

------
#### [ PowerShell ]

   ```
   Compress-Archive -Path .\ruby -DestinationPath .\layer.zip
   ```

------

   您的 .zip 文件的目录结构应如下所示：

   ```
   ruby/              
   └── gems/
       └── 3.4.0/
           ├── gems/
           │   ├── concurrent-ruby-1.3.5/
           │   └── tzinfo-2.0.6/
           ├── specifications/
           ├── cache/
           ├── build_info/
           └── (other bundler directories)
   ```
**注意**  
您必须在函数代码中单独引入各个 Gem。您不能使用 `bundler/setup`，也不能使用 `Bundler.require`。有关更多信息，请参阅 [在函数中使用层中的 Gem](#ruby-layers-bundler-limitations)。

### 带有本机扩展的 Gem
<a name="ruby-layers-native-extensions"></a>

许多主流的 Ruby Gem 包含必须针对目标平台进行编译的本机扩展（C 代码）。带本机扩展的主流 Gem 包括 [nokogiri](https://rubygems.org/gems/nokogiri/)、[pg](https://rubygems.org/gems/pg/)、[mysql2](https://rubygems.org/gems/mysql2/)、[sqlite3](https://rubygems.org/gems/sqlite3/) 和 [ffi](https://rubygems.org/gems/ffi/)。这些 Gem 必须在兼容 Lambda 运行时的 Linux 环境中构建。

**使用带本机扩展的 Gem 来创建层**

1. 创建 `Gemfile`。  
**Example Gemfile**  

   ```
   source 'https://rubygems.org'
   
   gem 'nokogiri'
   gem 'httparty'
   ```

1. 使用 Docker 在兼容 Lambda 的 Linux 环境中构建 Gem。在 Dockerfile 中指定一个[AWS基础映像](ruby-image.md#ruby-image-base)：  
**Example 适用于 Ruby 3.4 的 Dockerfile**  

   ```
   FROM public.ecr.aws/lambda/ruby:3.4
   
   # Copy Gemfile
   COPY Gemfile ./
   
   # Install system dependencies for native extensions
   RUN dnf update -y && \
       dnf install -y gcc gcc-c++ make
   
   # Configure bundler and install gems
   RUN bundle config set --local path vendor/bundle && \
       bundle install
   
   # Create the layer structure
   RUN mkdir -p ruby/gems/3.4.0 && \
       cp -r vendor/bundle/ruby/3.4.0*/* ruby/gems/3.4.0/
   
   # Create the layer zip file
   RUN zip -r layer.zip ruby/
   ```

1. 构建映像并提取层：

   ```
   docker build -t ruby-layer-builder .
   docker run --rm -v $(pwd):/output --entrypoint cp ruby-layer-builder layer.zip /output/
   ```

   这将在合适的 Linux 环境中构建 Gem，并将 `layer.zip` 文件复制到本地目录。您的 .zip 文件的目录结构应如下所示：

   ```
   ruby/
   └── gems/
       └── 3.4.0/
           ├── gems/
           │   ├── bigdecimal-3.2.2/
           │   ├── csv-3.3.5/
           │   ├── httparty-0.23.1/
           │   ├── mini_mime-1.1.5/
           │   ├── multi_xml-0.7.2/
           │   ├── nokogiri-1.18.8-x86_64-linux-gnu/
           │   └── racc-1.8.1/
           ├── build_info/
           ├── cache/
           ├── specifications/
           └── (other bundler directories)
   ```
**注意**  
您必须在函数代码中单独引入各个 Gem。您不能使用 `bundler/setup`，也不能使用 `Bundler.require`。有关更多信息，请参阅 [在函数中使用层中的 Gem](#ruby-layers-bundler-limitations)。

### 自定义 Ruby 模块
<a name="custom-ruby-modules"></a>

**使用您自己的代码创建层**

1. 为您的层创建所需的目录结构：

   ```
   mkdir -p ruby/lib
   ```

1. 在 `ruby/lib` 目录中创建您的 Ruby 模块。以下示例模块通过确认订单包含所需信息来验证订单。  
**Example ruby/lib/order\$1validator.rb**  

   ```
   require 'json'
   
   module OrderValidator
     class ValidationError < StandardError; end
   
     def self.validate_order(order_data)
       # Validates an order and returns formatted data
       required_fields = %w[product_id quantity]
       
       # Check required fields
       missing_fields = required_fields.reject { |field| order_data.key?(field) }
       unless missing_fields.empty?
         raise ValidationError, "Missing required fields: #{missing_fields.join(', ')}"
       end
       
       # Validate quantity
       quantity = order_data['quantity']
       unless quantity.is_a?(Integer) && quantity > 0
         raise ValidationError, 'Quantity must be a positive integer'
       end
       
       # Format and return the validated data
       {
         'product_id' => order_data['product_id'].to_s,
         'quantity' => quantity,
         'shipping_priority' => order_data.fetch('priority', 'standard')
       }
     end
   
     def self.format_response(status_code, body)
       # Formats the API response
       {
         statusCode: status_code,
         body: JSON.generate(body)
       }
     end
   end
   ```

1. 压缩层内容：

------
#### [ Linux/macOS ]

   ```
   zip -r layer.zip ruby/
   ```

------
#### [ PowerShell ]

   ```
   Compress-Archive -Path .\ruby -DestinationPath .\layer.zip
   ```

------

   您的 .zip 文件的目录结构应如下所示：

   ```
   ruby/              
   └── lib/
       └── order_validator.rb
   ```

1. 在您的函数中，需要并使用这些模块。您必须在函数代码中单独引入各个 Gem。您不能使用 `bundler/setup`，也不能使用 `Bundler.require`。有关更多信息，请参阅 [在函数中使用层中的 Gem](#ruby-layers-bundler-limitations)。示例：

   ```
   require 'json'
   require 'order_validator'
   
   def lambda_handler(event:, context:)
     begin
       # Parse the order data from the event body
       order_data = JSON.parse(event['body'] || '{}')
       
       # Validate and format the order
       validated_order = OrderValidator.validate_order(order_data)
       
       OrderValidator.format_response(200, {
         message: 'Order validated successfully',
         order: validated_order
       })
     rescue OrderValidator::ValidationError => e
       OrderValidator.format_response(400, {
         error: e.message
       })
     rescue => e
       OrderValidator.format_response(500, {
         error: 'Internal server error'
       })
     end
   end
   ```

   您可以使用以下[测试事件](testing-functions.md#invoke-with-event)调用函数：

   ```
   {
       "body": "{\"product_id\": \"ABC123\", \"quantity\": 2, \"priority\": \"express\"}"
   }
   ```

   预期的回应：

   ```
   {
     "statusCode": 200,
     "body": "{\"message\":\"Order validated successfully\",\"order\":{\"product_id\":\"ABC123\",\"quantity\":2,\"shipping_priority\":\"express\"}}"
   }
   ```

## 在 Lambda 中创建层
<a name="publishing-layer"></a>

您可以使用 AWS CLI 或 Lambda 控制台发布层。

------
#### [ AWS CLI ]

运行 [publish-layer-version](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/publish-layer-version.html) AWS CLI 命令以创建 Lambda 层：

```
aws lambda publish-layer-version --layer-name my-layer --zip-file fileb://layer.zip --compatible-runtimes ruby3.4
```

[兼容的运行时](https://docs.aws.amazon.com/lambda/latest/api/API_PublishLayerVersion.html#lambda-PublishLayerVersion-request-CompatibleRuntimes)参数是可选的。指定后，Lambda 将使用此参数在 Lambda 控制台中筛选层。

------
#### [ Console ]

**创建层（控制台）**

1. 打开 Lambda 控制台的 [Layers page](https://console.aws.amazon.com/lambda/home#/layers)（层页面）。

1. 选择 **Create layer**（创建层）。

1. 选择**上传 .zip 文件**，然后上传您之前创建的 .zip 存档。

1. （可选）对于**兼容的运行时**，请选择与您用于构建层的 Ruby 版本相对应的 Ruby 运行时。

1. 选择**创建**。

------

## 在函数中使用层中的 Gem
<a name="ruby-layers-bundler-limitations"></a>

在函数代码中，必须显式引入要使用的各个 gem。不支持 `bundler/setup` 和 `Bundler.require` 等捆绑程序命令。下面介绍了如何在 Lambda 函数中正确使用层中的 Gem：

```
# Correct: Use explicit requires for each gem
require 'nokogiri'
require 'httparty'

def lambda_handler(event:, context:)
  # Use the gems directly
  doc = Nokogiri::HTML(event['html'])
  response = HTTParty.get(event['url'])
  # ... rest of your function
end

# Incorrect: These Bundler commands will not work
# require 'bundler/setup'
# Bundler.require
```

## 将层添加到函数
<a name="ruby-layer-adding"></a>

------
#### [ AWS CLI ]

要将层附加到函数，请运行 [update-function-configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/update-function-configuration.html) AWS CLI 命令。对于 `--layers` 参数，使用层 ARN。ARN 必须指定版本（例如 `arn:aws:lambda:us-east-1:123456789012:layer:my-layer:1`）。有关更多信息，请参阅 [层和层版本](chapter-layers.md#lambda-layer-versions)。

```
aws lambda update-function-configuration --function-name my-function --cli-binary-format raw-in-base64-out --layers "arn:aws:lambda:us-east-1:123456789012:layer:my-layer:1"
```

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

------
#### [ Console ]

**向函数添加层**

1. 打开 Lamba 控制台的[函数页面](https://console.aws.amazon.com/lambda/home#/functions)。

1. 选择函数。

1. 向下滚动到**层**部分，然后选择**添加层**。

1. 在**选择层**下，选择**自定义层**，然后选择您的层。
**注意**  
如果您在创建层时没有添加[兼容的运行时](https://docs.aws.amazon.com/lambda/latest/api/API_PublishLayerVersion.html#lambda-PublishLayerVersion-request-CompatibleRuntimes)，则您的层将不会在此处列出。您可以改为指定层 ARN。

1. 选择**添加**。

------

## 示例应用程序
<a name="ruby-layer-sample-app"></a>

有关如何使用 Lambda 层的更多示例，请参阅 AWS Lambda Developer Guide GitHub 存储库中的 [layer-ruby](https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/layer-ruby) 示例应用程序。此应用程序中有一个包含 [tzinfo](https://rubygems.org/gems/tzinfo) 库的层。创建层后，即可部署并调用相应的函数来确认层是否按预期运行。