Terraformのtemplatefile関数で使うファイルの中に別のファイルを埋め込むとインデントが崩れた

2023.02.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

やりたいこと

以下のFluent Bitの設定の一部を切り出したファイルparsers.confがあります。

parsers.conf

[PARSER]
    Name crio
    Format regex
    Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
    Time_Key time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z
    Time_Keep On

これを、AWS for fluent bitのvaluesを定義した以下YAMLファイルの中に埋め込みたいです。

override-values.yaml.tmpl

image:
  tag: 2.25.0
serviceAccount:
  name: '${sa_name}'
  create: true
  annotations:
    eks.amazonaws.com/role-arn: ${sa_role_arn}
input:
  parser: crio
cloudWatch:
  enabled: true
firehose:
  enabled: false
kinesis:
  enabled: false
elasticsearch:
  enabled: false

先程のparsers.confを使ってこういうキーを定義したいです。

service:
  extraParsers: |-
    [PARSER]
        Name crio
        Format regex
        Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
        Time_Key time
        Time_Format %Y-%m-%dT%H:%M:%S.%L%z
        Time_Keep On

失敗1

templatefile関数のvarsに、file関数で読み込んだ文字列を渡してみました。

resource "helm_release" "ec2_node_logging" {
  name      = "ec2-node-logging"
  namespace = kubernetes_namespace.aws_observability.id

  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-for-fluent-bit"
  version    = "0.1.16"

  recreate_pods = true

  values = [
    templatefile(
      "${path.module}/helm/override-values.yaml.tmpl",
      {
        sa_name       = local.ec2_node_logging_service_account_name
        sa_role_arn   = module.ec2_node_logging_sa_iam_role.this_iam_role_arn
        extra_parsers = file("${path.module}/conf/parsers.conf")
      }
    )
  ]
}
image:
  tag: 2.25.0
serviceAccount:
  name: '${sa_name}'
  create: true
  annotations:
    eks.amazonaws.com/role-arn: ${sa_role_arn}
service:
  extraParsers: |-
    ${extra_parsers}
input:
  parser: crio
cloudWatch:
  enabled: true
firehose:
  enabled: false
kinesis:
  enabled: false
elasticsearch:
  enabled: false

結果、この方法は上手くいきませんでした。

service.extraParsersの値を元に/fluent-bit/etc/parser_extra.confという設定ファイルが作成されるのですが、インデントレベルエラーとなりました。

% kubectl logs ec2-node-logging-aws-for-fluent-bit-6mrzs
(略)
[2023/02/24 12:28:27] [error] [config] error in /fluent-bit/etc/parser_extra.conf:3: invalid indentation level
(略)

Podの中に入ってファイルを確認してみたところ、2行目以降のインデントがなくなっていることがわかります。

 % kubectl exec ec2-node-logging-aws-for-fluent-bit-6mrzs -- cat
 /fluent-bit/etc/parser_extra.conf
[PARSER]
Name crio
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
Time_Keep On%

helmコマンドでも確認。

% helm get values ec2-node-logging
USER-SUPPLIED VALUES:
(略)
service:
  extraParsers: |-
    [PARSER]
    Name crio
    Format regex
    Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
    Time_Key time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z
    Time_Keep On
(略)

失敗2

テンプレートファイルの中でfile関数を使ってみましたが、結果は同じでした。

image:
  tag: 2.25.0
serviceAccount:
  name: '${sa_name}'
  create: true
  annotations:
    eks.amazonaws.com/role-arn: ${sa_role_arn}
service:
  extraParsers: |-
    ${file("conf/parsers.conf")}
input:
  parser: crio
cloudWatch:
  enabled: true
firehose:
  enabled: false
kinesis:
  enabled: false
elasticsearch:
  enabled: false

成功1

「失敗1」をベースに、indenttrimspace関数を追加したところ成功しました。

resource "helm_release" "ec2_node_logging" {
  name      = "ec2-node-logging"
  namespace = kubernetes_namespace.aws_observability.id

  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-for-fluent-bit"
  version    = "0.1.16"

  recreate_pods = true

  values = [
    templatefile(
      "${path.module}/helm/override-values.yaml.tmpl",
      {
        sa_name       = local.ec2_node_logging_service_account_name
        sa_role_arn   = module.ec2_node_logging_sa_iam_role.this_iam_role_arn
        extra_parsers = trimspace(indent(4, file("${path.module}/conf/parsers.conf")))
      }
    )
  ]
}

「失敗1」の結果の通り、file関数で読み込んだ直後はインデントがなくなっています。

[PARSER]
Name crio
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
Time_Keep On%

そこでindent関数で全行インデントします。

    [PARSER]
    Name crio
    Format regex
    Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
    Time_Key time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z
    Time_Keep On%

最後にtrimspace関数で最初の行だけ冒頭のスペースを削除しています。

[PARSER]
    Name crio
    Format regex
    Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) [PF] (?<log>.+)$
    Time_Key time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z
    Time_Keep On%

ただこれは、今回のファイルの中身がこうだったから成功しただけなので、もっと複雑なインデントが必要だったら失敗しますね。

成功2

helm_releaseのvalues attributeはlist型になっていて、複数のYAMLを指定できます。それら複数YAMLはマージされた状態でリリースに使われます。

List of values in raw yaml to pass to helm. Values will be merged, in order, as Helm does with multiple -f options. (引用元)

templatefile関数内で頑張るのをやめて、こちら(複数YAML定義する)でやってみたところうまく行きました。具体的には、Terraformのオブジェクトとして定義してから、yamlencode関数でYAML化したところ、インデントは保存されていました。

resource "helm_release" "ec2_node_logging" {
  name      = "ec2-node-logging"
  namespace = kubernetes_namespace.aws_observability.id

  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-for-fluent-bit"
  version    = "0.1.16"

  recreate_pods = true

  values = [
    templatefile(
      "${path.module}/helm/override-values.yaml.tmpl",
      {
        sa_name     = local.ec2_node_logging_service_account_name
        sa_role_arn = module.ec2_node_logging_sa_iam_role.this_iam_role_arn
      }
    ),
    yamlencode({
      service = {
        extraParsers = file("${path.module}/conf/parsers.conf")
      }
    })
  ]
}

なぜTerraformのオブジェクトとして定義する場合はインデントが保存されるのかはわかりませんでした。が、以下の文を読むにYAMLやJSONを定義する場合は、Terraformオブジェクトとして定義してからencode関数をかますことを公式は推奨しているのではと思います。

Don't use "heredoc" strings to generate JSON or YAML. Instead, use the jsonencode function or the yamlencode function so that Terraform can be responsible for guaranteeing valid JSON or YAML syntax.