← Kubernetes: разбор поломок
6 мин · средне · Урок 4 из 12

ConfigMap не найден

Разбор CreateContainerConfigError: где Pod ссылается на внешние объекты и почему после создания ConfigMap перезапуск не нужен.

Образ скачался, нода нашлась, а контейнер всё равно не стартует:

NAME     READY  STATUS                       RESTARTS  AGE
webapp   0/1    CreateContainerConfigError   0         45s

Статус длинный и на вид пугающий, но означает простую вещь: Pod ссылается на объект, которого в кластере нет. Обычно это ConfigMap - объект с конфигурацией в виде пар ключ-значение.

CreateContainerConfigError и соседние ошибки

Статус похож на ImagePullBackOff, но ломается совсем другой шаг. ImagePullBackOff про сам образ. CreateContainerConfigError про то, что spec ссылается на несуществующие ConfigMap, Secret или PVC.

СтатусНа каком шагеКто виноват
ImagePullBackOffpull образаruntime или registry
CreateContainerConfigErrorподготовка контейнеране найден ConfigMap, Secret или PVC
CreateContainerErrorсоздание контейнерасломался runtime, например ошибка в command
RunContainerErrorзапуск процессаобраз битый, ENTRYPOINT падает

Все четыре сидят в состоянии Waiting, отличается только reason. Слово Config в reason это подсказка: искать надо ссылки на внешние объекты в spec.

Куда Pod может ссылаться

В spec контейнера есть несколько мест, где ссылка идёт по имени:

spec:
  containers:
    - name: app
      image: ...
      env:
        - name: DB_URL
          valueFrom:
            configMapKeyRef:
              name: db-config        # ← ссылка на ConfigMap
              key: url
        - name: API_TOKEN
          valueFrom:
            secretKeyRef:
              name: api-secret       # ← ссылка на Secret
              key: token
      envFrom:
        - configMapRef:
            name: app-config         # ← ссылка на ConfigMap
        - secretRef:
            name: db-creds           # ← ссылка на Secret
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      configMap:                     # ← ссылка на ConfigMap
        name: static-files
    - name: secrets
      secret:                        # ← ссылка на Secret
        secretName: tls-cert
    - name: storage
      persistentVolumeClaim:         # ← ссылка на PVC
        claimName: app-data

Любая из этих ссылок роняет старт контейнера, если объекта нет.

Что показывает диагностика

kubectl describe pod webapp

В секции State:

State:        Waiting
  Reason:     CreateContainerConfigError

В Events:

Events:
  Type     Reason   Message
  Normal   Pulled   Successfully pulled image
  Warning  Failed   Error: configmap "app-config" not found

Ровно одна строка с именем объекта, и этого достаточно для починки. Если ссылок несколько, в сообщении будет имя того объекта, который kubelet не смог найти первым. Контейнер не стартует, пока все ссылки не зарезолвятся.

Опечатка в имени даёт ровно ту же ошибку, что и полное отсутствие объекта. Первым делом имя из Events сверяют со списком kubectl get configmap.

Ещё две команды, которые сокращают разбор:

# На что ссылается Pod
kubectl get pod webapp -o jsonpath='{.spec.containers[0].envFrom}'

# Что реально есть в namespace
kubectl get configmap

optional, чтобы не падать

Ссылку можно отметить как необязательную:

envFrom:
  - configMapRef:
      name: app-config
      optional: true

Тогда при отсутствии ConfigMap контейнер стартует без этих переменных. Подходит для конфигурации, которой может и не быть.

Значение по умолчанию другое: optional: false. Pod будет ждать объект, и это правильное поведение для обязательных настроек. Молча стартовать без пароля к базе хуже, чем не стартовать вовсе.

Когда контейнер увидит новый ConfigMap

Зависит от способа ссылки:

СпособКогда меняется
env / envFromпри запуске контейнера, дальше значения зафиксированы
volume с configMapkubelet периодически синхронизирует, примерно раз в минуту
монтирование через subPathне обновляется без пересоздания Pod

Отсюда неочевидное следствие. При envFrom переменные фиксируются на старте, но у Pod в статусе CreateContainerConfigError контейнер ещё ни разу не стартовал. kubelet продолжает попытки, и как только ConfigMap появится, следующая попытка пройдёт. Пересоздавать Pod не нужно.

Другое дело, если Pod давно работает, а ConfigMap изменили. Тогда переменные окружения останутся старыми, и нужен рестарт:

kubectl rollout restart deploy/webapp

Как чинят

Недостающий ConfigMap создают императивно:

# Один ключ
kubectl create configmap app-config --from-literal=MODE=production

# Несколько ключей
kubectl create configmap app-config \
  --from-literal=MODE=production \
  --from-literal=LOG_LEVEL=info

# Из файла: имя файла станет ключом
kubectl create configmap app-config --from-file=config.json

Через несколько секунд Pod сам перейдёт в Running.

Как не допустить

ConfigMap и Secret применяют раньше, чем Pod, который на них ссылается. В Kustomize и Helm это делается порядком ресурсов, в обычном kubectl apply -f ./manifests/ порядок алфавитный по имени файла, поэтому конфигурацию кладут в файл, который идёт раньше.

Для необязательной конфигурации ставят optional: true явно. Для обязательной оставляют значение по умолчанию, чтобы Pod не поднялся с пустыми настройками.

Домашка

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

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

Коротко

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