使用sam框架可以在部署serverless应用之前,在本地调试application是否符合预期
sam框架安装
serverless应用是lambda函数,事件源和其他资源的组合
使用sam能够基于docker容器在本地测试lambda函数
安装sam
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install --update
sam --version
示例程序中三个比较重要的文件
 sam-app/
   ├── README.md
   ├── events/
   │   └── event.json
   ├── hello_world/
   │   ├── __init__.py
   │   ├── app.py            #Contains your AWS Lambda handler logic.
   │   └── requirements.txt  #Contains any Python dependencies the application requires, used for sam build
   ├── template.yaml         #Contains the AWS SAM template defining your application's AWS resources.
   └── tests/
       └── unit/
           ├── __init__.py
           └── test_handler.py
- template.yaml:包含Amazon SAM定义应用程序的模板Amazon资源的费用
- hello_world/app.py:包含实际的 Lambda 处理程序逻辑
- hello_world/requirements.txt:包含应用程序所需的任何 Python 依赖项,用于- sam build
测试和调试serverless
测试和调试无服务器应用程序
本地调用lambda
sam local invoke对应lambda命令aws lambda invoke
$ sam local invoke "Ratings" -e event.json
$ echo '{"message": "Hey, are you there?" }' | sam local invoke --event - "Ratings"
注意:如果函数包含layer,本地调用或测试时层包将下载并缓存在本地
sam本地调用主要通过template.yaml识别函数
$ sam build
Build Succeeded
Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml
使用官方image构建lambda镜像,将构建完毕的
$ echo '{"message": "Hey, are you there?" }' | sam local invoke "HelloWorldFunction"
Invoking app.lambda_handler (python3.7)
Image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-python3.7
Building image.....
Skip pulling image and use local one: public.ecr.aws/sam/emulation-python3.7:rapid-1.66.0-x86_64.
Mounting xxxxx/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: c95f8298-2ff8-4eed-ab34-8ba730ac62f1 Version: $LATEST
END RequestId: c95f8298-2ff8-4eed-ab34-8ba730ac62f1
REPORT RequestId: c95f8298-2ff8-4eed-ab34-8ba730ac62f1  Init Duration: 2.54 ms  Duration: 111.97 ms     Billed Duration: 112 ms      Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
SAM CLI update available (1.68.0); (1.66.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
本地调用apigateway
使用sam local start-api在本地启动api,可以热重载。sam识别template.yaml模板中。
默认sam使用lambda代理集成,并支持http api 和 rest api
示例程序如下
import json
import requests
def lambda_handler(event, context):
    try:
        ip = requests.get("http://checkip.amazonaws.com/")
    except requests.RequestException as e:
        print(e)
        raise e
    
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
            "location": ip.text.replace("\n", "")
        }),
    }
示例模板如下
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: rating app
Globals:
  Function:
    Timeout: 3
    MemorySize: 128
Resources:
  Ratings:
    Type: AWS::Serverless::Function
    Properties:
      Handler: ratings.lambda_handler
      Runtime: python3.7
      Architectures:
      - x86_64
      Events:
        Api:
          Type: Api
          Properties:
            Path: /ratings
            Method: get
      CodeUri: Ratings
    Metadata:
      SamResourceId: Ratings
当访问对应的api时,会启动构建,并将代码通过挂载卷的方式挂载到容器中
$ sam local start-api
Mounting Ratings at http://127.0.0.1:3000/ratings [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. If you used sam build before running local commands, you will need to re-run sam build for the changes to be picked up. You only need to restart SAM CLI if you update your AWS SAM template
2023-01-09 15:19:21  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
Invoking ratings.lambda_handler (python3.7)
Skip pulling image and use local one: public.ecr.aws/sam/emulation-python3.7:rapid-1.66.0-x86_64.
Mounting /home/ec2-user/efs/aws-sam-test/ratingdemo/.aws-sam/build/Ratings as /var/task:ro,delegated inside runtime container
START RequestId: a5b6a479-c011-4139-b142-98c0e88a0e6c Version: $LATEST
END RequestId: a5b6a479-c011-4139-b142-98c0e88a0e6c
REPORT RequestId: a5b6a479-c011-4139-b142-98c0e88a0e6c  Init Duration: 0.13 ms  Duration: 592.02 ms     Billed Duration: 593 ms  Memory Size: 128 MB     Max Memory Used: 128 MB
No Content-Type given. Defaulting to 'application/json'.
2023-01-09 15:19:25 127.0.0.1 - - [09/Jan/2023 15:19:25] "GET /ratings HTTP/1.1" 200 -
此外,sam还提供了模拟lambda终端节点,生成模拟事件的命令
sam local start-lambda
sam local generate-event s3 put
可以使用vscode插件方便管理和编写lambda函数
https://docs.aws.amazon.com/zh_cn/toolkit-for-vscode/latest/userguide/remote-lambda.html
sam集成codedeploy部署lambda
使 Lambda 码部署和Amazon无服务器应用程序模型
将应用程序规范文件添加到 CodeDeploy 的 revision
sam本身能够将将代码打包为lambda函数,并通过蓝绿部署的方式发布。sam使用cloudformation创建lambda函数和codedeploy,同时完成版本切换和蓝绿发布
打包并部署命令如下
sam package \
  --template-file template.yml \
  --output-template-file package.yml \
  --s3-bucket s3://xxxxx/xxxx
  
$ sam deploy \
  --template-file package.yml \
  --stack-name my-date-time-app \
  --capabilities CAPABILITY_IAM
实例代码的template.yaml文件,注释的非常清楚,需要注意以下几点
-  函数的资源类型为 AWS::Serverless::Function,AWS::Serverless是cloudformation提供的官方宏,实质是对lambda函数写法的简化,cfn会将模板转换并扩展为兼容的cfn模板AWSTemplateFormatVersion : '2010-09-09' Transform: AWS::Serverless-2016-10-31 Resources: myDateTimeFunction: Type: AWS::Serverless::Function Properties: Handler: myDateTimeFunction.handler Runtime: nodejs16.x AutoPublishAlias: live ... DeploymentPreference: Type: Linear10PercentEvery1Minute Hooks: PreTraffic: !Ref beforeAllowTraffic PostTraffic: !Ref afterAllowTraffic
-  当在serverless中指定 AutoPublishAlias时,lambda函数会创建alias,检测函数变化并通过名为live的别名部署
-  DeploymentPreference是独属于sam的属性,启用该属性后转换的cfn模板会创建codedeploy 应用,部署组和角色对lambda,同时可以在hook中指定流量切换前后的测试函数。测试函数在lambda控制台上显示为关联函数

测试函数的主要逻辑在于
- 调用主函数(函数名通过环境变量传入)
- 将调用结果回传codedeploy(需要使用事件中的deploymentId和lifecycleEventHookExecutionId参数)
'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!");
    var deploymentId = event.DeploymentId;
    var lifecycleEventHookExecutionId = event.LifecycleEventHookExecutionId;
    var functionToTest = process.env.NewVersion;
    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);
            if (result.statusCode != "400") {
                console.log("Validation succeeded");
                lambdaResult = "Succeeded";
            }
            else {
                console.log("Validation failed");
            }
            var params = {
                deploymentId: deploymentId,
                lifecycleEventHookExecutionId: lifecycleEventHookExecutionId,
                status: lambdaResult
            };
            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");
                }
            });
        }
    });
}
生成的codedeploy修订如下,即通过将别名指向不同版本完成流量切换
{
  "version": "0.0",
  "Resources": [
    {
      "my-date-time-app-myDateTimeFunction-L7EvikmOAZm9": {
        "Type": "AWS::Lambda::Function",
        "Properties": {
          "Name": "my-date-time-app-myDateTimeFunction-L7EvikmOAZm9",
          "Alias": "live",
          "CurrentVersion": "1",
          "TargetVersion": "2"
        }
      }
    }
  ],
  "Hooks": [
    {
      "BeforeAllowTraffic": "CodeDeployHook_beforeAllowTraffic"
    },
    {
      "AfterAllowTraffic": "CodeDeployHook_afterAllowTraffic"
    }
  ]
}
-  serverless模板转换后的serverless函数会转化成普通的lambda函数资源,通过alias资源的update策略,当函数版本变化时触发codedeploy操作 myDateTimeFunctionAliaslive: Type: 'AWS::Lambda::Alias' UpdatePolicy: CodeDeployLambdaAliasUpdate: ApplicationName: Ref: ServerlessDeploymentApplication DeploymentGroupName: Ref: myDateTimeFunctionDeploymentGroup BeforeAllowTrafficHook: Ref: beforeAllowTraffic AfterAllowTrafficHook: Ref: afterAllowTraffic Properties: Name: live FunctionName: Ref: myDateTimeFunction FunctionVersion: 'Fn::GetAtt': - myDateTimeFunctionVersion368eb951f6 - Version
-  部署配置为 Linear10PercentEvery1Minute,即codedeploy的流量切换为线性每分钟切换10% 



















