Как 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— конкретный ключ внутриdataoptional— еслиtrueи ConfigMap или ключ отсутствует, env переменная просто не будет установлена; еслиfalse(default), Pod не запустится (CreateContainerConfigError)
Если 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.
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 — все ключи.
Способ 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.
Если используете subPath, после изменения ConfigMap обязательно делайте rollout: kubectl rollout restart deploy/myapp. Иначе обновления конфига в Pod не доедут, и вы будете долго гадать “почему изменил CM, а ничего не работает”. Это самый частый источник CKAD и продакшн-бэгов.
Альтернативы subPath:
- Mount директорию без subPath — все остальные файлы из CM тоже окажутся в
mountPath(если они есть). Если в CM один ключ — ок, mount-уем/etc/myapp/и получаем один файл. - Mount во временную директорию + init-container — init копирует нужный файл из временной директории в финальную, потом контейнер запускается. Это даёт snapshot-семантику.
- 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, изменить их извне нельзя.
Резюме обновлений:
- 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
Здесь:
- Одна env-переменная
LOG_LEVELиз ключаlog_levelConfigMapapp-config - Все ключи
feature-flagsConfigMap как env vars с префиксомFEATURE_ - Весь
app-configсмонтирован как директория/etc/app/config/с auto-update - Один ключ
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