09. Наблюдаемость и диагностика на основе доказательств
Незнакомый термин? Откройте словарь Kubernetes и терминов курса.
Назначение, предварительные знания и результаты обучения
Нужны главы 00–08 и знакомство со всеми уровнями рабочей нагрузки. После главы вы сможете:
- разделить метрики, журналы, трассировки, Events и
statusобъекта; - выполнить воспроизводимую первичную диагностику от определения области до проверки исправления;
- использовать
get,describe,events,logs --previous,exec,debug,port-forward,auth can-i, JSONPath и команды rollout; - диагностировать Pending, CrashLoopBackOff, ImagePullBackOff, проверки, селектор/DNS/политику, OOM, RBAC и PVC;
- перейти к доказательствам на узле, не начиная с его перезапуска;
- оформить доказательства инцидента без учётных данных.
Ментальная модель: симптом не равен причине
Наблюдаемость — способность выводить внутреннее состояние из телеметрии:
- метрики отвечают «сколько и как меняется?» и подходят для сигналов тревоги и SLO;
- журналы содержат отдельные контекстные события;
- трассировки показывают путь одного запроса через сервисы;
- Kubernetes Events — краткоживущие наблюдения плоскости управления;
spec/status/conditionsобъекта — желаемое и наблюдаемое контроллером состояние;- аудит показывает, кто вызвал API, но не заменяет журнал запросов приложения.
Events не являются долговечным журналом событий, могут агрегироваться и
истекать. kubectl logs по умолчанию читает один контейнер и текущий экземпляр;
локальные журналы узла без централизованного конвейера могут исчезнуть вместе с
Pod или узлом.
Цикл диагностики:
определить влияние/время/масштаб
→ сравнить желаемое и наблюдаемое
→ собрать самые дешёвые данные
→ сформулировать одну проверяемую гипотезу
→ выполнить различающий тест
→ изменить источник истины
→ проверить путь пользователя + регрессии
→ очистить окружение и записать результат
Перезапуск Pod до сбора --previous, Events и состояния уничтожает
доказательства, а контроллер создаст тот же сломанный spec.
Полная наблюдаемая рабочая нагрузка
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-api-observe
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: observe-lab
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: observe-lab
template:
metadata:
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: observe-lab
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: /metrics
prometheus.io/port: "8000"
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: kube-python-course:1.0.0
ports:
- name: http
containerPort: 8000
readinessProbe:
httpGet: {path: /health/ready, port: http}
periodSeconds: 3
livenessProbe:
httpGet: {path: /health/live, port: http}
periodSeconds: 10
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {cpu: 500m, memory: 256Mi}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {sizeLimit: 64Mi}
- name: data
emptyDir: {sizeLimit: 64Mi}
Аннотации Prometheus — соглашение, а не встроенный сбор: нужен сборщик.
Учебное приложение предоставляет /metrics; в эксплуатационной среде используйте
поддерживаемую клиентскую библиотеку, ограниченный набор меток и метрики процесса и
среды исполнения. Не помещайте идентификатор пользователя или путь с
идентификаторами в метку: это повышает кардинальность, стоимость и риск
для приватности.
Базовый набор первичной диагностики
Область и инвентаризация
kubectl config current-context
kubectl get deployment,replicaset,pod,service,endpointslice,pvc,hpa,pdb \
-n kube-course -o wide
kubectl get events -n kube-course \
--sort-by=.metadata.creationTimestamp
Новый kubectl events -n kube-course --types=Warning удобен, но get events
переносимо. Смотрите время, число, причину, сообщение, UID и имя объекта.
Желаемое состояние и поля status и conditions
kubectl describe deployment python-api -n kube-course
kubectl get deployment python-api -n kube-course -o yaml
kubectl get pods -n kube-course \
-o custom-columns='NAME:.metadata.name,PHASE:.status.phase,READY:.status.conditions[?(@.type=="Ready")].status,RESTARTS:.status.containerStatuses[0].restartCount,NODE:.spec.nodeName'
kubectl get pod -n kube-course -l app.kubernetes.io/instance=course \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].state}{"\tlast="}{.status.containerStatuses[0].lastState}{"\n"}{end}'
Маскируйте Secret, окружение и токен перед передачей YAML. Предпочитайте точечный JSONPath.
Журналы
kubectl logs -n kube-course deployment/python-api \
--all-pods=true --prefix --since=10m
kubectl logs -n kube-course "$POD" -c api --tail=200 --timestamps
kubectl logs -n kube-course "$POD" -c api --previous --tail=200
--previous работает, только если предыдущий экземпляр контейнера ещё доступен.
Для Pod с несколькими контейнерами всегда указывайте -c.
Команды exec, debug и port-forward
kubectl exec -n kube-course "$POD" -c api -- \
python -c 'import socket; print(socket.getaddrinfo("python-api", 80))'
kubectl port-forward -n kube-course service/python-api 8080:80
curl --fail --silent http://127.0.0.1:8080/metrics
exec проверяет окружение контейнера, но может менять состояние и требует
сильных разрешений RBAC. Минимальные образы не обязаны иметь командную оболочку
и сетевые инструменты. Тогда используйте эфемерный контейнер:
kubectl debug -n kube-course -it pod/"$POD" \
--target=api \
--image=busybox:1.37.0 \
--profile=restricted
Эфемерный контейнер имеет стабильный статус с v1.25, но видимость процессов зависит
от среды исполнения и общего namespace процессов. Диагностический образ несёт
риски цепочки поставки и доступа к данным; нужны аудит и очистка.
kubectl debug node/<node> создаёт привилегированный диагностический Pod и
требует отдельного разрешения; это не первый шаг.
Авторизация и rollout
kubectl auth can-i get pods/log -n kube-course
kubectl auth can-i create pods/exec -n kube-course
kubectl rollout status deployment/python-api -n kube-course --timeout=60s
kubectl rollout history deployment/python-api -n kube-course
Доказательства с узла
Переходите к узлу, когда несколько несвязанных Pods на нём имеют симптомы, связанные с образом, средой исполнения, монтированием или сетью, либо conditions узла плохи:
kubectl describe node kube-course-worker
kubectl get node kube-course-worker \
-o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{"\n"}{end}'
kubectl get pods -A --field-selector spec.nodeName=kube-course-worker -o wide
kind export logs /tmp/kube-course-node-evidence --name kube-course
docker exec kube-course-worker crictl ps -a
docker exec kube-course-worker journalctl -u kubelet --since '15 minutes ago'
Последние команды специфичны для kind и не меняют состояние. В управляемой эксплуатационной среде доступ к узлу может быть запрещён; используйте журналы провайдера и пакет диагностических данных. Не публикуйте пакет без проверки на секреты.
Практическое упражнение: базовый набор доказательств
Подготовка
kubectl apply -k manifests/overlays/dev
kubectl rollout status deployment/python-api -n kube-course --timeout=180s
Если CNI по умолчанию не поддерживает NetworkPolicy, отсутствует контроллер Ingress или недоступны метрики, применение всё равно может принять объекты; базовую доступность оценивайте через Deployment, Service, EndpointSlice и port-forward.
Создайте /tmp/kube-course-evidence и записывайте только нечувствительный
вывод:
mkdir -p /tmp/kube-course-evidence
kubectl version > /tmp/kube-course-evidence/version.txt
kubectl get nodes -o wide > /tmp/kube-course-evidence/nodes.txt
kubectl get all -n kube-course -o wide > /tmp/kube-course-evidence/workloads.txt
kubectl get events -n kube-course \
--sort-by=.metadata.creationTimestamp \
> /tmp/kube-course-evidence/events.txt
kubectl get endpointslice -n kube-course -o wide \
> /tmp/kube-course-evidence/endpoints.txt
Проверка:
test -s /tmp/kube-course-evidence/version.txt
test -s /tmp/kube-course-evidence/nodes.txt
rg -n 'python-api' /tmp/kube-course-evidence
Ожидается код завершения 0. Не записывайте вывод
kubectl get secrets -o yaml, kubeconfig или полное окружение Pod. После
инцидента добавьте отметки времени UTC, точные команды, гипотезы, изменение или
откат и проверку пользовательского пути.
Признаки отказов
| Симптом | Первое доказательство | Различающая проверка |
|---|---|---|
Pending | condition PodScheduled, Events | запросы ресурсов и ограничения/PVC относительно узлов |
CrashLoopBackOff | restartCount, lastState, предыдущие журналы | завершение процесса или liveness |
ImagePullBackOff | Events | точный образ, DNS и авторизация реестра, кеш узла |
| Провал проверки | Events из describe и прямой вызов endpoint | неверный порт или путь либо задержка приложения |
| Несовпадение селектора | селектор Service и EndpointSlice | сравнение меток Pod |
| DNS или политика | запрос и политика в обе стороны | имя, IP Service и IP Pod |
OOMKilled | причина, код 137 и метрики памяти | лимит, рабочий набор или OOM узла |
| Forbidden | status API и auth can-i | аутентификация, RBAC или admission |
| Непривязанный PVC | фаза и Events PVC, StorageClass | provisioner, топология или ёмкость |
| Неудачный rollout | generation, ReplicaSet и Pods | отказ только новой версии, доступность старой |
Сломанный сценарий: ImagePullBackOff
kubectl apply -f manifests/broken/03-imagepull.yaml
kubectl get pod broken-imagepull -n kube-course -w
Ожидается переход ErrImagePull → ImagePullBackOff. Соберите:
kubectl describe pod broken-imagepull -n kube-course
kubectl get events -n kube-course \
--field-selector involvedObject.name=broken-imagepull
kubectl get pod broken-imagepull -n kube-course \
-o jsonpath='{.status.containerStatuses[0].state.waiting.reason}{"\n"}'
Журналы приложения отсутствуют, потому что процесс не был создан. Сравните
точный образ registry.invalid.example/python-api:9.9.9, DNS и авторизацию
реестра. Восстановление:
kubectl delete -f manifests/broken/03-imagepull.yaml
kubectl apply -f manifests/labs/pod.yaml
kubectl wait pod/python-api-pod -n kube-course \
--for=condition=Ready --timeout=120s
kubectl delete pod python-api-pod -n kube-course
Самостоятельная задача
Выберите шесть манифестов из manifests/broken/. Для каждого до применения
запишите ожидаемые уровень и симптом, три команды, опровержимую гипотезу,
исправление, проверку и очистку. Ограничение: ни разу не используйте перезапуск
или удаление как первую проверку. Критерий приёмки: коллега может восстановить
ход рассуждений по доказательствам.
Универсальное дерево диагностических решений
Путь пользователя нарушен
├─ объект API отсутствует/отклонён → контекст/namespace/аутентификация/схема/admission
├─ желаемое состояние нагрузки != наблюдаемому
│ ├─ нет Pods → владелец/контроллер/generation
│ ├─ Pending → scheduler/PVC Events
│ ├─ waiting → образ/конфигурация
│ ├─ перезапуски → предыдущие журналы/lastState/проверки/OOM
│ └─ Running, но не Ready → readiness/приложение
├─ нагрузка Ready, Service не работает
│ ├─ селектор/EndpointSlice
│ ├─ DNS
│ ├─ NetworkPolicy/CNI
│ └─ порт/плоскость данных
├─ Service работает, граница нет → контроллер/class/маршрут/TLS/LB
└─ затронуто несколько нагрузок/узел → conditions узла/среда исполнения/CNI/хранилище
Изменяйте по одному уровню. Вызов curl PodIP с хоста часто недействителен
из-за сети кластера; проверяйте из диагностического Pod.
Эксплуатационные компромиссы, безопасность и стоимость
Журналы, метрики и трассировки потребляют CPU, хранилище и egress; срок хранения и кардинальность имеют бюджет. Выборочное сохранение трассировок снижает стоимость, но выбор по хвостам и ошибкам лучше сохраняет инциденты. Сигналы тревоги SLO должны опираться на пользовательские симптомы — задержку, ошибки и доступность, а сигналы ресурсов объясняют риск. Диагностические разрешения и телеметрия содержат данные клиентов и секреты: обязательны RBAC, шифрование, срок хранения и аудит. Централизованный сборщик, Prometheus и OpenTelemetry — дополнения, а не ядро Kubernetes.
Самопроверка
- Почему Events недостаточно для аудита и истории?
- Когда нужны
logs --previous? - Что доказывает пустой EndpointSlice?
- Почему
port-forwardне является полной проверкой сети? - Когда переходить на уровень узла?
- Почему перезапуск первым действием вреден?
- Что надо проверить после исправления?
Ответы: 1) короткий срок хранения, агрегация и другая цель; 2) предыдущий
экземпляр контейнера завершился или перезапустился; 3) нет выбранных готовых
целевых ресурсов; 4) туннель обходит часть DNS, Service, плоскость данных и внешнюю точку
входа; 5) симптомы у нескольких Pods коррелируют с одним узлом или его
conditions; 6) теряются доказательства, а spec остаётся; 7) исходный
пользовательский путь, rollout, здоровье и регрессии, затем очистку.
Резюме и источники
Сильный специалист по диагностике локализует уровень доказательствами и проводит различающие проверки. Далее: управление конфигурацией.