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 资源发生变化,这里是您重新部署的选项,可能对仍在寻找选项的人有所帮助 -

  1. 尝试将 AutoDeploy 设置为 true。如果您使用的是 V2 版本部署。请注意,您需要通过 V2 创建 APIGW。 V1 和 V2 彼此不兼容。 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html#cfn-apigatewayv2-stage-autodeploy

  2. Lambda 支持的自定义资源,Lambda 依次调用 createDeployment API - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html

  3. CodePipeline 具有调用 Lambda 函数的操作,就像自定义资源一样 - https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html

  4. SAM(无服务器应用程序模型)遵循与 CloudFormation 类似的语法,将资源创建简化为抽象,并使用它们来构建和部署普通的 CloudFormation 模板。 https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html

  5. 如果您正在使用任何抽象层来像 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 网关管理。

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

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

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

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

  • stack_exists 变量传递到您的 CF 模板,一直传递到创建 DeploymentStage[= 的堆栈27=]

  • 满足以下条件:

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}

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

重要的是要知道每个 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 更新的示例,请随时联系我 - 然后我会在此处添加它。目前我找到的链接只引用了一个普通模板。

我希望这能帮助其他人更好地理解上下文。

来源: