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», а сможете:
- создать и пересоздать конкретный локальный кластер и образ;
- развернуть Pod → Deployment → Service → защищённую рабочую нагрузку;
- проверить конфигурацию и её обновление, проверки, ресурсы, 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 смешаются.
| № | Сценарий | Ожидаемый признак | Обязательные доказательства | Восстановление |
|---|---|---|---|---|
| 1 | 01-pending-cpu.yaml | Pending | PodScheduled, FailedScheduling | удалить; задать реалистичный запрос ресурсов или ёмкость |
| 2 | 02-crashloop.yaml | CrashLoopBackOff | текущие и предыдущие журналы, код 42 | удалить Deployment или исправить команду |
| 3 | 03-imagepull.yaml | ImagePullBackOff | Events, точный образ и причина ожидания | удалить или исправить образ и авторизацию |
| 4 | 04-service-selector.yaml | пустые endpoints | селектор и метки Pod | исправить селектор, проверить HTTP |
| 5 | 05-probe.yaml | перезапуски | Events Unhealthy, restartCount | порт http/8000 |
| 6 | 06-oom.yaml | OOMKilled | причина и код 137 | подобрать размер и проанализировать код |
| 7 | 07-pvc.yaml | PVC Pending | Events PVC и StorageClass | пересоздать с реальным классом |
| 8 | 08-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, доказательств резервного копирования и восстановления и регламента провайдера.
Самопроверка
- Какая лабораторная доказывает замену контроллером?
- Чем серверный dry-run отличается от фактической среды исполнения?
- Где находятся доказательства несовпадения селектора?
- Как отличить неудачное планирование от ошибки загрузки образа?
- Что сделать после неудачного drain?
- Почему отрицательная проверка NetworkPolicy может дать ложный успех?
- Какие данные переживают замену Pod в лабораторной 7?
- Что удаляет финальная очистка kind?
Ответы: 1) лабораторная 2; 2) admission и назначение значений по умолчанию без планирования и
процесса; 3) селектор Service, пустой EndpointSlice и метки Pod; 4) Events
nodeName/PodScheduled в сравнении с Events образа у назначенного Pod; 5)
uncordon; 6) CNI без применения политики; 7) данные PVC/PV; 8) весь локальный
кластер и его локальное состояние.
Резюме и источники
Лабораторные завершены только при наличии записанных доказательств и объяснений. Следующий этап — итоговый проект.