8.2 基础设施即代码与配置管理
概述与动机
基础设施即代码(IaC)是指通过机器可读的定义文件来定义和配置基础设施(网络、服务器、数据库、负载均衡器、权限),而不是通过手动点击控制台或临时脚本。配置管理把同样的理念延伸到系统一旦存在之后的设置和状态上。二者合起来,把基础设施从一个手工打造、脆弱的产物,变成了一个经过版本管理、可评审、可复现的产品,遵循与你用于应用代码相同的工程纪律。
对大型团队而言,IaC 不是一种便利,而是一种必需。当数百名工程师需要环境、数千项资源必须在各个区域和账户之间保持一致时,手动配置既跟不上节奏,也无法保持正确。人工配置的基础设施迟早会漂移成独特的”雪花”服务器,没有人完全理解它们,也无法在故障后可靠地重建它们。把基础设施代码化,使其变得一致、可审计、可随时丢弃。任何环境都能从它的定义中被重新创建,任何改动都是一份可评审的差异。
企业和政府组织还能获得另一项决定性的好处:可强制执行的治理。诸如静态加密、网络分段、批准的区域以及用于成本分摊的标签等安全和合规要求,可以直接嵌入代码,并在任何东西被配置之前自动进行检查。与其在事后审计基础设施、追查违规,不如从一开始就不让不合规的基础设施存在。这种从检测到预防的转变,正是 IaC 成为现代平台实践基石的核心原因。
关键原则
- 优先选择描述期望状态的声明式定义,而不是描述操作步骤的命令式脚本。
- 把所有基础设施定义存放在版本控制中,像其他代码一样接受评审。
- 把基础设施当作不可变的:替换而不是原地修改。
- 让配置操作具有幂等性,使反复应用同一份定义得到同样的结果。
- 持续检测并协调漂移()实际运行的环境偏离其已声明定义的情况;代码而不是实际运行的系统才是真相来源。
- 用可复用的、经过版本管理的模块来组合基础设施,而不是复制粘贴。
- 把策略当作代码来编码,也就是把组织规则表达为机器可检查的代码,使护栏是自动生效的,而不只是建议性的。
- 把密钥排除在定义之外;从一个专门的密钥管理器中引用它们。
建议
选择声明式工具,并围绕模块组织它
采用一个声明式的 IaC 工具,例如 Terraform、Pulumi,或者一个云原生的选项,例如 CloudFormation,并在整个组织范围内将其标准化,以避免工具体系碎片化。关键的架构实践是模块化:构建小型的、文档完善的、经过版本管理的模块,来封装常见的模式(一个合规的网络、一个加固过的数据库、一个标准的服务)。各团队随后从这些模块中组合出各自的环境,而不是从原始资源开始编写。这能自动地推广良好的默认设置和安全配置,并大幅减少重复。
有意识地管理状态
声明式工具在一个状态文件中跟踪代码与真实资源之间的映射关系。要把状态远程存储在一个共享的、加密的、有访问控制的后端中,并使用锁定机制,使并发修改不会破坏它。绝不要把状态保存在笔记本电脑上,也绝不要手动编辑它,除非是作为万不得已的恢复手段。状态是敏感的,因为它可能包含资源元数据和密钥,所以要相应地予以保护。
用黄金镜像构建不可变基础设施
与其对运行中的服务器打补丁,不如烘焙一个经过版本管理的”黄金镜像”(一个预先配置好、经过加固的机器或容器镜像),并从中部署全新的实例。当你需要一次改动或打一个补丁时,构建一个新镜像并将其推出,同时淘汰旧的实例。这消除了配置漂移,使回滚变得轻而易举,并使每一个实例都保持相同、并可追溯到一个已知良好的构建版本。自动化的镜像流水线应当包含安全加固和扫描,使合规性在镜像层面就被内置进去。
检测并协调配置漂移
当实际运行的环境偏离其定义时,漂移就发生了,通常是因为有人做了一次紧急的手动改动。要定期运行漂移检测,把实际状态与声明的状态进行比较,并标记出差异。把漂移当作一个缺陷来对待:通过更新代码并重新应用来加以协调,而不是任由那次手动改动留在原地。对于需要持续配置强制执行的系统,使用一个能够持续将主机收敛到其声明状态的配置管理工具。
采用 GitOps 与拉取式部署
在 GitOps 模式中,一个 Git 代码仓库保存着系统已声明的期望状态,而一个运行在目标环境内部的自动化代理持续地拉取这个状态,并把实际运行的系统协调到与之一致。这颠覆了传统的推送模式。不需要任何外部系统持有长期有效的凭据来改动环境,因为环境会自己拉取自己的配置。GitOps 给你带来完整的审计轨迹(每一次改动都是一次提交)、简便的回滚(回退那次提交),以及强大的漂移纠正能力(代理会持续地重新确立期望状态)。这对于 Kubernetes 以及那些希望拥有单一、可评审的真相来源的组织而言,尤其强大。
用策略即代码来强制执行护栏
用诸如 Open Policy Agent(OPA)或诸如 Sentinel 这样的平台原生策略引擎,把组织规则()例如允许的区域、强制加密、必需的标签,以及禁止的公开暴露()表达为机器可检查的策略。在配置发生之前,在流水线中运行这些检查,使违规自动被阻止。策略即代码把安全团队的意图变成了一种可执行的、统一应用的控制手段,其规模可以扩展到成千上万次改动,这是人工评审永远做不到的。
权衡:优缺点
| 选择 | 优点 | 缺点 | 最适合 |
|---|---|---|---|
| 声明式 IaC(Terraform/Pulumi) | 可复现、可评审、可检测漂移 | 学习曲线陡;状态管理复杂 | 规模化运作的几乎所有团队 |
| 命令式脚本 | 熟悉;对一次性任务灵活 | 不具幂等性;难以审计和重复 | 狭窄的、过渡性的场景 |
| 不可变 + 黄金镜像 | 没有漂移;回滚轻而易举 | 镜像构建流水线开销 | 需要一致性的机群 |
| 可变配置管理 | 精细的持续控制 | 漂移风险;收敛更慢 | 遗留或长期存在的主机 |
| GitOps(拉取式) | 审计轨迹强;能自我修复 | 需要集群内代理和 Git 纪律 | Kubernetes 和云原生场景 |
| 策略即代码 | 自动、统一的护栏 | 前期的策略编写工作量 | 受监管的环境 |
主要的张力在于灵活性与控制力之间。手动和命令式的方法对于单次改动而言感觉更快,但它们会积累隐藏的不一致,而这在规模化之后会变得举步维艰。声明式的、不可变的、由策略治理的基础设施要求更多的前期投入,也需要真正的文化转变,因为工程师必须停止在控制台上进行快速改动,但它会在可靠性、可审计性,以及按需重建任何东西的能力上,多倍地回报这份投入。
与团队讨论的问题
谁负责共享模块库,一个模块中的改进如何传播到使用它的每一个团队? 模块只有在修复和加固后的默认设置能够传播时才有价值,而这需要明确的所有权和真正的版本管理,而不是一个人人都从中复制的文件夹。要决定谁维护那些合规网络模块和加固数据库模块、你们如何对它们进行版本管理(带有变更日志的语义化版本),以及各团队如何在不引发一场救火演习的情况下获取升级。在规模化的场景下,这就是一次性修复一个错误配置、和在一千个手工编辑的资源中追查它之间的区别。拿出证据:今天同一个模式存在多少个不同的副本、一次安全修复需要多长时间才能到达每一个环境,以及各团队是固定模块版本还是任其漂移。如果一个关键补丁无法在几天之内到达整个资产范围,你的模块化就只是表面功夫。
你们的漂移检测节奏是什么,当发现漂移时实际会发生什么? 漂移是指实际运行的环境悄悄偏离其已声明的状态,通常源于一次紧急的控制台改动,而容忍它会把你的代码变成一纸空文。要决定多久把实际状态与声明状态进行一次比较(每晚一次是一个合理的默认值),更重要的是决定响应方式:通过更新代码并重新应用来协调,绝不任由那次手动改动留在原地。在受监管的场景中,这是一项控制要求,因为审计人员需要声明状态持续与现实相符。拿出你当前的数字:每周有多少资源出现漂移、它们保持漂移状态多久,以及是否有人对消除它们负责。把每一次漂移都当作一个有负责人的缺陷来对待,否则真相来源的保证就会不断侵蚀,直到没有人再信任代码。
你们是否已经转向 GitOps 和拉取式协调,还是仍有一个外部系统持有能够改动生产环境的长期凭据? 在拉取模式中,目标环境内部的一个代理持续地把实际运行的系统协调到与 Git 一致,这消除了任何外部系统持有写访问权限的必要,并且它会重新确立期望状态,使漂移能够自我修正。这是一种强有力的安全和审计姿态,因为每一次改动都是一次提交,也不需要任何操作人员持有生产环境的长期凭据。成本是真实存在的:需要运行一个集群内代理,并保持严格的 Git 纪律,所以要把它与你当前基于推送的自动化进行权衡。拿出一份清单,说明当前谁、以及什么系统能够直接修改生产环境,以及这些改动留下了什么审计轨迹。对于 Kubernetes 和高保障的隔离环境而言,这种转变通常是值得的;对于少数几个静态资源而言,它可能有些过度。
你们的基础设施状态是如何存储、锁定和访问控制的,当它有一天被破坏或丢失时会发生什么? 状态是你的代码与真实资源之间的映射,所以一个丢失或损坏的状态文件,可能让一个工具对它自己创建的资源视而不见,并诱使某人进行一次破坏性的重新应用。对大型团队而言,这个风险会成倍增加,因为许多工程师都在针对共享状态进行操作,需要一个远程的、加密的、锁定的后端,使并发运行不会相互破坏。要权衡单一大状态的便利性与它所造成的影响范围,并考虑按环境或按域拆分状态,使单一一次失误不会拖垮一切。拿出事实:状态今天存放在哪里、锁定是否被强制执行、谁能够读取它(它可能包含密钥),以及你们是否曾经演练过一次恢复。在企业和政府场景中,要把状态后端当作一项敏感的、受访问控制的资产来对待,配有自己的备份、审计日志和恢复操作手册,因为丢失它就是丢失了关于存在什么的记录。
当一次真正的紧急情况要求进行手动改动时,被批准的应急通道是什么,那次改动又是如何被折返回代码中的? 每一种成熟的 IaC 实践最终都会遇到凌晨三点的事故,那时等待流水线是不可接受的,而诚实的问题不是手动改动是否会发生,而是你们如何约束它们。要事先决定谁可以绕过流水线、他们被允许触碰什么、这个操作如何被记录,以及改动必须被协调回代码中或被撤销的最后期限。没有这份约定,紧急例外就会悄悄变成日常习惯,ClickOps(点击式运维)就会从后门重新回来。拿出证据:上个季度发生了多少次带外改动、每一次未被协调的状态持续了多久,以及漂移检测是否真的捕捉到了它们。对受监管机构和公共机构而言,一套有文档记录、带自动日志的应急通道流程,通常是一项控制要求,因为审计人员既期望紧急情况是可能发生的,也期望每一次都留下轨迹,并使系统回到其声明状态。
你们的安全和合规基线中,有多少是表达为能够自动阻止一次不良改动的策略,又有多少只是存在于一份文档中、依赖某个人记住它? 写在维基页面上的散文式护栏经常被违反,因为它们依赖每一位工程师在截止日期压力下阅读并遵循它们,而同样的规则一旦表达为策略即代码,就会在一次不合规的改动被配置之前就将其拒绝。对大型组织而言,这是安全团队的意图能够扩展到成千上万次改动、而不会变成一个评审瓶颈的唯一方式。要把编写和维护策略的前期成本,与人工评审和事后补救的持续成本进行权衡,并决定哪些控制(加密、批准的区域、强制标签、不允许公开暴露)足够不可协商,值得作为硬性关卡来强制执行。拿出你们当前基线规则的清单,标出哪些是自动化的、哪些只是建议性的,以及每一项在实践中被违反的频率。在企业和政府场景中,自动化的策略把一次审计从数周的人工取证工作,变成了一次针对已强制执行控制的查询,并把合规从检测变成了预防。
行业视角
初创企业。 速度制胜,所以把你们整个技术栈放进一个声明式代码仓库中(Terraform 是一个常见的默认选择),把状态保存在一个托管的加密后端中,即使团队只有三个人,也要让每一次改动都经过一次拉取请求。跳过沉重的平台设备:暂时不需要一个中央模块团队,也不需要一个策略引擎,只需要版本控制和绝不在控制台上点击的纪律。仅此一点,就能给你带来可复现的环境,你可以拆掉它们以节省成本,再为下一次演示重新构建它们。
小型企业。 由于没有专职的平台专家,应依靠托管服务以及你的云提供商或供应商已经支持的任何 IaC,而不是建立一套你无力维护的定制工具体系。优先购买一个把合理默认设置(加密、备份、打补丁)都替你处理好的托管平台,而不是构建一个没有人手去运行的黄金镜像流水线。把目标框定得窄一些:把你少数几项关键资源纳入代码中,这样你就能在一次故障或一位承包商离职之后重建它们。
企业。 核心问题是在众多团队、账户和区域之间保持一致性,所以要投资于一个经过版本管理的共享模块库、远程锁定的状态,以及在流水线中强制执行的策略即代码。一个中央平台团队发布加固过的模块和护栏,产品团队在这些范围内自助使用,漂移检测持续运行,使成千上万项资源保持在一个已知的状态。要为维护模块和策略的持续成本做预算,因为它们的价值来自一次修复或一个加固后的默认设置能够同时传播到各处。
政府。 采购规则、认证要求和公共问责,都在把你推向不可变基础设施、签名提交,以及在一个经过认证的隔离环境内部进行的 GitOps 协调,使没有任何操作人员持有能够改动生产环境的长期凭据。把所需的安全基线编码进黄金镜像和策略即代码中,让提交历史充当防篡改、持续可用的审计证据。优先选择开放的、可移植的工具,而不是会把你困住的专有格式,并把应急通道流程及其日志记录明确下来,使紧急改动依然满足配置控制要求。
示例
初创企业。 一家五人初创公司在一个单一的 Terraform 代码仓库中定义了它整个 AWS 设置()也就是 VPC、数据库和容器服务()状态保存在一个加密的 S3 后端中,并通过 DynamoDB 进行锁定。每一次改动都要经过一次拉取请求,因此即使是一位独自值班的工程师,也能在运行 apply 之前确切地看到将会发生什么改动。当他们需要为一次重要的演示搭建一个全新的预发布环境时,他们复制一个小模块,几分钟内就把它搭建起来,事后也同样迅速地拆掉它,以控制云账单。
企业。 一家跨国零售商在多个云账户和区域中管理基础设施。一个中央平台团队发布经过版本管理的 Terraform 模块,用于合规网络、数据库和服务脚手架,并强制执行 OPA 策略,拒绝任何缺少加密或成本分摊标签的资源。产品团队自助配置各自的环境,但每一次改动都要流经这条流水线,在其中策略会被自动检查。漂移检测每晚运行,为任何手动改动开出工单,使成千上万项资源持续处于一个已知的、合规的状态。
政府。 一家在高保障环境中运作的国防机构,构建了内嵌所需安全基线的加固黄金镜像,并只从这些镜像中部署不可变实例。所有基础设施都在 Git 中声明,并由一个位于经过认证的隔离环境内部的 GitOps 代理进行协调,因此没有任何操作人员持有能够直接改动生产环境的长期凭据。每一次改动都是一次签名提交。这为审计人员提供了一份完整的、防篡改的历史记录,并在无需人工取证的情况下满足了持续监控和配置控制的要求。
商业案例:动机、投资回报率与总拥有成本
IaC 的投资回报来自速度、可靠性和风险降低。曾经需要数周工单驱动的手动配置才能建成的环境,如今可以在几分钟内创建出来,这解放了工程师,也加快了项目进度。可复现性大幅缩短了故障之后的恢复时间,因为任何环境都能从代码中重建。自动化的策略强制执行,降低了安全事件和审计发现问题的频率和成本,对受监管的组织而言,这可能是一笔可观的数目。
在总拥有成本这本账上,采用成本包括工具、培训、构建一个模块和策略库,以及停止手动改动所需的纪律。不采用的代价则更为陡峭,并随时间不断累积:没有人能够重建的雪花基础设施、缓慢且容易出错的配置过程、导致数据泄露的安全错误配置,以及消耗数周人工工作量的审计。对领导层而言,应把 IaC 阐述为把基础设施从一项无人管理的负债转变为一项受治理的、可复现的资产,也是使安全和合规变得自动化、而不只是一种愿景的机制。
反模式与陷阱
- 在生产环境中使用 ClickOps。 在控制台上手动进行改动,必然导致漂移,并摧毁可复现性。
- 把密钥写进代码。 在定义文件中硬编码凭据,会把它们泄露进版本历史和状态中。
- 单体化、未模块化的定义。 一个没有人敢改动的庞大配置,会变得和它所取代的手动设置一样脆弱。
- 无人管理的状态。 本地的或未锁定的状态文件,会导致损坏和基础设施丢失。
- 容忍漂移。 任由手动改动留在原地,会不断侵蚀真相来源的保证,直到代码变成一纸空文。
- 把策略当作文档。 存在于维基页面上、而不是自动化检查中的规则,经常被违反。
- 复制粘贴的泛滥。 在各团队之间重复配置,意味着修复和改进永远无法传播开来。
成熟度模型
第 1 级:启动。 基础设施通过控制台和临时脚本手动配置。环境不一致、缺乏文档记录,也无法被可靠地复现,故障之后的恢复缓慢且充满不确定性。
第 2 级:发展。 一些基础设施已经代码化,但实践因团队而异。状态管理不一致,漂移很常见,密钥有时会泄露进定义中,策略即便被强制执行,也主要依靠人工评审。
第 3 级:标准化。 声明式 IaC 是整个组织范围内有文档记录的标准,建立在共享的、经过版本管理的模块之上,配有受管理的、远程的、锁定的状态。策略即代码在流水线中强制执行护栏,密钥从一个专门的管理器中被引用,漂移检测按固定节奏运行。
第 4 级:管理。 这项实践被对照基线进行度量。你跟踪漂移率和平均协调时间、各团队的模块版本采用情况、被阻止与被漏过的策略违规、配置的前置时间,以及真正处于代码管控之下的资源比例。这些指标为改动把关,并指导你的投入方向,使决策依据证据而不是轶事。
第 5 级:编排。 基础设施是不可变的、由 GitOps 驱动的,能够针对漂移自我修复,合规证据被自动生成。模块和策略库随着真实的使用和事故不断改进,基础设施实践与安全、成本和交付规划相整合,使整个资产范围能够随着需求变化而适应。
讨论话题
- 在中央治理的模块与团队自主定义定制基础设施的自由之间,界线应当划在哪里?
- 在不使 ClickOps 变成常态的前提下,你们如何处理那种必须绕过流水线的真正紧急改动?
- 在多个账户和团队之间管理和保护状态的正确策略是什么?
- 相对于完全不可变的基础设施,可变配置管理在什么时候仍然合理?
- 你们如何让策略即代码库与不断演进的安全和监管要求保持一致?
- 对于早于 IaC 存在的遗留基础设施而言,一条现实可行的迁移路径是什么样子的?
关键要点
- 以声明式的方式定义基础设施,对其进行版本管理,并把它当作可评审、可复现的代码来对待。
- 从小型的、经过版本管理的模块出发进行构建,以传播良好的默认设置并消除重复。
- 优先选择不可变基础设施和黄金镜像,以消除漂移并简化回滚。
- 有意识地管理状态,把密钥排除在定义之外。
- 采用 GitOps,以获得强有力的审计轨迹和自我修复式的协调。
- 用策略即代码来强制执行护栏,使合规是被预防出来的,而不是事后被审计出来的。
参考资料与延伸阅读
- Kief Morris, Infrastructure as Code: Dynamic Systems for the Cloud Age.
- Yevgeniy Brikman, Terraform: Up & Running.
- Betsy Beyer, Chris Jones, Jennifer Petoff, and Niall Richard Murphy (eds.), Site Reliability Engineering.
- Gene Kim, Jez Humble, Patrick Debois, and John Willis, The DevOps Handbook.
- Weaveworks,关于 “GitOps” 的奠基性著述(Alexis Richardson 等人)。
- Open Policy Agent 文档与 Rego 策略语言。
- NIST 特别出版物 800-53,安全与隐私控制措施(配置管理族)。