Конфигурация в 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 }}" }
}
Проблемы:
- В
values.yamlнакапливается 40+ ключей для умеренно сложного сервиса. - Многострочные значения в YAML — боль (отступы, escape-последовательности).
- Внутри values нельзя использовать условия (
if eq $env "Production"). - Секреты появляются в открытом виде в
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 }}
Как это работает:
include (print $.Template.BasePath "/configmap.yaml") .рендерит шаблон ConfigMap в строку.sha256sumхеширует её.- Хеш сохраняется в аннотации Pod template.
- Когда секреты в 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 |