Terraform @ Scale - 第1b部分:用于模块化云基础架构的多租户架构示例

在本系列的上一篇文章中,我们解释了Terraform中的远程状态(Remote State)概念的基础知识,以及如何在多租户(Multi-Tenancy)环境中利用它来继承信息。现在,我们将通过一个具体的架构示例对此进行说明。

引言

一个有效的多租户架构应遵循企业的组织结构,同时考虑云平台的最佳实践。现代云平台提供了多种方式来映射层次化的组织结构 - 无论是Compartments(OCI)、Organizations和Accounts(AWS)、资源组(Azure)还是项目和文件夹(GCP)。

架构概念介绍

Datacenter View DE

在我们的架构示例中,我们将一个“客户的全球数据中心”视为最高的组织单元。在此单元内存在不同的区域:

全球系统和服务: 包括IAM、监控、计费和其他中央服务。
区域数据中心: 面向不同需求的地理分布式基础架构。
组织性分区: 例如按部门、安全区域或其他标准划分。

此模型的特别之处在于:每个组织单元代表一个独立的逻辑区域,具有明确的责任范围。这些单元构成了不同租户或组织单元之间的自然边界,同时提供了权限和配置继承的框架。

作为自然多租户模型的层次化组织结构

云基础架构的层次化结构为多租户场景提供了多个优势:

层次化组织: 单元可以嵌套,类似于文件夹结构。
策略继承: 权限可以从较高级别继承到较低级别。
资源管理: 可以为每个组织单元定义配额限制。
成本核算: 计费和测量在组织单元级别进行。

举例来说:一家公司可以为每个业务部门设置一个顶层单元。在这些单元内,可能存在生产、开发和测试等进一步的子单元。最底层可能是与具体项目相关的单元。
这种层次结构不仅反映了组织结构,还简化了对访问权限和资源的管理。例如,管理员可以获得对特定部门单元的访问权限,而无需访问其他部门的资源。

不同的分区层次

我们可以沿着不同的维度来划分基础架构:

技术性分区: 例如“站点A区域数据中心”,其中包含不同的技术区(DC1、DC2、DC3、DC4)。
组织性分区: 例如“站点B区域数据中心”,其中包含不同的部门(部门1、部门2等)。
安全区域: 例如“站点n区域数据中心”,其中包括高度安全(High Security)、中等(Medium)、低级(Low)和公共(Public)区域。
价值流: 跨越其他结构的业务流程。

这些不同的分区维度可能会重叠和互补。挑战在于如何在Terraform中清晰地表示它们,并同时实现它们之间的信息流。
特别重要的是通过远程状态(Remote State)继承数据。我们看到信息如何从较高层(全球数据中心)传递到较低层(区域数据中心及其子单元)。这正是我们在上一部分中讨论的远程状态模式的体现。

使用Terraform模块的实际实现

在Terraform中实现此类架构需要采用结构化的模块方法。我们为每个层次结构级别创建专用的Terraform项目。

提示: 远程状态数据源的具体配置根据所使用的后端而有所不同。对于常见选项(如S3、GCS或Consul),需要进行相应的调整。

1. 全局基础架构的Root模块:

Glabl Services DE


# 1. 全局基础架构 (Root级别)
module "global_datacenter" {
  source = "./modules/organization"
  
  name        = "global-datacenter-customer"
  description = "客户的全球数据中心"
  parent_id   = var.root_organization_id
}

module "global_network" {
  source = "./modules/network"
  
  organization_id = module.global_datacenter.id
  cidr_block      = "10.0.0.0/16"
  name            = "global-network"
}

# 全局必要的系统和服务
module "global_services" {
  source = "./modules/organization"
  
  name        = "global-services"
  description = "客户所需的全局系统和服务"
  parent_id   = module.global_datacenter.id
}

# IAM、监控、计费服务模块将在此处实现...

output "global_datacenter_id" {
  value = module.global_datacenter.id
}

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

output "global_services_id" {
  value = module.global_services.id
}

2. 按技术分区的区域基础架构:

Rrgional Datacenter ZN


# 2. 位置A的区域数据中心 (多个数据中心)
data "terraform_remote_state" "global" {
  backend = "remote"
  config = {
    organization = "my-org"
    workspaces = {
      name = "global-infrastructure"
    }
  }
}

module "location_a" {
  source = "./modules/organization"
  
  name        = "location-a-regionales-datacenter"
  description = "位置A的区域数据中心"
  parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
}

# 配置4个数据中心作为子单元
module "dc1" {
  source = "./modules/organization"
  
  name        = "dc1"
  description = "数据中心 1"
  parent_id   = module.location_a.id
}

module "dc2" {
  source = "./modules/organization"
  
  name        = "dc2"
  description = "数据中心 2"
  parent_id   = module.location_a.id
}

module "dc3" {
  source = "./modules/organization"
  
  name        = "dc3"
  description = "数据中心 3"
  parent_id   = module.location_a.id
}

module "dc4" {
  source = "./modules/organization"
  
  name        = "dc4"
  description = "数据中心 4"
  parent_id   = module.location_a.id
}

# 为位置A的全局网络配置子网
module "subnet_dc1" {
  source = "./modules/subnet"
  
  organization_id = module.dc1.id
  network_id      = data.terraform_remote_state.global.outputs.global_network_id
  cidr_block      = "10.0.1.0/24"
  name            = "subnet-dc1"
}

# 更多的子网配置 (DC2、DC3、DC4...)

# 在其他Terraform项目中使用的输出
output "location_a_id" {
  value = module.location_a.id
}

output "dc1_id" {
  value = module.dc1.id
}

output "subnet_dc1_id" {
  value = module.subnet_dc1.id
}

# 其他输出项

3. 按组织分区的区域基础架构:

Regional Organizations ZN


# 3. 位置B的区域数据中心 (组织架构)
data "terraform_remote_state" "global" {
  backend = "remote"
  config = {
    organization = "my-org"
    workspaces = {
      name = "global-infrastructure"
    }
  }
}

module "location_b" {
  source = "./modules/organization"
  
  name        = "location-b-regional-datacenter"
  description = "位置B的区域数据中心"
  parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
}

# 组织部门
module "department_1" {
  source = "./modules/organization"
  
  name        = "department-1"
  description = "部门 1"
  parent_id   = module.location_b.id
}

module "department_2" {
  source = "./modules/organization"
  
  name        = "department-2"
  description = "部门 2"
  parent_id   = module.location_b.id
}

module "department_3" {
  source = "./modules/organization"
  
  name        = "department-3"
  description = "部门 3"
  parent_id   = module.location_b.id
}

module "department_4" {
  source = "./modules/organization"
  
  name        = "department-4"
  description = "部门 4"
  parent_id   = module.location_b.id
}

# 部门专属子网
module "subnet_department_1" {
  source = "./modules/subnet"
  
  organization_id = module.department_1.id
  network_id      = data.terraform_remote_state.global.outputs.global_network_id
  cidr_block      = "10.0.10.0/24"
  name            = "subnet-department-1"
}

# 在其他Terraform项目中使用的输出
output "location_b_id" {
  value = module.location_b.id
}

output "department_1_id" {
  value = module.department_1.id
}

output "subnet_department_1_id" {
  value = module.subnet_department_1.id
}

4. 按安全分区的区域基础架构:

REgional Security Zones ZN


# 4. 位置n的区域数据中心 (安全区域)
data "terraform_remote_state" "global" {
  backend = "remote"
  config = {
    organization = "my-org"
    workspaces = {
      name = "global-infrastructure"
    }
  }
}

module "location_n" {
  source = "./modules/organization"
  
  name        = "location-n-regional-datacenter"
  description = "位置n的区域数据中心"
  parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
}

# 安全区域作为子单元
module "high" {
  source = "./modules/organization"
  
  name        = "high"
  description = "高级安全区域"
  parent_id   = module.location_n.id
}

module "med" {
  source = "./modules/organization"
  
  name        = "med"
  description = "中级安全区域"
  parent_id   = module.location_n.id
}

module "low" {
  source = "./modules/organization"
  
  name        = "low"
  description = "低级安全区域"
  parent_id   = module.location_n.id
}

module "pub" {
  source = "./modules/organization"
  
  name        = "pub"
  description = "公共区域"
  parent_id   = module.location_n.id
}

# 安全区域特定的子网
module "subnet_high" {
  source = "./modules/subnet"
  
  organization_id       = module.high.id
  network_id            = data.terraform_remote_state.global.outputs.global_network_id
  cidr_block            = "10.0.20.0/24"
  name                  = "subnet-high"
  prohibit_public_ip    = true  
}

# 其他安全资源,如Security Lists和Network Security Groups...

# 输出
output "location_n_id" {
  value = module.location_n.id
}

output "high_security_id" {
  value = module.high.id
}

output "subnet_high_id" {
  value = module.subnet_high.id
}

5. 部门或团队特定的基础架构:

Department Datacenter ZN


# 5. 示例:部门中的资源使用远程状态信息
data "terraform_remote_state" "standort_b" {
  backend = "remote"
  config = {
    organization = "my-org"
    workspaces = {
      name = "regional-location-b"
    }
  }
}

module "vm_instances" {
  source = "./modules/compute"
  
  instances = {
    app_server_department_1 = {
      organization_id = data.terraform_remote_state.location_b.outputs.department_1_id
      subnet_id       = data.terraform_remote_state.location_b.outputs.subnet_department_1_id
      size            = "medium"
      image           = var.app_image_id
    }
    # 其他虚拟机实例 ...
  }
}

动态模块配置以实现灵活的部署模式

为了进一步提高灵活性,我们还可以使用动态配置。这在我们需要管理大量相似资源时特别有用:


module "regional_datacenters" {
  source   = "./modules/regional_datacenter"
  for_each = var.datacenter_configs
  
  name            = each.key
  description     = each.value["description"]
  parent_id       = data.terraform_remote_state.global.outputs.global_datacenter_id
  network_cidr    = each.value["network_cidr"]
  security_level  = each.value["security_level"]
  subunits        = each.value["subunits"]
}

# 使用示例,如何从根模块调用此模块
# datacenter_configs = {
#   "europe-west" = {
#     description    = "欧洲西部数据中心"
#     network_cidr   = "10.1.0.0/16"
#     security_level = "high"
#     subunits       = ["prod", "staging", "dev", "test"]
#   },
#   "us-east" = {
#     description    = "美国东海岸数据中心"
#     network_cidr   = "10.2.0.0/16"
#     security_level = "medium"
#     subunits       = ["prod", "dev"]
#   }
# }

模块化结构的优势


这种模块化结构使我们能够将组织的逻辑层次结构转换为独立的Terraform项目,同时确保这些项目之间的信息交换:

明确的责任划分: 每个团队管理其自己的Terraform代码和状态。
自动的信息继承: 管理全局基础架构的团队可以进行更改,而无需区域或部门级别的团队调整其代码。
独立的部署: 某个区域出现的错误不会自动影响其他区域。
可扩展性: 新的组织单元可以轻松添加,而无需更改现有结构。
一致的治理: 通过输出的继承,确保所有层级的一致配置。

完整的多租户设置示例

以下是一个示例,展示如何在实际项目中结合这些模式:


module "tenant_environments" {
  source = "./modules/tenant_environment"
  
  for_each = {
    for tenant in var.tenants : tenant.name => tenant
  }
  
  name            = each.key
  parent_id       = module.tenant_root_organization.id
  environments    = each.value["environments"]
  network_cidr    = each.value["network_cidr"]
  security_policy = each.value["security_policy"]
}

# tenant_environments 可用于不同的客户、
# 业务部门或项目。
# 
# 示例:
# tenants = [
#   {
#     name = "finance-department"
#     environments = ["prod", "dev", "test"]
#     network_cidr = "10.10.0.0/16"
#     security_policy = "high"
#   },
#   {
#     name = "marketing-department"
#     environments = ["prod", "staging"]
#     network_cidr = "10.20.0.0/16"
#     security_policy = "medium"
#   }
# ]


借助这种结构化的方法,我们能够以技术上稳健且组织上合理的方式管理复杂的多租户环境。通过明确的责任划分,结合远程状态(Remote State)的精准信息继承,使我们即便在大型、分布式团队中也能高效地使用Terraform。

远程状态(Remote-State)信息的安全性考量

尽管远程状态(Remote States)非常有用,但它们可能包含潜在的敏感信息。尤其是在多租户(Multi-Tenancy)环境中,保护这些信息至关重要。
为确保仅授权的团队能够访问远程状态的输出,您应采取以下措施:

  • 访问控制限制: 使用您的后端的访问控制机制(例如S3的IAM策略、Consul的ACL,或Terraform Cloud/Enterprise中的基于角色的访问控制)。
  • 最小化输出: 仅将绝对必要的信息定义为输出。每个额外的输出属性都会增加敏感数据泄露的风险。
  • 避免不必要的输出: 具有安全关键值(例如密码、私钥)的输出应尽可能避免作为输出提供,而应通过机密管理服务(如HashiCorp Vault或Secrets Manager)来管理。
  • 信息汇总: 与其单独提供数据库密码、API密钥或其他敏感详细信息作为输出,不如优先使用结构化输出,仅聚合相关参数。
  • 使用代理状态文件(Proxy-Statefiles):过滤远程状态的输出,仅包含与特定租户相关的信息,并将其作为新的状态提供。该租户仅允许访问该代理状态。

远程状态依赖的错误处理

在多租户架构中使用远程状态会带来特殊的错误处理挑战。错误来源可能包括不可用的后端、输出不一致或意外更改,这些都可能导致严重问题。为了尽量减少这些风险,您应采取以下措施:

  • 可用性检查: 在Terraform的terraform_remote_state块中结合查询结构(如lookup())使用,以减少因缺失输出引发的错误。
  • 输出正确性检查: 在模块和CI/CD流水线中添加测试,确保远程状态输出始终包含预期的结构和内容。
  • 回退机制: 如果远程状态暂时无法访问,可以使用本地缓存的值(例如通过环境变量)作为临时解决方案。

以下是一个带有错误处理的稳健远程状态查询示例:


data "terraform_remote_state" "network" {
  backend = "remote"
  config = {
    organization = "my-org"
    workspaces = {
      name = "global-infrastructure"
    }
  }
}

locals {
  private_subnet_id = lookup(data.terraform_remote_state.network.outputs.subnets, "private", null)
}

resource "example_vm" "app_server" {
  subnet_id = local.private_subnet_id != null ? local.private_subnet_id : var.default_subnet_id
}

在下一部分中,我们将探讨远程后端(Remote Backends),并了解如何以最佳方式对远程状态进行层次化组织。