我今天更新 CloudFormation 堆栈时观察到奇怪的行为,想知道我是否做错了什么。下面是简化的示例。
重现步骤
第 1 步: 创建一个包含两个队列的堆栈,并为两个队列创建一个队列策略。
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Cloudformation queue policy change bug repro step one: create stack with
this template.
Resources:
FirstQueue:
Type: "AWS::SQS::Queue"
SecondQueue:
Type: "AWS::SQS::Queue"
FirstPolicy:
Type: "AWS::SQS::QueuePolicy"
Properties:
Queues:
- !Ref FirstQueue
- !Ref SecondQueue
PolicyDocument:
Statement:
- Action:
- "SQS:SendMessage"
Effect: "Deny"
Principal: "*"
第 2 步: 使用模板更新堆栈,其中原始策略仅应用于第一个队列,新策略应用于第二个队列:
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Cloudformation queue policy change bug repro step two: update stack with
this template.
Resources:
FirstQueue:
Type: "AWS::SQS::Queue"
SecondQueue:
Type: "AWS::SQS::Queue"
FirstPolicy:
Type: "AWS::SQS::QueuePolicy"
Properties:
Queues:
- !Ref FirstQueue
PolicyDocument:
Statement:
- Action:
- "SQS:SendMessage"
Effect: "Deny"
Principal: "*"
SecondPolicy:
Type: "AWS::SQS::QueuePolicy"
Properties:
Queues:
- !Ref SecondQueue
PolicyDocument:
Statement:
- Action:
- "SQS:ReceiveMessage"
Effect: "Deny"
Principal: "*"
结果
堆栈更新后我期望的结果是每个队列都有自己的队列策略。具体来说,我希望第二个队列应用新的第二个策略。我观察到的是第二个队列有一个空策略。
如果我查看 CloudTrail 中的事件历史记录,我会看到 CloudFormation 在堆栈更新期间对第二个队列发出两个
SetQueueAttributes
请求:
{
"eventVersion": "1.09",
"eventSource": "sqs.amazonaws.com",
"eventName": "SetQueueAttributes",
"sourceIPAddress": "cloudformation.amazonaws.com",
"userAgent": "cloudformation.amazonaws.com",
/* ... */
"requestParameters": {
"queueUrl": "https://sqs.ca-central-1.amazonaws.com/XXXXXXXXXXXX/BugRepro-SecondQueue-hZyO53RvsmdK",
"attributes": {
"Policy": ""
}
},
/* ... */
}
问题
对我来说,CloudFormation 似乎没有意识到第二个队列上的策略正在被替换,因此,它不仅设置新策略,还设置它(设置为新策略)并清除它(以删除旧策略) )。我在这里错过了什么或者做错了什么吗?这是预期的行为吗?
我认为这是由简单的竞争条件以及
AWS::SQS::Queue
和 AWS::SQS::QueuePolicy
之间不直观的关系引起的。
CloudFormation 的工作原理是识别模板中已定义资源的更改,并根据依赖关系构建这些更改的有向无环图 (DAG):如果
ResourceB
依赖于 ResourceA
,则 CloudFormation 将确保 A
将之前修改过B
。如果 CloudFormation 无法确定两个资源之间的依赖顺序,则可以随意并行修改它们,这就是竞争条件出现的地方。
但这只是故事的一部分。更重要的是,CloudFormation 资源与物理 AWS 资源并不完全匹配。
更常见的是,CloudFormation 资源对应于 AWS API 调用——通常是单个 API 调用。例如,AWS::SQS::Queue
对应于 SQSCreateQueue API 调用。如果您查看该 API,您会发现没有地方可以指定队列策略。相反,您必须调用 SetQueueAttributes API 来更新队列策略。 我不知道为什么 CloudFormation 开发人员决定创建一个单独的
QueuePolicy
资源来执行此操作,而不是将其合并到
Queue
资源中。我怀疑这是因为许多/大多数 CloudFormation 资源类型是根据通用 API 定义自动生成的。这也可以解释为什么
QueuePolicy
指的是
Queue
,而不是相反(这就是 API 的工作原理)。但结果是,当您删除从
FirstPolicy
到
SecondQueue
的引用时,您导致 CloudFormation 将这两个策略放在 DAG 的不同分支上,从而允许它们并行执行。这意味着对
QueueTwo
的实际更改取决于最后处理这两个资源中的哪一个。解决此问题的一种方法是更新堆栈两次:第一次更新时,您将
QueueTwo
与
PolicyOne
分离,第二次更新时,您将其附加到
PolicyTwo
。这意味着
QueueTwo
在这些更新之间不会有策略,如果您已经部署了需要该策略的应用程序,这可能会出现问题。另一种方法是使用
DependsOn
资源属性声明
PolicyOne
和
PolicyTwo
之间的显式依赖关系。指定 PolicyTwo
取决于
PolicyOne
,CloudFormation 将正确排序更新。请注意,仍然有一个(非常小的)窗口,其中
QueueTwo
没有策略,因为堆栈更新不是事务性的。从长远来看,您不需要这种关系,因此(1)评论需要它以确保正确的堆栈更新,或者(2)在更新成功完成后删除。