Kube/Pythonproduction course
Курс · RU01-architecture-and-api-model.md

01. Архитектура и модель Kubernetes API

Незнакомый термин? Откройте словарь Kubernetes и терминов курса.

Назначение, предварительные знания и результаты обучения

Нужна глава 00 и работающий kind-kube-course. Здесь Kubernetes перестаёт быть набором команд: мы строим причинно-следственную модель цикла управления.

После главы вы сможете:

Ментальная модель: 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.selector Deployment неизменяем и обязан совпадать с метками шаблона.

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 выполняет:

  1. TLS и аутентификация: кто вы?
  2. авторизация: разрешены ли операция, ресурс и область?
  3. мутация admission-контроллерами и назначение значений по умолчанию: какие поля добавлены или изменены?
  4. проверка admission-контроллерами: допустим ли объект?
  5. сохранение в 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. Варианты восстановления:

  1. если метка не является селектором — меняйте только метку шаблона;
  2. создайте новый Deployment с новым именем и переключите Service;
  3. удаление и пересоздание допустимы только при явно принятом простое и влиянии на владение ресурсами.

Не используйте --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 и часто отдельного кластера.

Самопроверка

  1. Кто принимает решение создать заменяющий Pod?
  2. Почему Running не равно Ready?
  3. Чем аннотация отличается от метки?
  4. Что доказывает observedGeneration == generation?
  5. Может ли kubelet создать дополнительную реплику по своему решению?
  6. Почему отказ плоскости управления не всегда немедленно прерывает существующий трафик?

Ответы: 1) контроллер ReplicaSet; 2) phase описывает уровень процесса, readiness — уровень трафика; 3) метка участвует в выборе, аннотация — нет; 4) контроллер увидел текущее желаемое значение generation, но ещё мог не достичь здорового результата; 5) нет; 6) работающие Pods и плоскость данных на узлах могут продолжить работу без новых записей API.

Резюме и источники

Kubernetes — управляемая через API система согласующих контроллеров. Всегда спрашивайте: кто владелец или контроллер, каково желаемое состояние, какие есть доказательства наблюдаемого состояния и на каком уровне произошёл отказ. Далее: Pods и ресурсы рабочих нагрузок.