← К программе курса

Модуль 18 / 19

Evidence-first troubleshooting Kubernetes

KUBERNETES

Содержание
01

1. Цели и результат

Вы освоите **evidence-first troubleshooting**: локализацию отказа, проверку гипотезы и доказательство восстановления.

  • workload: status, exit code, logs
  • network: Service та EndpointSlice
  • storage: PVC, PV, binding
  • scheduling: conditions, Events, labels
02

2. Теория: симптом, причина и evidence

Сформулируйте expected state, зафиксируйте observed state и найдите первое расхождение. CrashLoopBackOff, Pending и timeout — симптомы, а не root cause.

Не выполняйте restart до сбора status, Events и logs: он уничтожает evidence.

03

3. Методические указания

**Triage → Scope → Evidence → Hypothesis → Test → Repair → Verify → Prevent**

  • фиксируйте UTC, context и namespace
  • меняйте одну причину за шаг
  • повторяйте исходную проверку после repair
04

4. Baseline

Докажите здоровье кластера до создания инцидентов.

terminal
date -u
kubectl config current-context
kubectl get nodes -o wide
kubectl get --raw='/readyz?verbose'
kubectl get events -A --sort-by=.metadata.creationTimestamp | tail -30
kubectl top node
05

5. Четыре намеренных отказа

Создайте изолированные workload, network, storage и scheduling инциденты.

terminal
mkdir -p /tmp/k8s-m18
cat > /tmp/k8s-m18/lab.yaml <<'YAML'
apiVersion: v1
kind: Namespace
metadata: {name: k8s-m18}
---
apiVersion: v1
kind: PersistentVolume
metadata: {name: k8s-m18-data}
spec:
  capacity: {storage: 128Mi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: m18-static
  hostPath: {path: /var/lib/k8s-m18-data, type: DirectoryOrCreate}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: broken-app, namespace: k8s-m18}
spec:
  replicas: 1
  selector: {matchLabels: {app: broken-app}}
  template:
    metadata: {labels: {app: broken-app}}
    spec:
      containers:
      - name: app
        image: busybox:1.37
        command: ["sh","-c","echo MODE=$MODE; test "$MODE" = production && exec httpd -f -p 8080"]
        env: [{name: MODE, value: staging}]
        ports: [{name: http, containerPort: 8080}]
---
apiVersion: v1
kind: Service
metadata: {name: broken-app, namespace: k8s-m18}
spec:
  selector: {app: wrong-label}
  ports: [{port: 80, targetPort: http}]
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: broken-data, namespace: k8s-m18}
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: class-does-not-exist
  resources: {requests: {storage: 128Mi}}
---
apiVersion: v1
kind: Pod
metadata: {name: unschedulable, namespace: k8s-m18}
spec:
  nodeSelector: {course.goretskiy.pro/disk: nvme-impossible}
  containers: [{name: app, image: busybox:1.37, command: ["sh","-c","sleep 3600"]}]
YAML
kubectl apply --dry-run=server -f /tmp/k8s-m18/lab.yaml
kubectl apply -f /tmp/k8s-m18/lab.yaml
sleep 15
kubectl -n k8s-m18 get deploy,pod,service,endpointslice,pvc -o wide
kubectl -n k8s-m18 get events --sort-by=.metadata.creationTimestamp
06

6. Workload: CrashLoopBackOff

BackOff — следствие. Причину доказывают state, exit code и logs.

terminal
pod=$(kubectl -n k8s-m18 get pod -l app=broken-app -o jsonpath='{.items[0].metadata.name}')
kubectl -n k8s-m18 get pod "$pod" -o jsonpath='phase={.status.phase}{" waiting="}{.status.containerStatuses[0].state.waiting.reason}{" restarts="}{.status.containerStatuses[0].restartCount}{" exit="}{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}'
kubectl -n k8s-m18 describe pod "$pod"
kubectl -n k8s-m18 logs "$pod" --previous --timestamps || kubectl -n k8s-m18 logs "$pod" --timestamps
kubectl -n k8s-m18 set env deployment/broken-app MODE=production
kubectl -n k8s-m18 rollout status deployment/broken-app --timeout=120s
07

7. Network: Service без backend

ClusterIP не гарантирует endpoints. Сравните selector и Pod labels.

terminal
kubectl -n k8s-m18 get service broken-app -o yaml
kubectl -n k8s-m18 get endpointslice -l kubernetes.io/service-name=broken-app
kubectl -n k8s-m18 get pods --show-labels
kubectl -n k8s-m18 patch service broken-app --type=merge -p '{"spec":{"selector":{"app":"broken-app"}}}'
kubectl -n k8s-m18 run client --image=curlimages/curl:8.17.0 --restart=Never --command -- sleep 3600
kubectl -n k8s-m18 wait --for=condition=Ready pod/client --timeout=120s
kubectl -n k8s-m18 exec client -- curl -fsS http://broken-app
08

8. Storage: PVC Pending

На стенде нет dynamic provisioner, поэтому используется static PV. storageClassName claim неизменяемо.

terminal
kubectl -n k8s-m18 describe pvc broken-data
kubectl get pv k8s-m18-data; kubectl get storageclass
kubectl -n k8s-m18 delete pvc broken-data
sed 's/class-does-not-exist/m18-static/' /tmp/k8s-m18/lab.yaml | kubectl apply -f -
kubectl -n k8s-m18 wait --for=jsonpath='{.status.phase}'=Bound pvc/broken-data --timeout=60s
kubectl -n k8s-m18 get pvc; kubectl get pv k8s-m18-data
09

9. Scheduling: Pod Pending

До scheduling container ещё не создан; ищите причину в condition и FailedScheduling Event.

terminal
kubectl -n k8s-m18 get pod unschedulable -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{" message="}{.message}{"\n"}{end}'
kubectl -n k8s-m18 describe pod unschedulable
kubectl -n k8s-m18 get events --field-selector involvedObject.name=unschedulable
kubectl -n k8s-m18 patch pod unschedulable --type=json -p='[{"op":"remove","path":"/spec/nodeSelector"}]' || true
kubectl -n k8s-m18 delete pod unschedulable
kubectl -n k8s-m18 run unschedulable --image=busybox:1.37 --restart=Never --command -- sleep 3600
kubectl -n k8s-m18 wait --for=condition=Ready pod/unschedulable --timeout=120s
10

10. Evidence bundle и acceptance

Сохраните факты и повторите end-to-end проверку.

terminal
dir=/tmp/k8s-m18/evidence-$(date -u +%Y%m%dT%H%M%SZ); mkdir -p "$dir"
kubectl -n k8s-m18 get all,pvc -o yaml > "$dir/objects.yaml"
kubectl -n k8s-m18 get events --sort-by=.metadata.creationTimestamp > "$dir/events.txt"
kubectl -n k8s-m18 logs deployment/broken-app --all-pods=true --prefix --tail=100 > "$dir/app.log" 2>&1 || true
kubectl get nodes -o wide > "$dir/nodes.txt"
kubectl get --raw='/readyz?verbose' > "$dir/readyz.txt"
find "$dir" -type f -printf '%f %s bytes\n' | sort
kubectl -n k8s-m18 get pod,service,endpointslice,pvc
kubectl -n k8s-m18 exec client -- curl -fsS http://broken-app
11

11. Диагностические маршруты

  • CrashLoop: state → exit → logs → config
  • No endpoints: selector → labels/readiness → EndpointSlice
  • PVC Pending: Events → class → PV/provisioner
  • Pod Pending: Events → selectors/requests/taints → Node
12

12. Контрольные вопросы и cleanup

  • Почему BackOff — не причина?
  • Как доказать отсутствие backend?
  • Почему PVC не bindится?
  • Когда нужен kubelet journal?
  • Как доказать восстановление?
terminal
kubectl delete namespace k8s-m18 --wait=true
kubectl delete pv k8s-m18-data --ignore-not-found
sudo rm -rf /var/lib/k8s-m18-data
rm -rf /tmp/k8s-m18
kubectl get nodes
kubectl get apiservice v1beta1.metrics.k8s.io

Модуль освоен, если четыре причины локализованы без случайных restart.