01. Архитектура и модель Kubernetes API
Незнакомый термин? Откройте словарь Kubernetes и терминов курса.
Назначение, предварительные знания и результаты обучения
Нужна глава 00 и работающий kind-kube-course. Здесь Kubernetes перестаёт
быть набором команд: мы строим причинно-следственную модель цикла управления.
После главы вы сможете:
- объяснить желаемое и наблюдаемое состояния, согласование и идемпотентность;
- проследить запросы записи и чтения через
kube-apiserver; - различать
metadata,spec,status, метки, аннотации и селекторы; - доказать владение ресурсами в цепочке Deployment → ReplicaSet → Pod;
- назвать области отказа плоскости управления и узла без ухода в детали реализации;
- предсказать, какие изменения неизменяемы и почему.
Ментальная модель: API, а не оркестрация через SSH
Декларативный цикл управления — повторяемый цикл: контроллер читает желаемое
состояние (spec), наблюдает фактическое, выполняет минимальное действие и
снова сравнивает. Согласование — сведение наблюдаемого состояния к желаемому.
Идемпотентность означает: повтор одной декларации не должен множить результат.
YAML → kubectl → аутентификация → авторизация → admission
→ kube-apiserver → etcd
↑ `watch`/`list`
контроллер/scheduler ← объекты API → status/events
Файл YAML не является источником истины после применения. Источник истины для
управления кластером — объект в API; etcd хранит состояние плоскости управления.
Процесс в контейнере — наблюдаемая реальность. kubectl apply не «запускает
Pod»: он создаёт или обновляет объект, а контроллеры реагируют асинхронно.
Компоненты и ответственность
| Компонент | Делает | Не делает |
|---|---|---|
kube-apiserver | точка входа API, проверка/admission, граница сохранения | не запускает контейнеры |
etcd | согласованное хранилище ключей и значений для состояния плоскости управления | по умолчанию не хранит данные приложения |
kube-scheduler | выбирает узел для ещё не назначенного Pod | не запускает контейнер |
kube-controller-manager | запускает основные контроллеры согласования | не обслуживает HTTP-трафик приложения |
cloud-controller-manager | интегрирует облачные узлы, маршруты и LB | отсутствует или заменён в локальном кластере |
kubelet | воплощает PodSpec на назначенном узле через CRI | не определяет желаемое число реплик |
| Среда исполнения контейнеров | загружает, создаёт, запускает и останавливает контейнер | не знает о Deployment |
kube-proxy или иная плоскость данных | реализует перенаправление Service | не обязан быть отдельным прокси-процессом во всех CNI |
| Дополнение CNI | сеть Pod и плоскость данных | не является частью основного Kubernetes API |
Отказ плоскости управления может запретить новые записи и планирование, но уже запущенный трафик иногда продолжит идти. Отказ kubelet или среды исполнения влияет на конкретный узел. Отказ процесса приложения может быть локален одному Pod. Это три разных радиуса поражения.
Анатомия объекта
Полный манифест Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-api-model
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: model-lab
app.kubernetes.io/part-of: kube-python-course
annotations:
course.example.com/purpose: api-model
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: model-lab
template:
metadata:
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: model-lab
app.kubernetes.io/part-of: kube-python-course
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: kube-python-course:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 192Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
apiVersionвыбирает группу и версию API;apps/v1— группаapps, версияv1.kindзадаёт схему ресурса.metadata.name/namespaceобразуют идентичность в namespace; UID различает пересозданные объекты с тем же именем.- метки — индексируемая идентичность для выбора и группировки; аннотации — метаданные, не участвующие в идентификации.
spec— желаемое состояние пользователя.statusпишет система или контроллер; не копируйте его в исходные манифесты..spec.selectorDeployment неизменяем и обязан совпадать с метками шаблона.
Namespace изолирует имена и задаёт область действия RBAC, квот и политик, но сам по себе не даёт сетевую изоляцию или изоляцию безопасности. OwnerReference связывает зависимый объект с владельцем для контроллеров и сборки мусора. Это не обычная метка.
Практическое упражнение: увидеть согласование
Подготовка:
kubectl create namespace kube-course --dry-run=client -o yaml |
kubectl apply -f -
kubectl label namespace kube-course \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.36 --overwrite
Сохраните манифест выше как /tmp/python-api-model.yaml, затем:
kubectl apply --dry-run=client -f /tmp/python-api-model.yaml
kubectl apply --dry-run=server -f /tmp/python-api-model.yaml
kubectl apply -f /tmp/python-api-model.yaml
kubectl rollout status deployment/python-api-model -n kube-course --timeout=120s
kubectl get deployment,replicaset,pod -n kube-course \
-l app.kubernetes.io/instance=model-lab
Ожидаются deployment.apps/python-api-model created, rollout
successfully rolled out и два Pod в Running/Ready. Код завершения — 0.
Исследуйте состояние:
kubectl get deployment python-api-model -n kube-course \
-o jsonpath='{.spec.replicas}{" desired; "}{.status.readyReplicas}{" ready\n"}'
kubectl get pod -n kube-course -l app.kubernetes.io/instance=model-lab \
-o custom-columns='NAME:.metadata.name,UID:.metadata.uid,NODE:.spec.nodeName,READY:.status.conditions[?(@.type=="Ready")].status'
kubectl get rs -n kube-course -l app.kubernetes.io/instance=model-lab \
-o jsonpath='{range .items[*]}{.metadata.name}{" owner="}{.metadata.ownerReferences[0].kind}{"/"}{.metadata.ownerReferences[0].name}{"\n"}{end}'
Теперь удалите один Pod:
VICTIM="$(kubectl get pod -n kube-course \
-l app.kubernetes.io/instance=model-lab \
-o jsonpath='{.items[0].metadata.name}')"
kubectl delete pod "$VICTIM" -n kube-course
kubectl wait deployment/python-api-model -n kube-course \
--for=condition=Available --timeout=120s
kubectl get pods -n kube-course -l app.kubernetes.io/instance=model-lab
Имя и UID заменяющего Pod отличаются, число реплик снова равно двум. Решение создать замену принял не scheduler и не kubelet: контроллер ReplicaSet согласовал число реплик с желаемым. Scheduler только назначил новый Pod на узел.
Проверьте идемпотентность:
kubectl apply -f /tmp/python-api-model.yaml
Ожидается deployment.apps/python-api-model unchanged, а не четыре Pod.
Очистка:
kubectl delete -f /tmp/python-api-model.yaml
kubectl wait --for=delete pod -n kube-course \
-l app.kubernetes.io/instance=model-lab --timeout=120s
Путь API-запроса и конкурентные изменения
kubectl apply получает сведения обнаружения API/OpenAPI, вычисляет намерение и отправляет
запрос. Сервер API выполняет:
- TLS и аутентификация: кто вы?
- авторизация: разрешены ли операция, ресурс и область?
- мутация admission-контроллерами и назначение значений по умолчанию: какие поля добавлены или изменены?
- проверка admission-контроллерами: допустим ли объект?
- сохранение в etcd и формирование ответа.
Контроллеры используют list/watch. Изменения защищены resourceVersion;
конкурирующее устаревшее изменение может получить HTTP 409 Conflict. generation растёт
при изменении желаемого состояния, а .status.observedGeneration показывает,
обработал ли контроллер это значение generation. Успешный HTTP-ответ на применение ещё
не означает здоровый rollout.
kubectl get deployment python-api -n kube-course \
-o jsonpath='{.metadata.generation}{" desired generation; observed "}{.status.observedGeneration}{"\n"}'
Самостоятельная задача
Создайте python-api-model с replicas: 3. Запишите:
generation/observedGenerationдо и после масштабирования;- UID владельцев для ReplicaSet и Pods;
- что случилось при
kubectl delete rs ...; - почему новый ReplicaSet может выполнять ту же смысловую роль, но иметь другой UID.
Критерий приёмки: вы можете указать контроллер для каждой замены и подтвердить ответ выводом JSONPath.
Сломанный манифест: неизменяемый селектор
После создания измените только:
spec:
selector:
matchLabels:
app.kubernetes.io/instance: model-lab-v2
и соответствующую метку шаблона. Сервер отклонит изменение:
The Deployment "python-api-model" is invalid:
spec.selector: Invalid value: ...: field is immutable
Причина: смена идентичности существующего контроллера могла бы «усыновить» или осиротить Pods. Варианты восстановления:
- если метка не является селектором — меняйте только метку шаблона;
- создайте новый Deployment с новым именем и переключите Service;
- удаление и пересоздание допустимы только при явно принятом простое и влиянии на владение ресурсами.
Не используйте --force рефлекторно: он удаляет и пересоздаёт объект, меняя UID.
Дерево диагностических решений
Желаемое состояние != наблюдаемое
├─ запись в API отклонена?
│ ├─ Unauthorized → аутентификация/kubeconfig
│ ├─ Forbidden → RBAC / kubectl auth can-i
│ └─ Invalid → схема, неизменяемое поле, сообщение admission
└─ объект API принят
├─ observedGeneration < generation → задержка/сбой контроллера; события/журналы
├─ желаемое число реплик != текущему → conditions/events ReplicaSet
├─ Pod не назначен → Events scheduler
├─ Pod назначен, но не запущен → kubelet/среда исполнения/образ/том
└─ Running, но не Ready → результаты проверок/приложения
Типичные ошибки:
- считать метки документацией, хотя селектор использует их как идентичность;
- редактировать сгенерированный ReplicaSet вместо Deployment;
- ожидать, что namespace автоматически изолирует трафик;
- читать
statusиз локального YAML; - применять одноразовое императивное исправление, которое контроллер сразу отменит;
- путать удаление объекта и перезапуск процесса.
Эксплуатационные компромиссы, безопасность и стоимость
Высокодоступная плоскость управления уменьшает риск отказа API и etcd, но требует дополнительных узлов, хранилища, резервного копирования и усложняет эксплуатацию. Резервная копия etcd содержит состояние кластера и Secrets — шифруйте её и ограничивайте доступ. Метки должны иметь низкую кардинальность и быть стабильными; уникальные идентификаторы запросов относятся в журналы или аннотации, а не в селекторы. Записи API и работа контроллеров имеют стоимость: массовое создание и удаление объектов и Events нагружает etcd и API. Namespace — удобная граница арендатора, но реальная изоляция требует RBAC, квот, Pod Security, NetworkPolicy и часто отдельного кластера.
Самопроверка
- Кто принимает решение создать заменяющий Pod?
- Почему
Runningне равноReady? - Чем аннотация отличается от метки?
- Что доказывает
observedGeneration == generation? - Может ли kubelet создать дополнительную реплику по своему решению?
- Почему отказ плоскости управления не всегда немедленно прерывает существующий трафик?
Ответы: 1) контроллер ReplicaSet; 2) phase описывает уровень процесса,
readiness — уровень трафика; 3) метка участвует в выборе, аннотация — нет;
4) контроллер увидел текущее желаемое значение generation, но ещё мог не достичь
здорового результата; 5) нет; 6) работающие Pods и плоскость данных на узлах могут
продолжить работу без новых записей API.
Резюме и источники
Kubernetes — управляемая через API система согласующих контроллеров. Всегда спрашивайте: кто владелец или контроллер, каково желаемое состояние, какие есть доказательства наблюдаемого состояния и на каком уровне произошёл отказ. Далее: Pods и ресурсы рабочих нагрузок.