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

Terraform循环模板生成多端口K8s YAML文件报错解决方案

问题描述
  • 需求:生成约10份YAML配置文件,通过kubernetes_manifest资源完成Kubernetes资源下发。这类YAML大部分内容为固定常量,仅少量参数存在差异;部分YAML包含可重复的ports配置段:默认ports下包含http、https两个端口配置项,部分场景会新增sql端口共3个配置项,需要基于输入参数通过模板文件动态生成目标YAML。
  • 现状:当前使用Terraform v1.2.2版本,ports段仅含单个配置项(如仅http)时可正常运行,但无法实现ports段的循环渲染,运行配置后抛出模板for指令语法错误,需要修正问题或提供替代实现方案。
预期生成的YAML样例
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: test
  namespace: test
spec:
  hosts:
  - 'api.facebook.com'
  ports:
  - name: http
    number: 8080
    protocol: TCP
  - name: https
    number: 443
    protocol: TCP
  resolution: NONE
现有配置

模板文件

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: ${service_entry}
  namespace: ${namespace}
spec:
  hosts:
  - ${jsonencode(hosts)}
  ports:
  %{ for name, number, protocol in service_entry ~}
  - name: ${name}
    number: ${number}
    protocol: ${protocol}
  %{ endfor ~}
  resolution: ${resolution}

main.tf

resource "kubernetes_manifest" "service-entry" {
  for_each = var.service_entry
  manifest = yamldecode(templatefile("${path.module}/templates/service_entry.yaml.tpl", {
    service_entry_name = each.value.service_entry
    namespace          = each.value.namespace
    hosts              = each.value.hosts
    name               = each.value.name
    number             = each.value.number
    protocol           = each.value.protocol
    resolution         = each.value.resolution
  }))
}

variables.tf

variable "service_entry" {
  type = map(object({
    service_entry = string
    namespace     = string
    hosts         = string
    name          = list(string)
    number        = list(string)
    protocol      = list(string)
    resolution    = string
  }))
  default = {}
}

tfvars配置

预期生成2份YAML文件:第一份YAML的ports段包含http、https两个配置项,第二份YAML的ports段包含http、https、sql三个配置项

service_entry = {
  app1 = {
    service_entry = "test"
    namespace     = "test"
    hosts         = "api.facebook.com"
    resolution    = "NONE"
    name          = ["http", "https"]
    number        = ["8080", "443"] 
    protocol      = ["TCP", "TCP"]
 },
  app2 = {
    service_entry = "example"
    namespace     = "example"
    hosts         = "api.facebook.com"
    resolution    = "NONE"
    name          = ["http", "https", "sql"]
    number        = ["8080", "443", "5432"] 
    protocol      = ["TCP","TCP","TCP"]
    }
}
运行报错信息
错误:函数调用失败

│ 位于../../modules/service_entry/main.tf第3行,resource "kubernetes_manifest" "service-entry"代码块中:
│    3:   manifest = yamldecode(templatefile("${path.module}/templates/service_entry.yaml.tpl", {
│    4:     service_entry_name = each.value.service_entry_name
│    5:     namespace          = each.value.namespace
│    6:     hosts              = each.value.hosts
│    7:     name               = each.value.name
│    8:     number             = each.value.number
│    9:     protocol           = each.value.protocol
│   10:     resolution         = each.value.resolution
│   11:   }))
│     ├────────────────
│     │ each.value.hosts 将在apply后才可知
│     │ each.value.namespace 将在apply后才可知
│     │ each.value.name 将在apply后才可知
│     │ each.value.number 将在apply后才可知
│     │ each.value.protocol 将在apply后才可知
│     │ each.value.resolution 将在apply后才可知
│     │ each.value.service_entry_name 将在apply后才可知
│     │ path.module 取值为"../../modules/service_entry"
│
│ 调用"templatefile"函数失败:
│ ../../modules/service_entry/templates/service_entry.yaml.tpl:11,32-33:
│ 'for'指令无效;for指令需要在声明的变量名后添加'in'关键字,另有1条其他诊断信息。
╵
ERRO[0005] 发生1个错误:
    * 退出状态码 1
问题修复方案

错误根因

  1. Terraform模板的for指令不支持同时遍历多个独立列表,当前写法%{ for name, number, protocol in service_entry ~}不符合语法规则,且传入模板的变量名和模板内引用的变量名不匹配(比如传入的是service_entry_name,模板里写的是${service_entry})
  2. 把端口拆成name/number/protocol三个独立列表的结构维护成本高,容易出现三个列表长度不匹配、顺序错位的问题。

模板方案修正

1. 调整variables.tf

将三个独立的端口列表合并为ports对象列表,从结构上避免参数错位:

variable "service_entry" {
  type = map(object({
    service_entry = string
    namespace     = string
    hosts         = string
    ports = list(object({
      name     = string
      number   = number
      protocol = string
    }))
    resolution    = string
  }))
  default = {}
}

2. 调整tfvars配置

对应新的变量结构传参:

service_entry = {
  app1 = {
    service_entry = "test"
    namespace     = "test"
    hosts         = "api.facebook.com"
    resolution    = "NONE"
    ports = [
      { name = "http", number = 8080, protocol = "TCP" },
      { name = "https", number = 443, protocol = "TCP" }
    ]
 },
  app2 = {
    service_entry = "example"
    namespace     = "example"
    hosts         = "api.facebook.com"
    resolution    = "NONE"
    ports = [
      { name = "http", number = 8080, protocol = "TCP" },
      { name = "https", number = 443, protocol = "TCP" },
      { name = "sql", number = 5432, protocol = "TCP" }
    ]
  }
}

3. 调整模板文件

修正变量引用和for循环逻辑:

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: ${service_entry_name}
  namespace: ${namespace}
spec:
  hosts:
  - ${jsonencode(hosts)}
  ports:
  %{ for port in ports ~}
  - name: ${port.name}
    number: ${port.number}
    protocol: ${port.protocol}
  %{ endfor ~}
  resolution: ${resolution}

4. 调整main.tf

简化传入模板的参数:

resource "kubernetes_manifest" "service-entry" {
  for_each = var.service_entry
  manifest = yamldecode(templatefile("${path.module}/templates/service_entry.yaml.tpl", {
    service_entry_name = each.value.service_entry
    namespace          = each.value.namespace
    hosts              = each.value.hosts
    ports              = each.value.ports
    resolution         = each.value.resolution
  }))
}

推荐替代方案(无需模板文件)

直接在Terraform中构造资源结构,完全避免模板渲染的语法问题,可维护性更高:

resource "kubernetes_manifest" "service-entry" {
  for_each = var.service_entry
  manifest = {
    apiVersion = "networking.istio.io/v1beta1"
    kind       = "ServiceEntry"
    metadata = {
      name      = each.value.service_entry
      namespace = each.value.namespace
    }
    spec = {
      hosts = [each.value.hosts]
      ports = [
        for port in each.value.ports : {
          name     = port.name
          number   = port.number
          protocol = port.protocol
        }
      ]
      resolution = each.value.resolution
    }
  }
}

该写法不需要维护单独的tpl模板文件,Terraform会自动完成结构序列化,增减端口只需要调整tfvars里的ports列表即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 09:57:30