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
问题修复方案
错误根因
- Terraform模板的
for指令不支持同时遍历多个独立列表,当前写法%{ for name, number, protocol in service_entry ~}不符合语法规则,且传入模板的变量名和模板内引用的变量名不匹配(比如传入的是service_entry_name,模板里写的是${service_entry}) - 把端口拆成
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
相关产品推荐
相关产品推荐

