Certified Kubernetes Administrator (CKA) #23 Troubleshooting 2: 노드와 kubelet (NotReady, disk/memory pressure)
#22 Troubleshooting 1에서는 Pod와 앱 레벨의 장애를 다뤘습니다. Pending,CrashLoopBackOff,ImagePullBackOff,OOMKilled처럼 한 Pod의 상태만 봐도 원인이 좁혀지는 문제들이었습니다. 이번 글은 한 단계 아래로 내려가겠습니다. 노드 자체가 NotReady로 떨어진 상황입니다.
노드 장애는 Pod 장애와 결이 다릅니다. 노드 하나가 NotReady가 되면 그 위에 있던 여러 Pod가 한꺼번에 영향을 받고, kubectl만으로는 원인이 보이지 않을 때가 많습니다. 결국 노드에 직접 접속해 kubelet과 컨테이너 런타임을 시스템 레벨에서 들여다봐야 합니다. CKA에서 리눅스 운영 감각이 필요한 대표적인 영역이며, Troubleshooting도메인 안에서도 배점이 잘 붙는 유형입니다.
노드 NotReady란 무엇인가 #
각 노드의 kubelet은 일정 주기로 control plane에 자신의 상태를 보고합니다. 이 보고가 정상이면 노드는 Ready이고, 정해진 시간 안에 보고가 끊기거나 kubelet이 문제를 알리면 노드는 NotReady로 표시됩니다. 가장 먼저 보는 화면이 노드 목록입니다.
k get nodesNAME STATUS ROLES AGE VERSION
master Ready control-plane 40d v1.31.0
node01 NotReady <none> 40d v1.31.0
node02 Ready <none> 40d v1.31.0node01이 NotReady입니다. 여기서 곧장 노드에 접속하고 싶겠지만, 그 전에 control plane이 보고받은 정보부터 읽는 것이 순서입니다. 노드에 접속하지 않고도 원인의 절반은 describe에서 드러납니다.
1단계: k describe node로 conditions 읽기 #
노드 진단의 출발점은 언제나 describe입니다. 노드는 여러 condition을 가지며, 각 condition의 상태와 마지막 전환 시각, 그리고 사람이 읽을 수 있는 메시지가 함께 나옵니다.
k describe node node01출력에서 가장 먼저 볼 곳이 Conditions 블록입니다.
Conditions:
Type Status Reason Message
---- ------ ------ -------
MemoryPressure False KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False KubeletHasSufficientPID kubelet has sufficient PID available
Ready Unknown NodeStatusUnknown Kubelet stopped posting node status.노드 condition의 의미 #
| Condition | 의미 | True일 때 |
|---|---|---|
Ready | 노드가 Pod를 받을 수 있는 정상 상태 | 정상. False/Unknown이면 비정상 |
MemoryPressure | 노드의 가용 메모리가 임계치 아래 | 메모리 부족. eviction 발생 가능 |
DiskPressure | 노드의 디스크 용량 또는 inode가 임계치 아래 | 디스크 부족. 이미지,Pod eviction 발생 |
PIDPressure | 노드의 가용 PID가 임계치 아래 | 프로세스 수 포화 |
Ready만 다른 condition과 방향이 반대입니다. Ready=True가 정상이고, 나머지 pressure 계열은 False가 정상입니다. 위 예시에서 Ready가 Unknown이고 메시지가 Kubelet stopped posting node status.이므로, kubelet이 상태 보고를 멈췄다는 뜻입니다. pressure 계열은 모두 False이니 자원 부족은 아닙니다. 이 경우 원인은 거의 확실히 kubelet 자체에 있습니다.
Ready의 Status별 해석 #
Ready Status | 해석 | 다음 행동 |
|---|---|---|
True | 정상 | 노드 문제 아님. Pod 레벨로 |
False | kubelet은 살아 있으나 노드가 비정상을 보고 | 메시지의 Reason 확인. 런타임,네트워크,pressure의심 |
Unknown | kubelet 보고가 끊김 | 노드 접속. kubelet 정지,노드 다운,네트워크 단절 의심 |
Unknown은 control plane이 노드와의 연락이 끊겼다는 신호입니다. kubelet이 죽었거나, 노드가 꺼졌거나, 노드와 apiserver 사이 네트워크가 막힌 세 갈래입니다. 여기서부터는 노드에 직접 들어가야 합니다.
2단계: 노드에 접속해 kubelet 상태 확인 #
시험에서는 접속할 노드의 호스트명이 문제에 제시됩니다. SSH로 들어간 뒤, 권한이 필요하면 sudo로 올라갑니다.
ssh node01
sudo -i노드 위에서 가장 먼저 확인할 대상은 kubelet 서비스입니다. kubelet은 노드의 모든 Pod를 띄우고 관리하는 에이전트이므로, 이것이 멈추면 노드 전체가 NotReady가 됩니다.
systemctl status kubelet● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; ...)
Active: inactive (dead) since Sat 2026-06-06 09:12:41 UTC; 3min agoActive: inactive (dead)라면 kubelet이 멈춰 있습니다. active (running)인데도 노드가 NotReady라면 kubelet은 떠 있으나 정상 동작을 못 하는 상황이므로, 로그를 봐야 합니다.
journalctl로 kubelet 로그 읽기 #
kubelet은 systemd 유닛으로 돌기 때문에 로그는 journalctl로 봅니다. 최근 로그부터 거꾸로 훑는 것이 빠릅니다.
# kubelet 로그 전체 (페이저)
journalctl -u kubelet
# 최근 100줄만
journalctl -u kubelet -n 100 --no-pager
# 실시간 추적 (kubelet을 재시작하며 같이 보면 좋음)
journalctl -u kubelet -f로그에서 error, failed, unable to 같은 키워드를 찾으면 원인의 한 줄이 거의 항상 나옵니다. 설정 파일 경로 오류, 인증서 만료, 런타임 소켓 연결 실패가 대표적입니다.
흔한 원인과 해결 #
노드 NotReady의 원인은 크게 다섯 갈래입니다. 하나씩 증상과 해결을 정리하겠습니다.
원인 1: kubelet이 정지함 #
가장 단순하면서 시험에 자주 나오는 경우입니다. systemctl status kubelet이 inactive (dead)이면 kubelet을 다시 띄웁니다.
systemctl start kubelet
# 부팅 시 자동 시작도 켜둠
systemctl enable kubelet
# 상태 재확인
systemctl status kubelet다시 띄웠는데 곧바로 죽는다면 단순 정지가 아니라 설정 문제입니다. journalctl -u kubelet -n 50 --no-pager로 죽는 이유를 확인하고 다음 항목으로 넘어갑니다.
원인 2: kubelet 설정이 잘못됨 #
kubelet은 여러 설정 파일을 읽고 뜹니다. 시험에서는 이 경로 가운데 하나가 일부러 망가뜨려져 있는 경우가 있습니다.
| 경로 | 역할 |
|---|---|
/var/lib/kubelet/config.yaml | kubelet의 주 설정. cgroup driver, eviction 임계치 등 |
/etc/kubernetes/kubelet.conf | apiserver 접속용 kubeconfig |
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf | systemd가 kubelet을 띄울 때 쓰는 인자 |
/var/lib/kubelet/kubeadm-flags.env | kubeadm이 추가하는 런타임 인자(예: 런타임 소켓 경로) |
로그에 failed to load Kubelet config file 또는 unable to load client CA file 같은 줄이 보이면 위 파일들의 경로와 내용을 점검합니다. 자주 나오는 함정 하나는 kubeconfig가 가리키는 apiserver 포트나 서버 주소가 잘못된 경우입니다.
# kubelet kubeconfig가 가리키는 서버 확인
cat /etc/kubernetes/kubelet.conf | grep server설정 파일을 고친 뒤에는 systemd가 변경을 다시 읽도록 한 다음 kubelet을 재시작합니다.
systemctl daemon-reload
systemctl restart kubelet원인 3: 인증서 문제 #
kubelet은 apiserver와 통신할 때 클라이언트 인증서를 씁니다. 이 인증서가 만료되면 kubelet은 떠 있어도 apiserver에 상태를 보고하지 못해 노드가 NotReady가 됩니다. 로그에 x509: certificate has expired or is not yet valid 같은 줄이 보이면 인증서 만료입니다.
journalctl -u kubelet -n 50 --no-pager | grep -i x509인증서 만료는 그 자체로 다룰 내용이 많아 #25 Troubleshooting 4에서 kubelet 인증서 자동 갱신과 수동 갱신을 따로 정리하겠습니다. 이번 글에서는 NotReady의 원인 후보에 인증서 만료가 있다는 점만 기억하면 됩니다.
원인 4: 컨테이너 런타임이 정지함 #
kubelet은 컨테이너를 직접 띄우지 않고 CRI를 통해 컨테이너 런타임(보통 containerd)에 위임합니다. 런타임이 죽으면 kubelet은 Pod를 띄울 수 없어 노드가 NotReady가 됩니다. 로그에 failed to get container runtime 또는 connection refused가 보이면 런타임을 확인합니다.
systemctl status containerd
# 멈춰 있으면 다시 띄움
systemctl start containerd
systemctl enable containerd
# 런타임이 응답하는지 확인
crictl info
crictl ps런타임을 다시 띄운 뒤에는 kubelet도 재시작해 둡니다. 런타임 소켓 경로가 틀린 경우라면 /var/lib/kubelet/kubeadm-flags.env의 --container-runtime-endpoint 값을 확인합니다.
systemctl restart kubelet원인 5: 디스크 풀과 메모리 압박 #
자원 고갈은 condition에 그대로 드러나므로 describe node에서 이미 단서를 얻습니다. DiskPressure=True면 디스크가 임계치 아래로 떨어진 상태이고, kubelet은 노드를 보호하기 위해 Pod를 eviction(축출)합니다.
# 디스크 사용량 확인
df -h
# inode 고갈도 함께 확인 (용량은 남았는데 inode가 차는 경우)
df -i디스크가 찼다면 가장 안전한 회수 대상은 사용하지 않는 컨테이너 이미지입니다.
# 미사용 이미지 정리
crictl rmi --prune
# 종료된 컨테이너 정리
crictl rm $(crictl ps -a -q --state Exited)로그 파일이 비대해진 경우도 흔하므로 /var/log 아래 용량이 큰 파일을 함께 점검합니다. 디스크를 확보하면 kubelet이 다시 DiskPressure=False를 보고하고 노드가 Ready로 돌아옵니다.
MemoryPressure=True는 노드의 가용 메모리가 eviction 임계치 아래로 떨어진 상태입니다.
# 메모리 사용량
free -h
# 메모리를 많이 쓰는 프로세스
top -o %MEM특정 Pod가 노드 메모리를 과도하게 쓰는 경우라면 그 워크로드의 requests/limits를 조정하거나(#15 리소스 관리), 다른 노드로 분산하는 것이 근본 해결입니다. 시험에서는 보통 원인이 명확한 한 가지로 설계되므로, condition이 가리키는 자원을 회수하는 데 집중하면 됩니다.
증상별 진단 표 #
지금까지의 흐름을 한눈에 정리하겠습니다. NotReady를 만나면 이 표를 따라 좁혀 가면 됩니다.
| 증상 / 단서 | 의심 원인 | 확인 명령 | 해결 |
|---|---|---|---|
Ready=Unknown, “Kubelet stopped posting” | kubelet 정지 | systemctl status kubelet | systemctl start/restart kubelet |
| kubelet이 떴다 곧 죽음 | 설정 파일 오류 | journalctl -u kubelet | /var/lib/kubelet, kubelet.conf 수정 후 daemon-reload |
로그에 x509 ... expired | 인증서 만료 | journalctl -u kubelet | grep x509 | 인증서 갱신(#25) |
로그에 runtime ... connection refused | 런타임 정지 | systemctl status containerd | systemctl start containerd 후 kubelet 재시작 |
DiskPressure=True | 디스크 풀 | df -h, df -i | crictl rmi --prune, 로그 정리 |
MemoryPressure=True | 메모리 압박 | free -h, top | 워크로드 분산, requests/limits 조정 |
Ready=False, Reason이 네트워크 계열 | CNI 플러그인 문제 | kubelet 로그의 NetworkPluginNotReady | CNI Pod 상태 확인(#20) |
문제 노드를 격리하기: cordon과 drain #
노드를 고치는 동안 새 Pod가 그 노드로 스케줄링되지 않게 막거나, 위에 있는 Pod를 안전하게 비워야 할 때가 있습니다. 이때 쓰는 명령이 cordon과 drain입니다.
# 새 Pod 스케줄링만 막음 (기존 Pod는 그대로)
k cordon node01
# 기존 Pod까지 다른 노드로 옮김 (cordon 포함)
k drain node01 --ignore-daemonsets --delete-emptydir-data
# 작업이 끝나면 다시 스케줄링 허용
k uncordon node01drain은 노드를 점검,업그레이드하기 전에 그 위의 워크로드를 안전하게 다른 노드로 재배치합니다. DaemonSet Pod는 모든 노드에 떠야 하므로 --ignore-daemonsets로 건너뛰고, emptyDir 볼륨을 쓰는 Pod가 있으면 데이터 손실을 인지했다는 의미로 --delete-emptydir-data를 붙여야 진행됩니다. drain을 실행하면 그 노드는 자동으로 cordon 상태가 됩니다.
cordon된 노드는 k get nodes에서 SchedulingDisabled로 표시됩니다.
NAME STATUS ROLES AGE VERSION
node01 Ready,SchedulingDisabled <none> 40d v1.31.0복구가 끝나면 반드시 uncordon으로 스케줄링을 다시 열어야 합니다. 이것을 잊으면 노드는 Ready인데 새 Pod가 올라오지 않아, 나중에 또 다른 문제로 오인하기 쉽습니다.
NotReady 노드의 taint #
노드가 NotReady가 되면 control plane은 자동으로 taint를 붙여 Pod 스케줄링을 막습니다. 어떤 taint가 붙어 있는지 보면 노드 상태를 빠르게 읽을 수 있습니다.
k describe node node01 | grep -i taintTaints: node.kubernetes.io/not-ready:NoSchedule주요 노드 taint는 다음과 같습니다. 이들은 노드 상태에 따라 쿠버네티스가 자동으로 부여합니다.
| Taint | 부여 조건 |
|---|---|
node.kubernetes.io/not-ready | 노드의 Ready=False |
node.kubernetes.io/unreachable | 노드의 Ready=Unknown(연락 끊김) |
node.kubernetes.io/disk-pressure | DiskPressure=True |
node.kubernetes.io/memory-pressure | MemoryPressure=True |
node.kubernetes.io/unschedulable | cordon된 노드 |
이 taint들은 노드가 정상으로 돌아오면 자동으로 제거됩니다. 즉 kubelet을 고쳐 노드가 다시 Ready가 되면 not-ready taint도 사라지고, 스케줄링이 정상화됩니다. taint를 손으로 떼는 것이 아니라 근본 원인을 고치는 것이 정답입니다. taint와 toleration의 동작은 #14 Scheduling 2에서 자세히 다뤘습니다.
핵심은 kubectl 레벨(describe)에서 시작해 노드 레벨(systemctl/journalctl)로 내려간다는 방향입니다. k get nodes로 대상을 찾고, k describe node로 conditions를 읽어 방향을 잡고, 노드에 접속해 kubelet과 런타임을 고친 뒤, 설정을 바꿨다면 systemctl daemon-reload 후 restart kubelet으로 마무리합니다. 이 순서가 거의 모든 노드 장애에 그대로 적용됩니다.
시험 포인트 #
describe node의 conditions를 먼저 읽는다.Ready=Unknown이면 kubelet 보고 끊김, pressure 계열True면 자원 고갈입니다. 노드에 접속하기 전에 방향을 잡으면 시간을 아낍니다.- kubelet은 systemd 유닛이다.
systemctl status kubelet과journalctl -u kubelet이 손에 붙어 있어야 합니다. kubelet 정지는systemctl enable --now kubelet으로 한 번에 띄울 수 있습니다. - 설정을 고쳤으면
daemon-reload를 잊지 않는다. systemd 유닛 파일이나 인자를 바꾼 뒤daemon-reload없이 재시작하면 변경이 반영되지 않습니다. - 런타임도 후보다.
systemctl status containerd와crictl info로 런타임 정지를 빠르게 배제합니다. - 디스크는
df -h와df -i둘 다 본다. 용량은 남았는데 inode가 차서 DiskPressure가 뜨는 경우를 놓치기 쉽습니다. - taint는 손으로 떼지 않는다. 원인을 고치면 자동으로 사라집니다. taint를 강제로 제거해도 근본 원인이 남아 있으면 노드는 다시 NotReady가 됩니다.
정리 #
이번 글에서 잡은 것:
- 노드 NotReady 진단은 describe에서 시작해 노드 시스템 레벨로 내려간다. conditions(
Ready/MemoryPressure/DiskPressure/PIDPressure)가 방향을 잡아 줍니다 - kubelet은 노드 에이전트다.
systemctl status kubelet과journalctl -u kubelet으로 정지,설정 오류,인증서,런타임 문제를 가려냅니다 - 흔한 다섯 원인. kubelet 정지, 설정 오류(
/var/lib/kubelet,/etc/kubernetes/kubelet.conf), 인증서 만료, 런타임 정지(containerd), 디스크 풀과 메모리 압박입니다 - cordon과 drain으로 격리한다. 점검 전 워크로드를 안전하게 비우고, 복구 후
uncordon으로 스케줄링을 다시 엽니다 - NotReady taint는 자동이다.
node.kubernetes.io/not-ready는 원인을 고치면 자동으로 제거됩니다
다음: Troubleshooting 3 #
노드를 고쳤습니다. 이제 더 위험한 층, control plane 자체가 다운된 상황으로 올라가겠습니다.
#24 Troubleshooting 3: Control plane (apiserver/etcd/scheduler 다운), etcd 복구에서는 kube-apiserver가 응답하지 않을 때 static Pod 매니페스트(/etc/kubernetes/manifests)와 컨테이너 런타임을 직접 들여다보는 법, scheduler와 controller-manager가 죽으면 클러스터에 무슨 일이 생기는지, 그리고 etcd가 깨졌을 때 스냅샷으로 복구하는 절차까지 다루겠습니다.