本章使用CloudFormation来实现API网关的创建和自动部署。主要包括:
- 设置API网关写CloudWatch权限
- 创建API
- 创建资源
- 创建方法
- 配置Lambda权限
- 部署API服务
工程目录结构
准备3个java文件
代码同 Serverless-HelloWorld-3可以直接copy即可。
2. 准备Gradle的2个文件
代码同AWS Lambda教程-自动部署-5 可以直接copy即可。
cloudformation.template 文件说明
为了让大家先感受API网关,先将代码直接贴出来,大家先deploy下,将以下内容直接copy到cloudformation.template 文件,保存后在工程目录运行 ./gradlew deploy。然后在运行以下命令,查看serverlessbook的所有资源信息,其中重点关注AWS::ApiGateway::RestApi资源,其中"PhysicalResourceId": "aj4k22d99h" 是新部署的API的ID(这边需要替换成你的ID哦),我们可以尝试访问测试下:https://aj4k22d99h.execute-api.us-east-2.amazonaws.com/production/test?value=hello+world,这是你可以在浏览器里看到 hello world。
aws cloudformation describe-stack-resources --region us-east-2 --stack-name serverlessbook
cloudformation.template,往下看有模版关系图哦。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"DeploymentBucket": {
"Type": "String",
"Description": "S3 bucket name where built artifacts are deployed"
},
"ProjectVersion": {
"Type": "String",
"Description": "Project Version"
},
"DeploymentTime": {
"Type": "String",
"Description": "It is a timestamp value which shows the deployment time. Used to rotate sources."
}
},
"Resources": {
"DeploymentLambdaRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
],
"Policies": [
{
"PolicyName": "LambdaExecutionPolicy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:PublishVersion",
"apigateway:POST"
],
"Resource": [
"*"
]
}
]
}
}
]
}
},
"DeploymentLambda": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Role": {
"Fn::GetAtt": [
"DeploymentLambdaRole",
"Arn"
]
},
"Handler": "serverless.handler",
"Runtime": "nodejs12.x",
"Code": {
"S3Bucket": {
"Fn::Sub": "serverless-arch-${AWS::Region}"
},
"S3Key": "serverless.zip"
}
}
},
"ApiGatewayCloudwatchRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"apigateway.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
},
"Path": "/",
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
]
}
},
"ApiGatewayAccount": {
"Type": "AWS::ApiGateway::Account",
"Properties": {
"CloudWatchRoleArn": {
"Fn::GetAtt": [
"ApiGatewayCloudwatchRole",
"Arn"
]
}
}
},
"RestApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": {
"Ref": "AWS::StackName"
}
}
},
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"Path": "/",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
]
}
},
"LambdaCustomPolicy": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "LambdaCustomPolicy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBuckets"
],
"Resource": "*"
}
]
},
"Roles": [
{
"Ref": "LambdaExecutionRole"
}
]
}
},
"TestLambda": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "com.serverlessbook.lambda.test.Handler",
"Runtime": "java8",
"Timeout": "300",
"MemorySize": "1024",
"Description": "Test lambda",
"Role": {
"Fn::GetAtt": [
"LambdaExecutionRole",
"Arn"
]
},
"Code": {
"S3Bucket": {
"Ref": "DeploymentBucket"
},
"S3Key": {
"Fn::Sub": "artifacts/lambda-test/${ProjectVersion}/${DeploymentTime}.jar"
}
}
}
},
"TestResource": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"PathPart": "test",
"RestApiId": {
"Ref": "RestApi"
},
"ParentId": {
"Fn::GetAtt": [
"RestApi",
"RootResourceId"
]
}
}
},
"TestGetMethod": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "GET",
"RestApiId": {
"Ref": "RestApi"
},
"ResourceId": {
"Ref": "TestResource"
},
"AuthorizationType": "NONE",
"RequestParameters": {
"method.request.querystring.value": "True",
"method.request.header.Accept": "True"
},
"MethodResponses": [
{
"StatusCode": "200"
}
],
"Integration": {
"Type": "AWS",
"Uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${TestLambda.Arn}/invocations"
},
"IntegrationHttpMethod": "POST",
"RequestParameters": {
"integration.request.querystring.value": "method.request.querystring.value",
"integration.request.header.Accept": "method.request.header.Accept"
},
"RequestTemplates": {
"application/json": "{\"value\":\"$input.params('value')\"}"
},
"PassthroughBehavior": "NEVER",
"IntegrationResponses": [
{
"SelectionPattern": ".*",
"StatusCode": "200"
}
]
}
}
},
"TestLambdaPermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Ref": "TestLambda"
},
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*"
}
}
},
"ApiDeployment": {
"DependsOn": [
"TestGetMethod"
],
"Type": "Custom::ApiDeployment",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"DeploymentLambda",
"Arn"
]
},
"RestApiId": {
"Ref": "RestApi"
},
"StageName": "production",
"DeploymentTime": {
"Ref": "DeploymentTime"
}
}
}
}
}
看完图,我们接下来一块一块的解释,每个配置的内容。
1. 设置API网关写CloudWatch权限
"Resources": {
"ApiGatewayCloudwatchRole": {
// AWS::IAM::Role** 为您的 AWS 账户创建新角色。(角色:可在账户中创建的具有特定权限的 IAM 身份。
//IAM 角色与 IAM 用户有一些相似之处。角色和用户均为 AWS 身份
//这些身份具有确定其在 AWS 中可执行和不可执行的操作的权限策略。)
"Type": "AWS::IAM::Role",
"Properties": {
//与此角色关联的信任策略。信任策略定义哪些实体可以代入该角色。只能将一个信任策略与一个角色关联。
"AssumeRolePolicyDocument": {
//策略元素指定用于处理策略的语言语法规则。目前有两种版本2012-10-17 和 2008-10-17,
//建议使用最新版本,否则有些变量无法识别例如: ${aws:username} 在 2008-10-17版本中将无法识别。
"Version": "2012-10-17",
"Statement": [ //元素为策略的主要元素。
{
//用户申明产生的结果是“Allow”还是“Deny”,默认“Deny”。
//如果允许访问资源必须设置为“Allow”。
"Effect": "Allow",
//指定允许或拒绝访问资源的委托人,可以指定的委托人
//(AWS 账户和 根用户,IAM 用户,IAM 角色,代入角色的会话,AWS 服务)
// 本实例用的是AWS服务委托。格式:long_service-name.amazonaws.com.cn,
//(apigateway.amazonaws.com,lambda.amazonaws.com)与Principal对应的是 NotPrincipal。
"Principal": {
"Service": [
"apigateway.amazonaws.com"
]
},
//元素描述将允许或拒绝的特定操作。通过将服务命名空间用作操作前缀
//(iam、ec2、sqs、sns、s3 等)并后跟允许或拒绝的操作名称来指定值。
//例如,"Action": "iam:ChangePassword",或 "Action":
//["iam:ListAccessKeys","iam:ChangePassword"] 或通配符 "Action": "s3:*"
//sts:AssumeRole:角色的临时访问凭证
"Action": "sts:AssumeRole"
}
]
},
"Path": "/", //角色的路径
//附加到用户的 IAM 托管策略的 Amazon 资源名称 (ARN) 的列表。例如:
//arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
//策略允许当前IAM角色想CloudWatch写入日志。
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
]
}
},
//ApiGatewayAccount 资源指定API Gateway用来将API日志写入CloudWatch Logs 的IAM角色。
//如果从未在 AWS 账户中创建某个 API Gateway 资源,则必须添加另一个 API Gateway 资源的依赖项,
//例如 AWS::ApiGateway::RestApi 或 AWS::ApiGateway::ApiKey 资源。如果您的 AWS 账户中已创建
//API Gateway 资源,则无需依赖关系(即使该资源已删除)。
"ApiGatewayAccount": {
"Type": "AWS::ApiGateway::Account",
"Properties": {
"CloudWatchRoleArn": { //账户中 CloudWatch Logs 写权限的 IAM 角色的 Amazon 资源名称 (ARN)
//内部函数返回模板中的资源的属性值。Fn::GetAtt: [ logicalNameOfResource, attributeName ]
//logicalNameOfResource 包含所需属性的资源的逻辑名称 (也称为逻辑 ID)。
//attributeName:需要获得其值的资源特定属性名称。
"Fn::GetAtt": [
"ApiGatewayCloudwatchRole",
"Arn"
]
}
}
}
}
说明:CloudWatch:AWS提供的日志存储服务,将应用程序的日志集中进行存储。API网关服务也需要一个IAM角色来调用CloudWatch服务。该IAM角色允许apigateway.amazonaws.com服务访问CloudWatch日志,并且作为AWS::ApiGateway::Account资源配置到API网关上。
2. 创建API
API网关有多个部分组成:API、阶段、资源、方法,例如该URL
https://aj4k34d22h.execute-api.us-east-2.amazonaws.com/production/test?value=hello+world
aj4k34d99h:API的ID
production:阶段名词(软件交付的阶段)
test:资源名
us-east-2:region
"RestApi": {
"Type": "AWS::ApiGateway::RestApi", //资源创建一个 REST API
"Properties": {
"Name": {
//返回使用 aws cloudformation create-stack 命令指定的堆栈的名称
//如我们在build.gradle中定义的stackName "serverlessbook"。
//注意我们在拼接API网关的时候使用的是“PhysicalResourceId”而非这里的“Name”
"Ref": "AWS::StackName"
}
}
}
3. 创建资源
"TestResource": {
"Type": "AWS::ApiGateway::Resource", //在 API 中创建资源。
"Properties": {
"PathPart": "test", //资源的路径名称。就是最后的/test
"RestApiId": { //创建的RestApi资源的 ID,就是上一步创建的RestApi
"Ref": "RestApi"
},
//如果需要创建子资源,则为父资源的 ID。对于没有父资源的资源,指定 RestApi 根资源 ID。
"ParentId": {
"Fn::GetAtt": [ //例如 { "Fn::GetAtt": ["MyRestApi", "RootResourceId"] }
"RestApi",
"RootResourceId"
]
}
}
}
4. 创建方法
"TestGetMethod": {
//创建 API Gateway 方法,这些方法定义客户端必须在其请求中发送的参数和正文.
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "GET",//客户端用于调用此方法的 HTTP 方法.
"RestApiId": { //方法需要指定 RestAPI
"Ref": "RestApi"
},
"ResourceId": { //方法需要指定 资源
"Ref": "TestResource"
},
"AuthorizationType": "NONE", //方法的授权类型。NONE 是表示不需要权限。
"RequestParameters": { //API Gateway 接受的请求参数。参数指定为键/值对,源作为键,布尔值作为值
"method.request.querystring.value": "True", //指定参数的value值。
"method.request.header.Accept": "True" //Accept头信息。
},
"MethodResponses": [ //可发送到调用方法的客户端的响应。这边定义了一个状态码为200
{
"StatusCode": "200"
}
],
"Integration": { //该方法在收到请求时调用的后端系统(用于指定有关方法调用的目标后端的信息)
"Type": "AWS", //方法运行的后端的类型,例如 HTTP 或 MOCK 或 AWS。
//集成的统一资源标识符 (URI) Type 属性指定AWS,请采用以下形式指定 AWS 服务:
//arn:aws:apigateway:region:subdomain.service|service:path|action/service_api。
//例如,Lambda 函数 URI 遵循以下形式:arn:aws:apigateway:region:lambda:path/path。
//路径通常采用以下形式:/2015-03-31/functions/LambdaFunctionARN/invocations。
"Uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${TestLambda.Arn}/invocations"
},
"IntegrationHttpMethod": "POST", //集成的 HTTP 方法类型。
//API Gateway 在方法的后端处理完请求后提供的响应。
//API Gateway 将拦截来自后端的响应,以便您能够控制 API Gateway 呈现后端响应的方式。
"RequestParameters": {
"integration.request.querystring.value": "method.request.querystring.value",
"integration.request.header.Accept": "method.request.header.Accept"
},
//对请求负载应用的 Apache Velocity 模板的映射。
//API Gateway 使用的模板基于客户端发送的 Content-Type 标头的值。
//内容类型值为密钥,模板为值(指定为字符串),
//例如以下代码段:"application/json": "{\n \"statusCode\": 200\n}"
//将参数在传入后台时进行格式化。value=hello+world 转化为 {"value":"hello+world"}
"RequestTemplates": {
"application/json": "{\"value\":\"$input.params('value')\"}"
},
//指示 API Gateway 将请求传递到目标后端的时间。
//此行为取决于请求的 Content-Type 标头以及您是否为其定义了映射模板。这里为“NEVER”。
"PassthroughBehavior": "NEVER",
//API Gateway 在方法的后端处理完请求后提供的响应。
//API Gateway 将拦截来自后端的响应,以便您能够控制 API Gateway 呈现后端响应的方式。
"IntegrationResponses": [
{
//一个正则表达式,用于指定来自后端的哪些错误字符串或状态代码将映射到集成响应。
"SelectionPattern": ".*",
//API Gateway 用于将集成响应映射到 MethodResponse 状态代码的状态代码。
//这里将所有的返回结果都对应状态码200.
"StatusCode": "200"
}
]
}
}
}
5. 配置Lambda权限
"TestLambdaPermission": {
//资源授予 AWS 服务或其他账户使用函数的权限。要向另一个账户授予权限,请将账户ID指定为Principal。
//对于 AWS服务,委托人是服务定义的域样式标识符例如 s3.amazonaws.com 或 apigateway.amazonaws.com。
//对于AWS服务,您还可以将关联资源的ARN指定为SourceArn。
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction", //委托人可以对函数执行的操作。这边的权限是调用函数。
"FunctionName": { //Lambda 函数的名称、版本或别名。这边是指定内部TestLambda。
"Ref": "TestLambda"
},
"Principal": "apigateway.amazonaws.com",
//对于AWS服务,为调用该函数的AWS资源的ARN。例如,Amazon S3 存储桶或 Amazon Lambda 函数。
"SourceArn": {
//将输入字符串中的变量替换为您指定的值。
"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*"
}
}
}
6. 部署API服务
创建了一个IAM角色,并赋予了lambda:PublishVersion,lambda:PublishVersion(从函数的当前代码和配置创建一个版本。使用版本创建不更改的功能代码和配置快照。)权限。
"DeploymentLambdaRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"ManagedPolicyArns": [
//Lambda功能的托管策略之一, 管理弹性网络接口以将您的函数连接到VPC的权限。
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
],
//添加或更新嵌入在指定 IAM 角色中的内联策略文档。有关附加策略的信息,
//附加策略已经附加到用户、组或角色的托管策略。
"Policies": [
{
"PolicyName": "LambdaExecutionPolicy", //策略文档。
"PolicyDocument": { //标识策略的友好名称。
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:PublishVersion",
"apigateway:POST"
],
"Resource": [
"*"
]
}
]
}
}
]
}
},
"DeploymentLambda": {
//创建 Lambda 函数。要创建函数,需要部署程序包和执行角色。部署程序包中包含您的函数代码。
//执行角色授予该函数使用 AWS 服务的权限。
"Type": "AWS::Lambda::Function",
"Properties": {
"Role": {
"Fn::GetAtt": [
"DeploymentLambdaRole",
"Arn"
]
},
"Handler": "serverless.handler",
"Runtime": "nodejs12.x",
"Code": {
"S3Bucket": {
"Fn::Sub": "serverless-arch-${AWS::Region}"
},
"S3Key": "serverless.zip"
}
}
}
创建完上面Lambda函数后,这是创建自定义资源的标准语法,其中ServiceToken时必须,且是Lambada函数的Arn名称,其他参数将在事件发生时传给Lambda函数进行处理。这里用于部署创建一个新的API部署任务,然后返回结果。
"ApiDeployment": {
"DependsOn": [
"TestGetMethod"
],
//将 Lambda 函数与自定义资源关联,则在创建、更新或删除自定义资源时就会调用该函数。
//AWS CloudFormation 将通过调用 Lambda API 来调用此函数以及将所有请求数据
//(如请求类型和资源属性)传递到此函数。
"Type": "Custom::ApiDeployment",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"DeploymentLambda",
"Arn"
]
},
"RestApiId": {
"Ref": "RestApi"
},
"StageName": "production",
"DeploymentTime": {
"Ref": "DeploymentTime"
}
}
}
到此完成API网关的部署,但是这个程序是自动生产URL无法记忆不是很友好,后面将解决URL变成我们自己定义的URL。