Terraform templatefile循环缩进致YAML格式异常求助
使用Terraform的templatefile函数生成Envoy配置YAML时,若给%{ for }或%{ if }这类模板控制语句添加缩进,会导致生成内容中控制语句后的第一行出现多余空格,破坏YAML格式;移除控制语句前的缩进则生成正常,但会降低模板可读性。以下是问题根源和解决办法:
问题根源
Terraform的templatefile会将控制语句所在行的前置缩进,自动叠加到控制语句块内每一行的开头。比如你在filter_chains:下缩进8个空格写%{ for ... ~},循环块内的每一行原本的8个空格缩进,会被加上这8个前置空格,变成16个,最终导致YAML格式错误。
解决办法
方法一:调整模板控制语句的缩进方式
保留模板可读性的前提下,有两种调整方式:
控制语句顶格(相对于父级),块内内容保持正确缩进
即你当前使用的正确模板方式:将%{ for }、%{ if }等控制语句与父级键(如filter_chains:)对齐,块内内容按YAML层级缩进。虽然控制语句没有缩进,但可以通过空行分隔提升可读性:static_resources: listeners: # ... 省略其他配置 ... filter_chains: %{ for filter_chain_match in filter_chain_matches ~} - filter_chain_match: server_names: ${jsonencode(filter_chain_match.server_names)} # ... 省略块内其他配置 ... %{ endfor ~} clusters: # ... 省略其他配置 ...保留控制语句缩进,用
indent()函数控制块内缩进
给控制语句添加缩进以保持模板结构对齐,块内内容去掉手动缩进,改用indent(N)函数生成目标缩进(N为需要的空格数):static_resources: listeners: # ... 省略其他配置 ... filter_chains: %{ for filter_chain_match in filter_chain_matches ~} ${indent(8)} - filter_chain_match: ${indent(12)} server_names: ${jsonencode(filter_chain_match.server_names)} ${indent(12)} filters: ${indent(16)} - name: envoy.filters.network.http_connection_manager # ... 省略块内其他配置 ... %{ endfor ~} clusters: # ... 省略其他配置 ...这种方式下,控制语句的前置缩进不会被叠加,块内缩进完全由
indent()函数控制,生成的YAML格式正确,同时模板结构保持对齐。
方法二:用yamlencode()替代templatefile(更优方案)
完全抛弃手写模板,改用Terraform的HCL语法构建Envoy配置的结构化数据,通过for表达式和条件表达式处理循环、判断逻辑,最后用yamlencode()函数直接生成符合格式的YAML。这种方式彻底避免缩进问题,配置逻辑更清晰,维护成本更低:
locals { filter_chain_matches = [ { server_names = ["api.mydomain.com"] require_client_certificate = true server_cert_chain_path = "/path/to/server/cert.pem" server_private_key_path = "/path/to/server/key.pem" trusted_ca_path = "/path/to/trusted/cacert.pem" allowed_subject_alt_names = [ { san_type = "DNS" matcher_type = "exact" san = "my-api-client-one.somedomain.com" } ] } ] cluster = "my_cluster" cluster_ip = "10.0.0.5" envoy_config = { static_resources = { listeners = [ { name = "listener_0" address = { socket_address = { address = "0.0.0.0" port_value = 10000 } } listener_filters = [ { name = "envoy.filters.listener.tls_inspector" typed_config = { "@type" = "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" } } ] # 用for表达式生成filter_chains filter_chains = [for fcm in local.filter_chain_matches : { filter_chain_match = { server_names = fcm.server_names } filters = [ { name = "envoy.filters.network.http_connection_manager" typed_config = { "@type" = "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" stat_prefix = "ingress_http" access_log = [ { name = "envoy.access_loggers.stdout" typed_config = { "@type" = "type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog" } } ] set_current_client_cert_details = { subject = true } http_filters = [ { name = "envoy.filters.http.router" typed_config = { "@type" = "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" } } ] route_config = { name = "local_route" virtual_hosts = [ { name = "local_service" domains = ["*"] routes = [ { match = { prefix = "/" }, route = { cluster = local.cluster } } ] } ] } } } ] transport_socket = { name = "envoy.transport_sockets.tls" typed_config = { "@type" = "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext" require_client_certificate = fcm.require_client_certificate common_tls_context = { tls_certificates = [ { certificate_chain = { filename = fcm.server_cert_chain_path } private_key = { filename = fcm.server_private_key_path } } ] # 用条件表达式处理validation_context validation_context = fcm.require_client_certificate ? { trusted_ca = { filename = fcm.trusted_ca_path } match_typed_subject_alt_names = [for san in fcm.allowed_subject_alt_names : { san_type = san.san_type matcher = { (san.matcher_type) = san.san } }] } : null } } } }] } ] clusters = [ { name = local.cluster connect_timeout = "30s" type = "LOGICAL_DNS" dns_lookup_family = "V4_ONLY" load_assignment = { cluster_name = local.cluster endpoints = [ { lb_endpoints = [ { endpoint = { address = { socket_address = { address = local.cluster_ip port_value = 443 } } } } ] } ] } transport_socket = { name = "envoy.transport_sockets.tls" typed_config = { "@type" = "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext" } } } ] } } } # 输出生成的YAML配置 output "envoy_config" { value = yamlencode(local.envoy_config) }
内容的提问来源于stack exchange,提问作者chriaass

