← Kubernetes: разбор поломок
6 мин · жёстко · Урок 10 из 12

Пропал ConfigMap: подъём сервиса после катастрофы

CreateContainerConfigError, восстановление конфига и почему Pod-ы не чинятся сами.

Кто-то выполнил kubectl delete cm не в том контексте. Через минуту сервис перестал отвечать, Pod-ы висят в CreateContainerConfigError, а приложение даже не пыталось стартовать. Это инцидент про recovery: восстановить конфиг и вернуть трафик, разбор виноватых потом.

Что значит CreateContainerConfigError

Это статус, когда kubelet физически не может создать контейнер из-за проблем с конфигурацией. Самый частый сценарий: Pod ссылается на ConfigMap или Secret через envFrom, env.valueFrom.configMapKeyRef или volumes.configMap, а объект отсутствует.

В описании Pod это видно так:

kubectl describe pod api-xxx

В секции Events:

Warning  Failed  10s (x5 over 30s)  kubelet  Error: configmap "api-config" not found

Принципиально, что Pod не падает в CrashLoopBackOff: он даже не стартовал. CrashLoop это «контейнер запустился и упал», CreateContainerConfigError это «контейнер не создался вообще». Разница важна: логов приложения не будет, смотреть надо события.

Проверка, что объекта правда нет:

kubectl get cm

Как восстановить ConfigMap

Источник значений бывает двух видов. Если конфиг простой и значения известны из документации сервиса, его собирают руками:

kubectl create configmap api-config \
  --from-literal=API_PORT=8080 \
  --from-literal=API_HOST=0.0.0.0 \
  --from-literal=LOG_LEVEL=info

Если бэкап снимали заранее, достаточно применить его:

kubectl apply -f backup/api-config.yaml

Результат один. Объект появится, но Pod-ы он сам не починит.

Почему Pod-ы не поднимаются сами

kubelet читает ConfigMap в трёх ситуациях:

  1. При создании контейнера: читает объект один раз и формирует env-vars или монтирует volume. Нет ConfigMap - нет контейнера.
  2. При обновлении смонтированного volume: kubelet обновляет файлы через свой sync period (по умолчанию 60 секунд). Работает для volume-mount, но не для envFrom.
  3. При перезапуске Pod: новый старт читает ConfigMap заново.

Pod-ы, застрявшие в CreateContainerConfigError, в Running так и не вышли. После появления ConfigMap kubelet попробует создать контейнер на следующем sync-цикле, и Pod поднимется. Это может занять до минуты.

Ускоряют удалением Pod: ReplicaSet тут же создаст новые, и они увидят ConfigMap при старте.

kubectl delete pods -l app=api
# или
kubectl rollout restart deploy api

Терять нечего: Pod-ы и так в нерабочем состоянии.

envFrom vs volumeMount

Способ ссылкиЧто после восстановления ConfigMap
envFromзначения вшиты при старте, нужен рестарт Pod
volume + volumeMountskubelet обновит файлы примерно за минуту
envFrom:
  - configMapRef:
      name: api-config

Все ключи ConfigMap вшиваются в env при старте. После старта изменения конфига до работающего контейнера не доедут, нужен рестарт Pod.

volumes:
  - name: config
    configMap:
      name: api-config
volumeMounts:
  - name: config
    mountPath: /etc/api

Здесь ConfigMap монтируется как файлы. kubelet обновит их автоматически примерно через минуту после изменения объекта. Контейнер должен сам уметь перечитывать конфиг: через SIGHUP, watch на файл, что угодно.

Для recovery первый вариант неприятнее: даже после восстановления ConfigMap приложение продолжает жить со старым env, пока Pod не перезапустят.

Как не наступать снова

Критичные ConfigMap регулярно выгружают:

kubectl get cm api-config -o yaml > backup/api-config.yaml

Дамп чище получается через kubectl-neat или yq, которые снимают служебные поля:

kubectl get cm api-config -o yaml \
  | yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.managedFields)' \
  > backup/api-config.yaml

На проде эту роль обычно закрывает GitOps: ConfigMap лежат в репозитории, кластер тянет их через ArgoCD или Flux. Тогда восстановление это re-sync, а не ручной apply по памяти. Плюс права на delete в проде стоит выдавать отдельно от прав на apply.

Полезные команды

kubectl get pods
kubectl describe pod <pod-name>
kubectl get cm
kubectl rollout status deploy api --timeout=120s
kubectl get events --sort-by=.lastTimestamp | tail

kubectl explain pod.spec.containers.envFrom
kubectl explain configmap.data

Домашка

Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.

./run.sh start k8s-configmap-disaster     # развернуть стартовое состояние
./run.sh verify k8s-configmap-disaster    # проверить текущий шаг

Коротко

Прошли материал до конца? Отметьте урок пройденным.