Модуль 18 / 19
Evidence-first troubleshooting Kubernetes
Зміст
1. Цілі та результат
Ви опануєте **evidence-first troubleshooting**: локалізацію відмови, перевірку гіпотези та доказ відновлення.
- workload: status, exit code, logs
- network: Service та EndpointSlice
- storage: PVC, PV, binding
- scheduling: conditions, Events, labels
2. Теорія: симптом, причина та evidence
Сформулюйте expected state, зафіксуйте observed state та знайдіть першу розбіжність. CrashLoopBackOff, Pending і timeout — симптоми, а не root cause.
Не робіть restart до збору status, Events і logs: він знищує evidence.
3. Методичні вказівки
**Triage → Scope → Evidence → Hypothesis → Test → Repair → Verify → Prevent**
- фіксуйте UTC, context і namespace
- змінюйте одну причину за крок
- повторюйте початкову перевірку після repair
4. Baseline
Доведіть здоров’я кластера до створення інцидентів.
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 node5. Чотири навмисні відмови
Створіть ізольовані workload, network, storage та scheduling інциденти.
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.creationTimestamp6. Workload: CrashLoopBackOff
BackOff — наслідок. Причину доводять state, exit code та logs.
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=120s7. Network: Service без backend
ClusterIP не гарантує endpoints. Порівняйте selector і Pod labels.
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-app8. Storage: PVC Pending
На стенді немає dynamic provisioner, тому використано static PV. storageClassName claim незмінне.
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-data9. Scheduling: Pod Pending
До scheduling container ще не створено; шукайте причину в condition та FailedScheduling Event.
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=120s10. Evidence bundle та acceptance
Збережіть факти та повторіть end-to-end перевірку.
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-app11. Діагностичні маршрути
- 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. Контрольні питання та cleanup
- Чому BackOff — не причина?
- Як довести відсутність backend?
- Чому PVC не bindиться?
- Коли потрібен kubelet journal?
- Як довести відновлення?
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.