Паттерны обновления ConfigMap/Secret и production best practices
Главное правило, которое необходимо понять про обновления: обновление ConfigMap или Secret НЕ триггерит автоматический redeploy Pod-ов. Это не баг, это feature — но без понимания этого факта продакшен ломается регулярно. В этом уроке разбираем три проверенных паттерна для триггера rollout и собираем чек-лист production best practices.
Override-файлы в Compose: base + dev + prod конфигурации
Что происходит когда обновлён ConfigMap/Secret
Сценарий: вы делаете kubectl apply -f app-config.yaml с новым значением, рассчитывая, что Pods увидят изменения. Реальность:
| Способ подключения | Поведение при update CM/Secret |
|---|---|
env.valueFrom.configMapKeyRef | НЕ обновляется. Env var фиксируется при старте контейнера. |
envFrom.configMapRef | НЕ обновляется. То же самое — env vars фиксируются. |
volume.configMap без subPath | Обновляется в пределах ~1 минуты (kubelet sync period). |
volume.configMap с subPath | НЕ обновляется. Bind-mount на конкретный inode. |
volume.secret без subPath | Обновляется ~1 минуту. |
volume.secret с subPath | НЕ обновляется. |
Главное: никакого автоматического rollout Deployment — kubelet никого не уведомляет. Если Pod использует env vars или subPath — пока сами не перезапустите, изменения никуда не доедут.
Ещё важно: даже когда volume обновляется автоматически, приложение обычно не перечитывает файл (если оно не написано с file watcher). Поэтому даже “auto-update” volumes часто требует рестарт процесса в Pod.
Паттерн 1: Hash в annotation (checksum/config)
Самый чистый паттерн — добавить hash содержимого CM/Secret как annotation в Pod template. Если CM меняется, hash меняется, template меняется, Deployment делает rollout автоматически.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
annotations:
checksum/config: <SHA256 of CM>
checksum/secret: <SHA256 of Secret>
spec:
containers:
- name: app
image: myapp:1.0
envFrom:
- configMapRef: { name: app-config }
- secretRef: { name: app-secrets }
Hash считается deploy-tool-ом (Helm, Kustomize, custom script). Например, в Helm:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
Каждый раз когда helm upgrade рендерит manifest, hash пересчитывается. Если CM поменялся — hash другой → Pod template другой → Deployment делает rolling update.
checksum/config — это defacto стандарт в Helm-чартах от vendors (Prometheus, Grafana, Bitnami charts). Если используете Helm — почти бесплатно получаете правильное поведение.
Плюсы:
- Автоматический rollout при изменении конфигурации
- Линейная история ревизий (видно какая конфигурация была в какой версии Deployment)
- Работает с любым способом подключения (env, envFrom, volume)
Минусы:
- Требует deploy-tool, который умеет считать hash (Helm, Kustomize-плагин, custom CI)
- При больших Deployments hash считается каждый apply — может быть лишняя работа
Паттерн 2: Versioned naming
Не обновлять ConfigMap, а создавать новый с инкрементированным именем:
# было
apiVersion: v1
kind: ConfigMap
metadata: { name: app-config-v1 }
data: { log_level: info }
---
# стало
apiVersion: v1
kind: ConfigMap
metadata: { name: app-config-v2 }
data: { log_level: debug }
Deployment ссылается на конкретную версию:
envFrom:
- configMapRef: { name: app-config-v2 } # было v1, стало v2
Меняем v1 → v2 в template Deployment → template diff → rolling update.
Это нативный механизм Kustomize через configMapGenerator:
configMapGenerator:
- name: app-config
literals:
- log_level=debug
Kustomize автоматически добавляет hash суффикс: app-config-abc123. При следующем рендере с другими данными — app-config-xyz789. Ссылки в манифестах перепрошиваются под актуальное имя.
Плюсы:
- Атомарность: новый CM создан → Deployment рефернсится на новый → rolling update → старый CM можно безопасно удалить
- Чистая история — видно, какая версия конфига была в какой версии Pod-а
- Идеально работает с
immutable: true— старые CM нельзя случайно изменить - Поддерживается Kustomize нативно
Минусы:
- В etcd накапливаются старые CM (нужен периодический cleanup или GC по policy)
- Bare YAML (без kustomize) — нужно вручную инкрементировать имена
Паттерн 3: Reloader controllers
Внешние controllers, которые сами watch-ят CM/Secret и патчат Deployment-ы.
Stakater Reloader — популярный open-source controller. Устанавливается в кластер и watch-ит изменения CM/Secret. Если у Deployment есть аннотация reloader.stakater.com/auto: "true" (или явная ссылка на конкретный CM/Secret), Reloader при изменении конфигурации патчит Pod template (добавляет timestamp annotation) — что триггерит rolling update.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
annotations:
reloader.stakater.com/auto: "true"
# или явно:
# configmap.reloader.stakater.com/reload: "app-config"
# secret.reloader.stakater.com/reload: "app-secrets"
spec:
template:
spec:
containers:
- name: app
envFrom:
- configMapRef: { name: app-config }
- secretRef: { name: app-secrets }
Аналогичные controllers:
- Wave — от Pusher, делает то же самое через mutating webhook (добавляет hash в template при admission)
- ConfigMap Reloader — sidecar-подход (sidecar-контейнер watch-ит CM и шлёт SIGHUP в main контейнер)
Плюсы:
- Простота: одна аннотация, и все обновления CM/Secret автоматически делают rollout
- Не требует менять deploy-pipeline
Минусы:
- Дополнительный controller в кластере (точка отказа, добавочный RBAC)
- Не работает с GitOps в чистом виде (Reloader делает скрытые мутации, конфликт с FluxCD/ArgoCD reconcile loop)
Сравнение паттернов
Immutable ConfigMaps/Secrets в production
Связано с паттерном versioned naming. Если используем versioned имена — ставим immutable: true:
apiVersion: v1
kind: ConfigMap
metadata: { name: app-config-v2 }
data: { log_level: debug }
immutable: true
Что это даёт:
- API server отклоняет любые update — нельзя случайно сломать
- kubelet не watch-ит этот CM (нет смысла watch-ить если он не меняется) — снижение нагрузки на API server
- Помогает безопасно делать rolling update — пока новые Pods создаются с CM v2, старые читают v1, ничего не пересекается
Best practice: для production immutability — это default. Pipeline должен генерировать new versioned CM/Secret, а не апдейтить старый.
Production best practices для Secrets
1. Encryption at rest
Уже разобрали в уроке 03. Краткий повтор:
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: [secrets]
providers:
- aescbc:
keys:
- name: key1
secret: <base64 32-byte key>
- identity: {}
Без этого Secret в etcd хранится plaintext (после base64 decode). С этим — ciphertext, который не расшифровать без encryption key.
Для production-grade — KMS provider (AWS KMS, GCP KMS, Vault). DEK envelope encryption даёт защиту от компрометации одного ключа.
2. RBAC ограничения на Secret
По умолчанию любой пользователь, который имеет get/list на Secret в namespace, видит все Secret-значения (после base64 decode). Применяйте принцип least-privilege:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: app-reader, namespace: production }
rules:
- apiGroups: [""]
resources: [pods, services, configmaps]
verbs: [get, list, watch]
- apiGroups: [""]
resources: [secrets]
resourceNames: [app-config-secret] # только конкретный Secret
verbs: [get]
resourceNames ограничивает доступ конкретными именами Secret-а. Это сужает blast radius при компрометации credentials.
3. Audit log
Включите audit log на kube-apiserver, и фильтруйте события на Secret-ах:
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: [secrets]
omitStages: [RequestReceived]
Это даёт лог “кто и когда читал/менял конкретный Secret”. Критично для compliance (PCI, SOC2, HIPAA).
4. External Secret Stores
Не хранить sensitive данные в кластере вообще. Vault / AWS SM / GCP SM / Azure KV — централизованный store с:
- Audit log из коробки
- Авто-ротацией
- Dynamic credentials (короткоживущие токены)
- Fine-grained access policy
К кластеру это интегрируется через External Secrets Operator (синхронизирует) или CSI Secret Store Driver (mount напрямую без K8s Secret).
5. Не commit Secret в git
Никогда не коммитить plaintext Secret-ы в git. Альтернативы:
- SOPS (Mozilla) — encrypts конкретные поля YAML с KMS keys. Git коммитит encrypted версию.
- Sealed Secrets (Bitnami) — encrypted-by-cluster Secret, который коммитится в git. SealedSecret controller расшифровывает в Secret в кластере.
- GitOps + Vault — git хранит только ссылки (ExternalSecret), реальные данные в Vault.
CKAD типовое задание
You need to deploy 'web' Deployment which reads database configuration from a ConfigMap.
The configuration must be passed as environment variables.
When the ConfigMap changes (e.g., DB host updated), Pods must pick up the new values.
How would you architect this?
Минимальное правильное решение для экзамена:
# 1. Создать первую версию CM
kubectl create configmap app-config-v1 \
--from-literal=DB_HOST=db.production.svc.cluster.local
# 2. Создать Deployment с envFrom на app-config-v1
kubectl create deployment web --image=myapp:1.0 --dry-run=client -o yaml > deploy.yaml
# отредактировать deploy.yaml: добавить envFrom configMapRef name: app-config-v1
kubectl apply -f deploy.yaml
# 3. При обновлении — создать новый CM и поменять reference
kubectl create configmap app-config-v2 \
--from-literal=DB_HOST=db-new.production.svc.cluster.local
kubectl set env deploy/web --from=configmap/app-config-v2 --keys=DB_HOST
# или прямо отредактировать manifest и kubectl apply
На реальном экзамене ожидают понимания: env не auto-обновляется, нужен rollout, лучший паттерн — versioned naming.
Чек-лист готовности к production
- ConfigMap/Secret для каждого workload явно описаны в YAML (не imperative)
- Versioned naming +
immutable: trueдля production CM/Secret - Pod template содержит
checksum/configannotation (Helm) или ссылку на versioned name - encryption at rest настроен на kube-apiserver (AES-CBC, AES-GCM или KMS)
- RBAC ограничивает
get/list secretsролью конкретных Service Account - Audit log пишет все CRUD операции с Secret
- Sensitive данные в production идут через External Secret Store (Vault/AWS SM), не хранятся в кластере напрямую
- Secret подключается к Pod через volume mount (не env), особенно для самых чувствительных данных
- Pods, которым не нужен API access, имеют
automountServiceAccountToken: false - git репозиторий не содержит plaintext Secret YAML