Terraform @ Scale - 第1c部分:远程 State 数据流的实际实施

通过精心设计的远程 Backend、周密的 Output 设计以及 terraform_remote_state 数据源的精准使用,您可以在不同的租户层面之间建立受控的信息流 - 而不会影响各个租户的隔离性。

要有效利用远程 State 在组织单元之间进行信息交换,需要对 Terraform 环境进行周密的配置。关键在于为状态数据存储在所谓的 State 文件中的存储 Backend 的选择和设置。

远程 Backend 的配置

Terraform 提供了多种官方支持的 Backend 选项来存储状态数据。HashiCorp 官方支持的选项包括:

 示例配置
 HashiCorp Consul
 terraform {
  backend "consul" {
    address = "consul.example.com:8500"
    scheme  = "https"
    path    = "terraform/network/global"
    lock    = true
  }
}
 HashiCorp Terraform Enterprise
 terraform {
  backend "remote" {
    hostname     = "terraform.example.com"
    organization = "ict-technology"
    
    workspaces {
      name = "network-global"
    }
  }
}
  HashiCorp Terraform Cloud
 terraform {
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "ict-technology"
    
    workspaces {
      name = "network-global"
    }
  }
}

 

 这些存储 Backend 相较于本地 State 文件具有多项优势:

  • 集中存储:所有团队成员都使用同一份 State。
  • 锁定机制:防止并行更改及相关冲突。
  • 版本控制:记录更改的历史。
  • 访问控制:允许实施详细的权限管理。

在选择 Backend 时,您应考虑您组织的需求。

Consul 是那些使用 Terraform 免费版本的企业的理想选择。 

Terraform CloudTerraform Enterprise 则采用了客户端/服务器架构,并且其服务器端已集成了一个存储 Backend,因此无需额外设置存储,直接使用集成的存储即可。此外,Terraform Cloud/Enterprise 还提供额外功能,例如 Policy-as-Code(策略即代码)来实施和强制执行策略、治理控制和增强的协作功能。

Terraform 还支持其他 Backends,包括 Amazon S3HTTPS、各种数据库等。
不过需要注意的是,HashiCorp 仅为本地 State 文件、ConsulTerraform Cloud/Enterprise 提供支持。
您可以使用其他 Backends,但在出现问题时,即便您拥有 HashiCorp 的企业支持合同,也无法获得 HashiCorp 的支持或问题解决。
因此,在关键的生产环境中,您应仅使用 ConsulTerraform Cloud/Enterprise 作为远程 State Backend。

如果您使用 HashiCorp 不支持的 Backend,则必须确保该 Backend 提供锁定机制!
否则,若并行运行 terraform apply 命令,多个并行的 Terraform 实例可能会互相覆盖 State 文件,从而导致整个基础架构的不一致性。例如,NFSv3 共享或 HTTPS Backend 可能是一个较为不稳定的选择,您可能会面临数据丢失的风险。 

 

安全机制

State 文件的存储至关重要,因为其中可能包含敏感信息,如访问凭据和基础架构细节。

加密(Encryption) 

Terraform Cloud/Enterprise 中,状态数据在静态存储和传输过程中会自动加密。对于 Consul,您应为连接启用 TLS 并激活 Consul 内置的加密功能:


terraform {
  backend "consul" {
    address      = "consul.example.com:8500"
    access_token = var.consul_acl_token  
    scheme       = "https"
    path         = "terraform/network/global"
    ca_file      = "/etc/ssl/certs/ca.pem"
    cert_file    = "/etc/ssl/certs/cert.pem"
    key_file     = "/etc/ssl/private/key.pem"   
  }
}

 访问控制(Access Control)

Terraform Cloud/Enterprise 中,用户管理提供了详细的角色和权限控制。例如,不同角色可以拥有不同的权限:

  • 团队 Network-Admins:对 Workspace network-global 拥有完全访问权限。
  • 团队 App-Developers:对 Workspace network-global 拥有只读访问权限。

Consul 中,您可以使用 ACL(访问控制列表),并将其分配给角色。例如,以下 ACL 向团队 App-Developers 授予读取权限:


# Consul ACL policy
key_prefix "terraform/network/global" {
  policy = "read"
}

在生产环境中,您还应使用 ACL-Token 来防止其他客户端的未经授权访问:


 data "terraform_remote_state" "global_network" {
  backend = "consul"
  config = {
    address = "consul.example.com:8500"
    path    = "terraform/network/global"
    scheme  = "https"
    token   = var.consul_acl_token
  }
}

版本控制和恢复(Versioning and Recovery)

Terraform Cloud/Enterprise 在每次成功执行后都会自动创建 State 快照(Snapshot)。这些快照可用于在发生错误时恢复环境状态。您可以使用 UI 或 CLI 命令(terraform state pull)来查看版本历史并恢复所需的版本。

Consul 中,可以配置带有其他数据中心副本的高可用集群,以防止数据丢失。同时,您也可以创建 Raft 存储的快照。

 

State 文件的结构以优化多租户管理

State 的组织应反映您的组织结构,同时支持信息流。

Datacenter View ZN

Terraform Cloud/Enterprise 中,不同的配置被组织为 "Workspaces"。在 Consul 中,则通过 "Paths" 进行分隔,这些路径相当于逻辑命名空间。

一种行之有效的结构可能如下:

基础架构Workspace/Path
 全球必需系统和服务  global-infrastructure
区域性站点 region-{region_name}
客户专属基础架构 customer-{customer_name}
团队专属基础架构 team-{team_name}
应用专属基础架构 app-{app_name}

 

这样的结构有助于明确划分责任,同时便于找到相关的 State 文件。

Terraform Cloud/Enterprise 中,您还可以使用 Workspace 标记(Tagging)来对相关的 Workspaces 进行分组。例如,您可以在应用的基础架构和其相关团队的基础架构之间建立逻辑关联。

 

数据继承的代码示例(Code Examples for Data Inheritance between Tenants)

实际的数据继承是通过 terraform_remote_state 数据源实现的,该数据源允许访问其他 Terraform 配置的 Outputs。

示例:多租户环境中的网络配置

全局网络团队(在 Workspace global-infrastructure 中):


module "global_network" {
  source = "./modules/network"

  name            = "global-vcn"
  description     = "Global customer network"
  cidr_block      = "10.0.0.0/16"
  parent_id       = var.root_compartment_id
}

output "global_vcn_id" {
  value = module.global_network.id
}

区域团队(在 Workspace region-dc1 中):


data "terraform_remote_state" "global_network" {
  backend = "remote"
  config = {
    organization = "ict-technology"
    workspaces = {
      name = "network-global"
    }
  }
}

module "regional_network" {
  source = "./modules/network"

  name            = "regional-subnet-dc1"
  description     = "Subnet of europe region"
  parent_id       = data.terraform_remote_state.global_network.outputs.global_vcn_id
  cidr_block      = "10.0.1.0/24"
}

output "regional_subnet_id" {
  value = module.regional_network.id
}

 客户团队(在 Workspace customer-acme 中):


data "terraform_remote_state" "regional_network" {
  backend = "remote"
  config = {
    organization = "ict-technology"
    workspaces = {
      name = "region-europe"
    }
  }
}

module "customer_environment" {
  source = "./modules/environment"

  name            = "customer-instance"
  description     = "customer instance"
  parent_id       = var.customer_compartment_id
  subnet_id       = data.terraform_remote_state.regional_network.outputs.regional_subnet_id
  instance_shape  = "VM.Standard2.1"
}

 推荐的最佳模式之一是使用结构化的 Outputs。相比于提供众多单独的值,结构化 Output 可以将多种信息捆绑并导出:


output "network_config" {
  value = {
    vcn_id     = module.global_network.id
    cidr_block = module.global_network.cidr_block
    dns_label  = module.global_network.dns_label
    subnets    = {
      public  = module.subnet_public.id
      private = module.subnet_private.id
    }
  }
}

这样,依赖的配置可以更精准地访问相关信息:


module "app_server" {
  source = "./modules/compute"

  name            = "app-server"
  description     = "application server in private subnet"
  parent_id       = var.customer_compartment_id
  subnet_id       = data.terraform_remote_state.network.outputs.network_config.subnets.private
  instance_shape  = "VM.Standard2.1"
}

  在下一章中,我们将探讨使用 Remote States 时的陷阱、最佳实践和一些技巧。