CloudFormation 未在更新时部署到 API 个网关阶段
CloudFormation doesn't deploy to API gateway stages on update
当我 运行 CloudFormation deploy
使用具有 API 网关资源的模板时,我第一次 运行 它会创建并部署到阶段。随后我 运行 它会更新资源但不会部署到阶段。
这种行为是否符合预期?如果是,我如何让它在更新时部署到阶段?
(Terraform 提到了类似的问题:https://github.com/hashicorp/terraform/issues/6613)
似乎无法在您的 Cloudformation 资源之一发生变化时轻松创建新部署。
解决此问题的一种方法是使用 Lambda 支持的自定义资源(请参阅 http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)。
Lambda 应该创建新的 Deployment,前提是您的某个资源已更新。要确定您的某个资源是否已更新,
您可能必须围绕此 API 调用实现自定义逻辑:http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStackEvents.html
为了触发自定义资源的更新,我建议您提供一个 Cloudformation 参数,用于强制更新您的自定义资源(例如当前时间或版本号)。
请注意,您必须向您的自定义资源添加一个 DependsOn
子句,其中将包括与您的 API 相关的所有资源。否则,您的部署可能会在所有 API 资源更新之前创建。
希望这对您有所帮助。
Amazon的话CloudFormation是:
AWS CloudFormation takes care of provisioning and configuring those resources for you
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html
重新部署 APIs 不是配置任务...它是升级 activity,是软件发布过程中某个阶段的一部分。
AWS CodePipeline is a continuous delivery service you can use to model, visualize, and automate the steps required to release your software.
http://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html
CodePipeline 还支持从管道中的操作执行 Lambda 函数。因此,如前所述,创建一个 Lambda 函数来部署您的 API 但从 Codepipeline 而不是 CloudFormation 调用它。
查看此页面了解详情:
http://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html
当您的模板指定一个部署时,CloudFormation 将仅在该部署尚不存在时创建该部署。当您再次尝试 运行 它时,它发现部署仍然存在,因此不会重新创建它,因此没有部署。您需要一个新的部署资源 ID,以便它创建一个新的部署。阅读本文以获取更多信息:https://currentlyunnamed-theclassic.blogspot.com/2018/12/mastering-cloudformation-for-api.html
来自 TheClassic 链接的 blogspot post(迄今为止最佳答案!),您必须记住,如果您生成的模板没有使用可以插入有效时间戳的内容来代替$TIMESTAMP$,您必须使用时间戳或其他唯一 ID 手动更新它。这是我的功能示例,它成功删除了现有部署并创建了一个新部署,但是当我想创建另一个更改集时,我将不得不手动更新这些唯一值:
rDeployment05012019355:
Type: AWS::ApiGateway::Deployment
DependsOn: rApiGetMethod
Properties:
RestApiId:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-RestApi'
StageName: !Ref pStageName
rCustomDomainPath:
Type: AWS::ApiGateway::BasePathMapping
DependsOn: [rDeployment05012019355]
Properties:
BasePath: !Ref pPathPart
Stage: !Ref pStageName
DomainName:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-CustomDomainName'
RestApiId:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-RestApi'
使用山姆
AWS::无服务器::Api
这会在执行转换时为您进行部署
我使用的是上述方法,但对我来说仅仅部署 API 网关就显得很复杂。如果我们要更改资源的名称,则删除和重新创建资源需要时间,这会增加您的应用程序的停机时间。
我按照以下方法使用 AWS CLI 将 API 网关部署到阶段,它不会影响使用 Cloudformation 堆栈的部署。
我正在做的是,运行 在 API 网关部署完成后,在 AWS CLI 命令下。它将使用最新更新更新现有阶段。
aws apigateway create-deployment --rest-api-id tztstixfwj --stage-name stg --description 'Deployed from CLI'
这里的答案是使用Stage的AutoDeploy属性:
Stage:
Type: AWS::ApiGatewayV2::Stage
Properties:
StageName: v1
Description: 'API Version 1'
ApiId: !Ref: myApi
AutoDeploy: true
注意'DeploymentId' 属性在使用'AutoDeploy'时必须不指定。
在此处查看文档:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html
我可能来晚了,但如果 API 资源发生变化,这里是您重新部署的选项,可能对仍在寻找选项的人有所帮助 -
尝试将 AutoDeploy 设置为 true。如果您使用的是 V2 版本部署。请注意,您需要通过 V2 创建 APIGW。 V1 和 V2 彼此不兼容。 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html#cfn-apigatewayv2-stage-autodeploy
Lambda 支持的自定义资源,Lambda 依次调用 createDeployment API - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
CodePipeline 具有调用 Lambda 函数的操作,就像自定义资源一样 - https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html
SAM(无服务器应用程序模型)遵循与 CloudFormation 类似的语法,将资源创建简化为抽象,并使用它们来构建和部署普通的 CloudFormation 模板。 https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html
如果您正在使用任何抽象层来像 Sceptre 这样的 cloudformation,您可以有一个钩子在对资源进行任何更新后调用 createDeployment https://sceptre.cloudreach.com/2.3.0/docs/hooks.html
我选择了第三个选项,因为我一直使用 Scepter 进行 Cloudformation 部署。在权杖中实现钩子也很容易。
这对我有用:
cfn.yml
APIGatewayStage:
Type: 'AWS::ApiGateway::Stage'
Properties:
StageName: !Ref Environment
DeploymentId: !Ref APIGatewayDeployment$TIMESTAMP$
RestApiId: !Ref APIGatewayRestAPI
Variables:
lambdaAlias: !Ref Environment
MethodSettings:
- ResourcePath: '/*'
DataTraceEnabled: true
HttpMethod: "*"
LoggingLevel: INFO
MetricsEnabled: true
DependsOn:
- liveLocationsAPIGatewayMethod
- testJTAPIGatewayMethod
APIGatewayDeployment$TIMESTAMP$:
Type: 'AWS::ApiGateway::Deployment'
Properties:
RestApiId: !Ref APIGatewayRestAPI
DependsOn:
- liveLocationsAPIGatewayMethod
- testJTAPIGatewayMethod
bitbucket-pipelines.yml
脚本:
- python3 deploy_api.py
deploy_api.py
import time
file_name = 'infra/cfn.yml'
ts = str(time.time()).split(".")[0]
print(ts)
with open(file_name, 'r') as file :
filedata = file.read()
filedata = filedata.replace('$TIMESTAMP$', ts)
with open(file_name, 'w') as file:
file.write(filedata)
============================================= ===========================
阅读此内容了解更多信息:https://currentlyunnamed-theclassic.blogspot.com/2018/12/mastering-cloudformation-for-api.html
如果您需要进行 $TIMESTAMP$
替换,我可能会选择它,因为它更干净而且您无需进行任何手动 API 网关管理。
我发现此处发布的其他解决方案大部分 都完成了一项主要警告 - 你无法管理你的 Stage
和 Deployment
在 CloudFormation 中分开,因为无论何时部署 API 网关,在部署 API 和辅助进程(自定义资源/lambda、代码管道,你有什么)创建之间都会有某种停机时间您的新部署。此停机时间是因为 CloudFormation 仅将初始部署绑定到舞台。因此,当您对 Stage 进行更改并进行部署时,它将恢复为初始部署,直到您的辅助进程创建新部署。
*** 请注意,如果您在 Deployment
资源上指定 StageName
,而不是明确管理 Stage
资源,则其他解决方案将起作用。
就我而言,我没有那个 $TIMESTAMP$
替换件,我需要单独管理我的 Stage
以便我可以执行诸如启用缓存之类的操作,所以我必须找到另一个方法。所以工作流程和相关的CF片如下
在触发CF更新之前,先看看你要更新的堆栈是否已经存在。设置 stack_exists: true|false
将 stack_exists
变量传递到您的 CF 模板,一直传递到创建 Deployment
和 Stage
[= 的堆栈27=]
满足以下条件:
Conditions:
StackExists: !Equals [!Ref StackAlreadyExists, "True"]
- 以下
Deployment
和Stage
:
# Only used for initial creation, secondary process re-creates this
Deployment:
DeletionPolicy: Retain
Type: AWS::ApiGateway::Deployment
Properties:
Description: "Initial deployment"
RestApiId: ...
Stage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !If
- StackExists
- !Ref AWS::NoValue
- !Ref Deployment
RestApiId: ...
StageName: ...
- 执行以下操作的辅助进程:
# looks up `apiId` and `stageName` and sets variables
CURRENT_DEPLOYMENT_ID=$(aws apigateway get-stage --rest-api-id <apiId> --stage-name <stageName> --query 'deploymentId' --output text)
aws apigateway create-deployment --rest-api-id <apiId> --stage-name <stageName>
aws apigateway delete-deployment --rest-api-id <apiId> --deployment-id ${CURRENT_DEPLOYMENT_ID}
通读这篇文章,我并没有立即得出结论,因为这里的信息来自多个来源。我尝试将此处(和链接来源)的所有发现总结为我的个人测试,以帮助其他人避免追捕。
重要的是要知道每个 API 总是有一个专用的 URL。关联的阶段只有一个单独的后缀。更新部署不会更改 URL,重新创建 API 会。
API
├─ RestAPI (incl. Resource, Methods etc)
├─ Deployment
├─ Stage - v1 https://6s...com/v1
├─ Stage - v2 https://6s...com/v2
关系阶段和部署:
要通过 CloudFormation (Cfn) 部署 AWS API 网关,您需要一个 RestApi-Cfn-Resource 和一个 Deployment-Cfn-Resource。如果您为 Deployment-Resource 提供阶段名称,则部署会自动在“正常”创建之上创建一个部署。如果您省略它,则 API 将在没有任何阶段的情况下创建。无论哪种方式,如果您有一个部署,您可以通过链接这两个阶段向部署添加 n 个阶段,但是一个阶段及其 API 始终只有一个部署。
更新简单 API:
现在,如果你想更新这个“简单的 API”,它只包含一个 RestAPI 加上一个部署,你就会面临这个问题,如果部署有阶段名称 - 它不能因为它已经“存在”而被更新。要首先检测到部署必须更新,您必须在 CloudFormation 中向部署资源名称添加时间戳或哈希,否则甚至不会触发更新。
解决部署更新:
现在要启用更新部署,您必须将部署拆分为单独的 Cfn-Resources。这意味着,您从 Deployment-Cfn-Resource 中删除阶段名称并创建一个引用部署资源的新 Stage-Cfn-Resource。这样您就可以更新部署。不过,舞台 - 您通过 URL 引用的部分 - 不会自动更新。
正在将更新从部署传播到您的阶段:
现在我们可以更新部署 - 即 API 的蓝图 - 我们可以将更改传播到其各自的阶段。据我所知,使用 CloudFormation 无法执行此步骤。因此,要触发更新,您需要手动添加“自定义资源”。其他“none”CloudFormation 方法由@Athi 的回答 总结,但对我来说没有解决方案,因为我想限制使用的工具。
如果有人有 Lambda 更新的示例,请随时联系我 - 然后我会在此处添加它。目前我找到的链接只引用了一个普通模板。
我希望这能帮助其他人更好地理解上下文。
来源:
- Problem description with Cfn-template, 2
- Adding timestamp to deployment resource, 2
- Using CodePipeline as a solution
- Related question and CLI update answer
- Related terraform issue
- Related AWS forum thread
当我 运行 CloudFormation deploy
使用具有 API 网关资源的模板时,我第一次 运行 它会创建并部署到阶段。随后我 运行 它会更新资源但不会部署到阶段。
这种行为是否符合预期?如果是,我如何让它在更新时部署到阶段?
(Terraform 提到了类似的问题:https://github.com/hashicorp/terraform/issues/6613)
似乎无法在您的 Cloudformation 资源之一发生变化时轻松创建新部署。
解决此问题的一种方法是使用 Lambda 支持的自定义资源(请参阅 http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)。
Lambda 应该创建新的 Deployment,前提是您的某个资源已更新。要确定您的某个资源是否已更新,
您可能必须围绕此 API 调用实现自定义逻辑:http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStackEvents.html
为了触发自定义资源的更新,我建议您提供一个 Cloudformation 参数,用于强制更新您的自定义资源(例如当前时间或版本号)。
请注意,您必须向您的自定义资源添加一个 DependsOn
子句,其中将包括与您的 API 相关的所有资源。否则,您的部署可能会在所有 API 资源更新之前创建。
希望这对您有所帮助。
Amazon的话CloudFormation是:
AWS CloudFormation takes care of provisioning and configuring those resources for you http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html
重新部署 APIs 不是配置任务...它是升级 activity,是软件发布过程中某个阶段的一部分。
AWS CodePipeline is a continuous delivery service you can use to model, visualize, and automate the steps required to release your software. http://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html
CodePipeline 还支持从管道中的操作执行 Lambda 函数。因此,如前所述,创建一个 Lambda 函数来部署您的 API 但从 Codepipeline 而不是 CloudFormation 调用它。
查看此页面了解详情: http://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html
当您的模板指定一个部署时,CloudFormation 将仅在该部署尚不存在时创建该部署。当您再次尝试 运行 它时,它发现部署仍然存在,因此不会重新创建它,因此没有部署。您需要一个新的部署资源 ID,以便它创建一个新的部署。阅读本文以获取更多信息:https://currentlyunnamed-theclassic.blogspot.com/2018/12/mastering-cloudformation-for-api.html
来自 TheClassic 链接的 blogspot post(迄今为止最佳答案!),您必须记住,如果您生成的模板没有使用可以插入有效时间戳的内容来代替$TIMESTAMP$,您必须使用时间戳或其他唯一 ID 手动更新它。这是我的功能示例,它成功删除了现有部署并创建了一个新部署,但是当我想创建另一个更改集时,我将不得不手动更新这些唯一值:
rDeployment05012019355:
Type: AWS::ApiGateway::Deployment
DependsOn: rApiGetMethod
Properties:
RestApiId:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-RestApi'
StageName: !Ref pStageName
rCustomDomainPath:
Type: AWS::ApiGateway::BasePathMapping
DependsOn: [rDeployment05012019355]
Properties:
BasePath: !Ref pPathPart
Stage: !Ref pStageName
DomainName:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-CustomDomainName'
RestApiId:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-RestApi'
使用山姆
AWS::无服务器::Api
这会在执行转换时为您进行部署
我使用的是上述方法,但对我来说仅仅部署 API 网关就显得很复杂。如果我们要更改资源的名称,则删除和重新创建资源需要时间,这会增加您的应用程序的停机时间。
我按照以下方法使用 AWS CLI 将 API 网关部署到阶段,它不会影响使用 Cloudformation 堆栈的部署。
我正在做的是,运行 在 API 网关部署完成后,在 AWS CLI 命令下。它将使用最新更新更新现有阶段。
aws apigateway create-deployment --rest-api-id tztstixfwj --stage-name stg --description 'Deployed from CLI'
这里的答案是使用Stage的AutoDeploy属性:
Stage:
Type: AWS::ApiGatewayV2::Stage
Properties:
StageName: v1
Description: 'API Version 1'
ApiId: !Ref: myApi
AutoDeploy: true
注意'DeploymentId' 属性在使用'AutoDeploy'时必须不指定。
在此处查看文档:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html
我可能来晚了,但如果 API 资源发生变化,这里是您重新部署的选项,可能对仍在寻找选项的人有所帮助 -
尝试将 AutoDeploy 设置为 true。如果您使用的是 V2 版本部署。请注意,您需要通过 V2 创建 APIGW。 V1 和 V2 彼此不兼容。 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html#cfn-apigatewayv2-stage-autodeploy
Lambda 支持的自定义资源,Lambda 依次调用 createDeployment API - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
CodePipeline 具有调用 Lambda 函数的操作,就像自定义资源一样 - https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html
SAM(无服务器应用程序模型)遵循与 CloudFormation 类似的语法,将资源创建简化为抽象,并使用它们来构建和部署普通的 CloudFormation 模板。 https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html
如果您正在使用任何抽象层来像 Sceptre 这样的 cloudformation,您可以有一个钩子在对资源进行任何更新后调用 createDeployment https://sceptre.cloudreach.com/2.3.0/docs/hooks.html
我选择了第三个选项,因为我一直使用 Scepter 进行 Cloudformation 部署。在权杖中实现钩子也很容易。
这对我有用:
cfn.yml
APIGatewayStage:
Type: 'AWS::ApiGateway::Stage'
Properties:
StageName: !Ref Environment
DeploymentId: !Ref APIGatewayDeployment$TIMESTAMP$
RestApiId: !Ref APIGatewayRestAPI
Variables:
lambdaAlias: !Ref Environment
MethodSettings:
- ResourcePath: '/*'
DataTraceEnabled: true
HttpMethod: "*"
LoggingLevel: INFO
MetricsEnabled: true
DependsOn:
- liveLocationsAPIGatewayMethod
- testJTAPIGatewayMethod
APIGatewayDeployment$TIMESTAMP$:
Type: 'AWS::ApiGateway::Deployment'
Properties:
RestApiId: !Ref APIGatewayRestAPI
DependsOn:
- liveLocationsAPIGatewayMethod
- testJTAPIGatewayMethod
bitbucket-pipelines.yml
脚本: - python3 deploy_api.py
deploy_api.py
import time
file_name = 'infra/cfn.yml'
ts = str(time.time()).split(".")[0]
print(ts)
with open(file_name, 'r') as file :
filedata = file.read()
filedata = filedata.replace('$TIMESTAMP$', ts)
with open(file_name, 'w') as file:
file.write(filedata)
============================================= ===========================
阅读此内容了解更多信息:https://currentlyunnamed-theclassic.blogspot.com/2018/12/mastering-cloudformation-for-api.html
如果您需要进行 $TIMESTAMP$
替换,我可能会选择它,因为它更干净而且您无需进行任何手动 API 网关管理。
我发现此处发布的其他解决方案大部分 都完成了一项主要警告 - 你无法管理你的 Stage
和 Deployment
在 CloudFormation 中分开,因为无论何时部署 API 网关,在部署 API 和辅助进程(自定义资源/lambda、代码管道,你有什么)创建之间都会有某种停机时间您的新部署。此停机时间是因为 CloudFormation 仅将初始部署绑定到舞台。因此,当您对 Stage 进行更改并进行部署时,它将恢复为初始部署,直到您的辅助进程创建新部署。
*** 请注意,如果您在 Deployment
资源上指定 StageName
,而不是明确管理 Stage
资源,则其他解决方案将起作用。
就我而言,我没有那个 $TIMESTAMP$
替换件,我需要单独管理我的 Stage
以便我可以执行诸如启用缓存之类的操作,所以我必须找到另一个方法。所以工作流程和相关的CF片如下
在触发CF更新之前,先看看你要更新的堆栈是否已经存在。设置
stack_exists: true|false
将
stack_exists
变量传递到您的 CF 模板,一直传递到创建Deployment
和Stage
[= 的堆栈27=]满足以下条件:
Conditions:
StackExists: !Equals [!Ref StackAlreadyExists, "True"]
- 以下
Deployment
和Stage
:
# Only used for initial creation, secondary process re-creates this
Deployment:
DeletionPolicy: Retain
Type: AWS::ApiGateway::Deployment
Properties:
Description: "Initial deployment"
RestApiId: ...
Stage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !If
- StackExists
- !Ref AWS::NoValue
- !Ref Deployment
RestApiId: ...
StageName: ...
- 执行以下操作的辅助进程:
# looks up `apiId` and `stageName` and sets variables
CURRENT_DEPLOYMENT_ID=$(aws apigateway get-stage --rest-api-id <apiId> --stage-name <stageName> --query 'deploymentId' --output text)
aws apigateway create-deployment --rest-api-id <apiId> --stage-name <stageName>
aws apigateway delete-deployment --rest-api-id <apiId> --deployment-id ${CURRENT_DEPLOYMENT_ID}
通读这篇文章,我并没有立即得出结论,因为这里的信息来自多个来源。我尝试将此处(和链接来源)的所有发现总结为我的个人测试,以帮助其他人避免追捕。
重要的是要知道每个 API 总是有一个专用的 URL。关联的阶段只有一个单独的后缀。更新部署不会更改 URL,重新创建 API 会。
API
├─ RestAPI (incl. Resource, Methods etc)
├─ Deployment
├─ Stage - v1 https://6s...com/v1
├─ Stage - v2 https://6s...com/v2
关系阶段和部署:
要通过 CloudFormation (Cfn) 部署 AWS API 网关,您需要一个 RestApi-Cfn-Resource 和一个 Deployment-Cfn-Resource。如果您为 Deployment-Resource 提供阶段名称,则部署会自动在“正常”创建之上创建一个部署。如果您省略它,则 API 将在没有任何阶段的情况下创建。无论哪种方式,如果您有一个部署,您可以通过链接这两个阶段向部署添加 n 个阶段,但是一个阶段及其 API 始终只有一个部署。
更新简单 API:
现在,如果你想更新这个“简单的 API”,它只包含一个 RestAPI 加上一个部署,你就会面临这个问题,如果部署有阶段名称 - 它不能因为它已经“存在”而被更新。要首先检测到部署必须更新,您必须在 CloudFormation 中向部署资源名称添加时间戳或哈希,否则甚至不会触发更新。
解决部署更新:
现在要启用更新部署,您必须将部署拆分为单独的 Cfn-Resources。这意味着,您从 Deployment-Cfn-Resource 中删除阶段名称并创建一个引用部署资源的新 Stage-Cfn-Resource。这样您就可以更新部署。不过,舞台 - 您通过 URL 引用的部分 - 不会自动更新。
正在将更新从部署传播到您的阶段:
现在我们可以更新部署 - 即 API 的蓝图 - 我们可以将更改传播到其各自的阶段。据我所知,使用 CloudFormation 无法执行此步骤。因此,要触发更新,您需要手动添加“自定义资源”。其他“none”CloudFormation 方法由@Athi 的回答
如果有人有 Lambda 更新的示例,请随时联系我 - 然后我会在此处添加它。目前我找到的链接只引用了一个普通模板。
我希望这能帮助其他人更好地理解上下文。
来源:
- Problem description with Cfn-template, 2
- Adding timestamp to deployment resource, 2
- Using CodePipeline as a solution
- Related question and CLI update answer
- Related terraform issue
- Related AWS forum thread