Модуль 2 / 19
Kubernetes API, kubectl, манифесты и жизненный цикл объектов
Содержание
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
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
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 требуют разных гипотез.
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.
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.
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 8006. Теория: 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, когда различия найдены; это не ошибка проверки.
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 обходит часть гарантий и не является обычным способом устранения зависшего ресурса.
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
9. Лабораторная работа: discovery и server-side apply
Создайте два отдельных manifests. Сначала примените Namespace и дождитесь Active, затем проверьте Deployment через server dry-run и выполните Server-Side Apply с явным field manager.
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
EOFkubectl 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. Лабораторная работа: metadata, diff и watch
Сопоставьте uid, resourceVersion, generation, observedGeneration и managers. Команда watch блокирует терминал; выполняйте её отдельно и завершите Ctrl+C после наблюдения изменений.
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.
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. Диагностика: schema, stale version и SSA conflict
Сохраните старую версию объекта, измените replicas через scale и попробуйте replace старым YAML. API вернёт Conflict: stale resourceVersion не должен перезаписывать более новое состояние.
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 до создания ресурса.
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. Контроль, критерии освоения и 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 требуют отдельного обоснования?
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 без потери чужого изменения.