Модуль 6 / 19
ConfigMap, Secret, Downward API и доставка конфигурации
Содержание
1. Цели и результат модуля
После модуля вы сможете отделять конфигурацию от образа, выбирать ConfigMap или Secret, доставлять значения через env и volumes, использовать Downward API и диагностировать ошибку отсутствующего объекта или ключа.
- ConfigMap хранит несекретную конфигурацию
- Secret ограничивает назначение и доступ, но base64 не является шифрованием
- env фиксируется при запуске контейнера
- projected volume обычно обновляется с задержкой
- subPath mount не получает автоматические обновления
2. Теория: ConfigMap и границы конфигурации
ConfigMap содержит пары data string/string и binaryData bytes в base64. Он подходит для флагов, файлов конфигурации и небольших справочников, но не для секретов и не для больших артефактов. Общий размер объекта ограничен API Server примерно одним MiB.
Конфигурация — часть release contract. Приложение должно валидировать обязательные поля при старте, сообщать понятную ошибку и документировать precedence между defaults, файлом, env и аргументами.
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 не следует создавать вручную для обычных краткоживущих токенов
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.
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.
6. Теория: immutable configuration и rollout
ConfigMap и Secret можно отметить immutable: true. Это защищает от случайной mutation и снижает наблюдение kubelet, но значение и флаг immutable уже нельзя изменить — нужен объект с новым именем и обновление consumers.
Versioned names делают rollback явным и позволяют старым и новым Pods одновременно читать совместимую конфигурацию. Mutable object удобнее в лаборатории, но в production требует определённой стратегии reload и аудита.
7. Методические указания
- Не помещайте секреты в manifest, Git, annotation или ConfigMap
- Используйте server dry-run до создания consumers
- Делайте обязательные keys явными и проверяйте их startup validation
- Выбирайте env только для значений, которые меняются через restart
- Не ожидайте мгновенного volume refresh
- Избегайте subPath для динамической конфигурации
- Задавайте defaultMode и запускайте process без root, где возможно
- Для production связывайте изменение конфигурации с наблюдаемым rollout
8. Лабораторная работа: ConfigMap и Secret
Создайте несекретную конфигурацию и Secret через stringData. Значения учебные; настоящие credentials нельзя помещать в репозиторий или командную строку.
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.yaml9. Лабораторная работа: env, volumes и Downward API
Pod получает те же ConfigMap данные одновременно через env, полный volume и subPath. Downward API публикует identity и metadata самого Pod.
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=120skubectl -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. Лабораторная работа: поведение при обновлении
Измените ConfigMap и дождитесь фактического refresh полного volume. Результат должен показать volume=debug, но env=info и subPath=training: два последних значения требуют пересоздания контейнера/Pod.
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. Диагностика: отсутствующий Secret key
Pod с обязательной ссылкой на отсутствующий key остаётся в CreateContainerConfigError. Container logs ещё нет: причину показывают Waiting.message и Events. Добавление key позволяет kubelet продолжить запуск без пересоздания Pod.
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. Контроль, критерии освоения и cleanup
- Почему Secret data не зашифрованы base64?
- Когда использовать env, а когда volume?
- Обновится ли env работающего контейнера?
- Почему subPath сохраняет старое значение?
- Как конфигурация создаёт Deployment revision?
- Какие поля предоставляет Downward API?
- Что меняет immutable: true?
- Почему у CreateContainerConfigError обычно нет app logs?
- Какие слои защищают Secret?
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 без случайного удаления ресурсов.