Sägetstrasse 18, 3123 Belp, Switzerland +41 79 173 36 84 info@ict.technology

    Terraform @Scale - 第 2 部分:State 大小的最佳实践艺术

    Infrastructure-as-Code 早已不再是可有可无的选项。那些希望认真运营并扩展其云基础设施的企业,都在使用 Terraform。然而,随着成功的不断扩大与复杂度的提升,一个关键问题随之而来:一个 Terraform State 究竟应该有多大或多小?

    过大的 State 会阻碍团队协作、拖慢流程并引入不必要的风险;而过小的 State 又会导致额外的管理负担以及状态一致性的脆弱。因此,关键在于找到一个恰到好处的平衡点 - 不多不少,刚刚好。欢迎来到 Terraform 的 Goldilocks 原则。

     

    IaC 世界中的 Goldilocks 原则

    所谓的 Goldilocks 原则最初来源于一个英语童话故事。主角 Goldilocks 反复尝试各种选项 —— 例如太热、太冷,最终选择那个“刚刚好”的方案。这个画面非常贴切地映射到了 Terraform 的应用场景中。

    在这里我们同样在寻找理想的平衡点。目标是构建一个既不细碎也不庞杂的配置结构。三个核心目标如下:

    1. 最大化效率 —— 在基础设施的部署与管理过程中实现高效运作
    2. 避免不必要的冗余 —— 如在过大 State 中常见的重复资源定义
    3. 避免状态不完整 —— 这种情况常由过度碎片化引起

    在实际操作中,这意味着我们需要一种结构,既能赋予各团队必要的独立性,又能确保对整个系统状态的持续可见性与一致性。

     

    如何正确划分 State 的挑战

    在任何一个稍具规模的环境中,关于 Terraform State 应该有“多大”的问题迟早都会浮现。对此并没有放之四海而皆准的答案 —— 成功的做法高度依赖于具体的使用场景,而这些场景往往因客户需求而异。

    尽管如此,经过多个面向不同客户的项目后,我们可以清晰地识别出一些典型的挑战。

    State 过大时会发生什么?

    看似庞大的 Terraform State 具有某种表面吸引力:所有内容集中在一个地方,易于版本控制,结构清晰......只需按下按钮即可构建整个数据中心,这曾是不少管理者与决策者的梦想,如今通过 Terraform 看似触手可及。我说这话并非空谈 —— 大约十年前我第一次使用 Terraform 时,自己也曾掉入这个陷阱。是的,它看起来非常漂亮,也令人印象深刻,许多问题似乎一劳永逸。但这只是表象,现实很快会揭示另一面。

    很快你就会遇到以下问题:

    • 性能问题:执行 terraform planapply 的时间极长,因为大型 State 占用大量计算资源和网络带宽。我见过的最夸张案例,是一个 terraform apply 同时部署网络、虚拟机和整套 Oracle 数据库集群 —— 最快也要 75 分钟完成。这完全是典型的新手错误。
    • 锁冲突阻塞:多个团队或员工同时操作同一个 State,会导致冲突与等待。结果往往是沮丧、抱怨声四起和生产力下降。
    • 复杂的风险管理:某个模块中的错误可能影响整片基础设施。这在生产环境中尤为严重。一个 Terraform 模块试图涵盖的资源越多,出错时的影响范围(Blast Radius)就越广。在 Terraform 的世界中也适用这句老话:系统越复杂,出事时越响亮且迅速。
    • 开发周期变慢:变更耗时增加,上市时间延长,Continuous Delivery 成为挑战。反馈循环应尽可能短,但若部署一次基础架构需要 60 分钟,那显然不够短。

    那么 State 太小呢?

    另一个极端同样问题重重。若将基础架构拆分为太多小型 State,很快就会掉入另一个碎片化的陷阱:

    • 管理开销:成几十上百个 State 需要维护、版本控制和协调。这极耗时间并引入额外复杂性。最糟糕的情况下,复杂性演变为混乱,使得系统难以理解,最终成为一个“没人愿意碰”的历史遗留问题,逐渐演变为潜在的关键性定时炸弹。
    • 一致性问题:多个 State 之间的依赖关系 —— 比如 VPC、子网或安全组 —— 难以追踪,错误频发。你在某处修改了基础架构,却在完全意想不到的地方触发问题。如果你不想再因为手动修复 State 而熬夜加班,这种场景显然是需要避免的。
    • 代码重复:可复用的逻辑不再集中管理,而是在多个模块中重复实现。这违背了 DRY 原则,使基础架构状态从“可清晰定义”逐渐滑向“虽有定义但难以理解”的模糊状态。明明是希望摆脱逆向工程的痛苦,但当每人都在重造轮子,这种痛苦终将回归。
    • 资源管理碎片化:缺乏对整体基础架构的统一视图,导致整体认知受限。谁在什么时候修改了什么?能问谁?往往只得到一声耸肩作为回应。

     

    用于优化 Terraform States 的架构示例

    “恰到好处”的理论只有在实践中得以有效应用时才有意义。因此,值得参考一些在可扩展 Terraform 设置中被广泛采用的成熟架构模式。两种方法尤其突出:一种是适用于多账号策略的 Layer 分层方法,另一种是为微服务环境设计的基于业务领域的结构。

    示例 1:采用 Layer 分层模型的多账号云策略

    一种经过大量实践验证的模型是将 States 按功能分为多个层级(Layer)。该方法依赖于基础设施组件的生命周期和变更频率,从而构建出一套结构清晰的 State 划分方式:


    ├── Foundation Layer
    │ ├── network-state.tf # VPCs, Subnets, Transit Gateways
    │ ├── security-baseline-state.tf # Security Groups, NACLs, IAM Baseline
    │ └── monitoring-state.tf # CloudWatch, Logging, Alarming

    ├── Platform Layer
    │ ├── data-services-state.tf # Databases, Queues, Storage
    │ ├── kubernetes-state.tf # EKS/OKE Cluster, Node Groups
    │ └── ci-cd-state.tf # CI/CD Infrastructure

    └── Application Layer
    ├── app-team-a-state.tf # Team A's Applications
    ├── app-team-b-state.tf # Team B's Applications
    └── shared-services-state.tf # Shared Application Services

    这个方法为何如此有吸引力?

    这种架构贴合工程师的思维方式,共分为三个层级:

    • 基础层(Foundation Layer):包含变更频率低且稳定性高的资源,例如网络、安全基线与可观测性组件。这些资源更符合基础设施的通用需求,而非业务的快速演进。变更频率低,因为没人会频繁修改 CIDR 范围或安全策略。
    • 平台层(Platform Layer):涉及数据库、Kubernetes 集群等组件,其变动频率适中。它们与业务密切相关,但通常遵循特定发布周期(如每 3、6 或 12 个月),以满足业务或合规性要求。
    • 应用层(Application Layer):这一层最具动态性。产品的功能开发与持续创新会频繁引发部署与测试,因此需要频繁更新基础设施。该层的关键是“按需”原则 —— 资源只在真正需要时才部署,用完即释放以节约成本。因此,这些 States 规模较小,按团队隔离。即使 Team A 出现故障,也不会影响 Team B。

    这种结构既支持职责的清晰划分,又提升了各团队之间工作的并行效率。同时,它也显著降低了变更带来的意外副作用风险。

    示例 2:用于微服务环境的领域驱动 Terraform 架构

    在以微服务为导向的组织中,有一种完全不同但同样高效的方法:基于业务领域的 State 划分方式。其思想类似于 Domain-Driven Design,将 Infrastructure-as-Code 按业务领域结构化。

    ├── Infrastructure Domain
    │ ├── networking-state.tf # Shared Networking
    │ └── security-state.tf # Security Controls

    ├── Service Domains
    │ ├── user-service-domain.tf # User Management Services + Infrastructure
    │ ├── payment-domain.tf # Payment Processing + Infrastructure
    │ └── content-domain.tf # Content Management + Infrastructure

    └── Cross-cutting Concerns
    ├── monitoring-state.tf # Observability Infrastructure
    ├── backup-state.tf # Backup and Recovery Systems
    └── compliance-state.tf # Compliance Controls

    为什么这种方法效果好?

    这种架构贴合 IT 管理者的思维方式。

    • 与业务逻辑一致:技术基础设施与业务领域组织保持一致。这使开发、运维与管理层之间的沟通更加顺畅。
    • 团队自治:每个团队管理其对应领域及相关基础设施,降低了对中央团队的依赖。当多个领域(如网络、数据库、存储等)都服务于某一业务服务时,这些元素会集中在该服务下统一管理。此方法提升了敏捷性与上市速度(Time-to-Market) —— 这两个词在管理者眼中尤为关键。
    • 减少协调成本:同一领域内的基础设施更改极少影响其他团队,显著降低了团队间的协作负担。这也是管理层乐见的,因为它强调了 Value Stream 与 Scrum 团队的自主权。

    这一架构的显著优势是 团队组织的灵活性。新增服务或领域变得更加轻松,无需重构或破坏现有结构。员工既可参与基础设施团队,也可参与服务团队(以虚拟团队形式存在)。这种灵活性同时也会带来代码重复的问题,因此 Infrastructure DomainCross-Cutting Concerns 层变得尤为重要,用以规范基础模块、避免代码冲突并为服务团队提供统一组件。为此,引入 Policy-as-Code 是关键步骤 —— 因为规则不仅要被定义,还需在 Terraform 层面上强制执行以确保合规性。

     

    Value Streams 与 Terraform State 管理

    Terraform State 的结构划分不应仅依赖于技术分层或模块划分。更值得关注的是一个组织的价值流(Value Streams)。沿着 Value Streams 构建 States,意味着真正围绕能带来实际业务价值的内容进行组织。这一点无论在基础设施部署还是新功能上线过程中都同样适用。

    在实践中,两个核心价值流尤其重要:基础设施部署(Infrastructure Provisioning)应用部署(Application Deployment)

    Value Stream 1:Infrastructure Provisioning

    在为新项目或新区域部署完整环境时,关键在于 Terraform States 之间具备合理的逻辑依赖关系,但彼此之间的耦合度应尽可能低。目标是让大部分基础设施能够独立部署,从而提高效率与可靠性。

    一个成熟的模型如下:

    Infrastructure Provisioning zh
    

    这个模型背后的逻辑是什么?

    • Remote State Layer:构成全局基础,包括 IAM 策略、集中化 Secrets 管理与 State 后端配置。
    • Network Layer:构建区域级资源,例如 VPC、子网或 Transit Gateway。
    • Platform Layer:部署云原生服务,如 Kubernetes 集群、数据库或消息系统。
    • Application Layer:此层包含工作负载、CI/CD 配置与应用逻辑,应尽可能实现独立部署。

    这种模块化架构使各团队能够快速、可控地引导完整环境,特别适用于新客户、区域或业务部门的拓展。

    Value Stream 2:Application Deployment

    另一个同样关键的价值流是功能部署。在此场景中,重点是速度、稳定性与对其他组件的最小影响。理想情况下,应用的变更可以隔离执行,不影响其他 States。

    理想架构如下:

    Application Deployment de
    

    关键要点包括:

    • 按环境分离:开发、测试与生产环境分别拥有独立 State。这样可以先在隔离环境中验证新功能,避免测试修改影响到生产系统。尽管希望各环境保持一致性与代表性,但 State 的分离是必要的。
    • 最小依赖性:State 尽量保持简洁,确保修改过程风险最低。如数据库或网络资源通过 Outputs 引用,而非直接变更。
    • 高并行性:通过 Git 的 Feature Branch 实现仅影响少数 States 的隔离变更,从而让部署团队快速响应,而不与其他团队发生冲突。

    将 Value Stream 思维与精心设计的 State 架构相结合,能够构建一个高效、安全且可扩展的部署框架。从基础设施的搭建到新功能上线,通过这种方法不仅可以保持高度灵活性,同时也能控制好基础结构与潜在错误带来的影响范围,从而实现对整个数据中心状态的清晰掌控。

    Goldilocks 原则的实际应用

    Goldilocks 原则不仅仅是一个有趣的比喻,它是设计健壮、可维护 Terraform 模块的实用工具。关键在于选择恰当数量的变量、配置与输出 —— 不多不少,刚刚好。

    下图展示了这种张力状态:左侧是配置不足,右侧是过度优化。中间的区域被称为 Goldilocks 区 —— 模块在此区域既具灵活性,又保持稳定性与可维护性。

    如果太少……

    配置不足的模块初看简单直接,似乎很有吸引力。但这是错觉,通常表现如下:

    • 单体结构:一个包含所有资源(网络、安全、工作负载)的中央 State 文件
    • 不完整配置:变量缺失或不足,依赖 Provider 默认值或硬编码,灵活性差
    • 静态代码:模块高度依赖具体使用场景,无法动态适配,重用性差
    • 输出结构不佳:输出字段僵化,难以与其他模块集成
    • 缺乏保护机制:输入值未验证(即 Variable Validations)、缺少 Lifecycle ConditionsRuntime Checks,这会妨碍资源间协同工作的正确性

    这类模块在面对灵活性需求时容易崩溃,难以维护与扩展。

    如果太多……

    另一方面,过度结构化也同样危险,常见症状包括:

    • 过度细化管理:大量微小模块与 States,整体结构难以掌控
    • 参数泛滥:几乎所有配置都由外部传入,连最基础的默认值都未设置或设置不合理
    • 复杂依赖:输出交叉引用,最终形成所谓的“输出狂欢”
    • 冗余输出:输出信息重复、泛化,缺乏实际用途,优于使用少量清晰的 Key/Value Map 输出结构

    结果是代码与认知的双重负担。新成员入门难度高,错误发生的可能性随之增加。

    Goldilocks 区域:刚刚好

    处于中间区域的模块,具备可复用性、可读性与良好的团队协作能力。典型特征如下:

    • 模块化的 States 与清晰边界:State 与模块按层级、领域或环境进行划分
    • 合理但简洁的参数:模块设置具有实用的默认值,覆盖大约 80% 的应用场景
    • 可复用模块:通过完整的动态设计,使其可在多个团队间灵活共享使用
    • 一致的输出结构:输出项命名规范、逻辑清晰,便于引用与集成

    找到这条中庸之道,就为构建能随企业扩展而稳健发展的 IaC 架构打下了坚实基础。

     

    可扩展 Terraform 实施的最佳实践

    Terraform 是一款功能强大的工具 —— 但其真正的力量只有在系统性、战略性地使用时才能充分发挥。尤其在扩展型环境中,一些基础原则对于构建长期可维护、稳健且高效的架构至关重要。以下是我们在复杂云环境中总结出的四条经实践验证的最佳实践。

    1. 按变更频率划分 State(Change Velocity)

    并非所有基础设施组件的变更频率都一样 —— Terraform 的结构应当反映这一点。根据变更速度进行划分,有助于避免冲突并加快开发周期。

    以下是 AWS 环境中一个实际的结构示例:

    └── AWS Infrastructure
        ├── low-velocity/        # 变更极少(如网络、IAM)
        │   ├── network/
        │   ├── security/
        │   └── dns/
        │
        ├── medium-velocity/     # 偶尔变更(如数据库)
        │   ├── rds/
        │   ├── elasticache/
        │   └── sqs/
        │
        └── high-velocity/       # 高频变更(如计算、工作负载)
            ├── app-cluster-a/
            ├── app-cluster-b/
            └── batch-processing/
    

    为何这种方法有效: 各团队可以独立工作,互不阻碍。变更频率低的资源不与快速部署流程绑定,从而提升稳定性与敏捷性。

    2. 使用变量验证构建动态 Terraform 模块

    结构清晰的模块不仅仅是资源的容器,它应能灵活适应不同上下文场景,同时保持可控性。

    核心要素包括:

    • 变量验证(Variable Validation): 所有变量在可能情况下应通过 validation 块进行校验,从而尽早发现无效值。
    • 类型安全: 使用精确类型定义(如 string、number、bool、list(object) 等),防止使用误解。
    • 支持 null 的可选变量: 可选变量需显式允许 null,以便在条件表达式中安全使用。

    示例:

    variable "mounts" {
      type = map(object({
        audit_non_hmac_request_keys  = optional(list(string))
        audit_non_hmac_response_keys = optional(list(string))
        allowed_managed_keys         = optional(set(string))
        default_lease_ttl_seconds    = optional(number)
        description                  = optional(string)
        external_entropy_access      = optional(bool)
        identity_token_key           = optional(string)
        listing_visibility           = optional(string)
        local                        = optional(bool)
        max_lease_ttl_seconds        = optional(number)
        namespace                    = optional(string)
        options                      = optional(map(any))
        passthrough_request_headers  = optional(list(string))
        plugin_version               = optional(string)
        seal_wrap                    = optional(bool)
        allowed_response_headers     = optional(list(string))
        delegated_auth_accessors     = optional(list(string))
        path                         = string
        type                         = string
      }))
      default = null
    
    description = <<-EOT
    Defines the configurations for Vault mounts. Each mount configuration should specify the following keys:\
    \[...]
    EOT

    validation { condition = ( var.mounts == null ? true : alltrue(\[ for mount in var.mounts : ( mount.listing\_visibility == null || mount.listing\_visibility == "unauth" || mount.listing\_visibility == "hidden" ) ]) ) error\_message = "The 'listing\_visibility' value must be either 'unauth' or 'hidden' if specified." } validation { condition = ( var.mounts == null ? true : alltrue(\[ for mount in var.mounts : ( mount.path == null || ( !startswith(mount.path, "/") && !endswith(mount.path, "/") ) ) ]) ) error\_message = "The 'path' value must not start or end with a '/'." }
    \[... etc. ...]
    }

    这类模块可以跨团队复用,显著减少支持成本。

    3. 使用 Remote State 管理跨 State 依赖

    在复杂环境中,资源之间的依赖关系几乎无法完全避免。与其直接建模依赖关系,不如采用 Remote State Outputs。核心原则很简单:一个 State 生成必要输出,另一个通过 terraform_remote_state data source 消费这些输出。

    关于此主题的深入探讨,请参阅我们“Terraform @ Scale” 系列文章的第一部分,该部分对此主题做了详细分解。

    4. 使用 HashiCorp 工具栈作为 State 策略的支撑

    Hashicorp Logos CompactStrap OnLight可扩展的 Terraform 实施不仅仅依赖于良好的代码,更要依赖于支持安全性、编排能力与重用性的生态系统。在 ICT.technology,我们始终使用 HashiCorp 工具栈来满足这些要求。以下是最关键的组件:

    • Terraform / Terraform Enterprise
      集中化 State 管理、Policy-as-Code(例如 Sentinel)与团队级 Workspace 隔离
    • Vault / Vault Enterprise
      安全的 Secret 管理、Cloud 访问动态凭证、自动轮换与审计日志
    • Consul / Consul Enterprise
      Service Discovery、健康检查、动态配置与平台集成
    • Nomad / Nomad Enterprise
      支持容器、虚拟机与裸机编排 —— 比 Kubernetes 占用资源更少
    • Packer
      创建标准化镜像(Golden Images),支持版本控制、幂等性与 CI/CD 集成
    • Boundary / Boundary Enterprise(仅适用于已运行具备企业级 SLA/OLA 的 PostgreSQL 集群的情况)
      基于身份的安全访问控制,尤其适用于生产数据库与受监管工作负载

    这些工具为在异构、高动态性的企业环境中开展一致、可追溯且安全的 Terraform 实践提供了坚实基础。

    State 大小调整的实用指南

    Terraform State 的最优大小不是照搬模板就能决定的,而应通过一个结构化流程来实现,使技术现实与组织需求达到一致。以下为一套经过实践验证的指南,可在规划与实施阶段提供有效支持。

    1. 分析组织结构

    第一步是深入理解自身组织:

    • 谁负责管理哪些资源? 是否存在集中式基础设施团队?应用团队是否独立运作?
    • 哪些地方需要自治? 部署频率高的团队应拥有独立的 States,以避免彼此阻塞。
    • 生命周期如何定义? 资源若需要共同部署或共同销毁,理想情况下应归属于同一个 State。

    2. 分析依赖关系

    第二步聚焦资源之间的技术连接:

    • 哪些资源强耦合? 如 Security Groups 与 EC2 实例、Subnets 与 Load Balancer 等。
    • 哪些可以解耦? 如监控、IAM 角色或备份系统通常可单独管理,从而降低整体复杂性。

    3. 测试与迭代

    在进行大范围重构前,应先进行有针对性的测试:

    • 使用可变 State 结构的试点项目:例如某个团队从单体结构切换到分段式结构,进行对比试验。
    • 可衡量的标准:如 Plan/Apply 的执行性能、错误频率、团队满意度、发布速度等。

    案例研究:某企业客户的云迁移

    一个颇具代表性的案例来自某金融服务商,该客户在从本地基础设施迁移至 Oracle Cloud Infrastructure (OCI) 的过程中,得到了 ICT.technology 的全面支持。

    初始情况:
    Terraform 环境由一个单一的巨型 State 组成,结果如下:

    • 每次执行 terraform apply 运行时间接近 45 分钟
    • 频繁发生 State Lock 冲突
    • 开发与发布周期缓慢

    解决方案:
    我们应用了 Goldilocks 原则,并将基础设施按如下方式进行分段(每个目录下的内容再通过 Terraform Enterprise 的租户能力进行团队或服务的映射,视具体需求而定):

    ├── Foundation
    │   ├── network/
    │   │   ├── transit/
    │   │   ├── network/
    │   │   └── interconnect/
    │   └── security/
    │       ├── iam/
    │       ├── security-groups/
    │       └── baseline/
    ├── Data Services
    │   ├── databases/
    │   ├── storage/
    │   └── analytics/
    └── Applications
        ├── frontend/
        ├── backend/
        ├── batch/
        └── monitoring/
    

    结果:

    • Apply 时间缩短至不足 5 分钟,基础设施变更部署速度显著提升
    • 实现并行团队作业,各团队可独立部署,互不干扰
    • 发布速度提升 70% ,得益于更短的反馈循环与更少的摩擦
    • 更高的稳定性 ,因为变更规模更小、边界更清晰,从而在配置错误或人为操作失误时能显著降低影响范围
    • 大幅降低成本 ,通过按需动态部署 Bare Metal 服务器与虚拟机,实现真正的“用时部署,用后回收”

    总结:实现最优 Terraform 扩展之路

    Terraform State 的划分并非一次性的架构任务,而是一个持续演进的成熟过程。Goldilocks 原则帮助我们避免走向极端 —— 既不能过于单体,也不能过度碎片化。

    关键在于深入理解自身组织、清晰划分职责,并在实际工作流的基础上执行技术方案。

    ICT.technology 正是在这条道路上为企业提供陪伴与支持 —— 从首次采用云计算,到全面过渡至 Infrastructure-as-Code 卓越之路。凭借在 Terraform、HashiCorp 技术栈与云架构领域的深厚专业能力,我们打造出技术上强健、组织上可持续的解决方案。

    合理结构化的 Terraform States 并非目的本身,而是推动可扩展性、部署速度与运营稳定性的关键杠杆,因此也是现代 IT 组织成功的决定性因素之一。