콘텐츠로 이동

18. 트러블슈팅

배점 30% — 앞의 모든 장이 재료다

고치려 하지 말고, 먼저 “어느 층에서 끊겼는지”를 확정하라.

층은 넷이다.

1 · 애플리케이션

Pod 자체의 문제. describelogs-o yaml

2 · 노드

kubelet, 런타임, 자원. journalctl -u kubelet, df -h

3 · 컨트롤 플레인

API 서버, 스케줄러, 컨트롤러, etcd. crictl ps -a, 매니페스트

4 · 네트워크

Service, DNS, CNI, 정책. get endpoints, nslookup

각 층은 확인 명령이 다르다. 층을 잘못 짚으면 엉뚱한 곳을 파게 된다. 시험에서는 이 판별이 곧 시간이다.

문제를 열면 무조건 이 세 줄부터 친다. 20초면 층이 확정된다.

flowchart TD
    START["문제를 연다"] --> Q1{"kubectl get nodes<br/>응답이 오는가?"}
    Q1 -->|"안 온다"| L3["3 · 컨트롤 플레인 층<br/>crictl · journalctl 로 내려간다"]
    Q1 -->|"온다"| Q2{"노드가 전부<br/>Ready 인가?"}
    Q2 -->|"NotReady 가 있다"| L2["2 · 노드 층<br/>kubelet 과 CNI 를 본다"]
    Q2 -->|"전부 Ready"| Q3{"kubectl get pods -A<br/>전부 Running · Ready 인가?"}
    Q3 -->|"아니다"| L1["1 · 애플리케이션 층<br/>describe → logs"]
    Q3 -->|"Running 인데 통신이 안 된다"| L4["4 · 네트워크 층<br/>안에서 밖으로 한 겹씩"]

    classDef app fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef node fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef cp fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef net fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class L1 app
    class L2 node
    class L3 cp
    class L4 net
    class START mute
Terminal window
kubectl get nodes # Q1 · Q2
kubectl get pods -A | grep -vE 'Running|Completed' # Q3
상황 도구
왜 안 뜨는가 kubectl describe → Events
앱이 무슨 말을 하는가 kubectl logs, logs --previous
시간순으로 무슨 일이 있었나 kubectl get events --sort-by=.lastTimestamp
정확한 값이 뭔가 kubectl get ... -o yaml
자원을 얼마나 쓰나 kubectl top, describe node
안에서 확인하고 싶다 kubectl exec, kubectl debug, 임시 Pod
kubectl이 안 될 때 crictl, journalctl -u kubelet
노드 자체 systemctl status, df -h, free -m, dmesg

순서가 중요하다.

flowchart LR
    D["kubectl describe<br/>★ Events"] --> L["kubectl logs<br/>--previous"] --> Y["kubectl get -o yaml<br/>정확한 값"]
    SKIP["로그부터 본다"] -.->|"스케줄링 · 볼륨 · 이미지 문제는<br/>아무것도 안 나온다 ❌"| X["시간 낭비"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class D,L,Y ok
    class X bad
    class SKIP mute

describe(Events) → logs-o yaml. 로그부터 보면 스케줄링·볼륨·이미지 문제는 아무것도 안 나온다.

STATUS를 보는 순간 다음 명령이 정해져야 한다.

flowchart LR
    S["kubectl get pods"] --> ST{"STATUS"}

    ST -->|Pending| P1["스케줄링<br/>describe → Events"]
    ST -->|ContainerCreating| P2["볼륨 · 네트워크<br/>describe → Events"]
    ST -->|ImagePullBackOff| P3["이미지<br/>describe → Events"]
    ST -->|CrashLoopBackOff| P4["앱<br/>★ logs --previous"]
    ST -->|Error| P5["앱<br/>logs · exit code"]
    ST -->|OOMKilled| P6["리소스<br/>describe → Last State"]
    ST -->|CreateContainerConfigError| P7["설정<br/>ConfigMap/Secret 확인"]
    ST -->|"Running 인데 0/1"| P8["readinessProbe<br/>describe → Events"]
    ST -->|"Terminating 지속"| P9["finalizer · 노드<br/>get pod -o yaml"]
    ST -->|"Completed 반복"| P10["restartPolicy<br/>Job 여부 확인"]

    classDef sched fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef app fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef cfg fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class P1,P2,P3 sched
    class P4,P5,P6 app
    class P7,P8,P9,P10 cfg
    class S mute
STATUS 첫 명령
Pending 스케줄링 describe pod → Events
ContainerCreating 볼륨/네트워크 describe pod → Events
ImagePullBackOff 이미지 describe pod → Events
CrashLoopBackOff logs --previous
Error logs, exit code
OOMKilled 리소스 describe pod → Last State
CreateContainerConfigError 설정 describe pod → ConfigMap/Secret
Running 인데 0/1 readinessProbe describe pod → Events
Terminating 지속 finalizer / 노드 get pod -o yaml
Completed 반복 restartPolicy Job 여부 확인
Terminal window
kubectl describe pod web | grep -A20 Events

Events 메시지 하나가 원인을 그대로 지목한다.

flowchart LR
    E{"Events 메시지"}
    E -->|"Insufficient cpu/memory"| R1["request 여유 부족<br/>→ request 낮추거나 노드 추가"]
    E -->|"had untolerated taint"| R2["taint<br/>→ toleration 추가 또는 taint 제거"]
    E -->|"didn't match node affinity/selector"| R3["라벨 불일치<br/>→ 노드 라벨 또는 셀렉터 수정"]
    E -->|"node(s) were unschedulable"| R4["cordon<br/>→ kubectl uncordon"]
    E -->|"unbound immediate PersistentVolumeClaims"| R5["PVC 미바인딩<br/>→ 13장"]
    E -->|"didn't match pod anti-affinity rules"| R6["antiAffinity<br/>→ 7장"]
    E -->|"Events 자체가 없다"| R7["스케줄러가 죽었다<br/>또는 schedulerName 오타<br/>→ 컨트롤 플레인 확인"]

    classDef norm fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef alarm fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    class R1,R2,R3,R4,R5,R6 norm
    class R7 alarm
메시지 원인 조치
Insufficient cpu/memory request 여유 부족 request 낮추거나 노드 추가
had untolerated taint taint toleration 추가 또는 taint 제거
didn't match node affinity/selector 라벨 불일치 노드에 라벨 추가 또는 셀렉터 수정
node(s) were unschedulable cordon kubectl uncordon
unbound immediate PersistentVolumeClaims PVC 미바인딩 13장
didn't match pod anti-affinity rules antiAffinity 7장
Events 자체가 없다 스케줄러가 죽었다 또는 schedulerName 오타 컨트롤 플레인 확인
Terminal window
kubectl describe node node01 | grep -A8 'Allocated resources'
kubectl get nodes # SchedulingDisabled 표시
Terminal window
kubectl describe pod web | grep -A5 Events
# Failed to pull image "nginx:1.99": rpc error: ... not found
Events 내용 원인
not found / manifest unknown 이미지 이름·태그 오타
unauthorized / authentication required imagePullSecrets 없음
dial tcp: i/o timeout 네트워크·레지스트리 접근 불가
no such host 레지스트리 도메인 해석 실패
Terminal window
# 이미지 이름이 맞는지 확인
kubectl get pod web -o jsonpath='{.spec.containers[*].image}'
# 노드에서 직접 받아보기
sudo crictl pull nginx:1.27
# 고치기
kubectl set image deploy/web nginx=nginx:1.27
Terminal window
kubectl logs web --previous # ★ 죽기 직전 로그
kubectl describe pod web | grep -A15 'Last State'
# Last State: Terminated
# Reason: Error
# Exit Code: 1

exit code 하나로 절반이 좁혀진다.

flowchart LR
    C["CrashLoopBackOff"] --> LOG["logs --previous"]
    LOG --> Q{"로그가 있는가"}
    Q -->|"있다"| EC{"Exit Code"}
    Q -->|"비어 있는데 계속 죽는다"| PR["liveness 프로브 의심<br/>Events 에 Liveness probe failed 반복"]

    EC -->|0| E0["정상 종료했는데 restartPolicy: Always<br/>→ sleep 이 필요하거나 Job 이어야 한다"]
    EC -->|"1 · 2"| E1["앱 에러 → 로그를 읽는다"]
    EC -->|126| E126["실행 권한 없음 → command 경로·권한"]
    EC -->|127| E127["명령을 못 찾음<br/>→ command 오타 또는 바이너리 없음"]
    EC -->|137| E137["SIGKILL → OOMKilled 또는 liveness 실패"]
    EC -->|143| E143["SIGTERM → 정상 종료 신호"]

    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class E0,E127,E137 bad
    class E1,E126,E143,PR warn
    class C,LOG mute
Exit Code 의미 다음 행동
0 정상 종료했는데 restartPolicy: Always 명령이 즉시 끝난다 — sleep이 필요하거나 Job이어야 한다
1, 2 앱 에러 로그를 읽는다
126 실행 권한 없음 command 경로·권한
127 명령을 못 찾음 command 오타 또는 이미지에 그 바이너리가 없다
137 SIGKILL OOMKilled 또는 liveness 실패로 강제 종료
143 SIGTERM 정상 종료 신호
Terminal window
kubectl describe pod web | grep -A8 'Last State'
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
Terminal window
kubectl top pod web --containers # 실제 사용량
kubectl get pod web -o jsonpath='{.spec.containers[0].resources}'

조치 순서

  1. kubectl top으로 실제 사용량을 본다

  2. limits.memory를 실사용의 1.5~2배로 올린다

  3. 그래도 계속 오르면 앱의 메모리 누수다 — 인프라 문제가 아니다

둘은 다른 사건이다.

flowchart LR
    A["컨테이너가 limits.memory 초과"] --> A1["OOMKilled<br/>해당 컨테이너만 재시작"]
    B["노드 전체 메모리 부족"] --> B1["kubelet 이 Pod 을 축출<br/>Evicted · QoS 순서대로"]

    classDef c1 fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef c2 fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class A1 c1
    class B1 c2
    class A,B mute
Terminal window
kubectl describe pod web | grep -A20 Events
메시지 원인
MountVolume.SetUp failed ... not found ConfigMap/Secret이 없다
Unable to attach or mount volumes PV/CSI 문제 (13장)
Multi-Attach error RWO 볼륨을 두 노드에서
failed to create pod sandbox ... cni CNI 문제
network plugin is not ready CNI Pod이 안 떴다
Terminal window
# 볼륨 관련
kubectl get cm,secret -n <ns>
kubectl get pvc,pv
# CNI 관련
kubectl get pods -n kube-system | grep -Ei 'calico|cilium|flannel'
ls /etc/cni/net.d/
sudo journalctl -u kubelet | grep -i cni | tail -20
Terminal window
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# web 0/1 Running 0 3m
kubectl describe pod web | grep -A10 Events
# Warning Unhealthy Readiness probe failed: HTTP probe failed with statuscode: 404

컨테이너는 살아 있는데 Service에서 빠진다. 그래서 “연결이 안 된다”로 보인다.

flowchart LR
    P["Pod<br/>Running · 0/1"] -->|"readinessProbe 실패"| NR["Ready 조건 False"]
    NR --> EP["EndpointSlice 에서 제외"]
    EP --> SVC["Service<br/>엔드포인트가 비어 있다"]
    SVC --> C["클라이언트<br/>연결 실패 ❌"]

    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class C bad
    class NR,EP,SVC warn
    class P mute
  • readinessProbe가 실패하고 있다 — 컨테이너는 살아 있다
  • 결과: Service 엔드포인트에서 빠진다 → 트래픽이 안 온다
Terminal window
# 프로브 설정 확인
kubectl get pod web -o jsonpath='{.spec.containers[0].readinessProbe}'
# 안에서 직접 호출해본다
kubectl exec -it web -- wget -qO- http://localhost:8080/healthz
Terminal window
kubectl get pod web -o yaml | grep -A5 finalizers
kubectl get ns stuck -o yaml | grep -A5 finalizers
원인 조치
finalizer가 남아 있고 처리할 컨트롤러가 없다 finalizer 제거
노드가 unreachable 노드 복구 또는 강제 삭제
preStop 훅이 안 끝난다 grace period 확인
Terminal window
# Pod 강제 삭제
kubectl delete pod web --force --grace-period=0
# finalizer 제거
kubectl patch pod web -p '{"metadata":{"finalizers":null}}' --type=merge
# 네임스페이스가 Terminating에서 안 끝날 때
kubectl get ns stuck -o json \
| jq '.spec.finalizers = []' \
| kubectl replace --raw "/api/v1/namespaces/stuck/finalize" -f -

로그 다루기 — 커리큘럼의 “컨테이너 출력 스트림”

섹션 제목: “로그 다루기 — 커리큘럼의 “컨테이너 출력 스트림””
Terminal window
kubectl logs web
kubectl logs web -c sidecar # 멀티 컨테이너
kubectl logs web --previous # 이전 컨테이너
kubectl logs web -f --tail=100
kubectl logs web --since=15m --timestamps
kubectl logs -l app=web --all-containers --prefix --max-log-requests=10
kubectl logs deploy/web # 컨트롤러 지정
kubectl logs job/import

로그가 어디를 거쳐 오는지 알면 kubectl이 죽었을 때 어디를 봐야 하는지도 안다.

flowchart LR
    APP["컨테이너<br/>stdout · stderr"] --> F["/var/log/pods/&lt;ns&gt;_&lt;pod&gt;_&lt;uid&gt;/&lt;container&gt;/0.log"]
    F --> SYM["/var/log/containers/<br/>심볼릭 링크"]
    F --> KL["kubelet"] --> API["kube-apiserver"] --> KC["kubectl logs"]
    FILE["앱이 파일에 쓴다"] -.->|"kubectl 로 안 보인다 ❌"| X["stdout 이 아니다"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class KC,SYM ok
    class X bad
    class APP,F,KL,API,FILE mute
  • 컨테이너는 stdout / stderr로 로그를 낸다. 파일에 쓰면 kubectl로 안 보인다
  • 실체: 노드의 /var/log/pods/<ns>_<pod>_<uid>/<container>/0.log
  • /var/log/containers/ 는 거기로 향하는 심볼릭 링크다
Terminal window
# kubectl이 안 될 때 노드에서 직접
sudo ls /var/log/containers/
sudo tail -f /var/log/containers/kube-apiserver-*.log
sudo crictl logs <container-id>
Terminal window
kubectl top nodes
kubectl top pods -A --sort-by=memory
kubectl top pod web --containers
kubectl describe node node01 | grep -A10 'Allocated resources'
kubectl get pods -A -o wide --field-selector spec.nodeName=node01
Terminal window
# 노드에서 직접
df -h # 디스크
free -m # 메모리
top # CPU
sudo journalctl -u kubelet | grep -i evict
Terminal window
kubectl get nodes
kubectl describe node node01 | grep -A15 Conditions
flowchart TD
    N["노드가 NotReady"] --> C{"describe node → Conditions"}
    C -->|"Ready: False<br/>cni plugin not initialized"| C1["CNI 미설치 · 고장<br/>→ CNI Pod 상태, /etc/cni/net.d/"]
    C -->|"Ready: Unknown<br/>NodeStatusUnknown"| C2["kubelet 이 보고를 멈췄다<br/>→ ssh 후 journalctl -u kubelet"]
    C -->|"MemoryPressure: True"| C3["메모리 부족 → 축출 시작"]
    C -->|"DiskPressure: True"| C4["디스크 부족 → 이미지 GC, 축출"]
    C -->|"PIDPressure: True"| C5["프로세스 수 초과"]

    C2 --> J{"journalctl 로그"}
    J -->|"running with swap on"| S1["swapoff -a"]
    J -->|"cgroup driver … != …"| S2["containerd SystemdCgroup = true"]
    J -->|"x509: certificate has expired"| S3["kubeadm certs renew"]
    J -->|"connection refused"| S4["API 서버가 죽었다<br/>→ 컨트롤 플레인 층으로"]

    classDef net fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
    classDef node fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef cp fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class C1 net
    class C2,C3,C4,C5,S1,S2,S3 node
    class S4 cp
    class N mute
Condition 원인
Ready: False, KubeletNotReady, cni plugin not initialized CNI 미설치/고장
Ready: Unknown, NodeStatusUnknown kubelet이 보고를 멈췄다
MemoryPressure: True 메모리 부족 → 축출 시작
DiskPressure: True 디스크 부족 → 이미지 GC, 축출
PIDPressure: True 프로세스 수 초과
Terminal window
# 노드에 들어가서
ssh node01
sudo systemctl status kubelet
sudo journalctl -u kubelet -n 100 --no-pager # ★ 여기에 이유가 있다
sudo systemctl status containerd
df -h /var/lib/kubelet /var/lib/containerd
free -m
sudo swapon --show # 스왑이 켜졌나
Terminal window
sudo systemctl status kubelet
sudo journalctl -u kubelet -n 50 --no-pager
로그 메시지 원인 조치
failed to run Kubelet: running with swap on 스왑 swapoff -a
misconfiguration: kubelet cgroup driver ... != ... cgroup 드라이버 불일치 containerd SystemdCgroup = true
x509: certificate has expired 인증서 만료 kubeadm certs renew
Unable to register node ... connection refused API 서버가 죽었다 컨트롤 플레인을 본다
open /var/lib/kubelet/config.yaml: no such file 설정 파일 없음 kubeadm 재초기화 필요
failed to load kubelet config file YAML 문법 오류 파일을 되돌린다
Terminal window
sudo systemctl restart kubelet
sudo systemctl enable kubelet # 부팅 시 자동 시작
sudo systemctl daemon-reload # 설정 파일을 바꿨다면
Terminal window
kubectl get nodes
# The connection to the server 192.168.1.10:6443 was refused

순서대로 확인한다.

  1. kubeconfig가 맞는가

    Terminal window
    kubectl config current-context
    ls -l ~/.kube/config
    kubectl cluster-info
  2. API 서버 컨테이너가 도는가 — ★ 컨트롤 플레인 노드에서

    Terminal window
    sudo crictl ps -a | grep kube-apiserver
  3. 죽었다면 왜 죽었는가

    Terminal window
    sudo crictl logs $(sudo crictl ps -a --name kube-apiserver -q | head -1)
  4. kubelet이 매니페스트를 읽고 있는가

    Terminal window
    sudo journalctl -u kubelet -n 50 --no-pager | grep -i apiserver
  5. 매니페스트 자체 확인

    Terminal window
    sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml

아래가 죽으면 위가 전부 죽는다. 그래서 고치는 순서가 정해진다.

flowchart BT
    DISK["노드 디스크 · systemd"] --> KLT["kubelet"]
    KLT --> ETCD["etcd<br/>스태틱 Pod"]
    ETCD --> API["kube-apiserver"]
    API --> SCH["kube-scheduler"]
    API --> CM["kube-controller-manager"]
    API --> KC["kubectl · 모든 조작"]

    classDef base fill:#f1f5f9,stroke:#94a3b8,color:#334155
    classDef core fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef up fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    class DISK,KLT base
    class ETCD,API core
    class SCH,CM,KC up

etcd가 죽으면 API 서버도 못 뜬다. API 서버 로그에 etcd 관련 에러가 있으면 etcd부터 고쳐야 한다. 위에서부터 고치려 들면 시간만 버린다.

원인 증상 조치
매니페스트 YAML 문법 오류 컨테이너가 아예 안 생긴다 journalctl -u kubelet에 파싱 에러
잘못된 플래그 컨테이너가 즉시 죽는다 crictl logsunknown flag
etcd에 못 붙는다 connection refused 반복 etcd 컨테이너 확인
인증서 만료/경로 오류 x509 에러 kubeadm certs check-expiration
포트 충돌 bind: address already in use 6443을 쓰는 프로세스 확인
Terminal window
sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/backup.yaml # 먼저 백업
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
watch sudo crictl ps # 다시 뜨는지 지켜본다

스케줄러 · 컨트롤러 매니저가 죽었을 때

섹션 제목: “스케줄러 · 컨트롤러 매니저가 죽었을 때”

증상이 다르다. 이 비대칭이 판별의 실마리다.

flowchart LR
    D["Deployment 를 만든다"] --> Q1{"ReplicaSet · Pod 이<br/>생기는가"}
    Q1 -->|"안 생긴다"| CM["kube-controller-manager 가 죽었다<br/>노드 상태도 갱신 안 됨"]
    Q1 -->|"생긴다"| Q2{"Pod 이 스케줄되는가"}
    Q2 -->|"영원히 Pending<br/>Events 가 비어 있다"| SCH["kube-scheduler 가 죽었다"]
    Q2 -->|"Running"| OK["둘 다 정상 ✅"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class OK ok
    class CM,SCH bad
    class D mute
죽은 것 증상
kube-scheduler 새 Pod이 영원히 Pending. Events가 비어 있다
kube-controller-manager Deployment를 만들어도 ReplicaSet/Pod이 안 생긴다. 노드 상태가 갱신 안 됨
Terminal window
kubectl get pods -n kube-system | grep -E 'scheduler|controller'
kubectl logs -n kube-system kube-scheduler-controlplane
sudo crictl ps -a | grep -E 'scheduler|controller'
sudo cat /etc/kubernetes/manifests/kube-scheduler.yaml
Terminal window
# 스케줄러가 죽은 상태에서 Pod을 띄워야 한다면
kubectl run web --image=nginx --dry-run=client -o yaml > pod.yaml
# spec.nodeName: node01 을 추가한 뒤
kubectl apply -f pod.yaml

컨트롤 플레인 컴포넌트의 로그는 kubectl logs -n kube-system으로 볼 수 있다 — API 서버가 살아 있을 때만. 아니면 crictl logs.

Terminal window
sudo crictl ps -a | grep etcd
sudo crictl logs $(sudo crictl ps -a --name etcd -q | head -1) 2>&1 | tail -30
sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
endpoint health --cluster
증상 원인
database space exceeded 쿼터 초과 → compact + defrag 필요
no leader / context deadline exceeded 과반이 안 산다 — 멤버를 확인
API 서버 로그의 etcdserver: request timed out etcd가 느리다 (디스크 I/O)
데이터 디렉터리 권한 오류 복구 후 chown 누락 (15장)

etcd가 죽으면 API 서버도 못 뜬다. API 서버 로그에 etcd 관련 에러가 있으면 etcd부터 고쳐야 한다.

네트워킹 진단 — 안에서 밖으로

섹션 제목: “네트워킹 진단 — 안에서 밖으로”

한 겹씩 벗긴다. 처음 실패하는 겹이 곧 원인이다.

flowchart TD
    L1["① 앱이 포트를 열었는가<br/>exec → localhost:8080"] -->|실패| F1["앱 · 포트 설정"]
    L1 -->|성공| L2["② Pod IP 로 직접<br/>임시 Pod → 10.244.1.5:8080"]
    L2 -->|실패| F2["CNI · NetworkPolicy"]
    L2 -->|성공| L3["③ Service ClusterIP 로<br/>get endpoints 먼저 확인"]
    L3 -->|"엔드포인트가 비어 있다"| F3["라벨 불일치 · Pod 미Ready"]
    L3 -->|"엔드포인트는 있는데 안 된다"| F3b["kube-proxy"]
    L3 -->|성공| L4["④ DNS 이름으로<br/>nslookup web-svc"]
    L4 -->|실패| F4["CoreDNS · dnsPolicy"]
    L4 -->|성공| L5["⑤ 외부에서<br/>NodePort · Ingress"]
    L5 -->|실패| F5["NodePort 범위 · 방화벽 · LB · Ingress 규칙"]
    L5 -->|성공| OK["정상 ✅"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef step fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    class OK ok
    class F1,F2,F3,F3b,F4,F5 bad
    class L1,L2,L3,L4,L5 step
Terminal window
# ① 앱이 포트를 열었는가
kubectl exec -it web -- wget -qO- http://localhost:8080
# ② Pod IP로 직접
kubectl get pod web -o wide
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- wget -qO- http://10.244.1.5:8080
# ③ Service(ClusterIP)로
kubectl get endpoints web-svc # ★ 비어 있으면 여기가 문제
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- wget -qO- http://web-svc:80
# ④ DNS로
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- nslookup web-svc
# ⑤ 외부에서
curl http://<노드IP>:30080
kubectl describe ingress web
증상 원인 후보 확인
엔드포인트가 비어 있다 라벨 불일치 / Pod 미Ready describe svc, get pods --show-labels
ClusterIP만 안 된다 kube-proxy 문제 kubectl get ds kube-proxy -n kube-system
이름만 안 된다 CoreDNS nslookup kubernetes.default
특정 Pod끼리만 안 된다 NetworkPolicy kubectl get netpol -A
노드 간 Pod 통신이 안 된다 CNI CNI Pod 상태, /etc/cni/net.d/
외부에서만 안 된다 NodePort 범위 / 방화벽 / LB describe svc
Ingress에서 404 / 503 규칙·백엔드 Service describe ingress, 컨트롤러 로그
egress가 전부 안 된다 NetworkPolicy의 DNS 미허용 12장

kube-proxy가 죽으면 Pod-to-Pod 직접 통신은 정상이고 ClusterIP만 죽는다. 이 비대칭이 판별의 실마리다.

Terminal window
sudo kubeadm certs check-expiration
# 개별 인증서 확인
sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A2 Validity
sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A3 'Subject Alternative Name'
에러 원인
x509: certificate has expired 만료 → kubeadm certs renew all
x509: certificate signed by unknown authority CA 불일치 — kubeconfig의 CA 확인
x509: cannot validate certificate for <IP> SAN에 그 IP가 없다
Unauthorized (401) 토큰/인증서가 유효하지 않다
Terminal window
sudo kubeadm certs renew all
# 컨트롤 플레인 Pod 재시작 (매니페스트를 잠시 옮겼다 되돌린다)
sudo mv /etc/kubernetes/manifests /tmp/m && sleep 20 && sudo mv /tmp/m /etc/kubernetes/manifests
# admin.conf도 갱신되었으므로 다시 복사
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
Terminal window
kubectl get pods -A --field-selector status.phase=Failed
kubectl get events -A --field-selector reason=Evicted
kubectl describe node node01 | grep -A5 Conditions
  • DiskPressure → kubelet이 이미지 GC를 하고, 그래도 부족하면 Pod을 축출한다
  • MemoryPressureQoS 순서로 축출 (BestEffort → Burstable → Guaranteed)
  • 축출된 Pod은 Failed 상태로 남는다. 상위 컨트롤러가 새로 만든다
flowchart LR
    MP["MemoryPressure: True"] --> E1["BestEffort<br/>request·limit 없음"] --> E2["Burstable<br/>request &lt; limit"] --> E3["Guaranteed<br/>request = limit"]

    classDef first fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mid fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef last fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class E1 first
    class E2 mid
    class E3 last
    class MP mute

왼쪽부터 축출된다. Guaranteed가 가장 오래 버틴다.

Terminal window
# 노드에서 공간 확보
sudo crictl rmi --prune # 안 쓰는 이미지 삭제
sudo journalctl --vacuum-size=200M # 저널 정리
df -h /var/lib/containerd /var/log
# 축출된 Pod 정리
kubectl delete pods --field-selector status.phase=Failed -A

종합 시나리오 — 자주 나오는 유형

섹션 제목: “종합 시나리오 — 자주 나오는 유형”
문제 진단 경로 조치
노드 하나가 NotReady journalctl -u kubelet 스왑/cgroup/서비스 재시작
앱이 계속 재시작 logs --previous + exit code limit 상향 또는 command 수정
Service 연결 실패 get endpoints 라벨 수정 / probe 수정
이름 해석 실패 nslookup kubernetes.default CoreDNS 확인
Pod이 계속 Pending describe pod Events taint/affinity/자원/PVC
kubectl이 죽었다 crictl ps -a 매니페스트 복구
클러스터를 되돌려야 한다 etcd 스냅샷 restore + etcd.yaml 수정
특정 사용자만 403 에러 메시지 RoleBinding 추가
Terminal window
# 층별 검증
kubectl get nodes # 전부 Ready
kubectl get pods -A | grep -vE 'Running|Completed' # 비정상 Pod 없음
kubectl get events -A --sort-by=.lastTimestamp | tail -20
# 대상 리소스 직접 확인
kubectl rollout status deploy/web
kubectl get endpoints web-svc
kubectl wait --for=condition=ready pod -l app=web --timeout=60s
# 실제로 동작하는지
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- wget -qO- http://web-svc
  • 고치기 전에 층을 확정한다get nodes / get nodes Ready / get pods -A 세 줄
  • 순서는 describe(Events) → logs-o yaml. 로그부터 보면 안 되는 문제가 많다
  • logs --previousget events --sort-by 가 두 기둥
  • exit code로 좁힌다: 0=명령이 끝남, 127=명령 없음, 137=OOM/강제종료
  • Running 0/1 = readinessProbe 실패 → 엔드포인트에서 빠진다
  • kubectl이 죽으면 crictl + journalctl -u kubelet
  • 컨트롤 플레인 고장은 거의 항상 /etc/kubernetes/manifests/의 값 하나
  • kube-proxy가 죽으면 ClusterIP만 죽는다 (Pod 직접 통신은 정상)
  • etcd가 죽으면 API 서버도 못 뜬다 — 순서대로 고친다
  • 고친 뒤에는 반드시 최종 상태를 확인한다