ConfigMap: основы и создание
Любое приложение нужно как-то конфигурировать: URL базы, log level, feature flags, имена кластеров, пути. Запекать всё это в Docker image — анти-паттерн: один и тот же image должен работать в dev/staging/prod без пересборки. В Twelve-Factor App конфигурация хранится в окружении. В Kubernetes для этого есть ConfigMap — API-объект, который хранит ключи и значения и подключается к Pod через env-переменные или volume-файлы.
ConfigMap — для non-sensitive данных. Для паролей, ключей, токенов — Secret (отдельный объект, разбираем в следующих уроках). Снаружи они похожи, но Secret имеет дополнительные механизмы защиты (encryption at rest, более строгий RBAC, отдельные типы).
.env, env_file и приоритеты переменных в Compose
Структура ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
log_level: "debug"
database_host: "db.production.svc.cluster.local"
feature_flags: "new_ui=true,beta_search=false"
app.properties: |
server.port=8080
server.compression.enabled=true
spring.datasource.url=jdbc:postgresql://db:5432/app
binaryData:
cert.pfx: bWFnaWMgbnVtYmVycyBoZXJlIGxvbA==
ConfigMap живёт в группе core API (apiVersion: v1), не в apps/v1 как Deployment. Это значит — namespaced (привязан к namespace), доступен через kubectl get cm (короткое имя cm).
Два поля для данных:
data— словарь string → string. Ключи — DNS subdomain (regex[a-zA-Z0-9._-]+), значения — UTF-8 строки. Если значение содержит multiline-контент или binary — используется blockstyle|илиbinaryData.binaryData— словарь string → base64-encoded bytes. Для бинарных данных (certificates, jks-keystores, gzip-архивы). При mount как volume — kubelet декодирует base64 и кладёт raw bytes в файл.
Один ключ не может одновременно быть в data и binaryData — API server отклонит такой ConfigMap.
ConfigMap не имеет spec секции (в отличие от Deployment, Pod, и почти всего остального в Kubernetes). Это reflection факта, что ConfigMap — это не желаемое состояние с reconcile-loop, а просто store-объект. Reconcile делает потребитель (Pod через kubelet), не controller.
Лимит размера: 1 MiB
ConfigMap хранится в etcd как одиночный объект. У etcd жёсткий лимит — --max-request-bytes (default 1.5 MiB на запрос), плюс лимит на размер value в kv-store. Поэтому Kubernetes ограничивает размер ConfigMap (data + binaryData + metadata) в 1 MiB.
API server отклонит ConfigMap больше лимита:
The ConfigMap "huge-config" is invalid: []: Too long: must have at most 1048576 bytes
Что делать с большими конфигами:
- Разбить на несколько ConfigMap-ов и mount как разные volumes
- Положить в Secret — у Secret такой же лимит, не поможет
- Использовать volume другого типа: PVC с pre-baked файлом, init-container, который скачивает конфиг из S3
- External config service: Spring Cloud Config, Consul KV, etcd как app-storage — Pod читает напрямую при старте
В практике лимит 1 MiB не достигается случайно — он чувствуется только когда кто-то пытается хранить в ConfigMap бинарь приложения, ML-модель или огромный JSON. Для нормальных конфигов хватает с большим запасом. Но в CKAD ответ “разбить на несколько CM или использовать другой механизм” — обязательное знание.
Imperative create: kubectl create configmap
CKAD-эксперты пишут ConfigMap через kubectl create, не через YAML — это быстрее. На экзамене это критично.
# Из inline key=value пар
kubectl create configmap app-config \
--from-literal=log_level=debug \
--from-literal=database_host=db.example.com
# Из файла: ключ = basename файла, значение = содержимое
kubectl create configmap nginx-config --from-file=nginx.conf
# Создаст ключ "nginx.conf"
# Из файла с переименованием ключа
kubectl create configmap nginx-config --from-file=server.conf=nginx.conf
# Создаст ключ "server.conf" со значением из nginx.conf
# Из целого каталога: каждый файл = отдельный ключ
kubectl create configmap nginx-bundle --from-file=./conf.d/
# Создаст столько ключей, сколько файлов в conf.d/
# Из env-file (формат KEY=VALUE на строку, как .env)
kubectl create configmap app-env --from-env-file=app.env
# Каждая строка KEY=VALUE станет отдельным ключом в data
# Комбинируем источники
kubectl create configmap mixed \
--from-literal=ENV=production \
--from-file=schema.sql \
--from-env-file=overrides.env
Все эти команды поддерживают --dry-run=client -o yaml — генерация YAML без отправки в API server:
kubectl create configmap app-config \
--from-literal=log_level=debug \
--dry-run=client -o yaml > app-config.yaml
Это — основной workflow на CKAD: сгенерировать YAML, отредактировать, применить через kubectl apply -f.
Declarative YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
log_level: "debug"
app.properties: |
server.port=8080
server.tomcat.max-threads=200
logging.level.root=INFO
redis_url: "redis://cache:6379/0"
Применяется через kubectl apply -f app-config.yaml. При повторном apply Kubernetes делает merge-patch — обновляет только изменённые ключи. Удалённые из YAML ключи не удаляются автоматически из ConfigMap, если использован apply — потому что apply делает strategic merge patch без удалений. Для синхронного state нужен kubectl replace или --prune флаг.
Multiline-значения через YAML | (literal block scalar) — самый чистый способ хранить целые конфиги (nginx.conf, application.properties, JSON-документы). Сохраняется и индентация, и переводы строк. Альтернатива > (folded block scalar) превращает переводы строк в пробелы — почти никогда не нужно для конфигов.
Immutable ConfigMap (v1.21 GA)
С Kubernetes v1.21 в API ConfigMap появилось поле immutable: true:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
data:
log_level: "info"
immutable: true
Что это даёт:
- API server отклоняет любые update на такой ConfigMap. Только delete + create нового объекта.
- kubelet не watch-ит изменения — а это означает существенно меньшую нагрузку на API server в кластерах с тысячами Pod-ов и сотнями ConfigMap-ов.
Зачем нужно поле immutable. По умолчанию каждый kubelet, который смонтировал ConfigMap в Pod-volume, подписан на watch — чтобы получать уведомление о изменениях и обновлять файлы в volume. Если в кластере 5000 Pod-ов смонтировали один CM, это 5000 активных watch-сессий. Для статичных конфигов это лишняя работа.
# Создать immutable ConfigMap
kubectl create configmap app-config-v1 --from-literal=log_level=info
kubectl patch configmap app-config-v1 -p '{"immutable":true}'
# Попытка update — ошибка
kubectl edit configmap app-config-v1
# error: ConfigMap "app-config-v1" is invalid: data: Forbidden: field is immutable when `immutable` is set
Best practice для production: версионируйте имена ConfigMap (app-config-v1, app-config-v2, …) и ставьте immutable: true. Это даёт два бонуса:
- kubelet работает быстрее (нет watch-нагрузки)
- Понятная история через имена объектов: какая версия конфига в какой версии Deployment
Обновление: создать новый CM (app-config-v2), обновить ссылку в Deployment.template (что триггерит rolling update), потом удалить старый.
immutable: true можно установить, но НЕЛЬЗЯ снять обратно. Если поставили — это навсегда для этого объекта. Хочется снять — удалить и создать заново.
Проверка и inspection
# Список ConfigMap-ов в namespace
kubectl get cm
# Детально
kubectl describe cm app-config
# Получить значение конкретного ключа
kubectl get cm app-config -o jsonpath='{.data.log_level}'
# Весь ConfigMap в YAML
kubectl get cm app-config -o yaml
# Только data часть
kubectl get cm app-config -o jsonpath='{.data}' | jq
kubectl describe cm показывает имя, метаданные, и все ключи с их значениями (с обрезкой длинных).