幾天前我在我們項(xiàng)目的API Gateway Cloudformation中添加了一些新的資源配置,然后使用這個(gè)Cloudformation更新了Cloudformation Stack,由于我只是新增了一些資源并且不會(huì)影響其它任何資源,所以運(yùn)行完我就悠哉的回家了。
由于第二天早上有事,所以中午才到公司,剛到公司有同事告訴我,我昨天API Gateway的更新使得我們有一些API Path無法訪問,我一臉茫然,我只不過是添加了一些新的資源怎么會(huì)導(dǎo)致API Gateway的API Path無法訪問,我并沒有對(duì)它做任何修改。
這時(shí),我的同事告訴我他們已經(jīng)把這個(gè)問題修改好了。在我還茫然于到底發(fā)生了什么事情的時(shí)候他們竟然已經(jīng)把問題解決了,我心中不禁有一絲羞愧。我問同事到底發(fā)生了什么,你們又是如何解決的,同事告訴我,修改API Gateway Cloudformation并更新后API Gateway Stage上的一些Resource奇怪的就不見了,但如果手動(dòng)在AWS Web Console上進(jìn)行一次Deployment后,所有的Resource就會(huì)正常發(fā)布出來。
這么神奇?此時(shí),這里有這么幾個(gè)問題:
- 我們不是在Cloudformation里定義了
AWS::ApiGateway::Deployment嗎?為什么還需要手動(dòng)在AWS Web Console上進(jìn)行Deployment? - 我沒有修改Cloudformation里定義的
AWS::ApiGateway::Deployment和相關(guān)配置,為什么更新Cloudformation的Stack后,Stage上的一些Resource奇怪的就不見了?
經(jīng)過分析之后,我們發(fā)現(xiàn)通過API Gateway Cloudformation更新Cloudformation stack后,Stage里的Resource會(huì)變成你第一次運(yùn)行這個(gè)Cloudformation所創(chuàng)建出來的Deployment里的Resource配置。說到這里你可能已經(jīng)明白了使用Cloudformation所創(chuàng)建出來的同一個(gè)Deployment一經(jīng)創(chuàng)建,便再也無法更新。
找到這個(gè)問題的原因后,再來反推一下這個(gè)事情為什么會(huì)發(fā)生:
- 程序員A創(chuàng)建這個(gè)API Gateway Cloudformation然后運(yùn)行,得到正確的API Gateway配置。
- 程序員B修改這個(gè)API Gateway Cloudformation然后運(yùn)行,發(fā)現(xiàn)API Gateway Stage并沒有被部署,于是程序B手動(dòng)在AWS Web Console上進(jìn)行Deployment發(fā)現(xiàn)這樣可以部署更新。
- 程序員C follow了程序員B的做法。所以每當(dāng)修改API Gateway Cloudformation并更新stack后,都需要手動(dòng)進(jìn)行一次Deployment。
- 到了我修改的時(shí)候,我并不知道這個(gè)上下文,所以我并沒有手動(dòng)進(jìn)行Deployment。
我要這樣繼續(xù)follow下去嗎?當(dāng)然不,這樣不make sense的事情,慘劇肯定還會(huì)發(fā)生。這樣做還有一個(gè)壞處就是它帶來了API Gateway 更新時(shí)API Path無法訪問,當(dāng)然這個(gè)時(shí)間取決于你手動(dòng)進(jìn)行Deployment的速度。
我真正想要的是通過Cloudformation自動(dòng)化完成Stage Deployment。怎么做呢?
讓我們先看看我們當(dāng)時(shí)使用的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
既然,同一個(gè)AWS::ApiGateway::Deployment無法更新,那我我每次更改的時(shí)候使用一個(gè)新的AWS::ApiGateway::Deployment可不可以呢?
于是我修改了Deployment這個(gè)Resource名稱到Deployment201905092310,運(yùn)行更新后我得到了這樣的Error: StageDescription cannot be specified when stage referenced by StageName already exists。
好吧,這種方式會(huì)將Stage和Deployment綁定在一起,我只想創(chuàng)建一個(gè)新Deployment但我并不想也創(chuàng)建一個(gè)新的Stage??磥?,我尋找一種方式將Stage配置分離出來,正好這里就有一個(gè)API Gateway Resouce Type就是AWS::ApiGateway::Stage。
經(jīng)過修改,我們的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后將其替換成一個(gè)不會(huì)重復(fù)的值。你可以通過Linux命令實(shí)現(xiàn)快速替換,如:
sed -i "s|__VERSION__|201905092334|g" ./cloudformation/api-gateway-template.yml
如果,你使用ansible實(shí)現(xiàn)cloudformation的部署,那么你可以這個(gè)做:
---
- 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