据我了解,API Gateway 部署设计存在逻辑缺陷。
API 部署是部署到一个阶段,如在 Amazon API Gateway 中部署 REST API。
要部署 API,您需要创建 API 部署并将其与阶段相关联。阶段是对 API 生命周期状态的逻辑引用(例如 dev、prod、beta、v2)。
但是,创建阶段 API 需要现有部署。我相信,部署和阶段之间的这种循环依赖是问题的根源。
问题 1
create-stage API 需要使用 --deployment-id 参数进行部署。因此,我们需要先创建一个 API 部署。
这是第一个问题。如果我们为创建部署指定一个阶段,它会创建该阶段。那我们就不能自己创造舞台。
当我们使用 CloudFormation 或 Terraform 等配置管理工具时,这会导致“Stage 已存在”异常,因为我们将尝试自己创建阶段资源。
因此,我们无法在首次创建 API 部署时指定阶段。
问题 2
由于问题 1,我们要管理阶段资源的创建,我们需要首先创建一个虚拟部署,以便我们可以使用虚拟创建一个阶段。这一步浪费部署创建是第二个问题。虽然阶段指向部署,但部署并不能完全识别阶段,因为如果尝试从部署中获取调用 URL,它不包括阶段。
创建阶段后,最后我们可以创建另一个指定阶段的 API 部署。由于该阶段已经存在,部署将引用该阶段,并且调用 URL 将包含该阶段。
例子
地形
#--------------------------------------------------------------------------------
# Dummy API Deployment
#--------------------------------------------------------------------------------
resource "aws_api_gateway_deployment" "dummy" {
rest_api_id = "${aws_api_gateway_rest_api.this.id}"
#--------------------------------------------------------------------------------
# To avoid State already exists
# https://github.com/terraform-providers/terraform-provider-aws/issues/2918
#--------------------------------------------------------------------------------
#stage_name = "${var.ENV}"
#--------------------------------------------------------------------------------
# Force re-deployment at each run. Alternative is to verify MD5 of API GW files.
#--------------------------------------------------------------------------------
# https://medium.com/coryodaniel/til-forcing-terraform-to-deploy-a-aws-api-gateway-deployment-ed36a9f60c1a
# https://github.com/hashicorp/terraform/issues/6613
# Terraform’s aws_api_gateway_deployment won’t deploy subsequent releases in the event
# that something has changed in an integration, method, etc
#--------------------------------------------------------------------------------
stage_description = "Deployment at ${timestamp()}"
lifecycle {
create_before_destroy = true
}
depends_on = [
#--------------------------------------------------------------------------------
# [aws_api_gateway_account.this]
# To avoid the error: Updating API Gateway Stage failed:
# BadRequestException: CloudWatch Logs role ARN must be set in account settings to enable logging.
#--------------------------------------------------------------------------------
"aws_api_gateway_account.this",
#--------------------------------------------------------------------------------
# To avoid NotFoundException: Invalid Integration identifier specified
#--------------------------------------------------------------------------------
"aws_api_gateway_integration.ping_put",
]
#--------------------------------------------------------------------------------
}
#--------------------------------------------------------------------------------
# Create a stage refering to the dummy.
# The 2nd/true deployment will later refer to this stage
#--------------------------------------------------------------------------------
resource "aws_api_gateway_stage" "this" {
stage_name = var.ENV
rest_api_id = aws_api_gateway_rest_api.this.id
deployment_id = aws_api_gateway_deployment.dummy.id
xray_tracing_enabled = var.apigw_xray_tracing_enabled
tags = {
Project = var.PROJECT
Environment = var.ENV
}
depends_on = [
aws_api_gateway_deployment.dummy
]
}
#--------------------------------------------------------------------------------
# Legitimate API Deployment
#--------------------------------------------------------------------------------
resource "aws_api_gateway_deployment" "this" {
rest_api_id = aws_api_gateway_rest_api.this.id
stage_name = aws_api_gateway_stage.this.stage_name
lifecycle {
create_before_destroy = true
}
}
云形成
AWSTemplateFormatVersion: "2010-09-09"
Description: "My API Gateway and Lambda function"
Parameters:
apiGatewayStageName:
Type: "String"
Default: "devStage"
lambdaFunctionName:
Type: "String"
Default: "my-lambda-function"
Resources:
apiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test-api"
Description: "My Test API"
apiGatewayRootMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
HttpMethod: "GET"
Integration:
IntegrationHttpMethod: "POST"
Type: "AWS_PROXY"
Uri: !Sub
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
- lambdaArn: !GetAtt "lambdaFunction.Arn"
ResourceId: !GetAtt "apiGateway.RootResourceId"
RestApiId: !Ref "apiGateway"
apiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
DependsOn:
- "apiGatewayRootMethod"
Properties:
RestApiId: !Ref "apiGateway"
StageName: ""
apiGatewayStage:
Type: "AWS::ApiGateway::Stage"
Properties:
StageName: !Ref "apiGatewayStageName"
RestApiId: !Ref "apiGateway"
DeploymentId: !Ref "apiGatewayDeployment"
MethodSettings:
- ResourcePath: /
HttpMethod: "GET"
MetricsEnabled: 'true'
DataTraceEnabled: 'true'
lambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Code:
ZipFile: |
def handler(event,context):
return {
'body': 'Hello World from Lambda',
'headers': {
'Content-Type': 'text/plain'
},
'statusCode': 200
}
Description: "My function"
FunctionName: !Ref "lambdaFunctionName"
Handler: "index.handler"
MemorySize: 256
Role: !GetAtt "lambdaIAMRole.Arn"
Runtime: "python3.7"
Timeout: 30
lambdaApiGatewayInvoke:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt "lambdaFunction.Arn"
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/GET/"
lambdaIAMRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "sts:AssumeRole"
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Policies:
- PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Effect: "Allow"
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${lambdaFunctionName}:*"
PolicyName: "lambda"
lambdaLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "/aws/lambda/${lambdaFunctionName}"
RetentionInDays: 90
Outputs:
apiGatewayInvokeURL:
Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}"
lambdaArn:
Value: !GetAtt "lambdaFunction.Arn"