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

Terraform templatefile循环缩进致YAML格式异常求助

Terraform templatefile生成Envoy YAML时缩进控制语句导致格式错误的解决办法

使用Terraform的templatefile函数生成Envoy配置YAML时,若给%{ for }或%{ if }这类模板控制语句添加缩进,会导致生成内容中控制语句后的第一行出现多余空格,破坏YAML格式;移除控制语句前的缩进则生成正常,但会降低模板可读性。以下是问题根源和解决办法:

问题根源

Terraform的templatefile会将控制语句所在行的前置缩进,自动叠加到控制语句块内每一行的开头。比如你在filter_chains:下缩进8个空格写%{ for ... ~},循环块内的每一行原本的8个空格缩进,会被加上这8个前置空格,变成16个,最终导致YAML格式错误。

解决办法

方法一:调整模板控制语句的缩进方式

保留模板可读性的前提下,有两种调整方式:

  1. 控制语句顶格(相对于父级),块内内容保持正确缩进
    即你当前使用的正确模板方式:将%{ 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:
        # ... 省略其他配置 ...
    
  2. 保留控制语句缩进,用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:15:00