Learning Platform
Глоссарий Troubleshooting
Урок 07.02 · 22 мин
Продвинутый
envenvFromconfigMapKeyRefvolume mountsubPathdefaultMode

Как Pod использует ConfigMap: env, envFrom, volume, subPath

ConfigMap сам по себе — просто store. Пока его не подключили к Pod, он бесполезен. Способов подключения четыре, и каждый имеет свои особенности и compromise. Самое опасное место — subPath: он молчаливо отключает обновление файлов, и это регулярно ломает продакшен.


stdin, stdout, stderr: три file descriptors процесса

Способ 1: env с одним ключом (configMapKeyRef)

Самый простой случай — взять одно значение из ConfigMap и положить в одну env-переменную Pod-а.

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:1.0
      env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log_level
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: database_host
              optional: true

Поля configMapKeyRef:

  • name — имя ConfigMap (должен быть в том же namespace что и Pod)
  • key — конкретный ключ внутри data
  • optional — если true и ConfigMap или ключ отсутствует, env переменная просто не будет установлена; если false (default), Pod не запустится (CreateContainerConfigError)
WARNING

Если ConfigMap или ключ отсутствует и optional не установлен (или false), Pod зависнет в статусе CreateContainerConfigError. kubelet будет retry создания контейнера, пока ConfigMap не появится. Это типичная отладка: смотрим kubectl describe pod, видим reason — ищем недостающий CM.


Способ 2: envFrom — все ключи как env vars

Когда нужно протащить весь ConfigMap как набор env-переменных, не выписывая каждый ключ вручную.

spec:
  containers:
    - name: app
      image: myapp:1.0
      envFrom:
        - configMapRef:
            name: app-config
        - configMapRef:
            name: feature-flags
          prefix: FEATURE_

envFrom принимает список источников. Каждый источник — это либо configMapRef (с именем CM), либо secretRef (для Secret, разбираем дальше). Можно указать prefix, который будет добавлен ко всем именам env переменных из этого источника.

Например, если в feature-flags есть ключ new_ui, в контейнере он станет переменной FEATURE_new_ui.

TIP

envFrom — удобно для apps, написанных по Twelve-Factor App, которые читают всю конфигурацию из env. Один envFrom — и приложение получает 20-30 переменных за один вызов. С env нужно было бы выписывать каждую вручную.

Ограничения envFrom:

  • Ключи ConfigMap должны быть валидными именами env-переменных C_IDENTIFIER (regex [a-zA-Z_][a-zA-Z0-9_]*). Невалидные ключи (с -, ., цифры в начале) пропускаются — будет warning event в Pod.
  • При коллизии имён (несколько источников дают одну переменную) — последний по порядку выигрывает.
  • env имеет приоритет над envFrom: если одна переменная задана в обоих, env побеждает.

Способ 3: volume mount — каждый ключ как файл

Mount ConfigMap как директорию: каждый ключ становится отдельным файлом в этой директории.

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:1.0
      volumeMounts:
        - name: config-volume
          mountPath: /etc/app/config
          readOnly: true
  volumes:
    - name: config-volume
      configMap:
        name: app-config
        defaultMode: 0440

Если в app-config есть ключи log_level, database_host, app.properties — в Pod появятся:

/etc/app/config/log_level         # содержит "debug"
/etc/app/config/database_host     # содержит "db.example.com"
/etc/app/config/app.properties    # содержит весь .properties файл

Поле defaultMode (octal) задаёт permissions для всех файлов в этой mount. По умолчанию 0644 (rw-r—r—). Часто ставят 0440 или 0400 для строгих конфигов.

Маппинг отдельных ключей через items

Если нужны только некоторые ключи или нужно переименовать их при mount:

volumes:
  - name: config-volume
    configMap:
      name: app-config
      items:
        - key: app.properties
          path: application.properties     # mount как другое имя
        - key: log_level
          path: subdir/level.txt           # с поддиректорией
          mode: 0400                       # individual permissions

С items mount-ятся только перечисленные ключи (остальные игнорируются). Без items — все ключи.

ConfigMap → Volume → файлы в Pod
ConfigMapИсточник. Хранится в etcd. data содержит ключи log_level, db_host, app.properties. Pod видит снапшот этого CM в момент старта.
kubelet читает CM
kubeletПри запуске Pod kubelet делает GET на API server, получает ConfigMap, и материализует его как файлы в emptyDir-подобной директории на node. Каждый ключ — отдельный файл. Файлы — symlinks на actual content, чтобы при обновлении CM атомарно подменять.
bind mount в контейнер
Контейнерkubelet через CRI делает bind-mount node-директории в контейнер по mountPath. Файлы доступны через filesystem syscalls. readOnly: true делает mount read-only.
auto-update (kubelet sync ~60s)
При update CMkubelet через watch получает уведомление, перечитывает CM, атомарно подменяет symlinks. Контейнер видит обновлённые файлы при следующем read. БЕЗ subPath. С subPath обновление не происходит — это критический момент.

Способ 4: subPath — один файл в существующую директорию

Иногда нужно положить один файл из ConfigMap в директорию, в которой уже есть другие файлы образа. Прямой mount директорией перетрёт всё (mount-семантика). Решение — subPath:

containers:
  - name: app
    image: myapp:1.0
    volumeMounts:
      - name: config-volume
        mountPath: /etc/myapp/config.yaml    # путь до файла, не директории
        subPath: config.yaml                  # ключ внутри volume
volumes:
  - name: config-volume
    configMap:
      name: app-config

Контейнер увидит файл /etc/myapp/config.yaml с содержимым ключа config.yaml из ConfigMap. Остальное содержимое /etc/myapp/ от image останется неприкосновенным.

Killer-момент: subPath отключает auto-update

Это самая частая ловушка в продакшене. По умолчанию kubelet обновляет файлы в volume mount при изменении ConfigMap — в течение ~1 минуты (определяется kubelet sync period, можно настроить). НО:

subPath отключает этот механизм. Файлы НЕ обновляются.

Почему — техническая причина: при subPath kubelet делает bind-mount конкретного файла, а не директории. Когда CM обновляется, kubelet атомарно подменяет содержимое в общей директории через symlinks. С subPath bind-mount происходит на конкретный inode — и подмена symlink не видна внутри контейнера, потому что bind-mount держит старый inode.

DANGER

Если используете subPath, после изменения ConfigMap обязательно делайте rollout: kubectl rollout restart deploy/myapp. Иначе обновления конфига в Pod не доедут, и вы будете долго гадать “почему изменил CM, а ничего не работает”. Это самый частый источник CKAD и продакшн-бэгов.

Альтернативы subPath:

  1. Mount директорию без subPath — все остальные файлы из CM тоже окажутся в mountPath (если они есть). Если в CM один ключ — ок, mount-уем /etc/myapp/ и получаем один файл.
  2. Mount во временную директорию + init-container — init копирует нужный файл из временной директории в финальную, потом контейнер запускается. Это даёт snapshot-семантику.
  3. mergeFiles через initContainer, который комбинирует базовый файл образа с overrides из CM.

Без auto-update: env vars никогда не обновляются

Env vars устанавливаются в момент старта контейнера и фиксируются. Если ConfigMap обновлён — env vars в работающем контейнере не изменятся, как бы вы ни ждали.

Чтобы env var обновилась — нужно пересоздать Pod (rollout restart или удаление). Это распространяется и на env.valueFrom.configMapKeyRef, и на envFrom.configMapRef.

Это архитектурное ограничение — env vars Linux процесса фиксируются в execve syscall, изменить их извне нельзя.

NOTE

Резюме обновлений:

  • env / envFrom — НЕ обновляются. Нужен rollout.
  • volume mount БЕЗ subPath — обновляются автоматически через ~1 минуту.
  • volume mount С subPath — НЕ обновляются. Нужен rollout.

Полный пример Pod с разными способами

apiVersion: v1
kind: Pod
metadata:
  name: full-example
spec:
  containers:
    - name: app
      image: myapp:1.0
      env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log_level
      envFrom:
        - configMapRef:
            name: feature-flags
          prefix: FEATURE_
      volumeMounts:
        - name: app-cfg
          mountPath: /etc/app/config
          readOnly: true
        - name: nginx-cfg
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
          readOnly: true
  volumes:
    - name: app-cfg
      configMap:
        name: app-config
        defaultMode: 0440
    - name: nginx-cfg
      configMap:
        name: nginx-config
        items:
          - key: nginx.conf
            path: nginx.conf

Здесь:

  1. Одна env-переменная LOG_LEVEL из ключа log_level ConfigMap app-config
  2. Все ключи feature-flags ConfigMap как env vars с префиксом FEATURE_
  3. Весь app-config смонтирован как директория /etc/app/config/ с auto-update
  4. Один ключ nginx.conf из nginx-config смонтирован как файл /etc/nginx/nginx.conf через subPath — без auto-update

CKAD common task

Create a ConfigMap 'app-config' with key 'log_level=debug'.
Mount it to /etc/config in Pod 'web' using nginx:1.27 image.
Verify the file /etc/config/log_level exists and contains "debug".

Решение в один проход:

# Создать CM
kubectl create configmap app-config --from-literal=log_level=debug

# Сгенерировать Pod YAML
kubectl run web --image=nginx:1.27 --dry-run=client -o yaml > web.yaml

# Отредактировать YAML (добавить volume и mount)
# ... edit web.yaml ...

# Применить
kubectl apply -f web.yaml

# Проверить
kubectl exec web -- cat /etc/config/log_level
# debug

Проверка знанийKnowledge check
DevOps-инженер настроил Pod так: nginx.conf смонтирован через subPath в /etc/nginx/nginx.conf. Команда обновила ConfigMap, поменяв nginx.conf на новую версию. Через 5 минут проверка nginx -T внутри Pod показала старую конфигурацию. Почему, и как правильно поступить?
ОтветAnswer
subPath отключает механизм auto-update от kubelet. По умолчанию kubelet через ~1 минуту обновляет файлы в volume mount при изменении ConfigMap — но это работает только без subPath. Технически: при subPath kubelet делает bind-mount конкретного inode файла, а не директории. Когда CM обновляется, kubelet атомарно подменяет файлы в общей директории через symlinks (поэтому update атомарный). Но bind-mount с subPath держит ссылку на конкретный inode — symlink swap его не затронет. Результат: контейнер видит старый файл навсегда. Правильно поступить — после изменения CM сделать kubectl rollout restart deploy/NAME. Это пересоздаст Pods, и они увидят новый CM при старте. Альтернатива — переделать на mount без subPath (например, mount директории /etc/nginx/conf.d/, где будет только наш файл). В производстве subPath популярен, но всегда требует rollout после change CM — это часто забывают.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Pod mounted ConfigMap через subPath: mountPath /etc/nginx/nginx.conf, subPath nginx.conf. После kubectl apply нового ConfigMap проверка показала старое значение в Pod через 10 минут. Почему?

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

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

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

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