You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform如何参数化多个Kubernetes清单做多环境部署

Terraform多K8s YAML清单环境差异化配置实现方案

以下是三种生产环境常用的实现方式,可根据自身场景选择:

  • 方案1:templatefile模板渲染(优先推荐,无额外依赖,维护成本低)
    这个方案基于Terraform原生的模板渲染能力,提前把YAML里需要差异化的字段替换成模板变量,部署时根据传入的环境参数动态渲染最终的YAML内容。

    1. 先把manifests目录下的YAML文件改成模板格式:需要根据环境变化的字段(比如副本数、镜像标签、资源配额、命名空间)替换为Terraform模板变量,格式为${变量名};YAML里原本K8s自身的${xxx}格式引用(比如Downward API、ConfigMap挂载引用)要转义成$${xxx},避免被Terraform误解析。
      举个修改后的gdp-collector-deployment.yaml示例:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gdp-collector
      namespace: gdp-${env}
    spec:
      replicas: ${replicas}
      selector:
        matchLabels:
          app: gdp-collector
      template:
        metadata:
          labels:
            app: gdp-collector
        spec:
          containers:
          - name: collector
            image: registry.internal/gdp/collector:${image_tag}
            resources:
              limits:
                cpu: ${cpu_limit}
                memory: ${memory_limit}
            # K8s原生变量引用加$转义
            env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
    
    1. 定义环境差异化参数,可以单独拆成dev.tfvars、prod.tfvars文件,也可以统一写在变量定义里:
    # variables.tf
    variable "env" {
      type = string
      validation {
        condition     = contains(["dev", "prod"], var.env)
        error_message = "环境仅支持dev/prod."
      }
    }
    
    variable "env_config" {
      type = map(any)
      default = {
        dev = {
          replicas     = 1
          image_tag    = "dev-latest"
          cpu_limit    = "500m"
          memory_limit = "512Mi"
        }
        prod = {
          replicas     = 3
          image_tag    = "v1.2.0-stable"
          cpu_limit    = "2000m"
          memory_limit = "4Gi"
        }
      }
    }
    
    1. 修改main.tf逻辑,替换原来直接读取YAML的写法,遍历模板文件逐个渲染后再提交给K8s:
    locals {
      # 遍历manifests下所有yaml文件,传入环境参数渲染
      rendered_manifests = {
        for yaml_file in fileset("${path.module}/manifests/", "*.yaml") :
        trimsuffix(yaml_file, ".yaml") => templatefile(
          "${path.module}/manifests/${yaml_file}",
          merge(
            { env = var.env },
            var.env_config[var.env]
          )
        )
      }
    }
    
    resource "kubectl_manifest" "yaml-manifests" {
      for_each  = local.rendered_manifests
      yaml_body = each.value
    }
    

    部署时指定对应环境的参数即可,比如开发环境执行terraform apply -var="env=dev",生产环境指定-var="env=prod",如果用独立tfvars文件就加-var-file=prod.tfvars参数。

  • 方案2:YAML结构化解码修改(适合不想改动原有YAML文件的场景)
    如果不想把现有YAML改成模板格式,可以先把读取到的YAML解析为Terraform原生对象,针对需要修改的字段做定向合并修改,再编码回YAML格式应用。示例代码:

    data "kubectl_path_documents" "yaml-files" {
      pattern = "${path.module}/manifests/*.yaml"
    }
    
    locals {
      decoded_yamls = {
        for idx, raw_doc in data.kubectl_path_documents.yaml-files.documents :
        idx => yamldecode(raw_doc)
      }
    
      # 按资源类型定向修改字段
      modified_yamls = {
        for k, resource in local.decoded_yamls : k => merge(
          resource,
          # 统一修改所有资源的命名空间
          { metadata = merge(resource.metadata, { namespace = "gdp-${var.env}" }) },
          # Deployment类型修改副本数、镜像、资源配置
          resource.kind == "Deployment" ? {
            spec = merge(resource.spec, {
              replicas = var.env_config[var.env].replicas
              template = merge(resource.spec.template, {
                spec = merge(resource.spec.template.spec, {
                  containers = [
                    for container in resource.spec.template.spec.containers :
                    merge(container, {
                      image = "${split(":", container.image)[0]}:${var.env_config[var.env].image_tag}"
                      resources = {
                        limits = {
                          cpu    = var.env_config[var.env].cpu_limit
                          memory = var.env_config[var.env].memory_limit
                        }
                      }
                    })
                  ]
                })
              })
            })
          } : {}
        )
      }
    
      final_manifests = {
        for k, v in local.modified_yamls : k => yamlencode(v)
      }
    }
    
    resource "kubectl_manifest" "yaml-manifests" {
      for_each  = local.final_manifests
      yaml_body = each.value
    }
    

    这个方案的缺点是如果YAML层级深、改动字段多,合并逻辑写起来很繁琐,容易出问题,适合改动字段非常少的场景。

  • 方案3:按环境拆分资源目录(适合环境差异极大的场景)
    如果dev和prod环境的K8s资源结构差异非常大,不是改几个字段就能兼容的,可以直接把manifests目录按环境拆分:

    ├── manifests
    │   ├── dev
    │   │   ├── gdp-configmap.yaml
    │   │   ├── gdp-agent-deamonset.yaml
    │   │   ├── gdp-collector-configmap.yaml
    │   │   ├── gdp-collector-deployment.yaml
    │   ├── prod
    │   │   ├── gdp-configmap.yaml
    │   │   ├── gdp-agent-deamonset.yaml
    │   │   ├── gdp-collector-configmap.yaml
    │   │   ├── gdp-collector-deployment.yaml
    │   │   ├── gdp-pdb.yaml # 生产环境额外的Pod中断预算资源
    

    之后读取文件时直接根据环境参数拼路径即可:

    data "kubectl_path_documents" "yaml-files" {
      pattern = "${path.module}/manifests/${var.env}/*.yaml"
    }
    
    resource "kubectl_manifest" "yaml-manifests" {
      for_each  = toset(data.kubectl_path_documents.yaml-files.documents)
      yaml_body = each.value
    }
    

    这个方案逻辑最简单,但如果两个环境的YAML大部分内容一致,会产生大量重复配置,后续更新需要同时改两份,维护成本高。

注意:使用templatefile方案时一定要记得转义YAML里原有的${}格式引用,否则Terraform渲染阶段会报变量未定义的错误。

内容的提问来源于stack exchange,提问作者fledgling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 10:45:49