← Весь справочник

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)ключ → значение в base64echo -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, passwordOpaque
immutabletrue — значения больше нельзя менять, только пересоздать объект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постоянный диск по заявке PVCclaimName, 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.accessModesReadWriteOnce (RWO) — запись с одного узла; ReadOnlyMany (ROX); ReadWriteMany (RWX) — запись с многих узлов; ReadWriteOncePod — только один подобязательно на практике; RWX умеют не все хранилища
spec.resources.requests.storageразмер: 1Gi, 500Mi
spec.storageClassNameкласс хранилища (standard, gp3, …)не указан — класс по умолчанию; "" — без динамического выделения
spec.volumeModeFilesystem / BlockFilesystem

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. Значение открытым текстом в data API-сервер отвергнет (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