kubelet 은 주기적으로 노드 상태를 갱신한다. 이 갱신이 끊기면 컨트롤러가 일정 시간 뒤 노드 상태를 NotReady 로 바꾸고, 조건의 상태 자체가 Unknown 으로 표시된다. 노드가 죽었다는 뜻이 아니라 컨트롤 플레인이 그 노드의 소식을 듣지 못하고 있다는 뜻이다. 노드는 멀쩡하고 통신만 끊긴 경우가 흔하다.
kubectl get nodes -o wide
kubectl describe node <노드> # Conditions 의 LastHeartbeatTime 을 본다
systemctl status kubelet
journalctl -u kubelet -xe | tail -50
워커에서 컨트롤 플레인의 6443, 컨트롤 플레인에서 워커의 10250 이 열려 있어야 한다.
nc -vz <컨트롤플레인 IP> 6443 # 워커에서
nc -vz <워커 IP> 10250 # 컨트롤 플레인에서
디스크가 가득 차면 kubelet 이 DiskPressure 를 올리고 파드를 쫓아낸다. 메모리 고갈로 kubelet 자체가 멈추기도 한다.
df -h /var/lib/kubelet /var/lib/containerd /var/log
free -m
시간이 어긋나면 TLS 검증이 실패한다.
timedatectl status
systemctl restart chronyd
로그에 certificate has expired 가 있으면 Kubernetes 인증서 만료 처리 로 간다.
kubelet 로그에 다음이 보이면 컨테이너 런타임 쪽 문제다.
failed to run kubelet: validate service connection:
validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock":
rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
Unimplemented 는 소켓에 붙기는 했는데 그쪽이 CRI 서비스를 제공하지 않는다는 뜻이다. 원인은 셋이다.
containerd 의 CRI 플러그인이 꺼져 있다. 패키지로 설치한 뒤 기본 설정 파일을 그대로 두면 disabled_plugins = ["cri"] 가 들어 있는 경우가 있다. 이 줄을 지우고 설정을 새로 만든다.
grep disabled_plugins /etc/containerd/config.toml
containerd config default > /etc/containerd/config.toml
systemctl restart containerd
cgroup 드라이버가 kubelet 과 다르다. systemd 를 쓰는 배포판에서는 양쪽 모두 systemd 여야 한다. config.toml 에서 런타임 옵션 아래의 SystemdCgroup 을 true 로 둔다. 위치는 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] 섹션이며, 다른 자리에 적으면 읽히지 않는다.
소켓 경로가 어긋난다. kubelet 이 보는 경로와 실제 소켓을 맞춘다.
ls -l /run/containerd/containerd.sock
cat /var/lib/kubelet/kubeadm-flags.env
crictl --runtime-endpoint unix:///run/containerd/containerd.sock info
crictl 이 경로를 못 찾아 경고를 내면 설정 파일을 만들어 둔다.
# /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
원인을 고친 뒤 kubelet 을 다시 띄우면 대개 수십 초 안에 Ready 로 돌아온다.
systemctl restart kubelet
kubectl get nodes -w
노드가 끝내 돌아오지 않고 다시 만들어야 한다면, 먼저 워크로드를 비우고 클러스터에서 뺀 뒤 재조인한다.
kubectl drain <노드> --ignore-daemonsets --delete-emptydir-data
kubectl delete node <노드>
# 해당 노드에서
kubeadm reset -f
kubeadm join <컨트롤플레인>:6443 --token <토큰> --discovery-token-ca-cert-hash sha256:<해시>
kubeadm reset 은 그 노드의 클러스터 관련 설정을 지운다. 되돌릴 수 없으므로 정말 재조인할 때만 쓴다.