CloudFormation 不会在更新时部署到 API 网关阶段

Posted

技术标签:

【中文标题】CloudFormation 不会在更新时部署到 API 网关阶段【英文标题】:CloudFormation doesn't deploy to API gateway stages on update 【发布时间】:2017-05-16 08:39:07 【问题描述】:

当我使用具有 API 网关资源的模板运行 CloudFormation deploy 时,我第一次运行它时,它会创建并部署到阶段。随后我运行它时,它会更新资源但不会部署到阶段。

这种行为是否符合预期?如果是,我如何让它在更新时部署到阶段?

(Terraform 提到了类似的问题:https://github.com/hashicorp/terraform/issues/6613)

【问题讨论】:

【参考方案1】:

通读这篇文章,我并没有立即得出结论,因为这里的信息来自多个来源。我尝试将此处(和链接来源)的所有发现总结为我的个人测试,以帮助其他人避免追捕。

重要的是要知道每个 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 Gateway,您需要一个 RestApi-Cfn-Resource 和一个 Deployment-Cfn-Resource。如果您为 Deployment-Resource 指定阶段名称,则部署会在“正常”创建的基础上自动创建部署。如果您忽略它,API 将在没有任何阶段的情况下创建。无论哪种方式,如果您有一个部署,您可以通过链接两者来将 n 阶段添加到部署中,但一个阶段及其 API 始终只有一个部署。

更新简单 API:

现在,如果您想更新这个仅由 RestAPI 和部署组成的“简单 API”,您将面临一个问题,即如果部署有阶段名称 - 它无法更新,因为它已经“存在”。要检测部署必须首先更新,您必须在 CloudFormation 中为部署资源名称添加时间戳或哈希,否则甚至不会触发更新。

解决部署更新:

现在要启用更新部署,您必须将部署拆分并升级为单独的 Cfn 资源。这意味着,您从 Deployment-Cfn-Resource 中删除阶段名称并创建一个引用部署资源的新 Stage-Cfn-Resource。这样您就可以更新部署。尽管如此,舞台 - 您通过 URL 引用的部分 - 不会自动更新。

将更新从部署传播到您的阶段:

现在我们可以更新部署 - 也就是 API 的蓝图 - 我们可以将更改传播到其各自的阶段。据我所知,此步骤无法使用 CloudFormation。因此,要触发更新,您需要手动添加“自定义资源”。 @Athi 的回答 above 总结了其他“无”CloudFormation 方式,但我想限制使用的工具,因此没有解决方案。

如果有人有 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

【讨论】:

【参考方案2】:

如果您有事情要做 $TIMESTAMP$ 替换,我可能会选择它,因为它更干净而且您不必进行任何手动 API 网关管理。

我发现此处发布的其他解决方案大部分在完成这项工作时有一个主要警告 - 您无法在 CloudFormation 中分别管理 StageDeployment,因为无论何时部署 API网关,在部署 API 和辅助流程(自定义资源/lambda、代码管道,你有什么)创建新部署之间,你有某种停机时间。此停机时间是因为 CloudFormation 仅将初始部署绑定到阶段。因此,当您对 Stage 进行更改并进行部署时,它会恢复为初始部署,直到您的辅助流程创建新部署为止。

*** 请注意,如果您在 Deployment 资源上指定 StageName,而不是显式管理 Stage 资源,则其他解决方案将起作用。

就我而言,我没有$TIMESTAMP$ 替换件,我需要单独管理我的Stage,以便我可以执行诸如启用缓存之类的操作,因此我必须找到另一种方法。所以工作流程和相关的CF片段如下

在触发 CF 更新之前,请查看您要更新的堆栈是否已经存在。设置stack_exists: true|false

stack_exists 变量传递到您的 CF 模板,一直到创建 DeploymentStage 的堆栈

以下条件:

Conditions:
  StackExists: !Equals [!Ref StackAlreadyExists, "True"]
以下DeploymentStage
  # 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

【讨论】:

【参考方案3】:

这对我有用:

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

【讨论】:

【参考方案4】:

我可能迟到了,但是如果 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

    具有调用 Lambda 函数的操作的 CodePipeline 与自定义资源非常相似 - 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,则可以在对资源 https://sceptre.cloudreach.com/2.3.0/docs/hooks.html 进行任何更新后调用 createDeployment

我选择了第三个选项,因为我一直使用 Scepter 进行 Cloudformation 部署。在 sceptre 中实现钩子也很容易。

【讨论】:

【参考方案5】:

这里的答案是使用 Stage 的 AutoDeploy 属性:

  Stage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      StageName: v1
      Description: 'API Version 1'
      ApiId: !Ref: myApi
      AutoDeploy: true

请注意,使用“AutoDeploy”时必须未指定“DeploymentId”属性。

在此处查看文档:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html

【讨论】:

Api Gateway V1 可以做到吗?到目前为止我还没有找到任何东西。 这仅适用于 V2 API(因此不包括私有 REST API) 私有 REST API 是 V1。此解决方案仅适用于 HTTP 和 Websocket API 等 V2 API。 这是一个解决方案,但不是 the 解决方案。 ApiGatewayV2 不支持 V1 REST API 提供的所有功能。示例:自定义网关响应、API 密钥、使用计划、金丝雀版本、模拟、边缘优化、X 射线等。见docs.aws.amazon.com/apigateway/latest/developerguide/…【参考方案6】:

我使用了上述方法,但部署 API 网关对我来说看起来很复杂。如果我们要更改资源的名称,则删除和重新创建资源需要时间,这会增加您的应用程序的停机时间。

我正在按照以下方法使用 AWS CLI 将 API 网关部署到阶段,它不会影响使用 Cloudformation 堆栈的部署。

我正在做的是,在 API Gateway 的部署完成后,在 AWS CLI 命令下运行。它将使用最新更新更新现有阶段。

aws apigateway create-deployment --rest-api-id tztstixfwj --stage-name stg --description 'Deployed from CLI'

【讨论】:

我切换了 API ID 和阶段名称,将此行添加到我在代码管道中使用的构建命令中,然后 BAM 开始工作! 4 小时的调试和摆弄在 2 分钟内解决。 10/10 谢谢!我也经历过类似的情况,我想到了这种方法。【参考方案7】:

使用 SAM

AWS::Serverless::Api

它在进行转换时为您进行部署

【讨论】:

说“使用这个其他工具”从来都不是特别有用。【参考方案8】:

从 TheClassic 链接的 blogspot 帖子中(迄今为止的最佳答案!),您必须记住,如果您没有使用可以插入有效时间戳来代替 $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'

【讨论】:

***.com/a/59249236/6566614 此解决方案对管理 ECS 任务超时有很大帮助 - 我已将其导入此处以通过在我的 Cloudformation 模板上运行转换来解决 APIGW 部署问题,该模板将自动更新资源带有参数更改的名称,通过使用当前纪元时间更新字符串参数来表示转换。 很好的标注。在管理测试和生产环境时,您甚至可能希望为每个阶段管理单独的部署。这样,您可以先部署以进行测试或暂存,运行一些测试,然后在一切通过后,在单独的堆栈更新中部署到生产环境。【参考方案9】:

当您的模板指定部署时,CloudFormation 将仅在该部署尚不存在时创建该部署。当您尝试再次运行它时,它会观察到部署仍然存在,因此不会重新创建它,因此没有部署。您需要一个新的资源 ID 用于部署,以便它创建一个新的部署。阅读本文了解更多信息:https://currentlyunnamed-theclassic.blogspot.com/2018/12/mastering-cloudformation-for-api.html

【讨论】:

正如@dontpanic42 建议的那样“请记住,如果您没有使用可以插入有效时间戳来代替 $TIMESTAMP$ 的内容生成模板,则必须使用时间戳手动更新该模板或否则唯一的 ID” 我曾使用 Python 将其替换为当前时间戳(不带符号),但很快将切换到 CDK 以生成我的模板。【参考方案10】:

用亚马逊的话来说,CloudFormation 是:

AWS CloudFormation 负责为您预置和配置这些资源 http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html

API 的重新部署不是一项供应任务...它是一种推广活动,是您的软件发布过程中某个阶段的一部分。

AWS CodePipeline 是一种持续交付服务,可用于对发布软件所需的步骤进行建模、可视化和自动化。 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

【讨论】:

【参考方案11】:

每当您的 Cloudformation 资源之一发生变化时,似乎都无法轻松创建新部署。

解决此问题的一种方法是使用 Lambda 支持的自定义资源(请参阅http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)。

仅当您的资源之一已更新时,Lambda 才应创建新部署。要确定您的资源之一是否已更新, 您可能必须围绕此 API 调用实现自定义逻辑:http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStackEvents.html

为了触发自定义资源的更新,我建议您提供一个 Cloudformation 参数,该参数将用于强制更新您的自定义资源(例如当前时间或版本号)。

请注意,您必须将DependsOn 子句添加到您的自定义资源中,该子句将包含与您的 API 相关的所有资源。否则,您的部署可能会在您的所有 API 资源更新之前创建。

希望这会有所帮助。

【讨论】:

当然有帮助。 :) 亚马逊建议了另一种方式 - 如果有兴趣可以分享。 @bjfletcher 当然想知道! @bjfletcher 也遇到这个问题,你从亚马逊上发现了什么? AWS 支持建议使用无服务器,这非常适合。 @bjfletcher 我很困惑...Serverless 只是 Amazon 提供的 Lambda、API Gateway、S3 和 DynamoDB 作为基于服务器的方法(如 EC2)的替代品。我不太明白那是“另一种方式”。

以上是关于CloudFormation 不会在更新时部署到 API 网关阶段的主要内容,如果未能解决你的问题,请参考以下文章

将对象上传到 S3 存储桶时如何触发 AWS Cloudformation 堆栈的更新?

通过 CloudFormation 部署 AWS UserPool 并更新属性

AWS - 错误 - 更新 Auto Scaling 组、Amazon CloudFormation、无法部署配置

如何使用 Cloudformation 强制重新部署我的 API 网关

如何在更新参数时强制 CloudFormation 堆栈更新?

我应该如何通过 cloudformation 部署我的 aws 状态机?