1 · 애플리케이션
Pod 자체의 문제.
describe → logs → -o yaml
배점 30% — 앞의 모든 장이 재료다
고치려 하지 말고, 먼저 “어느 층에서 끊겼는지”를 확정하라.
층은 넷이다.
1 · 애플리케이션
Pod 자체의 문제.
describe → logs → -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
kubectl get nodes # Q1 · Q2kubectl 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 여부 확인 |
kubectl describe pod web | grep -A20 EventsEvents 메시지 하나가 원인을 그대로 지목한다.
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 오타 |
컨트롤 플레인 확인 |
kubectl describe node node01 | grep -A8 'Allocated resources'kubectl get nodes # SchedulingDisabled 표시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 |
레지스트리 도메인 해석 실패 |
# 이미지 이름이 맞는지 확인kubectl get pod web -o jsonpath='{.spec.containers[*].image}'
# 노드에서 직접 받아보기sudo crictl pull nginx:1.27
# 고치기kubectl set image deploy/web nginx=nginx:1.27kubectl logs web --previous # ★ 죽기 직전 로그kubectl describe pod web | grep -A15 'Last State'# Last State: Terminated# Reason: Error# Exit Code: 1exit 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 | 정상 종료 신호 |
kubectl describe pod web | grep -A8 'Last State'# Last State: Terminated# Reason: OOMKilled# Exit Code: 137kubectl top pod web --containers # 실제 사용량kubectl get pod web -o jsonpath='{.spec.containers[0].resources}'조치 순서
kubectl top으로 실제 사용량을 본다
limits.memory를 실사용의 1.5~2배로 올린다
그래도 계속 오르면 앱의 메모리 누수다 — 인프라 문제가 아니다
둘은 다른 사건이다.
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
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이 안 떴다 |
# 볼륨 관련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 -20kubectl 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
# 프로브 설정 확인kubectl get pod web -o jsonpath='{.spec.containers[0].readinessProbe}'
# 안에서 직접 호출해본다kubectl exec -it web -- wget -qO- http://localhost:8080/healthzkubectl get pod web -o yaml | grep -A5 finalizerskubectl get ns stuck -o yaml | grep -A5 finalizers| 원인 | 조치 |
|---|---|
| finalizer가 남아 있고 처리할 컨트롤러가 없다 | finalizer 제거 |
| 노드가 unreachable | 노드 복구 또는 강제 삭제 |
preStop 훅이 안 끝난다 |
grace period 확인 |
# 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 -kubectl logs webkubectl logs web -c sidecar # 멀티 컨테이너kubectl logs web --previous # 이전 컨테이너kubectl logs web -f --tail=100kubectl logs web --since=15m --timestampskubectl logs -l app=web --all-containers --prefix --max-log-requests=10kubectl logs deploy/web # 컨트롤러 지정kubectl logs job/import로그가 어디를 거쳐 오는지 알면 kubectl이 죽었을 때 어디를 봐야 하는지도 안다.
flowchart LR
APP["컨테이너<br/>stdout · stderr"] --> F["/var/log/pods/<ns>_<pod>_<uid>/<container>/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
/var/log/pods/<ns>_<pod>_<uid>/<container>/0.log/var/log/containers/ 는 거기로 향하는 심볼릭 링크다# kubectl이 안 될 때 노드에서 직접sudo ls /var/log/containers/sudo tail -f /var/log/containers/kube-apiserver-*.logsudo crictl logs <container-id>kubectl top nodeskubectl top pods -A --sort-by=memorykubectl top pod web --containers
kubectl describe node node01 | grep -A10 'Allocated resources'kubectl get pods -A -o wide --field-selector spec.nodeName=node01# 노드에서 직접df -h # 디스크free -m # 메모리top # CPUsudo journalctl -u kubelet | grep -i evictkubectl get nodeskubectl describe node node01 | grep -A15 Conditionsflowchart 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 |
프로세스 수 초과 |
# 노드에 들어가서ssh node01sudo systemctl status kubeletsudo journalctl -u kubelet -n 100 --no-pager # ★ 여기에 이유가 있다sudo systemctl status containerddf -h /var/lib/kubelet /var/lib/containerdfree -msudo swapon --show # 스왑이 켜졌나sudo systemctl status kubeletsudo 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 문법 오류 | 파일을 되돌린다 |
sudo systemctl restart kubeletsudo systemctl enable kubelet # 부팅 시 자동 시작sudo systemctl daemon-reload # 설정 파일을 바꿨다면kubectl get nodes# The connection to the server 192.168.1.10:6443 was refused순서대로 확인한다.
kubeconfig가 맞는가
kubectl config current-contextls -l ~/.kube/configkubectl cluster-infoAPI 서버 컨테이너가 도는가 — ★ 컨트롤 플레인 노드에서
sudo crictl ps -a | grep kube-apiserver죽었다면 왜 죽었는가
sudo crictl logs $(sudo crictl ps -a --name kube-apiserver -q | head -1)kubelet이 매니페스트를 읽고 있는가
sudo journalctl -u kubelet -n 50 --no-pager | grep -i apiserver매니페스트 자체 확인
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 logs에 unknown flag |
| etcd에 못 붙는다 | connection refused 반복 |
etcd 컨테이너 확인 |
| 인증서 만료/경로 오류 | x509 에러 |
kubeadm certs check-expiration |
| 포트 충돌 | bind: address already in use |
6443을 쓰는 프로세스 확인 |
sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/backup.yaml # 먼저 백업sudo vim /etc/kubernetes/manifests/kube-apiserver.yamlwatch 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이 안 생긴다. 노드 상태가 갱신 안 됨 |
kubectl get pods -n kube-system | grep -E 'scheduler|controller'kubectl logs -n kube-system kube-scheduler-controlplanesudo crictl ps -a | grep -E 'scheduler|controller'sudo cat /etc/kubernetes/manifests/kube-scheduler.yaml# 스케줄러가 죽은 상태에서 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.
sudo crictl ps -a | grep etcdsudo 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
# ① 앱이 포트를 열었는가kubectl exec -it web -- wget -qO- http://localhost:8080
# ② Pod IP로 직접kubectl get pod web -o widekubectl 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>:30080kubectl 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만 죽는다. 이 비대칭이 판별의 실마리다.
sudo kubeadm certs check-expiration
# 개별 인증서 확인sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A2 Validitysudo 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) |
토큰/인증서가 유효하지 않다 |
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/configsudo chown $(id -u):$(id -g) ~/.kube/configkubectl get pods -A --field-selector status.phase=Failedkubectl get events -A --field-selector reason=Evictedkubectl describe node node01 | grep -A5 ConditionsFailed 상태로 남는다. 상위 컨트롤러가 새로 만든다flowchart LR
MP["MemoryPressure: True"] --> E1["BestEffort<br/>request·limit 없음"] --> E2["Burstable<br/>request < 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가 가장 오래 버틴다.
# 노드에서 공간 확보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 추가 |
# 층별 검증kubectl get nodes # 전부 Readykubectl get pods -A | grep -vE 'Running|Completed' # 비정상 Pod 없음kubectl get events -A --sort-by=.lastTimestamp | tail -20
# 대상 리소스 직접 확인kubectl rollout status deploy/webkubectl get endpoints web-svckubectl 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-svcget nodes / get nodes Ready / get pods -A 세 줄describe(Events) → logs → -o yaml. 로그부터 보면 안 되는 문제가 많다logs --previous 와 get events --sort-by 가 두 기둥0=명령이 끝남, 127=명령 없음, 137=OOM/강제종료Running 0/1 = readinessProbe 실패 → 엔드포인트에서 빠진다crictl + journalctl -u kubelet/etc/kubernetes/manifests/의 값 하나