쿠버네티스 트러블슈팅 모음

온프레미스 클러스터를 운영하며 실제로 만난 오류들을 주제별로 정리했습니다. 같은 증상을 만났을 때 빠르게 찾아 쓰라고 모아둔 노트예요.

인증서, CNI, 디스크 압박, NFS 문제를 점검하는 쿠버네티스 트러블슈팅 흐름

먼저 상태를 좁히기

장애가 나면 초기화 명령부터 실행하지 말고 오브젝트 상태, 이벤트, 노드 서비스를 차례로 확인합니다.

kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -50
kubectl describe pod [POD] -n [NAMESPACE]
kubectl logs [POD] -n [NAMESPACE] --all-containers --tail=200
kubectl logs [POD] -n [NAMESPACE] --previous

# 문제가 있는 노드
systemctl status kubelet containerd
journalctl -u kubelet -u containerd --since "30 min ago"

Pending, ImagePullBackOff, CrashLoopBackOff, NotReady는 원인이 서로 다릅니다. describe의 Events와 이전 컨테이너 로그를 먼저 저장해야 재시작 후 단서가 사라지지 않습니다.

접속 · 인증서

The connection to the server was refused 또는 x509: certificate signed by unknown authority

kubeconfig의 인증서나 파일에 문제가 생겨 클러스터에 접근이 안 되는 경우입니다.

rm ~/.kube/config
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# 또는
systemctl daemon-reload && systemctl restart kubelet

use of closed network connection (인증서 갱신 중)

갱신 도중 실행 중이던 apiserver·controller-manager가 예전 인증서를 들고 있어 발생합니다. systemctl restart kubelet 등 재로딩을 마치면 정리됩니다.

CNI · 네트워크

cni0 already has an IP address different from ...

CNI 설정의 Pod CIDR과 노드에 남은 cni0 주소가 다른 상황입니다. 클러스터 전체를 초기화하기 전에 문제가 있는 노드 하나만 격리합니다.

kubectl cordon [NODE]
kubectl drain [NODE] --ignore-daemonsets --delete-emptydir-data

# 해당 노드에서 CNI 설정과 실제 인터페이스 비교
cat /etc/cni/net.d/*
ip addr show cni0
ip addr show flannel.1
journalctl -u kubelet --since "30 min ago"

Pod CIDR이나 CNI 설정을 바꾼 직후 남은 인터페이스가 원인임을 확인했을 때만 해당 노드에서 kubelet과 런타임을 중지하고 CNI 상태를 정리합니다.

systemctl stop kubelet containerd
rm -rf /var/lib/cni/*
ip link delete cni0 2>/dev/null || true
ip link delete flannel.1 2>/dev/null || true
systemctl start containerd kubelet

노드가 다시 Ready가 되고 테스트 파드 통신이 확인되면 스케줄링을 엽니다.

kubectl uncordon [NODE]
kubectl run netcheck --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl get pod netcheck -o wide

/var/lib/kubelet/*/etc/kubernetes/*를 바로 삭제하면 노드 인증서와 상태까지 잃을 수 있으므로 CNI 복구 명령으로 사용하면 안 됩니다.

노드 IP를 변경했을 때

노드 주소를 바꾸면 kubelet의 InternalIP, API 서버 인증서, CNI가 예전 주소를 계속 참조할 수 있습니다. 먼저 워크로드를 이동하고 현재 설정 위치를 찾습니다.

kubectl cordon [NODE]
kubectl drain [NODE] --ignore-daemonsets --delete-emptydir-data
grep -R -- '--node-ip' /etc/systemd/system/kubelet.service.d /var/lib/kubelet 2>/dev/null
kubectl get node [NODE] -o wide

kubelet의 --node-ip 또는 배포 도구의 노드 주소를 수정한 뒤 서비스를 재시작합니다. 컨트롤 플레인 주소 변경은 API 서버 인증서 SAN과 etcd peer URL까지 연결되므로 단순 IP 수정으로 끝내지 말고 재구축 또는 인증서 재발급 절차를 계획해야 합니다.

systemctl daemon-reload
systemctl restart kubelet
kubectl get node [NODE] -o wide

노드 이름이 달라지거나 kubeadm 조인 정보가 꼬였다면 기존 Node 오브젝트를 정리하고 kubeadm reset 후 다시 join하는 편이 안전할 수 있습니다.

Service가 외부에서 안 열릴 때

애플리케이션 → Pod → Service → 외부 경로 순서로 한 단계씩 확인합니다.

kubectl get pod -n [NS] -l app=[APP] -o wide
kubectl get service,endpoints,endpointslice -n [NS]
kubectl describe service [SERVICE] -n [NS]
kubectl port-forward -n [NS] service/[SERVICE] 8080:[PORT]

포트포워딩은 되는데 NodePort가 안 되면 노드 방화벽, nodePort 범위, kube-proxy 또는 CNI 경로를 봅니다. Service의 Endpoint가 비어 있으면 네트워크보다 selector와 Pod label 불일치가 먼저입니다.

고가용성 환경에서는 특정 컨트롤 플레인 IP를 서비스의 externalIPs로 고정하지 않습니다. Ingress Controller나 LoadBalancer 구현을 여러 노드에 배치하고 상태 확인이 있는 로드밸런서를 앞에 둡니다. 컨트롤 플레인 자체의 HA 구성은 쿠버네티스 HA 클러스터를 참고하세요.

노드 · 자원

node(s) had untolerated taint {node.kubernetes.io/disk-pressure}

디스크 공간 부족입니다. VMware 기준으로 무중단 증설 흐름은 이렇습니다.

# 1) 문제 워커에 taint를 걸고 파드를 다른 노드로 이동
kubectl taint node k8sworker1 key1=value1:NoSchedule

# 2) 워커 종료 후 VMware에서 디스크 Expand (스냅샷 있으면 불가)
# 3) 파티션/파일시스템 확장
parted /dev/sda            # resizepart 로 최대 용량까지
resize2fs /dev/sda1
df -h                       # 확인

# 4) taint 해제
kubectl taint node k8sworker1 key1=value1:NoSchedule-

Evicted 상태의 파드

자원 부족·노드 장애·드레인 등으로 파드가 강제 퇴출된 상태입니다. 새 파드가 정상 동작 중이라면 Evicted 파드는 지워도 됩니다.

kubectl get pods --field-selector=status.phase=Failed -n <ns> \
  | grep Evicted | awk '{print $1}' | xargs kubectl delete pod -n <ns>

단, 컨트롤러 없이 수동 생성한 파드는 삭제 후 자동 복구되지 않으니 주의하세요.

ContainerStatus from runtime service failed ... NotFound

워커에 컨테이너 런타임이 제대로 안 깔린 경우입니다. 모든 워커에 런타임(containerd)이 정상 설치·구동 중인지 확인합니다.

스토리지 · NFS

you might need a /sbin/mount.<type> helper program

NFS 마운트 헬퍼가 없어서 나는 오류입니다. 모든 노드에 클라이언트 패키지를 설치합니다.

apt-get install -y nfs-common

재부팅 후 NFS가 이상하고 PVC가 안 붙을 때

VMware 재부팅 과정에서 /dev/sda/dev/sdb 순서가 바뀌어 /etc/fstab 의 마운트가 엉키는 경우가 있습니다. 다시 마운트한 뒤 PVC를 붙이면 Bound 됩니다. 근본 해결은 fstab을 장치명 대신 UUID로 지정하는 것입니다(다음 리눅스 편에서 다룹니다).

로그 · 모니터링

failed to create fsnotify watcher: too many open files

kubectl logs -f 중 감시 파일 한도를 넘겨 발생합니다. inotify 한도를 올립니다.

sysctl -w fs.inotify.max_user_instances=1280
sysctl -w fs.inotify.max_user_watches=81920
sysctl -w fs.inotify.max_queued_events=32768

Metrics Server가 노드 메트릭을 못 긁을 때 (cannot validate certificate ... doesn't contain any IP SANs)

kubelet 서빙 인증서 문제입니다. 모든 노드에 serverTLSBootstrap: true 를 넣고 kubelet 재시작 → 마스터에서 kubectl get csr 후 승인합니다. 워커 정보 수집이 안 되면 metrics-server Deployment에 hostNetwork: true 를 추가하면 해결되는 경우가 있습니다.

서비스 (MongoDB)

NotWritablePrimary (레플리카셋)

Node.js MongoDB 드라이버가 DNS 라운드로빈을 지원하지 않아 세컨더리에 쓰기를 시도하며 나는 오류입니다. 접속 URL에 모든 멤버를 명시하고 replicaSet 을 지정해 해결했습니다.

mongodb://mongodb-0.mongo-in,mongodb-1.mongo-in,mongodb-2.mongo-in:27017/admin?replicaSet=rs0&authSource=admin

마지막 수단: 노드 초기화

노드를 다시 조인해야 할 정도로 상태가 망가졌다면 워커는 먼저 drain하고, 컨트롤 플레인은 ETCD 백업과 남은 멤버의 정족수를 확인합니다.

kubectl drain [NODE] --ignore-daemonsets --delete-emptydir-data

# 초기화할 노드에서
kubeadm reset
systemctl stop kubelet containerd
rm -rf /etc/cni/net.d/* /var/lib/cni/*
ip link delete cni0 2>/dev/null || true
ip link delete flannel.1 2>/dev/null || true
systemctl start containerd kubelet

그다음 새 kubeadm join 명령으로 연결하고 Ready, CNI 파드, DNS, 실제 워크로드 통신을 확인합니다. 초기화 명령은 복구가 아니라 상태 삭제이므로 원인 로그와 설정 백업을 먼저 남겨야 합니다.


장애 대응의 공통점은 결국 하나예요. describe, journalctl -xe, kubectl logs 로 원인부터 확인하기. 증상만 보고 손대기 시작하면 더 꼬입니다. 그리고 스토리지를 만질 때는 항상 백업이 먼저입니다.