CloudFormation 堆栈更新时发生了什么 - 变更集、回滚与替换的内部机制
从变更集的差异检测、资源更新/替换/删除的判断逻辑以及回滚机制等方面,详细介绍 CloudFormation 更新堆栈时的内部处理流程。
模板差异检测 - 如何判定发生了什么变化
当向 CloudFormation 发出堆栈更新指令时,首先进行的是当前模板与新模板的差异检测。CloudFormation 以各资源的逻辑 ID(模板内的名称)为键,检测属性的变更。这里重要的是,CloudFormation 比较的是「模板中描述的属性」,而非「资源的实际当前状态」。也就是说,通过 AWS 控制台或 CLI 手动更改的属性,如果未反映到 CloudFormation 模板中,就不会被检测为差异。这种「漂移」(模板与实际状态的偏差)是 CloudFormation 运维中最棘手的问题之一。虽然可以使用漂移检测功能发现偏差,但不会自动修复。变更集(Change Set)是在实际执行更新之前,预览哪些资源将如何变更的功能。创建变更集后,CloudFormation 会分析差异,并为每个资源显示「添加」「修改」「删除」中的某个操作。
更新、替换与删除 - 各资源不同的更新行为
CloudFormation 的资源更新有 3 种模式。「无中断更新」(Update with No Interruption)在不停止资源的情况下更改属性。例如 Lambda 函数环境变量的更改或 S3 存储桶标签的添加属于此模式。「短暂中断更新」(Update with Some Interruption)会导致资源暂时不可用。EC2 实例的实例类型更改需要停止和重启实例。「替换」(Replacement)会删除现有资源并创建新资源。RDS 实例的引擎版本更改或 DynamoDB 表的分区键更改会触发替换。替换是最危险的模式。持有数据的资源(RDS、DynamoDB)被替换时,数据可能丢失。CloudFormation 文档中记载了每种资源类型的每个属性在变更时的行为(无中断/短暂中断/替换)。这是堆栈更新前必须确认的信息。
依赖关系解析与更新顺序
CloudFormation 会自动解析模板内资源间的依赖关系,并按正确顺序执行更新。依赖关系从 Ref 函数、Fn::GetAtt 函数和 DependsOn 属性推断。例如,如果 Lambda 函数引用了 IAM 角色,则先更新 IAM 角色,然后更新 Lambda 函数。CloudFormation 会尽可能并行执行更新。没有依赖关系的资源会同时更新,缩短更新时间。但并行更新也有风险。如果同时更新安全组和 EC2 实例,安全组规则可能暂时处于不一致状态。循环依赖(资源 A 引用资源 B,资源 B 引用资源 A)会在 CloudFormation 验证模板阶段被检测为错误。要解消循环依赖,需要将一方的引用改为 DependsOn,或重新设计资源。
回滚机制 - 失败后会回退到什么程度
堆栈更新过程中如果资源创建或变更失败,CloudFormation 会自动开始回滚。回滚是将所有资源恢复到更新前状态的处理。但回滚并不完美。通过「替换」创建的新资源会被删除,但通过「替换」删除的旧资源无法恢复。如果将 DeletionPolicy 设置为 Retain,替换时旧资源会被保留,回滚后可以手动恢复。回滚本身也可能失败。例如,更新过程中手动更改了资源,或回滚目标状态违反了当前的服务限制。回滚失败后,堆栈进入 UPDATE_ROLLBACK_FAILED 状态,需要手动干预。可以使用 ContinueUpdateRollback API 跳过有问题的资源继续回滚,但被跳过的资源将脱离 CloudFormation 管理。这种状态在运维上非常棘手,有时重新创建堆栈是最佳解决方案。
安全执行堆栈更新的实践对策
防止 CloudFormation 堆栈更新事故有 4 项对策。第一,务必确认变更集。不使用直接更新(UpdateStack),而是创建变更集进行预览,确认没有意外的替换或删除后再执行。第二,设置堆栈策略。堆栈策略可以禁止或限制特定资源的更新。为生产环境的 RDS 实例和 DynamoDB 表设置堆栈策略,禁止替换和删除,可防止因模板错误导致的数据丢失。第三,设置 DeletionPolicy。为重要资源设置 DeletionPolicy: Retain,在堆栈删除或资源替换时资源会被保留。第四,启用终止保护。为防止堆栈本身被误删除,为生产环境的堆栈启用终止保护。组合使用这些对策,可以大幅降低 CloudFormation 更新事故的风险。如果想系统学习 IaC 的设计与运维,可以参考专业书籍(Amazon)。