AWS Cloudformation 神坑 - API Gateway Deployment

几天前我在我们项目的API Gateway Cloudformation中添加了一些新的资源配置,然后使用这个Cloudformation更新了Cloudformation Stack,由于我只是新增了一些资源并且不会影响其它任何资源,所以运行完我就悠哉的回家了。

由于第二天早上有事,所以中午才到公司,刚到公司有同事告诉我,我昨天API Gateway的更新使得我们有一些API Path无法访问,我一脸茫然,我只不过是添加了一些新的资源怎么会导致API Gateway的API Path无法访问,我并没有对它做任何修改。

这时,我的同事告诉我他们已经把这个问题修改好了。在我还茫然于到底发生了什么事情的时候他们竟然已经把问题解决了,我心中不禁有一丝羞愧。我问同事到底发生了什么,你们又是如何解决的,同事告诉我,修改API Gateway Cloudformation并更新后API Gateway Stage上的一些Resource奇怪的就不见了,但如果手动在AWS Web Console上进行一次Deployment后,所有的Resource就会正常发布出来。

这么神奇?此时,这里有这么几个问题:

  • 我们不是在Cloudformation里定义了AWS::ApiGateway::Deployment吗?为什么还需要手动在AWS Web Console上进行Deployment?
  • 我没有修改Cloudformation里定义的AWS::ApiGateway::Deployment和相关配置,为什么更新Cloudformation的Stack后,Stage上的一些Resource奇怪的就不见了?

经过分析之后,我们发现通过API Gateway Cloudformation更新Cloudformation stack后,Stage里的Resource会变成你第一次运行这个Cloudformation所创建出来的Deployment里的Resource配置。说到这里你可能已经明白了使用Cloudformation所创建出来的同一个Deployment一经创建,便再也无法更新

找到这个问题的原因后,再来反推一下这个事情为什么会发生:

  • 程序员A创建这个API Gateway Cloudformation然后运行,得到正确的API Gateway配置。
  • 程序员B修改这个API Gateway Cloudformation然后运行,发现API Gateway Stage并没有被部署,于是程序B手动在AWS Web Console上进行Deployment发现这样可以部署更新。
  • 程序员C follow了程序员B的做法。所以每当修改API Gateway Cloudformation并更新stack后,都需要手动进行一次Deployment。
  • 到了我修改的时候,我并不知道这个上下文,所以我并没有手动进行Deployment。

我要这样继续follow下去吗?当然不,这样不make sense的事情,惨剧肯定还会发生。这样做还有一个坏处就是它带来了API Gateway 更新时API Path无法访问,当然这个时间取决于你手动进行Deployment的速度。

我真正想要的是通过Cloudformation自动化完成Stage Deployment。怎么做呢?

让我们先看看我们当时使用的API Gateway Cloudformation Template:

AWSTemplateFormatVersion: 2010-09-09
Description: Cloudformation template to create API gateway related resource
Parameters:
  DeployStage:
    Type: String
    Description: deployment stage

  CustomDomainName:
    Type: String
    Description: custom domain name

  CertificateArn:
    Type: String
    Description: ACM Certificate arn

Resources:
  MyRestAPI:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: my-rest-api
      Description: Rest API
      FailOnWarnings: true

  MyResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt MyRestAPI.RootResourceId
      RestApiId:
        Ref: MyRestAPI
      PathPart: test

  Deployment:
    DependsOn:
      - MyResourceGet
      - Account
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: MyRestAPI
      StageName:
        Ref: DeployStage
      StageDescription:
        CacheClusterEnabled: true
        CacheClusterSize: 1.6
        DataTraceEnabled: true
        LoggingLevel: ERROR
        MethodSettings:
          -
            CachingEnabled: false
            HttpMethod: GET
            LoggingLevel: ERROR
            ResourcePath: /test

  LiveDomainName:
    DependsOn: Deployment
    Type: AWS::ApiGateway::DomainName
    Properties:
      RegionalCertificateArn:
        Ref: CertificateArn
      DomainName:
        Ref: CustomDomainName
      EndpointConfiguration:
        Types:
          - REGIONAL

  LivePathMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      BasePath: "v1"
      DomainName:
        Ref: LiveDomainName
      RestApiId:
        Ref: MyRestAPI
      Stage:
        Ref: DeployStage

既然,同一个AWS::ApiGateway::Deployment无法更新,那我我每次更改的时候使用一个新的AWS::ApiGateway::Deployment可不可以呢?

于是我修改了Deployment这个Resource名称到Deployment201905092310,运行更新后我得到了这样的Error: StageDescription cannot be specified when stage referenced by StageName already exists

好吧,这种方式会将Stage和Deployment绑定在一起,我只想创建一个新Deployment但我并不想也创建一个新的Stage。看来,我寻找一种方式将Stage配置分离出来,正好这里就有一个API Gateway Resouce Type就是AWS::ApiGateway::Stage

经过修改,我们的API Gateway Cloudformation template变成了这样子:

AWSTemplateFormatVersion: 2010-09-09
Description: cloudformation template to create API gateway related resource
Parameters:
  DeployStage:
    Type: String
    Description: deployment stage

  CustomDomainName:
    Type: String
    Description: custom domain name

  CertificateArn:
    Type: String
    Description: ACM Certificate arn

Resources:
  MyRestAPI:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: my-rest-api
      Description: Rest API
      FailOnWarnings: true

  MyResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt MyRestAPI.RootResourceId
      RestApiId:
        Ref: MyRestAPI
      PathPart: test

  Deployment__VERSION__:
    DependsOn:
      - MyResourceGet
      - Account
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: MyRestAPI

  ApiStage:
    Type: AWS::ApiGateway::Stage
    DependsOn:
      - Deployment__VERSION__
    Properties:
      RestApiId:
        Ref: SingleStackRestAPI
      DeploymentId:
        Ref: Deployment__VERSION__
      StageName:
        Ref: DeployStage
      CacheClusterEnabled: !Ref CachingEnabled
      CacheClusterSize: 1.6
        -
          CachingEnabled: false
          HttpMethod: GET
          LoggingLevel: ERROR
          ResourcePath: /test

  LiveDomainName:
    DependsOn: Deployment__VERSION__
    Type: AWS::ApiGateway::DomainName
    Properties:
      RegionalCertificateArn:
        Ref: CertificateArn
      DomainName:
        Ref: CustomDomainName
      EndpointConfiguration:
        Types:
          - REGIONAL

  LivePathMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      BasePath: "v1"
      DomainName:
        Ref: LiveDomainName
      RestApiId:
        Ref: MyRestAPI
      Stage:
        Ref: DeployStage

其中__VERSION__是需要我们每次修改完Template后将其替换成一个不会重复的值。你可以通过Linux命令实现快速替换,如:

sed -i "s|__VERSION__|201905092334|g" ./cloudformation/api-gateway-template.yml

如果,你使用ansible实现cloudformation的部署,那么你可以这个做:

---
  - name: set deployment version
    command: date +%Y%m%d%H%M
    register: timestamp

  - replace:
      path: ./cloudformation/api-gateway-template.yml
      regexp: __TIMESTAMP__
      replace: "{{timestamp.stdout}}"
      backup: false

  - name: setup API gateway stack
    cloudformation:
      stack_name: "api-gateway-stack"
      state: present
      region: "{{aws_region}}"
      template: cloudformation/api-gateway-template.yml
      template_parameters:
        DeployStage: "{{deploy_stage}}"
        CustomDomainName: "{{domain_name}}"
        CertificateArn: "{{certificate_arn}}"

参考:https://currentlyunnamed-theclassic.blogspot.com/2018/12/mastering-cloudformation-for-api.html

你可能感兴趣的:(AWS Cloudformation 神坑 - API Gateway Deployment)