使用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%