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

Модуль 2 / 19

Kubernetes API, kubectl, манифесты и жизненный цикл объектов

KUBERNETES

Содержание
01

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

После модуля вы сможете исследовать API текущего кластера, читать Kubernetes manifest как API-запрос, безопасно проверять изменение до записи, объяснять metadata жизненного цикла и разбирать конфликты конкурентных клиентов.

  • GVK идентифицирует тип объекта в manifest, GVR — REST resource endpoint
  • kubeconfig выбирает cluster, identity и context, но не выдаёт права
  • dry-run client и server отвечают на разные вопросы
  • resourceVersion защищает optimistic concurrency
  • generation меняется при изменении желаемого состояния, observedGeneration показывает обработанную controller версию
  • Server-Side Apply хранит владение полями в managedFields
02

2. Теория: GVK, GVR и discovery

apiVersion и kind образуют Group-Version-Kind. Например apps/v1 Deployment описывает тип сериализованного объекта. API endpoint использует Group-Version-Resource: deployments — множественное имя REST resource. Один kind может обслуживаться несколькими API versions, а preferred version сообщает discovery API Server.

kubectl api-resources получает доступные ресурсы из discovery, а kubectl explain читает опубликованную OpenAPI schema. Не переносите поля между похожими kind по памяти: проверяйте точный apiVersion, scope и schema кластера, с которым работаете.

  • core group записывается как apiVersion: v1
  • именованные группы используют форму group/version, например apps/v1
  • namespaced resource требует namespace, cluster-scoped resource — нет
  • short name удобен человеку, но automation должна предпочитать полное имя resource
03

3. Теория: обработка API-запроса

Сначала клиент устанавливает TLS и предъявляет identity. API Server выполняет authentication, затем authorization, mutating admission, schema/defaulting, validating admission и запись в storage. Точный порядок admission зависит от настроенных plugins и webhooks.

Authentication отвечает «кто», authorization — «может ли identity выполнить verb над resource», admission — «допустим ли конкретный объект и как его дополнить». Успешный RBAC check не гарантирует прохождение schema или policy. Ошибки 401, 403, 404, 409 и 422 требуют разных гипотез.

04

4. Теория: identity, версии и состояние объекта

name уникален в пределах scope, namespace отделяет namespaced objects, uid различает разные воплощения одного имени. resourceVersion отражает версию записи для concurrency и watch, но не является порядковым номером, пригодным для арифметики.

generation увеличивается при релевантных изменениях desired state. Controller записывает status.observedGeneration, когда обработал соответствующую generation. Равенство этих значений ещё не доказывает готовность, поэтому его читают вместе с Conditions.

creationTimestamp, deletionTimestamp, finalizers, ownerReferences и managedFields описывают lifecycle и отношения. Status обычно обновляется через отдельный subresource, чтобы controller не владел пользовательским spec.

05

5. Теория: kubeconfig, команды и представления

Context связывает cluster, user и необязательный default namespace. Переключение context меняет цель следующих команд, поэтому identity и endpoint подтверждают до любого изменения. kubeconfig содержит чувствительные credentials и не должен попадать в Git или общий архив.

get даёт обзор, describe объединяет состояние и Events, -o yaml/json сохраняет машинное представление, JSONPath извлекает известные поля, --watch продолжает поток изменений. Табличный human-readable output удобен оператору, но нестабилен как интерфейс automation.

terminal
kubectl config current-context
kubectl config view --minify
kubectl auth whoami
kubectl api-resources --api-group=apps
kubectl api-versions
kubectl explain deployment.spec.strategy
kubectl get --raw='/apis/apps/v1' | head -c 800
06

6. Теория: declarative apply и field ownership

create создаёт отсутствующий объект, replace отправляет полное актуальное представление с resourceVersion, patch меняет часть объекта, apply согласует заявленную конфигурацию с live state. Imperative команды полезны для исследования и ограниченных операций, но воспроизводимое desired state хранится в manifests.

Server-Side Apply назначает field manager и фиксирует владение полями в managedFields. Два manager могут владеть разными полями одного объекта. Конфликт возникает, когда один пытается изменить поле, которым владеет другой. --force-conflicts означает сознательно забрать владение; применять его без анализа нельзя.

Client dry-run проверяет локальную генерацию и часть schema, server dry-run проходит актуальную server-side defaulting, admission и validation без сохранения. kubectl diff показывает прогноз live → desired и возвращает exit code 1, когда различия найдены; это не ошибка проверки.

07

7. Теория: create, update, watch и delete

API mutation создаёт новую resourceVersion, а watch доставляет последовательность событий относительно выбранной версии. При слишком старой версии сервер может вернуть 410 Gone, после чего клиент заново выполняет list и продолжает watch с актуального состояния.

Delete сначала может выставить deletionTimestamp. Finalizers удерживают объект, пока ответственный controller не завершит cleanup. OwnerReferences позволяют garbage collector удалять dependents согласно propagation policy. Force delete обходит часть гарантий и не является обычным способом устранения зависшего ресурса.

08

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

  • Подтвердите context, API endpoint, identity и namespace
  • Исследуйте resource через api-resources и explain до написания manifest
  • Применяйте Namespace отдельно до namespaced resources: набор YAML-документов не является транзакцией
  • Перед apply используйте server dry-run и diff
  • Для scripts выбирайте JSON/YAML/JSONPath и проверяйте exit codes
  • Не удаляйте resourceVersion из сценария replace и не обходите Conflict бесконечным retry
  • Перед --force-conflicts определите текущего field manager и владельца процесса
  • Не редактируйте status как пользовательское desired state
09

9. Лабораторная работа: discovery и server-side apply

Создайте два отдельных manifests. Сначала примените Namespace и дождитесь Active, затем проверьте Deployment через server dry-run и выполните Server-Side Apply с явным field manager.

terminal
mkdir -p /tmp/k8s-m02

cat > /tmp/k8s-m02/namespace.yaml <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: k8s-m02
  labels:
    course.goretskiy.pro/module: "02"
EOF

cat > /tmp/k8s-m02/app.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: k8s-m02
  labels:
    app.kubernetes.io/name: web
    app.kubernetes.io/managed-by: kubectl
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.29-alpine
          ports:
            - name: http
              containerPort: 80
EOF
terminal
kubectl apply --dry-run=server -f /tmp/k8s-m02/namespace.yaml
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m02/namespace.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Active namespace/k8s-m02 --timeout=30s

kubectl apply --dry-run=server -f /tmp/k8s-m02/app.yaml
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m02/app.yaml
kubectl -n k8s-m02 rollout status deployment/web --timeout=120s

Разделение файлов принципиально: multi-document apply не является транзакцией и не должен использоваться как гарантия, что Namespace станет доступен до следующего namespaced object.

10

10. Лабораторная работа: metadata, diff и watch

Сопоставьте uid, resourceVersion, generation, observedGeneration и managers. Команда watch блокирует терминал; выполняйте её отдельно и завершите Ctrl+C после наблюдения изменений.

terminal
kubectl -n k8s-m02 get deployment web -o jsonpath='uid={.metadata.uid}{"\n"}resourceVersion={.metadata.resourceVersion}{"\n"}generation={.metadata.generation}{"\n"}observedGeneration={.status.observedGeneration}{"\n"}'

kubectl -n k8s-m02 get deployment web -o jsonpath='{range .metadata.managedFields[*]}manager={.manager} operation={.operation} subresource={.subresource}{"\n"}{end}'

kubectl -n k8s-m02 get deployment web -o yaml
kubectl -n k8s-m02 get pods --watch

Измените replicas в исходном manifest. Сначала изучите diff, затем примените desired state и дождитесь controller.

terminal
sed -i 's/replicas: 2/replicas: 3/' /tmp/k8s-m02/app.yaml
kubectl diff -f /tmp/k8s-m02/app.yaml || [ "$?" -eq 1 ]
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m02/app.yaml
kubectl -n k8s-m02 rollout status deployment/web --timeout=120s
kubectl -n k8s-m02 get deployment web -o jsonpath='generation={.metadata.generation}{"\n"}observedGeneration={.status.observedGeneration}{"\n"}replicas={.status.readyReplicas}{"\n"}'

Ожидается новая resourceVersion, увеличение generation и последующее выравнивание observedGeneration. uid остаётся прежним, потому что Deployment был обновлён, а не пересоздан.

11

11. Диагностика: schema, stale version и SSA conflict

Сохраните старую версию объекта, измените replicas через scale и попробуйте replace старым YAML. API вернёт Conflict: stale resourceVersion не должен перезаписывать более новое состояние.

terminal
kubectl -n k8s-m02 get deployment web -o yaml > /tmp/k8s-m02/web-stale.yaml
kubectl -n k8s-m02 scale deployment/web --replicas=4
kubectl replace -f /tmp/k8s-m02/web-stale.yaml || true

kubectl -n k8s-m02 get deployment web -o jsonpath='{range .metadata.managedFields[*]}manager={.manager} operation={.operation} subresource={.subresource}{"\n"}{end}'

kubectl -n k8s-m02 scale deployment/web --replicas=3
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m02/app.yaml
kubectl -n k8s-m02 rollout status deployment/web --timeout=120s

После scale replicas принадлежит manager kubectl с subresource scale. Возврат к значению manifest согласует намерения, после чего course-lab снова может применить конфигурацию без захвата чужого поля.

Теперь отправьте объект, где строка two используется вместо integer. Server dry-run вернёт BadRequest до создания ресурса.

terminal
cat > /tmp/k8s-m02/invalid.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: invalid
  namespace: k8s-m02
spec:
  replicas: two
  selector:
    matchLabels:
      app: invalid
  template:
    metadata:
      labels:
        app: invalid
    spec:
      containers:
        - name: nginx
          image: nginx:1.29-alpine
EOF

kubectl apply --dry-run=server -f /tmp/k8s-m02/invalid.yaml || true
kubectl get deployment invalid -n k8s-m02 2>&1 || true
  • 401 Unauthorized — credentials отсутствуют или не приняты
  • 403 Forbidden — identity установлена, но authorization запретила действие
  • 404 NotFound — неверны resource, name, namespace или API endpoint
  • 409 Conflict — устаревшая resourceVersion либо SSA field conflict
  • 422 Invalid — объект понятен API, но нарушает validation rules
  • 429 Too Many Requests — сработало ограничение потока API
12

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

  • Чем GVK отличается от GVR?
  • Что выбирает context и почему он не предоставляет RBAC?
  • Чем client dry-run отличается от server dry-run?
  • Почему kubectl diff с exit code 1 может быть успешной проверкой?
  • Для чего нужны uid, resourceVersion и generation?
  • Как observedGeneration помогает читать controller status?
  • Почему набор YAML-документов не является транзакцией?
  • Когда SSA останавливается на field conflict?
  • Почему force delete и --force-conflicts требуют отдельного обоснования?
terminal
kubectl delete namespace k8s-m02 --wait=true
rm -rf /tmp/k8s-m02
kubectl get namespace k8s-m02 2>&1 || true

Модуль освоен, если вы исследуете schema живого API, прогнозируете mutation через dry-run и diff, объясняете metadata lifecycle и разрешаете Conflict без потери чужого изменения.