Kube/Pythonproduction course
Курс · RU90-kubectl-labs.md

90. Последовательные лабораторные работы с kubectl

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

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

Это единый путь выполнения для глав 00–11. Нужны Linux, Docker/Podman, kubectl v1.36.2, kind v0.32.0, 4 CPU, 8 GiB RAM, 20 GiB диска и корень репозитория course-kubernetes-python.

После лабораторных работ вы не просто «применяли YAML», а сможете:

  • создать и пересоздать конкретный локальный кластер и образ;
  • развернуть PodDeploymentService → защищённую рабочую нагрузку;
  • проверить конфигурацию и её обновление, проверки, ресурсы, DNS и политики, PVC, rollout, HPA и PDB;
  • доказать минимальные привилегии RBAC;
  • расследовать восемь сломанных сценариев;
  • сохранить доказательства, выполнить очистку и восстановление без воздействия на чужие ресурсы.

Ожидаемый вывод ниже описывает инвариант, а не точные UID, IP-адреса и отметки времени. Запишите реальные команды, коды завершения и наблюдения в собственный журнал лабораторных работ.

Правила и предварительная проверка

export PATH="$PWD/.tools:$PATH"
kubectl version --client
kind version
docker version
test "$(basename "$PWD")" = "course-kubernetes-python"

Перед любым изменением кластера:

test "$(kubectl config current-context 2>/dev/null || true)" = "kind-kube-course"

Исключение — лабораторная 0 до создания контекста. Не выводите kubeconfig или YAML Secret. Сломанные манифесты применяются только в kube-course.

Полный манифест диагностического клиента используется в сетевых лабораторных:

apiVersion: v1
kind: Pod
metadata:
  name: dns-client
  namespace: kube-course
  labels:
    app.kubernetes.io/name: dns-client
    app.kubernetes.io/part-of: kube-python-course
spec:
  restartPolicy: Never
  automountServiceAccountToken: false
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    runAsGroup: 10001
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: client
      image: busybox:1.37.0
      command: ["sh", "-c", "sleep 3600"]
      resources:
        requests: {cpu: 10m, memory: 16Mi}
        limits: {cpu: 100m, memory: 64Mi}
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]

Лабораторная 0. Жизненный цикл кластера и контекст — 45–75 мин

Подготовка и действия

ORIGINAL_CONTEXT="$(kubectl config current-context 2>/dev/null || true)"
kind create cluster \
  --name kube-course \
  --config cluster/kind-config.yaml \
  --image kindest/node:v1.36.1@sha256:3489c7674813ba5d8b1a9977baea8a6e553784dab7b84759d1014dbd78f7ebd5 \
  --wait 5m

Ожидаемый результат и проверка

test "$(kubectl config current-context)" = "kind-kube-course"
kubectl wait --for=condition=Ready nodes --all --timeout=180s
kubectl get nodes -o wide
kubectl get pods -A
kubectl get storageclass

Три узла находятся в Ready, системные Pods здоровы, код завершения — 0. Зафиксируйте реализации CNI и хранилища. Проверка пересоздания:

kind export logs /tmp/kube-course-initial --name kube-course
kind delete cluster --name kube-course
kind create cluster \
  --name kube-course \
  --config cluster/kind-config.yaml \
  --image kindest/node:v1.36.1@sha256:3489c7674813ba5d8b1a9977baea8a6e553784dab7b84759d1014dbd78f7ebd5 \
  --wait 5m

Восстановление и очистка

При ошибке создания выполните kind export logs, проверьте журналы провайдера, диск и DNS, затем точную команду kind delete cluster --name kube-course; повторяйте только после формулировки гипотезы. Кластер оставьте для остальных лабораторных.

Лабораторная 1. Образ и отдельный Pod — 30–45 мин

Подготовка и действия

docker build --file app/Containerfile --tag kube-python-course:1.0.0 app
docker image inspect kube-python-course:1.0.0 \
  --format '{{.Id}} {{.Config.User}}'
kind load docker-image kube-python-course:1.0.0 --name kube-course
kubectl apply -f manifests/base/namespace.yaml
kubectl apply --dry-run=server -f manifests/labs/pod.yaml
kubectl apply -f manifests/labs/pod.yaml

Ожидаемый результат и проверка

kubectl wait pod/python-api-pod -n kube-course \
  --for=condition=Ready --timeout=120s
kubectl port-forward -n kube-course pod/python-api-pod 8000:8000

В другом терминале:

curl --fail --silent http://127.0.0.1:8000/ | python3 -m json.tool
curl --fail --silent http://127.0.0.1:8000/health/live

Ожидается HTTP 200 с полями сервиса, версии и Pod. После kubectl delete pod Pod не восстанавливается: владелец отсутствует.

Восстановление и очистка

При ImagePullBackOff для локального образа повторите kind load, проверьте конкретный кластер, образы на узлах и imagePullPolicy. Очистка:

kubectl delete pod python-api-pod -n kube-course --ignore-not-found

Лабораторная 2. Deployment, Service и EndpointSlice — 45–60 мин

Подготовка и действия

kubectl apply -f manifests/base/configmap.yaml
kubectl apply -f manifests/base/secret.example.yaml
kubectl apply -f manifests/base/serviceaccount.yaml
kubectl apply -f manifests/base/deployment.yaml
kubectl apply -f manifests/base/service.yaml
kubectl rollout status deployment/python-api -n kube-course --timeout=180s

Ожидаемый результат и проверка

kubectl get deploy,rs,pod,svc -n kube-course \
  -l app.kubernetes.io/name=python-api
kubectl get endpointslice -n kube-course \
  -l kubernetes.io/service-name=python-api -o wide
kubectl port-forward -n kube-course service/python-api 8080:80

Три готовых Pod и три готовых адреса endpoints; HTTP 200 через port-forward. Удалите один управляемый Pod, подтвердите UID замены и восстановленные три реплики.

Восстановление и очистка

Если Deployment недоступен, используйте describe deployment, Pods и Events, текущие и предыдущие журналы. Не удаляйте работающий Service. Ресурсы остаются.

Лабораторная 3. ConfigMap, заглушка Secret и rollout — 45–60 мин

Подготовка и действия

kubectl port-forward -n kube-course service/python-api 8080:80
curl --fail --silent http://127.0.0.1:8080/config
kubectl patch configmap python-api-config -n kube-course --type merge \
  -p '{"data":{"APP_MESSAGE":"env-v2","message":"file-v2"}}'

Ожидаемый результат и проверка

В течение задержки проекции /config показывает старое message_from_env и новое message_from_mounted_file. UID Pod и число перезапусков прежние.

kubectl patch deployment python-api -n kube-course --type merge \
  -p '{"spec":{"template":{"metadata":{"annotations":{"course.example.com/config-checksum":"lab3-v2"}}}}}'
kubectl rollout status deployment/python-api -n kube-course --timeout=180s
curl --fail --silent http://127.0.0.1:8080/config

Теперь окружение содержит env-v2; для Secret показан только логический признак наличия, значение не выведено.

Восстановление и очистка

При отсутствующей конфигурации проверяйте Events и точные ключ, имя и namespace. Восстановите исходник:

kubectl apply -f manifests/base/configmap.yaml
kubectl apply -f manifests/base/secret.example.yaml
kubectl rollout restart deployment/python-api -n kube-course
kubectl rollout status deployment/python-api -n kube-course --timeout=180s

Лабораторная 4. Проверки и корректное завершение — 45–60 мин

Подготовка и действия

kubectl get pods -n kube-course -l app.kubernetes.io/instance=course \
  -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount'
POD="$(kubectl get pod -n kube-course \
  -l app.kubernetes.io/instance=course \
  -o jsonpath='{.items[0].metadata.name}')"
kubectl port-forward -n kube-course pod/"$POD" 8000:8000
curl --fail --silent http://127.0.0.1:8000/health/drain

Ожидаемый результат и проверка

Обработчик ждёт около пяти секунд, readiness становится false; адрес EndpointSlice становится неготовым и исключается. Liveness остаётся успешной, число перезапусков не растёт. Замените Pod:

time kubectl delete pod "$POD" -n kube-course --wait=true
kubectl rollout status deployment/python-api -n kube-course --timeout=180s

Удаление включает preStop и drain, замена переходит в Ready. Проверяйте не точные секунды, а переход endpoint, отсутствие принудительного SIGKILL и восстановление приложения.

Восстановление и очистка

Если Pod застрял в Terminating, проверьте describe, длительность preStop, льготный период, узел и kubelet; --force не используется без отдельного решения о риске для данных и процесса.

Лабораторная 5. Запросы ресурсов, Pending и OOM — 45–60 мин

Pending

kubectl apply -f manifests/broken/01-pending-cpu.yaml
kubectl describe pod broken-pending -n kube-course

Ожидаются Pending, FailedScheduling, Insufficient cpu; .spec.nodeName пуст, журналы приложения отсутствуют.

OOM

kubectl apply -f manifests/broken/06-oom.yaml
kubectl wait pod/broken-oom -n kube-course \
  --for=jsonpath='{.status.phase}'=Failed --timeout=120s
kubectl get pod broken-oom -n kube-course \
  -o jsonpath='{.status.containerStatuses[0].state.terminated.reason}{" "}{.status.containerStatuses[0].state.terminated.exitCode}{"\n"}'

Ожидается OOMKilled 137. Объясните, почему этот факт не доказывает утечку.

Восстановление и очистка

kubectl delete -f manifests/broken/01-pending-cpu.yaml
kubectl delete -f manifests/broken/06-oom.yaml

Исправление запроса ресурсов или лимита основано на измерениях и ёмкости, а не на произвольном умножении на десять.

Лабораторная 6. DNS, селектор и NetworkPolicy — 60–90 мин

Базовая проверка DNS

Сохраните полный диагностический Pod из начала файла как /tmp/dns-client.yaml.

kubectl apply -f /tmp/dns-client.yaml
kubectl wait pod/dns-client -n kube-course \
  --for=condition=Ready --timeout=120s
kubectl exec -n kube-course dns-client -- \
  nslookup python-api.kube-course.svc.cluster.local
kubectl exec -n kube-course dns-client -- \
  wget -qO- http://python-api/

Ожидаются IP-адрес Service из DNS и JSON по HTTP.

Ошибка селектора

kubectl apply -f manifests/broken/04-service-selector.yaml
kubectl get endpointslice -n kube-course \
  -l kubernetes.io/service-name=broken-selector -o yaml
kubectl get pods -n kube-course --show-labels

Ожидается пустой список адресов. Исправьте конкретный селектор:

kubectl patch service broken-selector -n kube-course --type merge \
  -p '{"spec":{"selector":{"app.kubernetes.io/name":"python-api","app.kubernetes.io/instance":"course"}}}'
kubectl get endpointslice -n kube-course \
  -l kubernetes.io/service-name=broken-selector -w

Endpoints появляются без перезапуска Pod.

Ошибка NetworkPolicy

kubectl apply -f manifests/broken/08-deny-dns.yaml
kubectl exec -n kube-course dns-client -- nslookup kubernetes.default.svc

На CNI с поддержкой политик ожидаются тайм-аут и ненулевой код. Если запрос успешен, CNI не применяет эту политику, что часто бывает в kind по умолчанию, и лабораторная помечается not enforced, а не «пройдена». Для полного пути пересоздайте kind с CNI, официально поддерживающим политики, и повторите базовую проверку, запрет и восстановление.

Восстановление и очистка

kubectl delete -f manifests/broken/08-deny-dns.yaml
kubectl delete service broken-selector -n kube-course
kubectl delete pod dns-client -n kube-course

После восстановления DNS и HTTP должны снова отвечать.

Лабораторная 7. Жизненный цикл PVC — 45–60 мин

Подготовка и действия

kubectl get storageclass
kubectl apply -f manifests/base/pvc.yaml
kubectl apply -f manifests/labs/storage-pod.yaml
kubectl wait pod/python-api-storage -n kube-course \
  --for=condition=Ready --timeout=180s
kubectl get pvc python-api-data -n kube-course

Если PVC находится в Pending с WaitForFirstConsumer, Pod запускает привязку. Если нет класса по умолчанию, укажите в копии точный существующий класс.

kubectl port-forward -n kube-course pod/python-api-storage 8000:8000
curl --fail -X POST http://127.0.0.1:8000/state/increment
curl --fail -X POST http://127.0.0.1:8000/state/increment

Удалите и пересоздайте Pod с хранилищем; ожидаемое значение /state равно двум. Запишите UID старого и нового Pod и неизменившиеся PVC/PV.

Восстановление и очистка

Перед удалением PVC:

PV="$(kubectl get pvc python-api-data -n kube-course \
  -o jsonpath='{.spec.volumeName}')"
kubectl get pv "$PV" \
  -o jsonpath='{.spec.persistentVolumeReclaimPolicy}{"\n"}'
kubectl delete pod python-api-storage -n kube-course
kubectl delete pvc python-api-data -n kube-course
kubectl get pv "$PV"

Зафиксируйте фактический результат Delete или Retain. Локальная сохранность данных не является резервной копией.

Лабораторная 8. Развёртывание, откат и HPA — 60–90 мин

Развёртывание и откат

kubectl rollout history deployment/python-api -n kube-course
kubectl set image deployment/python-api -n kube-course \
  api=registry.invalid.example/python-api:9.9.9
kubectl rollout status deployment/python-api -n kube-course --timeout=45s

Ожидаются тайм-аут с ненулевым кодом и ImagePullBackOff у новой версии; доступность старой сохраняется.

kubectl rollout undo deployment/python-api -n kube-course
kubectl rollout status deployment/python-api -n kube-course --timeout=180s
kubectl get deployment python-api -n kube-course \
  -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

Ожидается локальная версия 1.0.0.

Дополнительная зависимость HPA

kubectl apply -f \
  https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.8.1/components.yaml
kubectl patch deployment metrics-server -n kube-system --type=json \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl rollout status deployment/metrics-server -n kube-system --timeout=180s
kubectl top nodes
kubectl apply -f manifests/base/hpa.yaml
kubectl apply -f manifests/labs/hpa-load.yaml
kubectl get hpa python-api -n kube-course -w

Ожидается, что длительная нагрузка увеличит число реплик максимум до шести. Не продолжайте, если Metrics API не отвечает. --kubelet-insecure-tls допустим только в одноразовом kind.

Восстановление и очистка

kubectl delete pod hpa-load -n kube-course --ignore-not-found
kubectl delete hpa python-api -n kube-course --ignore-not-found
kubectl scale deployment python-api -n kube-course --replicas=3

Metrics Server можно оставить до удаления кластера.

Лабораторная 9. PDB и drain — 45–60 мин

Подготовка и действия

kubectl apply -f manifests/base/pdb.yaml
kubectl scale deployment python-api -n kube-course --replicas=3
kubectl rollout status deployment/python-api -n kube-course --timeout=180s
kubectl get pods -n kube-course -l app.kubernetes.io/instance=course -o wide
kubectl get pdb python-api -n kube-course
kubectl drain kube-course-worker \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --timeout=180s

Ожидаемый результат и проверка

Node находится в SchedulingDisabled, PDB не допускает более одной недоступной реплики, замены становятся Ready на другом узле; пользовательский путь продолжает отвечать, если ёмкости достаточно.

Восстановление и очистка

Независимо от результата drain:

kubectl uncordon kube-course-worker
kubectl rollout status deployment/python-api -n kube-course --timeout=180s
kubectl get nodes

При тайм-ауте проверьте disruptionsAllowed, владельцев Pods, emptyDir, PVC, finalizers и ёмкость. Не оставляйте узел закрытым для планирования.

Лабораторная 10. RBAC с минимальными привилегиями — 30–45 мин

Подготовка и действия

kubectl apply -f manifests/base/rbac.yaml
SUBJECT='system:serviceaccount:kube-course:python-api'
kubectl auth can-i get configmap/python-api-config \
  -n kube-course --as="$SUBJECT"
kubectl auth can-i get secrets \
  -n kube-course --as="$SUBJECT"
kubectl apply -f manifests/labs/rbac-check.yaml

Ожидаемый результат и проверка

yes, no; rbac-check Succeeded:

kubectl wait pod/rbac-check -n kube-course \
  --for=jsonpath='{.status.phase}'=Succeeded --timeout=120s
kubectl logs rbac-check -n kube-course

Команда читает конкретный ConfigMap и ожидает Forbidden для списка Secrets.

Восстановление и очистка

При Forbidden для ConfigMap проверьте идентичность, namespace и субъект RoleBinding, точное resourceName и операцию. Не давайте шаблон *. Очистка:

kubectl delete pod rbac-check -n kube-course

Лабораторная 11. Расследование восьми неисправностей — 2–3 ч

Для каждого: предсказать → применить → собрать → объяснить → исправить или восстановить → проверить → удалить. Не запускайте все одновременно: Events смешаются.

СценарийОжидаемый признакОбязательные доказательстваВосстановление
101-pending-cpu.yamlPendingPodScheduled, FailedSchedulingудалить; задать реалистичный запрос ресурсов или ёмкость
202-crashloop.yamlCrashLoopBackOffтекущие и предыдущие журналы, код 42удалить Deployment или исправить команду
303-imagepull.yamlImagePullBackOffEvents, точный образ и причина ожиданияудалить или исправить образ и авторизацию
404-service-selector.yamlпустые endpointsселектор и метки Podисправить селектор, проверить HTTP
505-probe.yamlперезапускиEvents Unhealthy, restartCountпорт http/8000
606-oom.yamlOOMKilledпричина и код 137подобрать размер и проанализировать код
707-pvc.yamlPVC PendingEvents PVC и StorageClassпересоздать с реальным классом
808-deny-dns.yamlтайм-аут DNS при применении политикиполитика, CNI и DNS-запросудалить политику, проверить DNS

Шаблон команд:

kubectl apply -f manifests/broken/NN-file.yaml
kubectl get pods,pvc,service,endpointslice -n kube-course -o wide
kubectl get events -n kube-course \
  --sort-by=.metadata.creationTimestamp
kubectl describe <kind>/<name> -n kube-course
kubectl logs <pod> -n kube-course -c <container> --previous
kubectl delete -f manifests/broken/NN-file.yaml

Не каждая команда применима: для ImagePull и Pending нет журналов приложения; проблема селектора требует EndpointSlice; для PVC нужны Events PVC. Выбор доказательств — часть оценки.

Лабораторная 12. Kustomize и проверка — 45–60 мин

Подготовка и действия

kubectl kustomize manifests/overlays/dev > /tmp/dev.yaml
kubectl kustomize manifests/overlays/prod > /tmp/prod.yaml
kubectl kustomize manifests/overlays/dev > /tmp/dev-second.yaml
cmp /tmp/dev.yaml /tmp/dev-second.yaml
kubectl apply --dry-run=client -f /tmp/dev.yaml
kubectl apply --dry-run=server -f /tmp/dev.yaml
diff -u /tmp/dev.yaml /tmp/prod.yaml

Ожидаемый результат и проверка

Ожидаются детерминированный рендеринг dev, успех обоих dry-run и намеренные различия ресурсов и конфигурации. Код 1 от diff ожидаем при различиях. Secret намеренно не входит в результат: фактическое развёртывание требует отдельной одобренной доставки секрета.

Восстановление и очистка

При ошибке рендеринга проверьте путь, цель patch, GVK и имя. При серверной ошибке — API, admission и RBAC. Никогда не исправляйте ошибку рендерера ручной правкой /tmp/rendered.

Лабораторная 13. Очистка и доказательства — 20–30 мин

Соберите итоговые нечувствительные доказательства:

mkdir -p /tmp/kube-course-final
kubectl version > /tmp/kube-course-final/version.txt
kubectl get nodes -o wide > /tmp/kube-course-final/nodes.txt
kubectl get all -n kube-course -o wide \
  > /tmp/kube-course-final/workloads.txt
kubectl get events -n kube-course \
  --sort-by=.metadata.creationTimestamp \
  > /tmp/kube-course-final/events.txt
kind export logs /tmp/kube-course-final/kind --name kube-course

Проверьте пакет на Secrets, токены и данные клиентов. Затем:

test "$(kubectl config current-context)" = "kind-kube-course"
kind delete cluster --name kube-course
kind get clusters
if [ -n "$ORIGINAL_CONTEXT" ]; then
  kubectl config use-context "$ORIGINAL_CONTEXT"
fi

Удаляется весь одноразовый кластер kind, его Pods, PV и локальные данные. Восстановление возможно только повторной сборкой и применением; данные лабораторного PVC не являются резервной копией.

Самостоятельная задача

Без подсказок пересоздайте кластер и выполните лабораторные 2, 3, 5, 6, 8, 10 и два случайных сломанных сценария за 90 минут. Для каждого инцидента ограничьтесь тремя начальными командами. Критерий приёмки: правильный уровень найден без перезапуска первым действием, очистка полная, чужой контекст не менялся.

Общее дерево диагностических решений

Команда лабораторной завершилась с ненулевым кодом
├─ клиент/инструмент/провайдер → версия/PATH/среда исполнения/контекст
├─ API отклонил запрос → аутентификация/RBAC/схема/admission
├─ запрос принят, но Pod отсутствует → владелец/контроллер/generation
├─ Pod Pending/waiting/перезапускается → Events/состояние/предыдущие журналы
├─ Ready, но запрос не проходит → DNS/Service/EndpointSlice/политика/порт
├─ хранилище → PVC/PV/Class/CSI/топология
└─ rollout/обслуживание → ёмкость/readiness/PDB/HPA/cordon

Типичные ошибки и заметка об эксплуатации, безопасности и стоимости

Ошибки: одновременно применять сценарии; путать ожидаемую отрицательную проверку с провалом лабораторной; копировать отметки времени и IP; забывать uncordon; оставлять HPA при ручном масштабировании; считать принятую NetworkPolicy применённой; удалять PVC до фиксации политики возврата; публиковать Secret или пакет доказательств.

kind — одноразовое приближение: локальные хранилище, CNI, LB, метрики и дополнения не дают гарантий промышленной эксплуатации. Три узла и образы занимают диск и RAM; очистка экономит ресурсы. Диагностика сети, RBAC и эфемерные контейнеры влияют на безопасность. Действия в эксплуатационной среде требуют окна изменений, проверок SLO, доказательств резервного копирования и восстановления и регламента провайдера.

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

  1. Какая лабораторная доказывает замену контроллером?
  2. Чем серверный dry-run отличается от фактической среды исполнения?
  3. Где находятся доказательства несовпадения селектора?
  4. Как отличить неудачное планирование от ошибки загрузки образа?
  5. Что сделать после неудачного drain?
  6. Почему отрицательная проверка NetworkPolicy может дать ложный успех?
  7. Какие данные переживают замену Pod в лабораторной 7?
  8. Что удаляет финальная очистка kind?

Ответы: 1) лабораторная 2; 2) admission и назначение значений по умолчанию без планирования и процесса; 3) селектор Service, пустой EndpointSlice и метки Pod; 4) Events nodeName/PodScheduled в сравнении с Events образа у назначенного Pod; 5) uncordon; 6) CNI без применения политики; 7) данные PVC/PV; 8) весь локальный кластер и его локальное состояние.

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

Лабораторные завершены только при наличии записанных доказательств и объяснений. Следующий этап — итоговый проект.