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

CKA — 쿠버네티스 관리자

노드가 여럿일 때 달라지는 것

TT Lab 에서 이어서 보기

한 줄 요약

노드가 여럿이 되면 쿠버네티스의 문제는 "파드가 안 뜬다" 에서 "어느 노드의 무엇이 틀렸나" 로 바뀝니다. 그 답은 대개 kubectl 이 아니라 그 노드 안에 있습니다.

왜 이게 필요했나

한 대짜리 클러스터에서 연습하면 노드는 배경처럼 보입니다. 노드 이름이 하나뿐이니 스케줄링도, 네트워크도, 정비도 고민할 거리가 없습니다. 그런데 실제 클러스터는 노드 여러 대이고, CKA 실기도 노드 여러 대에 ssh 로 들어가 일하게 합니다. 워커를 조인하고, 한 노드를 비우고, NotReady 인 노드를 살리는 것이 전부 노드 안에서 일어나는 일입니다.

주소를 주지 않으면 무엇이 선택되나

kubelet 은 자기 주소를 노드 객체의 InternalIP 로 등록하고, API 서버와 다른 노드와 CNI 가 그 주소로 이 노드를 찾아온다. 이 실습의 세 VM 은 첫 NIC 가 모두 같은 10.0.2.2 를 받는다.

  • 주소를 명시하지 않았을 때기본 경로가 나가는 NIC 의 주소가 등록된다. 관리망과 클러스터망이 갈라진 서버에서는 틀린 주소이고, 이 환경에서는 세 VM 이 같은 주소를 내놓는다.
  • eth1 주소를 명시했을 때구성요소마다 같은 질문에 따로 답한다. kubelet 은 --node-ip, kubeadm 은 --apiserver-advertise-address, Flannel 은 --iface 다.

여기서 구분할 것 한 곳이 맞다고 다른 곳이 따라 맞는 것은 아니다. 하나라도 빠뜨리면 기본값에 기대는 구성요소가 생기고, 이 환경에서는 그 증상이 바로 드러난다.

잠깐, 예측해 보세요 kubeadm 에는 광고 주소로 eth1 주소를 줬지만 kubelet 의 --node-ip 와 Flannel 의 --iface 는 비워 뒀다. 이것으로 충분할까?

설명 확인 · 채점 없는 자가 점검

충분하지 않다. 구성요소마다 주소를 따로 정해야 하므로, 비워 둔 kubelet 과 Flannel 은 각자 기본값으로 NIC 를 고른다. 각 구성요소가 실제로 등록한 주소가 eth1 인지 따로 확인한다.

근거 문서

어떻게 동작하나

노드의 주소. kubelet 은 자기 주소를 노드 객체의 InternalIP 로 등록하고, API 서버·다른 노드·CNI 가 그 주소로 이 노드를 찾아옵니다. 주소를 따로 주지 않으면 kubelet 은 기본 경로가 나가는 NIC 의 주소를 고릅니다. NIC 가 하나인 서버에서는 그것이 맞지만, 관리망과 클러스터망이 갈라진 서버에서는 틀린 주소가 등록됩니다. 그래서 --node-ip(kubelet), --apiserver-advertise-address(kubeadm), --iface(Flannel)처럼 같은 질문을 구성요소마다 따로 답해야 합니다. 이 실습의 VM 들은 첫 NIC 가 모두 10.0.2.2 라서, 한 곳이라도 빠뜨리면 증상이 바로 드러납니다.

조인. kubeadm join 은 부트스트랩 토큰으로 API 서버에 자기를 소개하고, --discovery-token-ca-cert-hash 로 그 API 서버가 진짜인지 확인한 뒤, kubelet 이 쓸 인증서를 받아 /etc/kubernetes/kubelet.conf 를 씁니다. 조인이 끝나면 kubelet 이 노드 객체를 만들고, CNI DaemonSet 이 그 노드에 파드를 띄워야 Ready 가 됩니다.

정비. kubectl cordon 은 노드에 스케줄 금지 표시만 합니다. kubectl drain 은 거기에 더해 파드를 축출합니다 — 축출은 삭제와 달리 PodDisruptionBudget 을 지킵니다. DaemonSet 파드는 축출해도 같은 노드에 다시 생기므로 --ignore-daemonsets 로 건너뜁니다. 정비가 끝나면 uncordon 으로 되돌리는데, 이미 옮겨 간 파드는 돌아오지 않습니다. 스케줄러는 새 파드를 놓을 때만 판단하기 때문입니다.

kubelet 은 서비스입니다. 컨트롤 플레인 구성요소는 정적 파드로 돌지만, 그 정적 파드를 띄우는 kubelet 자신은 systemd 가 관리합니다. 그래서 kubelet 이 죽으면 그 노드의 상태를 보고할 주체가 없어 노드가 NotReady 가 되고, 원인은 systemctl status kubelet 과 journalctl -u kubelet 에만 남습니다. 여기서 active(지금 떠 있다)와 enabled(부팅 때 켜진다)는 다른 질문입니다. kubeadm 은 조인할 때 kubelet 을 직접 띄우지만 enable 해 주지는 않고 경고만 남깁니다.

이 실습의 환경이 실제와 같은 점과 다른 점

같은 점부터 봅니다. 세 VM 은 각자 커널·systemd·containerd 를 가진 진짜 서버이고, 노드 사이 트래픽은 진짜 네트워크를 탑니다. Flannel 의 VXLAN 이 UDP 8472 로 나가고, API 서버는 6443 으로, kubelet 은 10250 으로 서로를 부릅니다. 그래서 설정 한 곳이 틀리면 증상이 실제 클러스터와 똑같이 나타납니다.

다른 점은 두 가지입니다. 첫째, eth1 은 실제 NIC 가 아니라 이 세션의 VM 끼리만 연결된 가상 L2 망입니다. 바깥에서 보면 VM 사이에 UDP 4789 하나만 오가고, 다른 수강생의 VM 에는 닿지 않습니다. 둘째, 세 VM 의 첫 NIC 는 모두 10.0.2.2 라는 같은 주소를 받습니다. 실제 서버에서는 드문 일이지만, 덕분에 "주소를 명시하지 않으면 무엇이 선택되는가" 가 흐릿하지 않고 분명하게 드러납니다. 기본값에 기대는 설정은 이 환경에서 반드시 실패합니다.

현장에서 만나는 모습

온프렘 서버는 관리망·스토리지망·서비스망 NIC 를 따로 두는 경우가 흔합니다. 여기에 kubeadm 을 기본값으로 올리면 노드들이 관리망 주소로 등록되고, 파드 사이 통신이 스토리지망 스위치를 타거나 아예 닿지 않습니다. 노드는 전부 Ready 로 보여서 한참 뒤에야 알게 됩니다. 커널 패치 뒤 재부팅한 노드가 돌아오지 않는 것도 흔합니다. 누군가 손으로 서비스를 띄웠을 뿐 enable 하지 않았고, 그 사실은 재부팅 전까지 아무 증상도 없습니다. 그래서 정비 절차서에는 "재부팅 뒤 Ready 로 돌아오는지 확인" 이 늘 마지막 줄에 들어갑니다. 정비는 노드를 끄는 순간이 아니라 돌아온 것을 확인하는 순간에 끝나고, 확인하지 않은 재부팅은 다음 장애를 예약해 두는 것과 같습니다.

정비는 돌아온 것을 확인할 때 끝난다

node01 을 정비하는 순서다. 앞의 두 걸음은 노드를 떠날 준비이고, 마지막 걸음에서야 정비가 끝난다.

  1. 비운다cordon 은 스케줄 금지 표시만 한다. drain 은 거기에 더해 파드를 축출하고, 축출은 삭제와 달리 PodDisruptionBudget 을 지킨다. DaemonSet 파드는 --ignore-daemonsets 로 건너뛴다.
  2. 작업하고 재부팅한다kubelet 은 systemd 가 관리하는 서비스다. 손으로 띄웠을 뿐 enable 하지 않았다면 재부팅 뒤 돌아오지 않고 노드는 NotReady 가 된다.
  3. 돌아온 것을 확인하고 되돌린다재부팅 뒤 Ready 로 돌아왔는지 본다. uncordon 으로 스케줄을 되돌려도 이미 옮겨 간 파드는 돌아오지 않는다. 스케줄러는 새 파드를 놓을 때만 판단하기 때문이다.

여기서 구분할 것 active 는 지금 떠 있다는 뜻이고 enabled 는 부팅 때 켜진다는 뜻이다. 재부팅하기 전에는 둘의 차이가 아무 증상도 만들지 않는다.

잠깐, 예측해 보세요 PodDisruptionBudget 이 걸린 Deployment 의 파드가 node01 에 있다. node01 에 cordon 만 하면 그 파드는 어떻게 될까?

설명 확인 · 채점 없는 자가 점검

그대로 남는다. cordon 은 새 파드가 놓이지 않게 표시할 뿐 기존 파드를 옮기지 않는다. 비우려면 drain 이 필요하고, 이때 PDB 때문에 축출이 멈출 수도 있다.

근거 문서

다음 실습에서 할 것

세 노드에 ssh 로 들어가 NIC 를 확인하고, eth1 주소로 컨트롤 플레인을 세운 뒤 Flannel 에게 터널 NIC 를 알려 줍니다. 워커 둘을 조인하고 다른 노드의 파드에 실제로 닿는지 확인한 다음, node01 을 비워 정비하고, 재부팅 뒤 돌아오지 않는 node02 를 그 노드 안에서 되살립니다.