CloudFormation在更新时无法部署到API网关阶段

65
当我使用包含API网关资源的CloudFormation模板运行deploy命令时,第一次运行它会创建并部署到阶段。之后每次运行它都会更新资源,但不会部署到阶段。
这个行为是否是预期的?如果是,那么如何在每次更新时进行部署到阶段?
(Terraform提到了类似的问题:https://github.com/hashicorp/terraform/issues/6613

在https://stackoverflow.com/a/75960998/2526327中已回答。 - undefined
11个回答

23

似乎没有简单地创建新部署的方法,每当您的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

为了触发Custom Resource上的更新,建议您提供一个CloudFormation参数,用于强制更新您的Custom Resource(例如,当前时间或版本号)。

请注意,您必须向Custom Resource添加一个DependsOn子句,其中包括与您的API相关的所有资源。否则,可能会在更新所有API资源之前创建您的部署。

希望这可以帮助。


2
@bjfletcher 当然想知道! - spg
1
@bjfletcher 我也遇到了这个问题,你从亚马逊找到了什么解决办法? - John T
3
AWS支持建议使用无服务器架构,这对此非常适用。 - bjfletcher
6
@bjfletcher 我有些困惑... Serverless 只是亚马逊提供的一种替代基于服务器方法(如 EC2)的 Lambda、API Gateway、S3 和 DynamoDB。我不太明白这是“另一种方式”的原因。 - JamesQMurphy
1
只是猜测亚马逊建议的无服务器(Serverless)可能指的是Serverless框架。当您添加/更新资源/方法时,它可以正常工作。https://serverless.com/ - ᴛʜᴇᴘᴀᴛᴇʟ
显示剩余3条评论

15

当您的模板指定了一个部署(deployment)时,CloudFormation只有在该部署不存在时才会创建它。 当您尝试再次运行它时,它会观察到该部署仍然存在,因此不会重新创建它,从而没有新的部署。 您需要为部署获取一个新的资源ID,以便它将创建一个新的部署。 阅读此处了解更多信息。


正如@dontpanic42所建议的那样,“请记住,如果您没有使用可以在$TIMESTAMP$的位置插入有效时间戳或其他唯一标识符的工具来生成模板,则必须手动更新该时间戳或唯一标识符”。 - TheClassic
我曾使用Python将其替换为当前时间戳(无符号),但很快将切换到CDK生成我的模板。 - TheClassic

14

Amazon 的 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


12

我之前使用了上述方法,但是它对于仅仅部署API网关来说似乎太过复杂了。如果我们需要更改资源名称,那么删除并重新创建这些资源需要花费时间,这会增加应用程序的停机时间。

现在我正在采用以下方法,使用AWS CLI部署API网关,并且不会影响Cloudformation堆栈的部署。

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

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

1
我更换了API ID和阶段名称,并将此行添加到我的构建命令中,我在代码管道中使用它,然后BAM它开始工作了!4个小时的调试和摆弄在2分钟内解决。10/10 - Gregory Ledray
谢谢!我也经历过类似的情况,这种方法浮现在我的脑海中。 - siraj pathan
完美,我正在运行CloudFormation,但是我通过AWS CLI从CI部署它,因此当我更新重要内容(例如ApiKeyRequired on a ::Method anybody?)时,任何其他CLI命令来强制重新部署API网关都是完美的 - 我正在这个方向上努力,但感谢您节省了我的时间!FYI aws apigateway update-deployment...实际上(目前)不起作用。 - mud
不错的解决方法!而且,当通过AWS CLI从CI/CD部署它时,您实际上可以从API Gateway名称开始检索--rest-api-id值:aws apigateway get-rest-apis --no-paginate --query "items[?name == 'my-rest-api-name'].id | [0] - Paolo Rovelli

11

1
使用Api Gateway的V1版本能否实现这个功能?目前我还没有找到相关信息。 - jones-chris
1
这仅适用于V2 API(因此不包括私有REST API,例如)。 - Deiv
私有REST API是V1版本。此解决方案仅适用于V2 API,例如HTTP和Websocket API。 - Suraj Bhatia
2
这是一个解决方案,但不是唯一的解决方案。ApiGatewayV2不支持所有V1 REST API的功能。例如:自定义网关响应、API密钥、使用计划、金丝雀发布、模拟、边缘优化、x-ray等等。请参见https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html。 - Lqueryvg

6

从TheClassic发布的博客文章(目前为止最佳答案!)中链接的内容来看,您必须记住,如果您没有使用可以在$TIMESTAMP$的位置插入有效时间戳的工具来生成模板,则必须手动使用时间戳或其他唯一标识更新该值。这是我的功能性示例,它成功删除了现有部署并创建了一个新的部署,但在我想创建另一个更改集时,我将不得不手动更新这些唯一值:

    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'

这个解决方案对于管理ECS任务超时非常有帮助 - 我已经将其导入到这里,通过在我的Cloudformation模板上运行转换来解决APIGW部署问题,该转换将使用参数更改自动更新资源名称,并通过更新字符串参数与当前时代时间来发出信号。 - kian
不错的调用。在管理测试和生产环境时,您甚至可能希望为每个阶段管理单独的部署。这样,您可以首先部署到测试或暂存区,运行一些测试,然后在一次单独的堆栈更新中部署到生产环境,当所有测试都通过时。 - DV82XL

4
我可能有些迟到,但这里提供几种选项供您在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函数的action,就像自定义资源一样 - https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html

  4. SAM(Serverless Application Model)遵循类似于CloudFormation的语法,将资源创建简化为抽象层,并使用这些抽象层来构建和部署常规CloudFormation模板。 https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html

  5. 如果您使用像Sceptre这样的云形态抽象层,则可以在任何更新资源后调用createDeployment钩子 https://sceptre.cloudreach.com/2.3.0/docs/hooks.html

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

SAM 在每次更改后是否负责部署 API Gateway? - Tommy
1
@Tommy 不,它不会。 - JobaDiniz

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网关,您需要一个RestApi-Cfn-Resource和一个Deployment-Cfn-Resource。如果您为Deployment-Resource提供了一个阶段名称,则部署会自动创建在“正常”创建之上的部署。如果您不加这个,API将被创建而没有任何阶段。无论哪种方式,如果您有一个部署,您可以通过链接两个来向部署添加n个阶段,但一个阶段及其API始终只有一个部署。

更新简单API:

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

解决部署更新问题:

为了使部署可更新,您需要将部署和阶段拆分为单独的 Cfn-Resources。也就是说,您需要从 Deployment-Cfn-Resource 中删除阶段名称,并创建一个引用部署资源的新 Stage-Cfn-Resource。这样,您就可以更新部署。但是,阶段 - 您通过 URL 引用的部分 - 不会自动更新。

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

现在我们可以更新部署 - 即 API 的蓝图 - 我们可以将更改传播到其相应的阶段。据我所知,CloudFormation 目前不支持此步骤。因此,要触发更新,您需要添加一个 "custom resource" 或手动执行。其他 "none" CloudFormation 方法在 @Athi's 回答 above 中总结,但对我来说并非解决方案,因为我想限制使用的工具。

Manual stage update

如果有人有Lambda更新的示例,请随时联系我-我会在这里添加它。到目前为止,我找到的链接仅涉及普通模板。

希望这有助于其他人更好地理解上下文。

来源:


0

如果你需要做 $TIMESTAMP$ 的替换,我建议使用这种方式,因为它更简洁,而且你不需要手动管理 API Gateway。

我发现这里发布的其他解决方案在大多数情况下都能完成任务,但有一个主要问题 - 在 CloudFormation 中无法分别管理你的 StageDeployment,因为每当你部署 API Gateway 时,在创建新部署的次要过程(自定义资源/lambda、代码管道等)之间会有某种形式的停机时间。这是因为 CloudFormation 只将初始部署与 Stage 相关联。所以当你对 Stage 进行更改并进行部署时,它会恢复到初始部署状态,直到你的次要过程创建新的部署。

*** 请注意,如果你在 Deployment 资源上指定了 StageName,而没有明确管理 Stage 资源,则其他解决方案将可行。

在我的情况下,我没有那个$TIMESTAMP$替换部分,我需要单独管理我的Stage,以便我可以做一些像启用缓存这样的事情,所以我不得不找到另一种方法。因此,工作流程和相关的CF部件如下:
  • 在触发CF更新之前,请查看您即将更新的堆栈是否已存在。设置stack_exists: true|false

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

  • 以下条件:

Conditions:
  StackExists: !Equals [!Ref StackAlreadyExists, "True"]

以下是关于部署阶段的内容:
  # 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}

0
这对我有用:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接