Конфигурация в Helm-чарте: Files.Get, tpl и директория config/

Published: 2026-05-25

У Helm есть два способа встроить внешнее содержимое в чарт: values.yaml и .Files.Get. Для конфиг-файлов приложений — appsettings.json, nginx.conf, log4j.properties — подход с Files.Get лучше, чем запихивать многострочные блоки в values. Этот пост описывает паттерн, на котором мы остановились после выкатки ~60 .NET-микросервисов: хранить конфиг как файлы-шаблоны в config/, рендерить секреты в CI, паковать их в ConfigMap через Files.Get и принудительно запускать rolling restart через checksum-аннотации.


Проблема с values.yaml для конфигурации

Типичный соблазн — положить конфиг в values.yaml:

yaml# Так не делать
config:
  connectionString: "Server=db;Database=app;User=app;Password=secret"
  rabbitHost: "rabbit.infra"

Затем в шаблоне:

yamldata:
  appsettings.json: |
    {
      "ConnectionStrings": { "Default": "{{ .Values.config.connectionString }}" },
      "RabbitMq": { "Host": "{{ .Values.config.rabbitHost }}" }
    }

Проблемы:

  1. В values.yaml накапливается 40+ ключей для умеренно сложного сервиса.
  2. Многострочные значения в YAML — боль (отступы, escape-последовательности).
  3. Внутри values нельзя использовать условия (if eq $env "Production").
  4. Секреты появляются в открытом виде в values-Production.yaml.

Паттерн Files.Get

Вместо этого держим appsettings.json как нормальный JSON-файл внутри config/:

my-service/
  config/
    appsettings.json         ← рендерится consul-template при деплое
    appsettings.ENV.json     ← переопределения для среды, тоже рендерятся
  crt/
    YC-CA.pem                ← CA-сертификат, рендерится из Vault
  files/
    config.hcl               ← источник consul-template для appsettings.json
    config-user.hcl          ← источник для appsettings.ENV.json
    config-crt.hcl           ← источник для YC-CA.pem

Шаблон configmap.yaml загружает отрендеренные файлы через Files.Get:

yamlapiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "my-service.fullname" . }}-app-settings
data:
  appsettings.json: |
{{ tpl (.Files.Get "config/appsettings.json") . | indent 4 }}
  appsettings.{{ .Values.aspnetcoreEnvironment }}.json: |
{{ tpl (.Files.Get "config/appsettings.ENV.json") . | indent 4 }}

Обратите внимание на tpl вокруг .Files.Get: содержимое файла прогоняется через движок шаблонов Helm, и внутри JSON-файла можно ссылаться на {{ .Values.* }}. Удобно для подстановки {{ .Values.replicaCount }} или {{ .Release.Namespace }} в конфиг.


Почему indent 4, а не nindent

indent 4 добавляет 4 пробела к каждой строке. nindent 4 добавляет ведущий перенос строки и затем 4 пробела. Для многострочного block scalar (|) в YAML правильно использовать indent — первая строка должна начинаться с того же отступа, что и остальные.

С nindent в начале значения появляется лишняя пустая строка — для большинства парсеров она безвредна, но выглядит некрасиво в kubectl get cm -o yaml.


ConfigMap с CA-сертификатом

CA PEM — в отдельном ConfigMap, чтобы его можно было обновлять независимо:

yamlapiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "my-service.fullname" . }}-ca-pemstore
data:
  YC-CA.pem: |
{{ .Files.Get "crt/YC-CA.pem" | indent 4 }}

Здесь tpl не нужен — PEM это просто текст, а не шаблон.


volumeMounts в Deployment

Два ConfigMap монтируются в контейнер по точным путям, которые ожидает .NET runtime:

yamlvolumeMounts:
  - name: config
    mountPath: /app/appsettings.json
    subPath: appsettings.json
  - name: config
    mountPath: /app/appsettings.{{ .Values.aspnetcoreEnvironment }}.json
    subPath: appsettings.{{ .Values.aspnetcoreEnvironment }}.json
  - name: ca-pemstore
    mountPath: /etc/ssl/certs/YC-CA.pem
    subPath: YC-CA.pem
    readOnly: true

volumes:
  - name: config
    configMap:
      name: {{ include "my-service.fullname" . }}-app-settings
  - name: ca-pemstore
    configMap:
      name: {{ include "my-service.fullname" . }}-ca-pemstore

subPath монтирует один ключ из ConfigMap как файл, не заменяя всю директорию. Без subPath монтирование в /app скрыло бы все остальные файлы в этой директории.


Принудительный rolling restart при изменении конфига

В .spec.template.metadata.annotations Deployment хранится хеш каждого ConfigMap:

yamlmetadata:
  annotations:
    checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
    checksum/configmap-ca: {{ include (print $.Template.BasePath "/capem-configmap.yaml") . | sha256sum }}

Как это работает:

  1. include (print $.Template.BasePath "/configmap.yaml") . рендерит шаблон ConfigMap в строку.
  2. sha256sum хеширует её.
  3. Хеш сохраняется в аннотации Pod template.
  4. Когда секреты в Vault меняются и пайплайн заново рендерит config/appsettings.json, меняется содержимое ConfigMap, за ним — хеш и спецификация Pod template, и Kubernetes автоматически запускает rolling update.

Без этой аннотации helm upgrade обновил бы ConfigMap, но существующие поды продолжили бы работать со старым конфигом: Kubernetes не перезапускает поды при изменении примонтированного ConfigMap, а файлы, смонтированные через subPath, не обновляются автоматически вовсе.


Плейсхолдер ENV

Имя файла appsettings.ENV.json содержит буквальную строку ENV — и в git, и в пути назначения consul-template:

bashconsul-template \
  -template="chart/files/config-user.hcl:chart/config/appsettings.ENV.json" \
  -once

ENV здесь остаётся как есть. Значение aspnetcoreEnvironment (Development, Production и т.д.) передаётся при деплое через --set aspnetcoreEnvironment=${APP_ENV}. Шаблон configmap использует {{ .Values.aspnetcoreEnvironment }} для именования ключа, так что в рантайме контейнер получает /app/appsettings.Production.json.


Helm lint

helm lint проверяет шаблоны чарта. Поскольку config/appsettings.json добавлен в .gitignore (это артефакт рендеринга), нужен файл-заглушка, чтобы lint проходил:

json{}

Закоммитьте эту заглушку как config/appsettings.json — consul-template перезапишет её при деплое. helm lint и локальные вызовы helm template увидят пустой, но корректный JSON-объект.


Итог

Задача Решение
Секреты в конфиге consul-template рендерит из Vault до helm upgrade
Формат конфиг-файла Чистый JSON/YAML в config/, не встроенный в values.yaml
Доступ из Helm-шаблона Files.Get + опциональный tpl
Монтирование в под subPath volumeMount на каждый файл
Rolling update при изменении аннотация checksum/* в Pod template
Lint/template локально Заглушки файлов config/ в git