

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 步骤 1：设置基础设施
<a name="tutorial-lambda-sam-setup-infrastructure"></a>

 本主题向您展示 AWS SAM 如何使用为您的 AWS SAM 模板和 Lambda 函数创建文件。然后，使用 AWS SAM `package`和`deploy`命令在基础架构中生成组件。基础设施准备就绪后，您将拥有一个 CodeDeploy 应用程序和部署组、一个要更新和部署的 Lambda 函数，以及两个 Lambda 函数，其中包含在您部署 Lambda 函数时运行的验证测试。完成后，您可以使用 CloudFormation 在 Lambda 控制台中查看您的组件，或者使用 AWS CLI 来测试您的 Lambda 函数。

**Topics**
+ [创建文件](tutorial-lambda-create-files.md)
+ [Package 打包 AWS SAM 应用程序](tutorial-lambda-sam-package.md)
+ [部署 S AWS AM 应用程序](tutorial-lambda-sam-deploy.md)
+ [（可选）检查并测试基础设施](tutorial-lambda-sam-confirm-components.md)

# 创建文件
<a name="tutorial-lambda-create-files"></a>

 要创建基础设施，必须创建以下文件：
+ `template.yml`
+ `myDateTimeFunction.js`
+ `beforeAllowTraffic.js`
+ `afterAllowTraffic.js`

**Topics**
+ [创建你的 AWS SAM 模板](tutorial-lambda-sam-template.md)
+ [为 Lambda 函数创建文件](tutorial-lambda-sam-create-lambda-function.md)
+ [为您的 BeforeAllowTraffic Lambda 函数创建一个文件](tutorial-lambda-sam-create-lambda-before-traffic.md)
+ [为您的 AfterAllowTraffic Lambda 函数创建一个文件](tutorial-lambda-sam-create-lambda-after-traffic.md)

# 创建你的 AWS SAM 模板
<a name="tutorial-lambda-sam-template"></a>

创建一个 AWS SAM 模板文件来指定基础架构中的组件。

**创建 AWS SAM 模板**

1.  创建一个名为 `SAM-Tutorial` 的目录。

1.  在 `SAM-Tutorial` 目录中创建名为 `template.yml` 的文件。

1.  将以下 YAML 代码复制到 `template.yml` 中。这是 AWS SAM 模板。

   ```
   AWSTemplateFormatVersion : '2010-09-09'
   Transform: AWS::Serverless-2016-10-31
   Description: A sample SAM template for deploying Lambda functions.
   
   Resources:
   # Details about the myDateTimeFunction Lambda function
     myDateTimeFunction:
       Type: AWS::Serverless::Function
       Properties:
         Handler: myDateTimeFunction.handler
         Runtime: nodejs18.x
   # Instructs your myDateTimeFunction is published to an alias named "live".      
         AutoPublishAlias: live
   # Grants this function permission to call lambda:InvokeFunction
         Policies:
           - Version: "2012-10-17"		 	 	 
             Statement: 
             - Effect: "Allow"
               Action: 
                 - "lambda:InvokeFunction"
               Resource: '*'
         DeploymentPreference:
   # Specifies the deployment configuration      
             Type: Linear10PercentEvery1Minute
   # Specifies Lambda functions for deployment lifecycle hooks
             Hooks:
               PreTraffic: !Ref beforeAllowTraffic
               PostTraffic: !Ref afterAllowTraffic
               
   # Specifies the BeforeAllowTraffic lifecycle hook Lambda function
     beforeAllowTraffic:
       Type: AWS::Serverless::Function
       Properties:
         Handler: beforeAllowTraffic.handler
         Policies:
           - Version: "2012-10-17"		 	 	 
   # Grants this function permission to call codedeploy:PutLifecycleEventHookExecutionStatus        
             Statement: 
             - Effect: "Allow"
               Action: 
                 - "codedeploy:PutLifecycleEventHookExecutionStatus"
               Resource:
                 !Sub 'arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*'
           - Version: "2012-10-17"		 	 	 
   # Grants this function permission to call lambda:InvokeFunction        
             Statement: 
             - Effect: "Allow"
               Action: 
                 - "lambda:InvokeFunction"
               Resource: !Ref myDateTimeFunction.Version
         Runtime: nodejs18.x
   # Specifies the name of the Lambda hook function      
         FunctionName: 'CodeDeployHook_beforeAllowTraffic'
         DeploymentPreference:
           Enabled: false
         Timeout: 5
         Environment:
           Variables:
             NewVersion: !Ref myDateTimeFunction.Version
             
   # Specifies the AfterAllowTraffic lifecycle hook Lambda function
     afterAllowTraffic:
       Type: AWS::Serverless::Function
       Properties:
         Handler: afterAllowTraffic.handler
         Policies:
           - Version: "2012-10-17"		 	 	 
             Statement: 
   # Grants this function permission to call codedeploy:PutLifecycleEventHookExecutionStatus         
             - Effect: "Allow"
               Action: 
                 - "codedeploy:PutLifecycleEventHookExecutionStatus"
               Resource:
                 !Sub 'arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*'
           - Version: "2012-10-17"		 	 	 
             Statement: 
   # Grants this function permission to call lambda:InvokeFunction          
             - Effect: "Allow"
               Action: 
                 - "lambda:InvokeFunction"
               Resource: !Ref myDateTimeFunction.Version
         Runtime: nodejs18.x
   # Specifies the name of the Lambda hook function      
         FunctionName: 'CodeDeployHook_afterAllowTraffic'
         DeploymentPreference:
           Enabled: false
         Timeout: 5
         Environment:
           Variables:
             NewVersion: !Ref myDateTimeFunction.Version
   ```

此模板指定以下内容。有关更多信息，请参阅 [AWS SAM 模板概念](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html)。

**一个名为 `myDateTimeFunction` 的 Lambda 函数**  
 发布此 Lambda 函数时，模板中的 `AutoPublishAlias` 行将其链接到名为 `live` 的别名。在本教程的后面部分，此函数的更新会触发部署 AWS CodeDeploy ，从而逐步将生产流量从原始版本转移到更新的版本。

**两个 Lambda 部署验证函数**  
 以下 Lambda 函数是在 CodeDeploy 生命周期挂钩期间执行的。该函数包含代码，用于验证更新的 `myDateTimeFunction` 的部署。验证测试的结果通过 `PutLifecycleEventHookExecutionStatus` API 方法传递给 CodeDeploy 。如果验证测试失败，则部署失败并回滚。  
+  `CodeDeployHook_beforeAllowTraffic` 在 `BeforeAllowTraffic` 挂钩期间运行。
+  `CodeDeployHook_afterAllowTraffic` 在 `AfterAllowTraffic` 挂钩期间运行。
这两个函数的名称以 `CodeDeployHook_` 开头。`CodeDeployRoleForLambda` 角色仅允许在 Lambda 函数中，采用以此前缀开头的名称调用 Lambda `invoke` 方法。有关更多信息，请参阅《*CodeDeploy API 参考*》中的[AppSpec AWS Lambda 部署的 “挂钩” 部分](reference-appspec-file-structure-hooks.md#appspec-hooks-lambda)和[PutLifecycleEventHookExecutionStatus](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_PutLifecycleEventHookExecutionStatus.html)。

**自动检测更新的 Lambda 函数**  
 `AutoPublishAlias` 术语指示框架检测 `myDateTimeFunction` 函数何时发生了变化，然后使用 `live` 别名进行部署。

**部署配置**  
 部署配置决定了您的 CodeDeploy应用程序将流量从原始版本的 Lambda 函数转移到新版本的速率。此模板指定预定义的部署配置 `Linear10PercentEvery1Minute`。  
 您无法在 SA AWS M 模板中指定自定义部署配置。有关更多信息，请参阅 [使用创建部署配置 CodeDeploy](deployment-configurations-create.md)。

**部署生命周期挂钩函数**  
 `Hooks` 部分指定在生命周期事件挂钩期间运行的函数。`PreTraffic` 指定在 `BeforeAllowTraffic` 挂钩期间运行的函数。`PostTraffic` 指定在 `AfterAllowTraffic` 挂钩期间运行的函数。

**Lambda 调用另一个 Lambda 函数的权限**  
 指定的`lambda:InvokeFunction`权限授予 AWS SAM 应用程序使用的角色调用 Lambda 函数的权限。当 `CodeDeployHook_beforeAllowTraffic` 和 `CodeDeployHook_afterAllowTraffic` 函数在验证测试期间调用部署的 Lambda 函数时，必须具备该权限。

# 为 Lambda 函数创建文件
<a name="tutorial-lambda-sam-create-lambda-function"></a>

本教程稍后将为更新和部署的函数创建文件。

**注意**  
 Lambda 函数可以使用 AWS Lambda支持的任何运行时。有关更多信息，请参阅 [AWS Lambda 运行时](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html)。

**创建 Lambda 函数**

1.  创建文本文件，并以 `myDateTimeFunction.js` 文件形式保存到 `SAM-Tutorial` 目录中。

1.  将以下 Node.js 代码复制到 `myDateTimeFunction.js` 中。

   

   ```
   'use strict';
       
       exports.handler = function(event, context, callback) {
       
         if (event.body) {
           event = JSON.parse(event.body);
         }
       
         var sc; // Status code
         var result = ""; // Response payload
       
         switch(event.option) {
           case "date": 
             switch(event.period) {
               case "yesterday":
                 result = setDateResult("yesterday");
                 sc = 200;
                 break;
               case "today":
                 result = setDateResult();
                 sc = 200;
                 break;
               case "tomorrow":
                 result = setDateResult("tomorrow");
                 sc = 200;
                 break;
               default:
                 result = {
                   "error": "Must specify 'yesterday', 'today', or 'tomorrow'."
                 };
                 sc = 400;
                 break;
             }
             break;
             
       /*      Later in this tutorial, you update this function by uncommenting 
               this section. The framework created by AWS SAM detects the update 
               and triggers a deployment by CodeDeploy. The deployment shifts 
               production traffic to the updated version of this function.
               
               case "time":
               var d = new Date();
               var h = d.getHours();
               var mi = d.getMinutes();
               var s = d.getSeconds();
       
               result = {
                 "hour": h,
                 "minute": mi,
                 "second": s
               };
               sc = 200;
               break;
       */
             default:
               result = {
                 "error": "Must specify 'date' or 'time'."
               };
               sc = 400;
             break;
         }
       
         const response = {
           statusCode: sc,
           headers: { "Content-type": "application/json" },
           body: JSON.stringify( result )
         };
       
         callback(null, response);
       
         function setDateResult(option) {
       
           var d = new Date(); // Today
           var mo; // Month
           var da; // Day
           var y; // Year
       
           switch(option) {
             case "yesterday":
               d.setDate(d.getDate() - 1);
               break;
             case "tomorrow":
               d.setDate(d.getDate() + 1);
             default:
              break;
           }
       
           mo = d.getMonth() + 1; // Months are zero offset (0-11)
           da = d.getDate();
           y = d.getFullYear();
       
           result = {
             "month": mo,
             "day": da,
             "year": y
           };
       
           return result;
         }
       };
   ```

Lambda 函数返回昨天、今天或明天的日期、月份和年份。在本教程后面的部分中，您将取消注释更新函数的代码，以返回有关您指定的日期或时间的信息（例如，日期、月份和年份，或当前小时、分钟和秒）。创建的框架 AWS SAM 会检测并部署该函数的更新版本。

**注意**  
 教程中也使用了此 Lambda 函数。 AWS Cloud9 AWS Cloud9 是一个基于云的集成开发环境。有关如何在中创建、执行、更新和调试此函数的信息 AWS Cloud9，请参阅[的AWS Lambda 教程 AWS Cloud9](https://docs.aws.amazon.com/cloud9/latest/user-guide/tutorial-lambda.html)。

# 为您的 BeforeAllowTraffic Lambda 函数创建一个文件
<a name="tutorial-lambda-sam-create-lambda-before-traffic"></a>

为 `beforeAllowTraffic` 挂钩 Lambda 函数创建文件。

1.  创建文本文件，并以 `beforeAllowTraffic.js` 文件形式保存到 `SAM-Tutorial` 目录中。

1.  将以下 Node.js 代码复制到 `beforeAllowTraffic.js` 中。该函数在部署的 `BeforeAllowTraffic` 挂钩期间执行。

   ```
   'use strict';
       
       const AWS = require('aws-sdk'); 
       const codedeploy = new AWS.CodeDeploy({apiVersion: '2014-10-06'});
       var lambda = new AWS.Lambda();
       
       exports.handler = (event, context, callback) => {
       
       	console.log("Entering PreTraffic Hook!");
       	
       	// Read the DeploymentId and LifecycleEventHookExecutionId from the event payload
         var deploymentId = event.DeploymentId;
       	var lifecycleEventHookExecutionId = event.LifecycleEventHookExecutionId;
       
       	var functionToTest = process.env.NewVersion;
       	console.log("BeforeAllowTraffic hook tests started");
       	console.log("Testing new function version: " + functionToTest);
       
       	// Create parameters to pass to the updated Lambda function that
       	// include the newly added "time" option. If the function did not
       	// update, then the "time" option is invalid and function returns
       	// a statusCode of 400 indicating it failed.
       	var lambdaParams = {
       		FunctionName: functionToTest,    
       		Payload: "{\"option\": \"time\"}", 
       		InvocationType: "RequestResponse"
       	};
       
       	var lambdaResult = "Failed";
       	// Invoke the updated Lambda function.
       	lambda.invoke(lambdaParams, function(err, data) {
       		if (err){	// an error occurred
       			console.log(err, err.stack);
       			lambdaResult = "Failed";
       		}
       		else{	// successful response
       			var result = JSON.parse(data.Payload);
       			console.log("Result: " +  JSON.stringify(result));
             console.log("statusCode: " + result.statusCode);
             
             // Check if the status code returned by the updated
             // function is 400. If it is, then it failed. If 
             // is not, then it succeeded.
       			if (result.statusCode != "400"){
               console.log("Validation succeeded");
       				lambdaResult = "Succeeded";
             }
             else {
               console.log("Validation failed");
             }
       
       			// Complete the PreTraffic Hook by sending CodeDeploy the validation status
       			var params = {
       				deploymentId: deploymentId,
       				lifecycleEventHookExecutionId: lifecycleEventHookExecutionId,
       				status: lambdaResult // status can be 'Succeeded' or 'Failed'
       			};
       			
       			// Pass CodeDeploy the prepared validation test results.
       			codedeploy.putLifecycleEventHookExecutionStatus(params, function(err, data) {
       				if (err) {
       					// Validation failed.
       					console.log("CodeDeploy Status update failed");
       					console.log(err, err.stack);
       					callback("CodeDeploy Status update failed");
       				} else {
       					// Validation succeeded.
       					console.log("CodeDeploy status updated successfully");
       					callback(null, "CodeDeploy status updated successfully");
       				}
       			});
       		}  
       	});
       }
   ```

# 为您的 AfterAllowTraffic Lambda 函数创建一个文件
<a name="tutorial-lambda-sam-create-lambda-after-traffic"></a>

为 `afterAllowTraffic` 挂钩 Lambda 函数创建文件。

1.  创建文本文件，并以 `afterAllowTraffic.js` 文件形式保存到 `SAM-Tutorial` 目录中。

1.  将以下 Node.js 代码复制到 `afterAllowTraffic.js` 中。该函数在部署的 `AfterAllowTraffic` 挂钩期间执行。

   ```
   'use strict';
       
       const AWS = require('aws-sdk');
       const codedeploy = new AWS.CodeDeploy({apiVersion: '2014-10-06'});
       var lambda = new AWS.Lambda();
       
       exports.handler = (event, context, callback) => {
       
       	console.log("Entering PostTraffic Hook!");
       	
       	// Read the DeploymentId and LifecycleEventHookExecutionId from the event payload
         var deploymentId = event.DeploymentId;
       	var lifecycleEventHookExecutionId = event.LifecycleEventHookExecutionId;
       
       	var functionToTest = process.env.NewVersion;
       	console.log("AfterAllowTraffic hook tests started");
       	console.log("Testing new function version: " + functionToTest);
       
       	// Create parameters to pass to the updated Lambda function that
       	// include the original "date" parameter. If the function did not 
       	// update as expected, then the "date" option might be invalid. If 
       	// the parameter is invalid, the function returns
       	// a statusCode of 400 indicating it failed.
       	var lambdaParams = {
       		FunctionName: functionToTest,    
       		Payload: "{\"option\": \"date\", \"period\": \"today\"}", 
       		InvocationType: "RequestResponse"
       	};
       
       	var lambdaResult = "Failed";
       	// Invoke the updated Lambda function.
       	lambda.invoke(lambdaParams, function(err, data) {
       		if (err){	// an error occurred
       			console.log(err, err.stack);
       			lambdaResult = "Failed";
       		}
       		else{	// successful response
       			var result = JSON.parse(data.Payload);
       			console.log("Result: " +  JSON.stringify(result));
             console.log("statusCode: " + result.statusCode);
             
             // Check if the status code returned by the updated
             // function is 400. If it is, then it failed. If 
             // is not, then it succeeded.
       			if (result.statusCode != "400"){
               console.log("Validation of time parameter succeeded");
       				lambdaResult = "Succeeded";
             }
             else {
               console.log("Validation failed");
             }
       
       			// Complete the PostTraffic Hook by sending CodeDeploy the validation status
       			var params = {
       				deploymentId: deploymentId,
       				lifecycleEventHookExecutionId: lifecycleEventHookExecutionId,
       				status: lambdaResult // status can be 'Succeeded' or 'Failed'
       			};
       			
       			// Pass CodeDeploy the prepared validation test results.
       			codedeploy.putLifecycleEventHookExecutionStatus(params, function(err, data) {
       				if (err) {
       					// Validation failed.
       					console.log("CodeDeploy Status update failed");
       					console.log(err, err.stack);
       					callback("CodeDeploy Status update failed");
       				} else {
       					// Validation succeeded.
       					console.log("CodeDeploy status updated successfully");
       					callback(null, "CodeDeploy status updated successfully");
       				}
       			});
       		}  
       	});
       }
   ```

# Package 打包 AWS SAM 应用程序
<a name="tutorial-lambda-sam-package"></a>

 现在，`SAM-Tutorial` 目录下应当具备四个文件：
+ `beforeAllowTraffic.js`
+ `afterAllowTraffic.js`
+ `myDateTimeFunction.js`
+ `template.yml`

 现在，您可以使用 AWS SAM **sam package** 命令为您的 Lambda 函数和应用程序创建和 CodeDeploy 打包工件。构件将被上传到 S3 存储桶。命令的输出是名为 `package.yml` 的新文件。 AWS SAM **sam deploy** 命令将在下一步中使用此文件。

**注意**  
 有关 **sam package** 命令的更多信息，请参阅《AWS Serverless Application Model 开发人员指南》**中的 [AWS SAM CLI 命令参考](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html)。

 在 `SAM-Tutorial` 目录中，运行以下命令。

```
sam package \
  --template-file template.yml \
  --output-template-file package.yml \
  --s3-bucket amzn-s3-demo-bucket
```

对于 `s3-bucket` 参数，指定作为本教程先决条件而创建的 Amazon S3 存储桶。指`output-template-file`定 AWS SAM **sam deploy** 命令使用的新文件的名称。

# 部署 S AWS AM 应用程序
<a name="tutorial-lambda-sam-deploy"></a>

 使用带有`package.yml`文件的 AWS SAM **sam deploy** 命令来创建您的 Lambda 函数以及 CodeDeploy 应用程序和部署组。 CloudFormation

**注意**  
有关 **sam deploy** 命令的更多信息，请参阅《AWS Serverless Application Model 开发人员指南》**中的 [AWS SAM CLI 命令参考](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html)。

 在 `SAM-Tutorial` 目录中，运行以下命令。

```
sam deploy \
  --template-file package.yml \
  --stack-name my-date-time-app \
  --capabilities CAPABILITY_IAM
```

 `--capabilities CAPABILITY_IAM` 参数是授权 CloudFormation 创建 IAM 角色的必需项。

# （可选）检查并测试基础设施
<a name="tutorial-lambda-sam-confirm-components"></a>

 本主题介绍了如何查看基础设施组件以及测试 Lambda 函数。

**在运行 `sam deploy` 后查看堆栈结果**

1. 在 [https://console.aws.amazon.com/cloudformat](https://console.aws.amazon.com/cloudformation/) ion 上打开 CloudFormation 控制台。

1.  在导航窗格中，选择 **Stacks（堆栈）**。`my-date-time-app` 堆栈显示在顶部。

1.  选择 **Events（事件）**选项卡，以查看哪些事件已完成。您可以在堆栈创建过程中查看事件。堆栈创建完成后，您可以查看所有的堆栈创建事件。

1.  在已选择堆栈的情况下，选择 **Resources（资源）**。在**类型**列中，您可以看到 Lambda 函数、`myDateTimeFunction`、`CodeDeployHook_beforeAllowTraffic` 和 `CodeDeployHook_afterAllowTraffic`。您的每个 Lambda 函数的**物理 ID** 列都包含一个用于在 Lambda 控制台中查看这些函数的链接。
**注意**  
 `myDateTimeFunction`Lambda 函数的名称前面有 CloudFormation 堆栈的名称，并添加了一个标识符，所以看起来像。`my-date-time-app-myDateTimeFunction-123456ABCDEF`

1. 打开 CodeDeploy 控制台，网址为[https://console.aws.amazon.com/codedeploy/](https://console.aws.amazon.com/codedeploy/)。

1.  在导航窗格中，展开 **Deploy（部署）**，然后选择 **Applications（应用程序）**。

1.  您应该会看到一个由 CloudFormation 创建的新 CodeDeploy 应用程序，其名称以开头`my-date-time-app-ServerlessDeploymentApplication`。选择此应用程序。

1.  您应当看到一个名称以 `my-date-time-app-myDateTimeFunctionDeploymentGroup` 开头的部署组。选择此部署组。

    在 “**部署配置”** 下，您应该看到**CodeDeployDefault。 LambdaLinear10 PercentEvery 1分钟**。

**（可选）测试函数（控制台）**

1. 打开 AWS Lambda 控制台，网址为[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

1.  从导航窗格中，选择 `my-date-time-app-myDateTimeFunction` 函数。在控制台中，其名称包含一个标识符，因此看起来类似 `my-date-time-app-myDateTimeFunction-123456ABCDEF`。

1.  选择**测试**。

1.  在 **Event name（事件名称）**中，为测试事件输入名称。

1.  为测试事件输入以下内容，然后选择 **Create（创建）**。

   ```
   {
     "option": "date",
     "period": "today"
   }
   ```

1.  选择**测试**。在测试事件列表中，您应当只看到自己的测试事件。

    对于 **Execution result（执行结果）**，您应当看到 **succeeded（已成功）**。

1.  在 **Execution result（执行结果）**下，展开 **Details（详细信息）**以查看结果。您应当看到当前的月份、日期和年份。

**（可选）测试函数（AWS CLI）**

1.  找到 Lambda 函数的 ARN。当您查看函数时，它显示在 Lambda 控制台的顶部。

1.  运行如下命令。*your-function-arn*替换为函数 ARN。

   ```
   aws lambda invoke \
   --function your-function-arn \
   --cli-binary-format raw-in-base64-out \
   --payload "{\"option\": \"date\", \"period\": \"today\"}" out.txt
   ```

1.  打开 `out.txt` 以确认结果中是否包含当前的月份、日期和年份。