CreateContainerConfigError: почему kubelet не может собрать конфигурацию контейнера
Команда KAI Lab · · 7 мин чтения
CreateContainerConfigError значит, что kubelet не смог подготовить конфигурацию контейнера перед запуском, поэтому контейнер даже не создан. Почти всегда причина — ссылка на Secret или ConfigMap, которого нет в пространстве имён пода, или на ключ, которого нет внутри них; реже — securityContext, например runAsNonRoot при образе, который запускается от root.
Хорошая новость: kubelet пишет причину прямым текстом в событии пода. Плохая: её легко спутать с падением приложения, если смотреть только на статус. Разберём, как это выглядит, где взять точный текст ошибки и как чинить каждую причину.
Что происходит при CreateContainerConfigError
Прежде чем создать контейнер, kubelet собирает его конфигурацию: подставляет переменные окружения из ConfigMap и Secret, готовит точки монтирования, применяет securityContext и проверяет, от какого пользователя запустится образ. Если любой шаг не удался, контейнер не создаётся. Он остаётся в состоянии Waiting с причиной CreateContainerConfigError, а kubelet пишет событие Failed с текстом ошибки и через некоторое время пробует снова.
Отсюда три отличия от CrashLoopBackOff:
RESTARTSостаётся 0: контейнер ни разу не запускался;- логов приложения нет,
kubectl logsничего не покажет; - фаза пода —
Pending, хотя нода уже назначена.
Как выглядит ошибка
$ kubectl get pods -n shop
NAME READY STATUS RESTARTS AGE
billing-6c9d7f5b8-q2w4e 0/1 CreateContainerConfigError 0 2m$ kubectl describe pod billing-6c9d7f5b8-q2w4e -n shop
...
State: Waiting
Reason: CreateContainerConfigError
Ready: False
Restart Count: 0
Environment:
DB_HOST: <set to the key 'DB_HOST' of config map 'app-config'> Optional: false
DB_PASSWORD: <set to the key 'password' in secret 'db-credentials'> Optional: false
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m default-scheduler Successfully assigned shop/billing-6c9d7f5b8-q2w4e to worker-2
Normal Pulled 11s (x12 over 2m) kubelet Container image "registry.example.kz/shop/billing:1.8.0" already present on machine
Warning Failed 11s (x12 over 2m) kubelet Error: secret "db-credentials" not foundБлок Environment сразу показывает, откуда контейнер ждёт каждую переменную и обязательна ли она (Optional: false). Событие Failed говорит, чего именно не хватило.
Какие бывают сообщения
Тексты ошибок формирует kubelet, и они почти дословно подсказывают решение:
Error: secret "db-credentials" not found— Secret с таким именем нет в пространстве имён пода;Error: configmap "app-config" not found— то же для ConfigMap;Error: couldn't find key password in Secret shop/db-credentials— объект есть, а ключа в нём нет;Error: couldn't find key DB_HOST in ConfigMap shop/app-config— то же для ConfigMap;Error: container has runAsNonRoot and image will run as root (pod: "…", container: billing)— политика запрещает root, а образ запускается от root;Error: container has runAsNonRoot and image has non-numeric user (app), cannot verify user is non-root (pod: "…", container: billing)— в образе пользователь задан именем, и kubelet не может проверить, что это не root.
Для нескольких подов текст удобнее достать из статуса:
$ kubectl get pod billing-6c9d7f5b8-q2w4e -n shop -o jsonpath='{.status.containerStatuses[0].state.waiting.message}'
secret "db-credentials" not foundПошаговая диагностика
Шаг 1. Проверить, есть ли объект и где он
$ kubectl get secret db-credentials -n shop
Error from server (NotFound): secrets "db-credentials" not found
$ kubectl get secrets -A --field-selector metadata.name=db-credentials
NAMESPACE NAME TYPE DATA AGE
default db-credentials Opaque 2 3dКлассика: секрет создали в default, а сервис живёт в shop. Документация Kubernetes прямо говорит, что под может ссылаться только на ConfigMap из своего пространства имён, и для Secret правило то же.
Шаг 2. Проверить ключи
kubectl describe secret показывает имена ключей и их размер, но не значения, поэтому команду можно спокойно запускать при коллегах:
$ kubectl describe secret db-credentials -n shop
Name: db-credentials
Namespace: shop
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
DB_PASSWORD: 24 bytes
DB_USER: 7 bytesВ манифесте ждут ключ password, а в секрете он называется DB_PASSWORD. Ключи чувствительны к регистру. Для ConfigMap ключи и значения видно так:
$ kubectl get configmap app-config -n shop -o jsonpath='{.data}'
{"DB_HOST":"postgres.shop.svc","LOG_LEVEL":"info"}Шаг 3. Создать недостающее или исправить ссылку
$ kubectl create secret generic db-credentials -n shop \
--from-literal=DB_USER=billing \
--from-literal=DB_PASSWORD="$DB_PASSWORD"Или исправить key: в манифесте, если ключ просто назван иначе. Пересоздавать под после появления секрета не нужно: kubelet сам повторяет попытку, и на следующей контейнер запустится. Если же поменялся шаблон Deployment, контроллер создаст новые поды.
Шаг 4. Если секрет создаёт кто-то другой
Часто Secret появляется не из вашего манифеста: его создаёт оператор вроде External Secrets из хранилища секретов, отдельный шаг пайплайна или Helm-чарт другой команды. Тогда CreateContainerConfigError — симптом, а причина — в этом инструменте: он ещё не успел синхронизировать секрет, у него нет доступа к хранилищу или изменилось имя. Проверьте его статус и логи, прежде чем создавать секрет руками: иначе оператор перезапишет или удалит вашу копию.
runAsNonRoot и пользователь образа
Если в securityContext стоит runAsNonRoot: true, kubelet перед запуском проверяет, от кого запустится контейнер. Образ без инструкции USER запускается от root, и kubelet откажется его запускать. Образ с USER app задаёт пользователя именем, и проверить UID kubelet не может. Решение — указать числовой UID:
securityContext:
runAsNonRoot: true
runAsUser: 10001Или прописать USER 10001 в Dockerfile. Убедитесь, что у этого пользователя есть права на файлы, которые приложение читает и пишет, иначе следующей ошибкой станет permission denied уже в логах приложения.
Похожая ошибка: Secret подключён как том
Если отсутствующий Secret или ConfigMap подключён не как переменные окружения, а как том, статус будет другим. Под зависнет в ContainerCreating, а в событиях появится FailedMount:
Warning FailedMount 18s (x9 over 2m) kubelet MountVolume.SetUp failed for volume "tls" : secret "api-tls" not foundЛечится так же: создать секрет в пространстве имён пода или исправить имя. Если это TLS-секрет с сертификатом, заодно проверьте срок его действия: как не пропустить истечение SSL-сертификата, мы разобрали отдельно. А если не хватает секрета для скачивания образа, будет событие FailedToRetrieveImagePullSecret и статус ImagePullBackOff.
Причины CreateContainerConfigError: таблица
| Причина | Как узнать | Как исправить |
|---|---|---|
| Нет Secret | Error: secret "…" not found | создать Secret в пространстве имён пода или исправить имя |
| Нет ConfigMap | Error: configmap "…" not found | создать ConfigMap или исправить имя |
| Нет ключа | couldn't find key … in Secret … или in ConfigMap … | добавить ключ или исправить key: с учётом регистра |
| Объект в другом пространстве имён | kubectl get secrets -A находит его в чужом namespace | создать объект рядом с подом |
| Секрет ещё не создан оператором или пайплайном | объекта нет, у оператора ошибки синхронизации | чинить оператор или порядок деплоя |
| runAsNonRoot и root в образе | container has runAsNonRoot and image will run as root | runAsUser с числовым UID или USER 10001 в образе |
| Пользователь образа задан именем | image has non-numeric user | числовой UID в runAsUser или в Dockerfile |
Если настройка действительно необязательна, отметьте ссылку optional: true. По документации, при отсутствии ConfigMap или ключа переменная тогда просто останется пустой, и под запустится:
env:
- name: FEATURE_FLAGS
valueFrom:
configMapKeyRef:
name: feature-flags
key: FLAGS
optional: trueЧастые вопросы
Чем CreateContainerConfigError отличается от CrashLoopBackOff? При CreateContainerConfigError контейнер ни разу не запускался: kubelet не смог собрать его конфигурацию. При CrashLoopBackOff контейнер запускается и падает. Отличить просто: у первого RESTARTS 0 и нет логов, у второго счётчик растёт, а в kubectl logs --previous есть вывод приложения.
Нужно ли пересоздавать под после того, как создал Secret? Нет. kubelet повторяет попытки сам и на следующей запустит контейнер. Пересоздание понадобится, только если нужно поменять сам манифест: имя секрета, ключ или securityContext.
Поменял значение в ConfigMap, а приложение видит старое. Почему? Переменные окружения читаются один раз при старте контейнера. Документация Kubernetes: ConfigMap в переменных окружения не обновляются автоматически, нужен перезапуск пода, например kubectl rollout restart deployment/billing -n shop. Подключённые томом ConfigMap обновляются сами, кроме тех, что смонтированы через subPath.
Почему kubectl describe не показывает значение секрета? Так задумано: kubectl describe secret выводит только ключи и их размер в байтах, чтобы значения не попадали на экран и в логи. Посмотреть значение можно через kubectl get secret -o jsonpath с декодированием из base64, но делайте это осознанно.
Работают ли ConfigMap в статических подах? Нет. Документация отдельно оговаривает, что kubelet не поддерживает ConfigMap для static pods: такие поды не могут ссылаться на объекты API. Конфигурацию для них кладут в файлы на ноде. Этот и соседние вопросы полезно повторить перед собеседованием по Kubernetes.
Потренироваться на практике
Такие ошибки проще всего запомнить, когда разбираешь их сам. В симуляторе DevOps KAI Lab месяц про Kubernetes построен на тикетах от коллег: поды, которые не стартуют, конфигурация, выкатки и ноды на обслуживании. Консоль в браузере отдаёт реальные выводы kubectl. Начать можно с бесплатного теста навыков DevOps.
Источники
- Kubernetes. Configure a Pod to Use a ConfigMap — под не стартует без ConfigMap или ключа,
optional: true, ограничение пространством имён, static pods. - Kubernetes. ConfigMaps — обновление томов и переменных окружения,
subPath. - Kubernetes. Secrets и Managing Secrets using kubectl — создание секретов,
kubectl describe secretбез значений. - Kubernetes. Configure a Security Context for a Pod or Container —
runAsNonRoot,runAsUser. - Kubernetes. Pod Lifecycle — состояние
Waiting, фазаPending. - Kubernetes, исходный код kubelet: kuberuntime_container.go — причина
CreateContainerConfigErrorи событиеError: …; kubelet_pods.go —couldn't find key; security_context_others.go — проверкиrunAsNonRoot.