Learning Platform
Глоссарий Troubleshooting
Урок 07.05 · 20 мин
Средний
update patternsrollout triggerchecksum annotationversioned namingReloaderRBACaudit log

Паттерны обновления 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.

Update propagation: kubelet sync для volume mount
kubectl applyОбновлён ConfigMap в etcd. resourceVersion увеличивается. Подписанные watch-ы получают событие MODIFIED.
watch event
kubelet receives eventkubelet на каждой node поддерживает watch на CM/Secrets, которые используются Pod-ами на этой node. Получает MODIFIED. Кладёт CM в локальный кеш.
next sync tick (default ~60s)
kubelet sync loopSync period контролируется флагом --sync-frequency (default 1 минута). Каждый tick kubelet ресинхронизирует state Pod-ов: проверяет volumes, перематериализует если CM/Secret поменялся.
atomic symlink swap
контейнер видит new fileskubelet атомарно подменяет symlinks в volume mount (только БЕЗ subPath). Файлы обновлены. Но приложение узнаёт об этом только если делает re-read (inotify, periodic reload) или после рестарта.

Паттерн 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.

TIP

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

Меняем v1v2 в 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)

Сравнение паттернов

Выбор паттерна обновления
Helm-based deployЕсли используете Helm — checksum/config через template-функцию. Это стандарт сообщества. Hash считается при каждом helm install/upgrade. Подходит почти всегда.
Kustomize-based deployЕсли используете Kustomize — configMapGenerator/secretGenerator с hash в имени. Это нативный механизм. Старые версии CM остаются (для rollback), новые создаются автоматически.
GitOps + bare YAMLЕсли ручные YAML без template-движка и без Kustomize — вручную инкрементировать имена (app-config-v1 → v2) и ссылку в Deployment. Скучно, но работает и эксплицитно.
Legacy / простота превыше всегоStakater Reloader даёт zero-config experience. Один раз поставить аннотацию reloader.stakater.com/auto и забыть. Но дополнительный controller в кластере и потенциальный конфликт с GitOps reconcile.

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/config annotation (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

Проверка знанийKnowledge check
Платформа использует Helm для деплоя. У всех приложений в Pod template стоит Helm-аннотация checksum/config, считающая sha256sum от файла configmap.yaml внутри чарта. Один из инженеров создал ConfigMap отдельным kubectl apply вне Helm-чарта, и потом обновил его. Что произойдёт с Pod-ами?
ОтветAnswer
Pod-ы НЕ перезапустятся. Причина: checksum/config считается Helm-ом только из файлов внутри чарта. Когда ConfigMap создан и обновлён вне Helm (kubectl apply), Helm о нём не знает — никаких файлов в чарте не изменилось, hash не пересчитывается, Pod template остаётся идентичным. Helm release не помечается как dirty. Что делать: 1) Перенести ConfigMap внутрь Helm-чарта (правильный путь — single source of truth); 2) Если CM должен быть external — использовать Reloader (Stakater) с аннотацией reloader.stakater.com/auto — он watch-ит CM независимо от того, кто его создал; 3) Forced rollout через kubectl rollout restart deploy/web — ручной шаг после каждого обновления external CM. Это типичная антипаттерн ловушка: смешивание Helm-managed и kubectl-managed объектов делает state неконсистентным. Best practice — один tool ответственен за один resource.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Команда `kubectl apply -f new-configmap.yaml` обновила ConfigMap, который используется Deployment через envFrom. Что произойдёт с Pod-ами этого Deployment?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5