TT Lab
블로그 시작하기
배우기 러닝패스 코스

쿠버네티스 전문가 — 장애를 진단하고 클러스터를 지킨다

노드가 NotReady 다 — 사건 다섯을 증상에서 원인까지

TT Lab 에서 이어서 보기

목표

진짜 kubeadm 클러스터(컨트롤 플레인 하나, 워커 하나)의 워커에 고장 다섯 가지를 차례로 내고, 무엇이 고장 났는지 모르는 채로 증상에서 출발해 원인을 찾고 고칩니다. 마지막에 재부팅해 고친 것이 설정 파일에까지 남았는지 확인합니다.

왜 중요한가

NotReady 는 진단이 아니라 증상입니다. 같은 한 단어 뒤에 kubelet 이 아예 뜨지 못하는 경우, 떠 있는데 API 서버가 거절하는 경우, 런타임이 반쯤 살아 있는 경우가 모두 숨어 있습니다. 그리고 노드 객체의 상태는 40초 남짓 늦게 바뀌고, 그사이 이미 돌던 컨테이너는 대개 계속 돕니다. 그래서 운영자는 kubectl 화면이 아니라 그 노드의 kubelet·containerd 로그와 커널 상태에서 출발해야 합니다. 이 실습의 다섯 사건은 모두 실제 운영에서 노드를 세운 원인이고, 그중 둘(inotify 상한, 런타임 설정 덧붙이기)은 이 사이트를 돌리는 클러스터에서 실제로 일어났던 일입니다.

단계

  1. node01 의 kubelet 을 멈추고(ssh node01 systemctl stop kubelet), controlplane 에서 node01 의 Ready 조건이 True 가 아니게 될 때까지 몇 초 걸리는지 재세요. 그동안 node01 에 붙은 테인트 키도 보고, kubelet 을 다시 띄운 뒤 /root/kx/node/baseline.txt 에 notready_after_sec=<초> 와 taints=<쉼표로 이은 키> 두 줄을 적습니다.
  2. kx-incident node-a 를 실행하면 node01 에 고장이 하나 납니다. 원인을 찾아 고쳐 node01 을 다시 Ready 로 만들고(재부팅해도 같은 고장이 돌아오지 않게), /root/kx/node/a.txt 에 cause: <코드> 와 evidence: <로그 한 줄> 을 적으세요. 코드는 아래 참고의 목록에서 고릅니다.
  3. kx-incident node-b 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/b.txt 에 원인 코드와 증거 한 줄을 적으세요. kubelet 이 쓰는 인증서는 클러스터 CA 가 서명했고 이 노드(system:node:node01)의 것이어야 합니다 — 파일 이름이나 날짜로 고르지 말고 내용으로 확인하세요.
  4. kx-incident node-c 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/c.txt 에 원인 코드와 증거 한 줄을 적으세요.
  5. kx-incident node-d 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/d.txt 에 원인 코드와 증거 한 줄을 적으세요. 고친 값은 지금 커널에도, 재부팅 뒤에도 맞아야 합니다.
  6. kx-incident node-e 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/e.txt 에 원인 코드와 증거 한 줄을 적으세요. 고친 런타임 설정은 이 클러스터의 원래 설정처럼 runc 를 systemd cgroup 드라이버(SystemdCgroup = true)로 돌려야 합니다.
  7. 다섯 사건을 모두 고쳤다면 node01 을 재부팅하세요(ssh node01 reboot). 돌아온 node01 이 스스로 Ready 가 되고, 다섯 고장 중 어느 것도 되살아나지 않아야 합니다 — 스왑 없음, 인증서는 node01 의 것, CNI 설정 있음, inotify 상한 128 이상, containerd 설정 정상.

참고

kubelet 이 멈추면 노드 객체는 언제 알아채나

node01 의 kubelet 을 멈추고(ssh node01 systemctl stop kubelet), controlplane 에서 node01 의 Ready 조건이 True 가 아니게 될 때까지 몇 초 걸리는지 재세요. 그동안 node01 에 붙은 테인트 키도 보고, kubelet 을 다시 띄운 뒤 /root/kx/node/baseline.txt 에 notready_after_sec=<초> 와 taints=<쉼표로 이은 키> 두 줄을 적습니다.

노드의 Ready 는 kubelet 이 스스로 보고하는 값이 아니라, 컨트롤 플레인의 노드 수명 주기 컨트롤러가 「보고가 끊겼다」 고 판단해 바꾸는 값입니다. until [ "$(kubectl get node node01 -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" != True ]; do sleep 1; done 처럼 1초마다 재 보세요. 그 순간의 kubectl get node node01 -o jsonpath='{.spec.taints}' 에 무엇이 붙는지도 봅니다.

사건 A — node01 이 NotReady 다

kx-incident node-a 를 실행하면 node01 에 고장이 하나 납니다. 원인을 찾아 고쳐 node01 을 다시 Ready 로 만들고(재부팅해도 같은 고장이 돌아오지 않게), /root/kx/node/a.txt 에 cause: <코드> 와 evidence: <로그 한 줄> 을 적으세요. 코드는 아래 참고의 목록에서 고릅니다.

노드가 NotReady 면 그 노드의 kubelet 부터 봅니다 — ssh node01 systemctl status kubelet, 그리고 journalctl -u kubelet -n 30. kubelet 이 아예 뜨지 못하는지(activating 반복), 떠 있는데 보고만 못 하는지부터 가르세요. 고친 것이 재부팅 뒤에도 유지되는지는 /etc 아래의 설정 파일이 정합니다.

사건 B — kubelet 은 떠 있는데 노드가 사라졌다고 한다

kx-incident node-b 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/b.txt 에 원인 코드와 증거 한 줄을 적으세요. kubelet 이 쓰는 인증서는 클러스터 CA 가 서명했고 이 노드(system:node:node01)의 것이어야 합니다 — 파일 이름이나 날짜로 고르지 말고 내용으로 확인하세요.

이번에는 kubelet 이 active 입니다. journalctl -u kubelet -n 200 -o cat | grep -E '^E' 로 오류만 모아 보세요 — not found 줄보다 forbidden 줄이 원인에 가깝습니다. kubelet 이 API 서버에 자기를 누구라고 소개하는지는 /var/lib/kubelet/pki/kubelet-client-current.pem 이 정합니다. openssl x509 -in <파일> -noout -subject -enddate 와 openssl verify -CAfile /etc/kubernetes/pki/ca.crt <파일> 로 후보를 하나씩 확인하세요.

사건 C — 파드는 Running 인데 노드는 NotReady

kx-incident node-c 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/c.txt 에 원인 코드와 증거 한 줄을 적으세요.

이번에는 노드 조건의 message 가 많은 것을 말해 줍니다 — kubectl describe node node01 의 Conditions. 그 메시지가 가리키는 디렉터리를 node01 에서 직접 보세요. 그 파일을 원래 누가 썼는지(어느 DaemonSet 의 initContainer 인지) 알면 손으로 다시 쓰지 않고도 되살릴 수 있습니다.

사건 D — containerd 는 active 인데 CRI 가 없다

kx-incident node-d 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/d.txt 에 원인 코드와 증거 한 줄을 적으세요. 고친 값은 지금 커널에도, 재부팅 뒤에도 맞아야 합니다.

kubelet 의 오류는 런타임이 CRI 를 모른다(Unimplemented)고 말합니다. 그 말을 믿고 containerd 판을 의심하기 전에 containerd 쪽 로그(journalctl -u containerd -n 100)에서 failed to load plugin 이 붙은 줄을 찾으세요. 커널 매개변수는 /etc/sysctl.d/ 의 파일이 부팅 때 정하고, 지금 값은 sysctl <이름> 이 보여 줍니다 — 파일을 지우는 것만으로는 지금 값이 돌아오지 않습니다.

사건 E — containerd 가 아예 뜨지 않는다

kx-incident node-e 를 실행한 뒤 node01 을 다시 Ready 로 만들고 /root/kx/node/e.txt 에 원인 코드와 증거 한 줄을 적으세요. 고친 런타임 설정은 이 클러스터의 원래 설정처럼 runc 를 systemd cgroup 드라이버(SystemdCgroup = true)로 돌려야 합니다.

systemctl status containerd 와 journalctl -u containerd -n 30 의 첫 오류를 읽으세요. 설정 파일이 맞는지는 데몬을 띄우기 전에 containerd --config /etc/containerd/config.toml config dump 로 먼저 확인할 수 있습니다. containerd config default 로 통째로 새로 만들면 문법은 맞지만 cgroup 드라이버 설정이 사라집니다. 이 사이 노드에서 이미 돌던 컨테이너는 계속 돌고 있다는 것도 확인해 보세요(ps -ef | grep containerd-shim).

고친 것이 재부팅을 견디는가

다섯 사건을 모두 고쳤다면 node01 을 재부팅하세요(ssh node01 reboot). 돌아온 node01 이 스스로 Ready 가 되고, 다섯 고장 중 어느 것도 되살아나지 않아야 합니다 — 스왑 없음, 인증서는 node01 의 것, CNI 설정 있음, inotify 상한 128 이상, containerd 설정 정상.

재부팅은 「지금 고친 것」 과 「설정 파일에 고친 것」 의 차이를 드러냅니다. 사건 A 의 fstab, 사건 D 의 sysctl 파일이 대표적입니다. 돌아온 뒤 Ready 를 볼 때는 노드 리스(kubectl -n kube-node-lease get lease node01)의 갱신 시각이 부팅 시각보다 뒤인지도 보세요 — 노드 객체의 Ready 는 늦게 바뀝니다.