Kubernetes: ConfigMap, Secret, переменные и тома
Настройки и данные пода — ConfigMap и Secret, env и envFrom, тома configMap/secret/emptyDir/PVC/hostPath, subPath
Образ контейнера один на все окружения, а настройки разные. Kubernetes хранит их отдельно от образа и передаёт в контейнер двумя путями — переменными окружения или файлами в смонтированном томе.
| Объект | Что хранит |
|---|---|
| ConfigMap | обычные настройки: ключ → строка (или содержимое целого файла) |
| Secret | пароли, токены, сертификаты; то же «ключ → значение», но значения в base64 и доступ обычно ограничен RBAC |
| PersistentVolumeClaim (PVC) | заявка на постоянный диск, который переживёт перезапуск пода |
Синтаксис
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
MODE: production
app.conf: |
listen 8080
workers 4
---
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo $MODE; cat /etc/app/app.conf; sleep 3600"]
env:
- name: MODE
valueFrom:
configMapKeyRef: {name: app-config, key: MODE}
volumeMounts:
- name: config
mountPath: /etc/app
volumes:
- name: config
configMap:
name: app-config
ConfigMap и Secret
| Поле | Что значит | Заметки |
|---|---|---|
data (ConfigMap) | ключ → строка | значения — только строки: "3", "true" в кавычках |
binaryData (ConfigMap) | ключ → двоичные данные в base64 | |
data (Secret) | ключ → значение в base64 | echo -n 'pass' | base64 |
stringData (Secret) | ключ → значение открытым текстом | при сохранении кодируется в data; ключ из stringData перекрывает такой же в data |
type (Secret) | Opaque — произвольные данные; kubernetes.io/tls — ключи tls.crt, tls.key; kubernetes.io/dockerconfigjson — доступ к реестру; kubernetes.io/basic-auth — username, password | Opaque |
immutable | true — значения больше нельзя менять, только пересоздать объект | false |
Ключи — латиница, цифры, -, _, .. Размер одного объекта — до 1 МиБ.
Переменные окружения
Поля элемента containers[]:
| Поле | Что значит |
|---|---|
env[].name + value | переменная с явным значением (строка) |
env[].valueFrom.configMapKeyRef | {name: конфигмап, key: ключ, optional: true} — значение одного ключа ConfigMap |
env[].valueFrom.secretKeyRef | {name: секрет, key: ключ} — значение из Secret (уже раскодированное) |
env[].valueFrom.fieldRef.fieldPath | данные о самом поде: metadata.name, metadata.namespace, metadata.labels['app'], status.podIP, spec.nodeName, spec.serviceAccountName |
env[].valueFrom.resourceFieldRef | {containerName, resource: limits.memory} — свои ресурсы |
envFrom[].configMapRef.name | все ключи ConfigMap как переменные |
envFrom[].secretRef.name | все ключи Secret как переменные |
envFrom[].prefix | приставка к именам: prefix: DB_ даст DB_HOST |
В value, command и args можно ссылаться на уже объявленные выше переменные: $(MODE). Если имя переменной есть и в env, и в envFrom, побеждает env.
Тома
Тома объявляются на уровне пода в spec.volumes[], а монтируются в каждый контейнер через volumeMounts[], по имени.
Тип в volumes[] | Что это | Основные поля |
|---|---|---|
configMap | ключи ConfigMap как файлы | name, items: [{key, path}] — выбрать ключи и имена файлов, defaultMode — права файлов (по умолчанию 0644), optional |
secret | ключи Secret как файлы (раскодированные) | secretName (не name!), items, defaultMode |
emptyDir | пустой каталог на время жизни пода; общий для его контейнеров | medium: Memory — в RAM (tmpfs), sizeLimit: 100Mi |
persistentVolumeClaim | постоянный диск по заявке PVC | claimName, readOnly |
hostPath | каталог или файл узла | path, type: Directory | DirectoryOrCreate | File | FileOrCreate | Socket |
projected | несколько источников (configMap, secret, downwardAPI, токен) в одном каталоге | sources: […] |
downwardAPI | метки, аннотации, имя пода как файлы | items: [{path, fieldRef}] |
Поля volumeMounts[]:
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
name | имя тома из volumes | обязательно |
mountPath | куда смонтировать в контейнере | обязательно; скрывает то, что было в этом каталоге образа |
subPath | смонтировать только один файл/подкаталог тома | содержимое каталога образа не скрывается, но файл не обновляется при изменении ConfigMap/Secret |
readOnly | только чтение | false (configMap и secret и так read-only) |
PersistentVolumeClaim
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
spec.accessModes | ReadWriteOnce (RWO) — запись с одного узла; ReadOnlyMany (ROX); ReadWriteMany (RWX) — запись с многих узлов; ReadWriteOncePod — только один под | обязательно на практике; RWX умеют не все хранилища |
spec.resources.requests.storage | размер: 1Gi, 500Mi | |
spec.storageClassName | класс хранилища (standard, gp3, …) | не указан — класс по умолчанию; "" — без динамического выделения |
spec.volumeMode | Filesystem / Block | Filesystem |
PVC — заявка; сам диск (PersistentVolume) создаёт по ней провижионер класса хранилища. Для StatefulSet заявки задают через volumeClaimTemplates (см. k8s-workloads).
Примеры
# ConfigMap: ключи из литералов и из файла (ключ = имя файла или явное)
printf 'listen 8080\nworkers 4\n' > app.conf
kubectl create configmap app-config --from-literal=MODE=production \
--from-file=app.conf --dry-run=client -o yaml > cm.yaml
# ConfigMap из env-файла: строки KEY=VALUE становятся ключами
printf 'MODE=production\nLOG_LEVEL=info\n' > app.env
kubectl create configmap app-env --from-env-file=app.env --dry-run=client -o yaml
# Secret: значения kubectl сам кодирует в base64
kubectl create secret generic db-cred --from-literal=username=app \
--from-literal=password='s3cr3t!' --dry-run=client -o yaml > secret.yaml
# прочитать значение обратно
yq '.data.password' secret.yaml | base64 -d; echo
# закодировать руками — обязательно с -n, иначе в значение попадёт \n
echo -n 's3cr3t!' | base64
# TLS-секрет для Ingress из пары сертификат/ключ
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj '/CN=example.com' \
-keyout tls.key -out tls.crt 2>/dev/null
kubectl create secret tls site-tls --cert=tls.crt --key=tls.key --dry-run=client -o yaml > tls.yaml
# подключить все ключи ConfigMap к Deployment как переменные (через yq:
# `kubectl set env --from=configmap/…` требует кластер)
kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml \
| yq '.spec.template.spec.containers[0].envFrom = [{"configMapRef": {"name": "app-config"}}]' > deploy.yaml
kubeconform -strict -summary cm.yaml secret.yaml tls.yaml deploy.yaml
Secret с stringData и его использование: одна переменная, все ключи с приставкой и файлы с правами 0400:
apiVersion: v1
kind: Secret
metadata:
name: db-cred
type: Opaque
stringData: # открытым текстом; в data окажется base64
username: app
password: "s3cr3t!"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
selector:
matchLabels: {app: api}
template:
metadata:
labels: {app: api}
spec:
containers:
- name: api
image: registry.example.com/api:2.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: {name: db-cred, key: password}
- name: POD_NAME
valueFrom:
fieldRef: {fieldPath: metadata.name}
- name: DSN
value: "postgres://app:$(DB_PASSWORD)@db:5432/app"
envFrom:
- secretRef: {name: db-cred}
prefix: DB_ # DB_username, DB_password
volumeMounts:
- name: creds
mountPath: /run/secrets/db
readOnly: true
volumes:
- name: creds
secret:
secretName: db-cred
defaultMode: 0400
Один файл из ConfigMap поверх каталога образа, общий emptyDir и постоянный диск:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: uploads
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d/site.conf # только этот файл
subPath: site.conf
- name: cache
mountPath: /var/cache/nginx
- name: uploads
mountPath: /srv/uploads
volumes:
- name: config
configMap:
name: nginx-config
items:
- {key: site.conf, path: site.conf}
- name: cache
emptyDir:
sizeLimit: 200Mi
- name: uploads
persistentVolumeClaim:
claimName: uploads
Подводные камни
- Secret не шифрует. base64 — это кодировка,
base64 -dчитает её без ключа. Защищает доступ (RBAC), а не формат. Не коммить Secret-манифесты с настоящими значениями в git. echo 'pass' | base64без-nкодирует и перевод строки: в пароле окажется лишний\n, и вход «необъяснимо» не работает.- В
dataу Secret — только base64. Значение открытым текстом вdataAPI-сервер отвергнет (illegal base64 data), аkubeconformпропустит. Для открытого текста естьstringData. - Права файлов
defaultMode— восьмеричные.kubectlчитает0400как восьмеричное число (256), а инструменты на YAML 1.2 — как десятичное 400:yq -o jsonпревратит его в400, то есть в другие права. В JSON-манифестах пиши десятичное значение:256для0400,420для0644. - Числа и булевы в
dataу ConfigMap нужно брать в кавычки:PORT: "8080",DEBUG: "true". Без кавычекkubeconformвернётexpected string, but got number. - Переменные окружения не обновляются. Изменил ConfigMap — контейнер видит старые значения до перезапуска пода. Файлы в томе обновляются сами (с задержкой до минуты-двух), но не при
subPath. mountPathна существующий каталог скрывает всё, что там было в образе. Смонтировал ConfigMap в/etc/nginx— пропалnginx.confиз образа. Для одного файла бериsubPath.secretName, а неname. У томаconfigMapполе называетсяname, у томаsecret—secretName. Перепутал — схема ответитadditionalProperties 'name' not allowed.--from-env-fileнельзя совмещать с--from-literalи--from-fileв одной командеkubectl create configmap.- ConfigMap или Secret ещё не создан — под не стартует (
CreateContainerConfigError), пока не появится объект. Если объект необязателен, ставьoptional: true. hostPathпривязывает под к узлу и даёт доступ к его файловой системе. Под на другом узле увидит другие данные; в общих кластерахhostPathчасто запрещён политиками.emptyDirживёт столько же, сколько под. Перезапуск контейнера данные сохраняет, а пересоздание пода (выкатка, переезд на другой узел) — стирает.- PVC
ReadWriteOnceи несколько реплик Deployment — второй под на другом узле не сможет подключить диск и зависнет вContainerCreating. Свой диск каждой реплике даёт StatefulSet.
См. также
kubectl · kubeconform · yq · base64 · openssl · env · k8s-workloads · k8s-networking · k8s-rbac