Kube/Pythonproduction course
Курс · RU09-observability-and-troubleshooting.md

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, точные команды, гипотезы, изменение или откат и проверку пользовательского пути.

Признаки отказов

СимптомПервое доказательствоРазличающая проверка
Pendingcondition PodScheduled, Eventsзапросы ресурсов и ограничения/PVC относительно узлов
CrashLoopBackOffrestartCount, lastState, предыдущие журналызавершение процесса или liveness
ImagePullBackOffEventsточный образ, DNS и авторизация реестра, кеш узла
Провал проверкиEvents из describe и прямой вызов endpointневерный порт или путь либо задержка приложения
Несовпадение селектораселектор Service и EndpointSliceсравнение меток Pod
DNS или политиказапрос и политика в обе стороныимя, IP Service и IP Pod
OOMKilledпричина, код 137 и метрики памятилимит, рабочий набор или OOM узла
Forbiddenstatus API и auth can-iаутентификация, RBAC или admission
Непривязанный PVCфаза и Events PVC, StorageClassprovisioner, топология или ёмкость
Неудачный rolloutgeneration, ReplicaSet и Podsотказ только новой версии, доступность старой

Сломанный сценарий: ImagePullBackOff

kubectl apply -f manifests/broken/03-imagepull.yaml
kubectl get pod broken-imagepull -n kube-course -w

Ожидается переход ErrImagePullImagePullBackOff. Соберите:

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.

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

  1. Почему Events недостаточно для аудита и истории?
  2. Когда нужны logs --previous?
  3. Что доказывает пустой EndpointSlice?
  4. Почему port-forward не является полной проверкой сети?
  5. Когда переходить на уровень узла?
  6. Почему перезапуск первым действием вреден?
  7. Что надо проверить после исправления?

Ответы: 1) короткий срок хранения, агрегация и другая цель; 2) предыдущий экземпляр контейнера завершился или перезапустился; 3) нет выбранных готовых целевых ресурсов; 4) туннель обходит часть DNS, Service, плоскость данных и внешнюю точку входа; 5) симптомы у нескольких Pods коррелируют с одним узлом или его conditions; 6) теряются доказательства, а spec остаётся; 7) исходный пользовательский путь, rollout, здоровье и регрессии, затем очистку.

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

Сильный специалист по диагностике локализует уровень доказательствами и проводит различающие проверки. Далее: управление конфигурацией.