KISS vs DRY 在基础设施即代码里的取舍:为什么“简单”往往赢过“聪明”
原文:https://rosesecurity.dev/2025/11/14/kiss-versus-dry-iac.html
rosecurity@dev_
我做的事情,是构建各种工具,让基础设施更容易运行,也更安全地扩展。日常我泡在 Terraform 和 Go 的战壕里,维护广泛使用的模块、编写 Provider,也在参与《Terraform 最佳实践》和 Terraform Proverbs 的编写。除了基础设施,我也设计数据工程流水线,并专注于为云上的公司打造可扩展的 ML/AI 平台。
我基本活在 CLI 里,是 “Red-Teaming TTPs” 的作者,也是 MITRE、OWASP 和 Debian 的贡献者!如果你喜欢我在社区里的代码、博客或工具,欢迎来找我聊聊、连接一下!
KISS vs DRY 在基础设施即代码里的取舍:为什么“简单”往往赢过“聪明”
发表于 2025 年 11 月 14 日
规模鸿沟问题(The Scale Gap Problem)
几乎所有的基础设施即代码(IaC)教程,都是这样开局的:
创建一个 S3 bucket,起一台 EC2 实例,再部署一个最简单的负载均衡器。
示例干净、简单、优雅。你一路跟着做,全都能跑通,然后你觉得 —— 嗯,我懂 Terraform 了。
然后你一回到真正的生产环境,一切都变了。
你面对的不是一个空白的 AWS 账号。
你已经有一堆两年前某个已经离职的人手动创建的资源。
到处都是“棕地”基础设施,没有清晰文档。
你要导入已有状态,要搞清楚现在到底跑着哪些东西,还要在“不炸掉生产”的前提下把这一切收拢到代码里。
除此之外,你还得管理开发、预发、生产三个环境里一共 200 台实例。
多个 AWS 账号,不同的配置和权限。
三个 Region 用来做灾备。
还有没人想碰的遗留业务跑在 Azure 上。
GCP 则负责你的容器化应用,跑着 GKE 集群。
于是,教程里那个优雅的小例子,一下子变成了现实世界里的梦魇:编排、状态管理、环境特异配置,以及各种历史遗留的棕地复杂度。
你不再只是“写基础设施代码”,而是要在真实世界那种又乱又在不停演化,而且充满历史包袱的环境里,去组织、编排、维护这堆代码。
这就是所谓的“规模鸿沟”(scale gap)。
也正是在这里,KISS vs DRY 的争论从“理论问题”,开始变成真金白银的时间、成本和工程投入。
DRY 革命:在解决“昨天”的问题
当团队撞上规模鸿沟时,第一反应通常是:消灭重复。
DRY(Don’t Repeat Yourself,不要重复自己)在软件工程里几乎就是福音书,所以基础设施工程师们也干了自己最擅长的事情:造工具来解决问题。
Terragrunt 出现,用来统一 backend 配置、减少环境之间的重复。
后来又有 Terraspace、Terrateam 等抽象框架,承诺通过复杂的层级继承模型和动态配置生成,来解决重复问题。
模块库慢慢长成了一个个复杂生态。
团队之所以采纳这些模式,往往是因为它们被贴上了“最佳实践”的标签 —— 而不是因为团队真的已经遇到了这些工具要解决的具体问题。
承诺听上去非常诱人:
只写一次基础设施代码,到处重用,在一个地方维护,就能轻松扩展。
Terraform 自身也在演进来支撑这些诉求,加入了 workspaces、dynamic blocks、for_each、更强大的模块能力,以及其他一系列原生支持 DRY 原则的特性。
纸面上,这一切都说得通。
但在实践中,代价比任何人想象的都高。
走向 DRY 的隐性成本
当抽象失效时,排障就变成考古学
现在是凌晨 3 点,生产挂了。
你得弄清楚:为什么 Terraform 想要销毁并重建你的数据库,而且你得马上搞清楚。
在一个基于 Terragrunt 和层级继承的 DRY 体系下,你看的就不只是 Terraform 代码了。
你得一路追踪各种值的来源:
- 根目录的 terragrunt.hcl 里定义的基础配置
- 不同环境目录下的覆写
- 动态生成的 backend 配置
- 调用其他模块的抽象模块
- 以及沿着继承链一路传下来的变量
那个数据库配置项,到底是从哪儿来的?
是全局配置?环境覆写?模块默认值?
你在当侦探,而不是在修问题。
每一层抽象都在增加认知负担 —— 偏偏是在你最不需要额外负担的时刻:凌晨 3 点的高压事故现场。
底层问题是:DRY 工具优化的是“写代码”的体验,而不是“在压力下阅读代码”的体验。
入职悬崖(The Onboarding Cliff)
新同事第一天入职,需要改一下预发环境里一个安全组的规则。
听上去挺简单,对吧?
在一个高度 DRY 化的抽象体系里,他/她需要学习的有:
- Terraform 本身
- 你们团队的模块库约定和抽象方式
- Terragrunt / Terraspace / 自研包装脚本
- 你们那套层级化配置结构
- 值是怎么在不同层级之间继承和覆写的
- 以及在哪改才不会把别的环境顺带搞崩
这不是入职,这是拜师学艺。
本来一个小时搞定的事情,变成要花几天。
原本应该是个简单改动,却变成了全程导览“我们基础设施哲学”的观光团。
对比一下另一种情况:
他打开一个目录,就能直接看到预发环境究竟会部署哪些东西;改完,提 PR。
从能产生价值到真正上手,中间的差距,就是以“周”来计量的。
生态锁定:一种隐形的技术债
一旦你在 DRY 抽象框架上投了大量资源,你就被锁在里面了。
- 整个代码库都默认依赖这套模式
- 团队成员都已经被训练成习惯它的写法
- CI/CD 流水线也绑定了它的执行方式
- 文档到处都在引用它
迁移出去会变成一个没有人愿意立项的大工程。
与此同时,这个工具的限制,也就成了你的限制。
当 Terraform 加新特性时,你只能等你的抽象层更新支持 —— 如果它有一天真的会支持的话。
你用更少的代码行数,换掉的是整个组织的灵活性。
KISS 的另一条路:把编排放到流水线里,把代码保持简单
在跟各种 Terraform 使用模式打了几年交道之后——从复杂 DRY 框架,到自研抽象层——我最后找到了一种“就是好用”的模式:
纯 Terraform + GitHub Actions 编排。
这并不是说要彻底否定 Terragrunt、Terraspace 之类的工具。
它们在特定规模和场景下绝对有用。
但对于绝大多数管理“中等规模基础设施”的团队,有一条更简单、效果更好的路径。
核心洞见:复杂度只能被“搬运”,不能被“消灭”
跨环境的编排复杂度是消不掉的。
你不可能凭空抹掉这样一些事实:
- dev / staging / prod 的配置本来就不同
- 多 Region 部署本来就需要协调
真正的问题不是:
“我们怎么消灭复杂度?”
而是:
“我们把复杂度放在哪个位置,才能让‘业务价值时间(time to business value)’最短?”
对比一下:
- DRY 路线: 把复杂度放进抽象工具和层级化配置结构里
- KISS 路线: 把复杂度放进 CI/CD 流水线里 —— 那是一个可观测、可调试的地方
仓库结构:嵌套但可直观导航
1 | ├── aws/ |
关键特征:
- 可以按“服务”(如 eks、mwaa、opensearch)拆,也可以按“逻辑域”拆,看你需要
- 每个服务有自己的 state 文件,爆炸半径隔离
- 可复用模块集中放在 modules 目录
- 没有 “terraliths”(巨石 Terraform monolith),也没有单一巨型 state 文件
- 完全可导航,任何东西都可以直接 grep
每一个服务目录都是一个完整的 Terraform root module。
例如打开 aws/us-east-1/prod/eks/,你就能一眼看到:us-east-1 里生产环境的 EKS 集群到底部署了什么。
没有继承链。
没有动态生成。
没有魔法。
只有最终真正会被 terraform apply 的配置。
是的,backend 配置是重复的(而且这是一个“特性”,不是“缺陷”)
1 | # aws/core-infrastructure/prod/backend.tf |
这段配置会在每个环境目录里出现,只是有一些小差异。
DRY 纯粹主义者看了会皱眉,但我很喜欢这样。
当 state 出问题的时候,我可以立刻看到:
- 这个环境的 state 存在哪个 bucket
- 用的是哪张 DynamoDB 表做锁
我不用去追踪什么动态生成逻辑。
只要 grep “myorg-terraform-state-prod”,就能马上知道哪些环境在用这个 bucket。
这些重复的代价是什么?
大概是在 20 个环境里,总共不到 100 行非常简单的配置。
换来的好处是:
- 排障效率秒级
- 几乎为零的认知负担
- 对“东西在哪里”的绝对清晰认知
把“编排”放进流水线
真正的魔法在这里发生 —— 而编排复杂度本来就应该放在这里。
自建的一套 GitHub Actions 可以提供:
在 Pull Request 阶段:
- 根据文件路径自动检测哪些环境受影响
- 对受影响的环境执行 terraform plan
- 把 plan 输出作为评论发到 PR 上
- 跑安全 / 合规检查
- plan 失败直接阻止合并
在主分支(main)上:
- 自动检测需要 apply 的环境
- 带审批地执行 terraform apply
- apply 失败发出告警
- 清理孤儿资源
- 追踪 drift(偏移),并创建工单
定时任务:
- 每晚对所有环境做一次 drift 检测
- 对比线上实际状态与代码
- 对异常变更发出告警
结果是:
排障成本极小,团队可以把精力集中在业务价值上,基础设施变得“隐形” —— 本来就应该是这样的。
回答一些典型反对意见
“但是你在重复 backend 配置!”
是的,这是有意为之。
在所有环境里重复大约 100 行 backend 配置,VS 花 40 小时去摸清 Terragrunt 的各种细节 —— 哪个的 ROI 更高?
重复带来了“可 grep 性”。
排查 state 问题时,grep “bucket-name” 就能马上知道所有使用这个 bucket 的环境。
不用去看动态生成逻辑,也不用问“这个值到底是从哪来的?”
在基础设施代码里,透明性永远比简洁漂亮更重要。
“你没有层级继承!”
没错,这也是故意的。
层级继承会制造隐式依赖:
值从“全局配置”一路流到“区域级配置”,再流到“环境级配置”。
一旦有东西出问题,你 debug 的不再是“基础设施”,而是那条继承链本身。
没有继承之后,每个值都显式地写在环境目录里。
新同事不需要先去理解你们那套继承模型——只需要读配置。
入职节省下来的时间,可以把重复配置的成本抵消一百次。
“这套东西撑不住大规模!”
这得看你对“规模”的定义是什么。
多账号、多 Region 合计 200 套环境?
这套模式能很干净地应对。
每个环境独立,改动隔离,爆炸半径可控。
它的确会在极端大规模场景下开始吃力,比如:
- 1000+ 环境
- 环境之间复杂的互相依赖
那时候,你确实需要更复杂的工具。
但要诚实问自己一句:
你现在真的有这样的规模问题吗?
还是只是在为一个“想象中的未来规模”提前买单?
大部分团队是在还没遇到 DRY 工具带来的“收益”之前,就先主动把那套复杂度揽在自己身上了。
什么时候用啥:更接地气的现实
适合用 KISS(Keep It Simple, Stupid)的情况:
- 环境数量少于 500 个
- 团队规模中小(< 50 个工程师)
- 基础设施改动频率不算高(大头是初始建设,之后变化不大)
- 运维清晰度极其重要(比如受监管行业、高风险基础设施)
- 团队成员背景多样(有不少传统运维 / Sysadmin,而不是清一色开发)
- 排障速度比“代码优雅程度”更重要
适合用 DRY 工具的情况:
- 你确实有超大规模(1000+ 环境,且相互依赖复杂)
- 团队以平台工程师为主,对抽象非常熟悉
- 有专门的平台团队来维护这套工具链
- 环境配置之间有大量复杂的公共逻辑,而且经常变
- 你在构建的是“基础设施即产品(Infra-as-a-Product)”,有很多内部“用户”
- 合规要求在所有部署中强制统一某些模式
真正的问题是:你的“成本指标”到底是什么?
如果你的成本指标是:
“我们写了多少行代码?”
那就选 DRY。
如果你的成本指标是:
“我们多快能完成业务目标?”
那就选 KISS。
所有那些会增加“从想法到业务价值”的时间的东西(因抽象带来的技术债、冗长的入职周期、晦涩难懂的排障过程),不管代码看起来多么“干净”,都是昂贵的。
反模式:为工程而工程(Engineering for Engineering’s Sake)
在基础设施工作里,最危险的陷阱之一,就是爱上工具或方案本身,而不是要解决的问题。
当团队花了几个月时间,去打造一整套复杂的层级结构、动态生成机制和继承模型时,他们往往是在为“代码美学”服务,而不是在为“业务需求”服务。
基础设施本身变成了主角,而不是它原本应该“enable”的那些东西。
真正好的基础设施工程应该是“隐形”的:
- 它让其他团队可以快速发版,而不用考虑底层平台
- 它不需要掌握某种“秘籍”才能做简单改动
- 它不会变成瓶颈,也不会变成某种“团队荣誉勋章”
- 它就在那里,安静工作,默默支撑业务
做到这一点需要一种“克制”:
- 能体现“工程才华”的“聪明方案”,往往不是对业务最好的方案
- 任何人都能看懂、改得动的“无聊方案”,反而很多时候才是正确选项
最小可行架构原则(Minimum Viable Architecture Principle)
从你现在真的需要的东西开始。
把它建得足够简单。
做成模块化的,这样以后可以替换局部。
随着真实需求的出现,再逐步迭代和改进。
不要为一个可能永远不会出现的“未来规模”提前造复杂系统。
不要因为某些工具被叫做“最佳实践”,就一股脑上它们 —— 如果你并没有它要解决的问题。
不要为了省几行代码,引入一套要多花几周入职时间才能搞明白的抽象。
基础设施是“辅助性操作”(auxiliary operation)。
它存在的意义,是不要挡路,让业务可以跑得更快。
每一层抽象、每一个复杂模式、每一处聪明的优化,都应该用一个标准来审视:
它到底创造了多少真实的业务影响?
而不是:这看起来工程上有多优雅。
结语:选择“无聊”的技术(Choose Boring Technology)
在不同规模下折腾了这么多年 IaC,我得到的结论是:
编排复杂度消不掉,只能换个地方放。
对大多数团队来说,把复杂度放进可观测、可调试的 CI/CD 流水线,要远远好过把它藏在抽象框架和层级配置结构里。
Terraform 本身对绝大多数场景来说已经够强了。
大多数团队并不需要再额外套一层抽象。
只用纯 Terraform,配上一套合理的仓库结构和流水线编排,就能在中等规模下跑得既优雅又好维护,同时排障简单、入职成本低。
复杂的 DRY 工具在极大规模场景、并有专门平台团队维护时是有用武之地的。
但大多数团队还没到那一步。
他们已经在为尚未获得的“收益”提前支付复杂度成本。
选择“无聊”的技术。
保持简单。
把重点放在业务速度上,而不是代码的优雅程度。
凌晨 3 点的那个你,会感谢现在做这个选择的你。
如果你喜欢(或讨厌)这篇博客,欢迎来我的 GitHub 逛逛!
相关文章
- Gang of Three: Pragmatic Operations Design Patterns
- Testing IaC with the TerraStack
- Rushing Toward Rewrite



