Модуль 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. Він підходить для flags, файлів конфігурації та невеликих довідників, але не для секретів і великих артефактів. Загальний розмір об’єкта обмежений 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 доступний процесу й потенційно dumps; 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-поля залишаються snapshot на старті. Labels та annotations у downwardAPI volume можуть оновлюватися. Не використовуйте Downward API як заміну service discovery або спосіб видати застосунку широкі API credentials.
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 без випадкового видалення ресурсів.