← К программе курса

Модуль 6 / 19

ConfigMap, Secret, Downward API и доставка конфигурации

KUBERNETES

Содержание
01

1. Цели и результат модуля

После модуля вы сможете отделять конфигурацию от образа, выбирать ConfigMap или Secret, доставлять значения через env и volumes, использовать Downward API и диагностировать ошибку отсутствующего объекта или ключа.

  • ConfigMap хранит несекретную конфигурацию
  • Secret ограничивает назначение и доступ, но base64 не является шифрованием
  • env фиксируется при запуске контейнера
  • projected volume обычно обновляется с задержкой
  • subPath mount не получает автоматические обновления
02

2. Теория: ConfigMap и границы конфигурации

ConfigMap содержит пары data string/string и binaryData bytes в base64. Он подходит для флагов, файлов конфигурации и небольших справочников, но не для секретов и не для больших артефактов. Общий размер объекта ограничен API Server примерно одним MiB.

Конфигурация — часть release contract. Приложение должно валидировать обязательные поля при старте, сообщать понятную ошибку и документировать precedence между defaults, файлом, env и аргументами.

03

3. Теория: Secret без ложного чувства безопасности

Secret имеет тип и поля data или stringData. API преобразует stringData в base64-представление data. Любой субъект с get Secret может декодировать содержимое, поэтому base64 — сериализация, не защита.

Защита требует минимального RBAC, encryption at rest для etcd, безопасного backup, ограничения доступа к Node и исключения secret из Git, logs, shell history и diagnostic bundles. Secret, переданный через env, доступен процессу и потенциально дампам; volume позволяет точнее ограничить файлы и permissions.

  • Opaque — произвольные пары
  • kubernetes.io/tls — tls.crt и tls.key
  • kubernetes.io/dockerconfigjson — registry credentials
  • ServiceAccount token Secrets не следует создавать вручную для обычных краткоживущих токенов
04

4. Теория: env, envFrom и volumes

env задаёт отдельные значения и явные имена, envFrom импортирует все keys и требует дисциплины именования. Значения читаются при создании контейнера и не изменяются в уже работающем process environment.

ConfigMap/Secret volume проецирует keys как файлы. Kubelet периодически обновляет projection атомарной сменой ссылок, поэтому обновление не мгновенное, а приложение должно уметь перечитать файл. Mount через subPath фиксирует конкретный файл и автоматическое обновление не получает.

Обновление ConfigMap само по себе не создаёт Deployment revision. Распространённый delivery pattern — versioned immutable ConfigMap либо checksum annotation в Pod template, вызывающая контролируемый rollout.

05

5. Теория: Downward API

Downward API передаёт контейнеру сведения о собственном Pod без обращения к Kubernetes API: name, namespace, uid, labels, annotations, nodeName и отдельные resource requests/limits. Доступны fieldRef, resourceFieldRef и downwardAPI volume.

Env-поля остаются снимком на старте. Labels и annotations в downwardAPI volume могут обновляться. Не используйте Downward API как замену service discovery или как способ выдать приложению широкие credentials API.

06

6. Теория: immutable configuration и rollout

ConfigMap и Secret можно отметить immutable: true. Это защищает от случайной mutation и снижает наблюдение kubelet, но значение и флаг immutable уже нельзя изменить — нужен объект с новым именем и обновление consumers.

Versioned names делают rollback явным и позволяют старым и новым Pods одновременно читать совместимую конфигурацию. Mutable object удобнее в лаборатории, но в production требует определённой стратегии reload и аудита.

07

7. Методические указания

  • Не помещайте секреты в manifest, Git, annotation или ConfigMap
  • Используйте server dry-run до создания consumers
  • Делайте обязательные keys явными и проверяйте их startup validation
  • Выбирайте env только для значений, которые меняются через restart
  • Не ожидайте мгновенного volume refresh
  • Избегайте subPath для динамической конфигурации
  • Задавайте defaultMode и запускайте process без root, где возможно
  • Для production связывайте изменение конфигурации с наблюдаемым rollout
08

8. Лабораторная работа: ConfigMap и Secret

Создайте несекретную конфигурацию и Secret через stringData. Значения учебные; настоящие credentials нельзя помещать в репозиторий или командную строку.

terminal
mkdir -p /tmp/k8s-m06
cat > /tmp/k8s-m06/config.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: k8s-m06
data:
  LOG_LEVEL: info
  APP_MODE: training
  APP_CONFIG: |
    server:
      port: 8080
    features:
      diagnostics: true
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
  namespace: k8s-m06
type: Opaque
stringData:
  API_USER: course-user
  API_PASSWORD: correct-horse-battery-staple
EOF
kubectl wait --for=condition=Ready node --all --timeout=120s
kubectl create namespace k8s-m06
kubectl apply --dry-run=server -f /tmp/k8s-m06/config.yaml
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m06/config.yaml
09

9. Лабораторная работа: env, volumes и Downward API

Pod получает те же ConfigMap данные одновременно через env, полный volume и subPath. Downward API публикует identity и metadata самого Pod.

terminal
cat > /tmp/k8s-m06/pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: inspector
  namespace: k8s-m06
  labels:
    app.kubernetes.io/name: config-lab
    course.goretskiy.pro/environment: training
  annotations:
    course.goretskiy.pro/owner: platform-team
spec:
  containers:
    - name: inspector
      image: busybox:1.37
      command: ["sh", "-c", "sleep 3600"]
      envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secret
      env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
      volumeMounts:
        - name: config
          mountPath: /etc/app-config
          readOnly: true
        - name: config
          mountPath: /snapshot-app-mode
          subPath: APP_MODE
          readOnly: true
        - name: secret
          mountPath: /etc/app-secret
          readOnly: true
        - name: podinfo
          mountPath: /etc/podinfo
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: app-config
        items:
          - {key: LOG_LEVEL, path: LOG_LEVEL}
          - {key: APP_MODE, path: APP_MODE}
          - {key: APP_CONFIG, path: app.yaml}
    - name: secret
      secret:
        secretName: app-secret
        defaultMode: 0400
    - name: podinfo
      downwardAPI:
        items:
          - path: labels
            fieldRef:
              fieldPath: metadata.labels
          - path: annotations
            fieldRef:
              fieldPath: metadata.annotations
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m06/pod.yaml
kubectl apply -f /tmp/k8s-m06/pod.yaml
kubectl -n k8s-m06 wait --for=condition=Ready pod/inspector --timeout=120s
terminal
kubectl -n k8s-m06 exec inspector -- sh -c 'printf "env: %s %s %s %s\n" "$LOG_LEVEL" "$APP_MODE" "$POD_NAMESPACE" "$NODE_NAME"'
kubectl -n k8s-m06 exec inspector -- cat /etc/app-config/app.yaml
kubectl -n k8s-m06 exec inspector -- cat /etc/podinfo/labels
kubectl -n k8s-m06 exec inspector -- stat -c '%a %n' /etc/app-secret/API_PASSWORD
kubectl -n k8s-m06 get secret app-secret -o jsonpath='{.data.API_USER}' | base64 -d; printf '\n'
kubectl -n k8s-m06 get secret app-secret -o jsonpath='{.data.API_PASSWORD}' | base64 -d; printf '\n'
10

10. Лабораторная работа: поведение при обновлении

Измените ConfigMap и дождитесь фактического refresh полного volume. Результат должен показать volume=debug, но env=info и subPath=training: два последних значения требуют пересоздания контейнера/Pod.

terminal
kubectl -n k8s-m06 patch configmap app-config --type=merge \
  -p '{"data":{"LOG_LEVEL":"debug","APP_MODE":"updated"}}'

for attempt in $(seq 1 60); do
  value=$(kubectl -n k8s-m06 exec inspector -- cat /etc/app-config/LOG_LEVEL)
  [ "$value" = debug ] && break
  sleep 2
done
kubectl -n k8s-m06 exec inspector -- sh -c 'printf "volume=%s env=%s subPath=%s\n" "$(cat /etc/app-config/LOG_LEVEL)" "$LOG_LEVEL" "$(cat /snapshot-app-mode)"'
11

11. Диагностика: отсутствующий Secret key

Pod с обязательной ссылкой на отсутствующий key остаётся в CreateContainerConfigError. Container logs ещё нет: причину показывают Waiting.message и Events. Добавление key позволяет kubelet продолжить запуск без пересоздания Pod.

terminal
cat > /tmp/k8s-m06/broken.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: broken-config
  namespace: k8s-m06
spec:
  containers:
    - name: app
      image: busybox:1.37
      command: ["sh", "-c", "sleep 3600"]
      env:
        - name: REQUIRED_VALUE
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: MISSING_VALUE
EOF
kubectl apply -f /tmp/k8s-m06/broken.yaml
kubectl -n k8s-m06 get pod broken-config
kubectl -n k8s-m06 describe pod broken-config
kubectl -n k8s-m06 get events --field-selector involvedObject.name=broken-config --sort-by=.metadata.creationTimestamp

kubectl -n k8s-m06 patch secret app-secret --type=merge \
  -p '{"stringData":{"MISSING_VALUE":"recovered"}}'
kubectl -n k8s-m06 wait --for=condition=Ready pod/broken-config --timeout=120s
kubectl -n k8s-m06 exec broken-config -- printenv REQUIRED_VALUE

Порядок: имя и namespace источника → существование key → optional flag → Events/Waiting.message → RBAC и admission → способ доставки → rollout/reload потребителя.

12

12. Контроль, критерии освоения и cleanup

  • Почему Secret data не зашифрованы base64?
  • Когда использовать env, а когда volume?
  • Обновится ли env работающего контейнера?
  • Почему subPath сохраняет старое значение?
  • Как конфигурация создаёт Deployment revision?
  • Какие поля предоставляет Downward API?
  • Что меняет immutable: true?
  • Почему у CreateContainerConfigError обычно нет app logs?
  • Какие слои защищают Secret?
terminal
kubectl delete namespace k8s-m06 --wait=true
rm -rf /tmp/k8s-m06
kubectl get namespace k8s-m06 2>&1 || true

Модуль освоен, если вы прогнозируете результат обновления для env/full volume/subPath, не раскрываете Secret и восстанавливаете Pod по evidence без случайного удаления ресурсов.