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

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

무중단 마이너 업그레이드 — 1.35.8 에서 1.36.4 로, 탐침을 켠 채

TT Lab 에서 이어서 보기

목표

kubeadm 클러스터(컨트롤 플레인 하나, 워커 하나)를 1.35.8 에서 1.36.4 로 올립니다. 서비스 앞에 탐침을 켜 두고, PDB 로 지키고, 스냅숏으로 되돌릴 길을 만든 뒤, 공식 순서대로 컨트롤 플레인 → 각 노드(비우고 → 올리고 → 되돌림)를 진행하고, 마지막에 서비스가 몇 번·얼마나 끊겼는지를 기록으로 답합니다.

왜 중요한가

쿠버네티스는 1년에 세 마이너가 나오고, 각 마이너는 약 14개월 동안만 지원됩니다. 업그레이드는 연례 행사가 아니라 분기 업무입니다. 그리고 업그레이드의 품질은 「끝났다」 가 아니라 「그동안 서비스가 어땠나」 로 잽니다. 버전 차이 규칙(API 서버는 건너뛸 수 없고, kubelet 은 세 마이너까지 뒤처져도 됨)이 순서를 정하고, PDB 와 파드 배치가 정비 중의 가용성을 정하고, 변경 전 스냅숏이 실패했을 때의 출구를 정합니다.

단계

  1. 지금 클러스터는 1.35.8 입니다. 한 마이너 위인 1.36.4 로 올립니다. 두 노드 모두 kubernetes apt 저장소를 v1.36 로 바꾸고(서명 키 포함), controlplane 에서 apt-cache madison kubeadm 이 보여 주는 1.36 의 최신 패키지 판을 확인한 뒤 /root/kx/up/plan.txt 에 from=v1.35.8 와 to=v1.36.4 두 줄을 적으세요.
  2. 서비스 shop/shop(2개)에 PodDisruptionBudget shop-pdb(한 번에 하나까지만 내려감, maxUnavailable: 1)를 걸고, kx-incident probe-start 로 탐침을 켜세요. 탐침은 0.2초마다 서비스 주소로 요청을 보내 /var/lib/labhub/kx/probe.log 에 <시각ms> <HTTP 코드> 를 남깁니다. shop 의 두 파드는 서로 다른 노드에 있어야 합니다.
  3. 업그레이드 전에 etcd 스냅숏을 /root/kx/up/pre-upgrade.db 에, /etc/kubernetes 디렉터리 전체를 /root/kx/up/etc-kubernetes.tgz 에 남기세요.
  4. controlplane 에서 kubeadm 을 1.36.4 로 올리고(apt-mark unhold → 설치 → hold), kubeadm upgrade plan 으로 확인한 뒤 kubeadm upgrade apply v1.36.4 로 컨트롤 플레인을 올리세요. API 서버가 v1.36.4 를 보고해야 합니다. 이 단계에서는 kubelet 패키지를 아직 올리지 않습니다.
  5. controlplane 을 비우고(kubectl drain controlplane --ignore-daemonsets), kubelet·kubectl 을 1.36.4 로 올려 kubelet 을 다시 띄운 뒤 다시 스케줄 가능하게(uncordon) 하세요. controlplane 노드가 v1.36.4 를 보고해야 합니다.
  6. node01 에서 kubeadm 을 1.36.4 로 올리고 kubeadm upgrade node 를 실행한 뒤, controlplane 에서 node01 을 비우고, node01 의 kubelet·kubectl 을 1.36.4 로 올려 kubelet 을 다시 띄우고, 다시 스케줄 가능하게 하세요.
  7. kx-incident probe-stop 으로 탐침을 멈추고, /var/lib/labhub/kx/probe.log 에서 전체 요청 수, 실패 수(200 이 아닌 것), 가장 길게 이어진 실패 구간(마지막 성공부터 다음 성공까지, ms)을 세어 /root/kx/up/report.txt 에 total=, failed=, longest_gap_ms= 세 줄로 적으세요. 두 노드와 kube-proxy 가 모두 새 판이어야 합니다.

참고

판을 정하고 저장소부터 바꾼다

지금 클러스터는 1.35.8 입니다. 한 마이너 위인 1.36.4 로 올립니다. 두 노드 모두 kubernetes apt 저장소를 v1.36 로 바꾸고(서명 키 포함), controlplane 에서 apt-cache madison kubeadm 이 보여 주는 1.36 의 최신 패키지 판을 확인한 뒤 /root/kx/up/plan.txt 에 from=v1.35.8 와 to=v1.36.4 두 줄을 적으세요.

pkgs.k8s.io 는 마이너마다 저장소가 따로입니다 — /etc/apt/sources.list.d/kubernetes.list 의 주소에 v1.35 가 박혀 있어서, 이것을 바꾸지 않으면 apt 는 1.36 을 아예 모릅니다. API 서버는 마이너를 건너뛸 수 없고(1.35 → 1.37 불가), kubelet 은 API 서버보다 세 마이너까지 뒤처져도 됩니다 — 그래서 컨트롤 플레인을 먼저, 노드를 나중에 올립니다.

업그레이드 동안 서비스를 지키는 장치와 눈

서비스 shop/shop(2개)에 PodDisruptionBudget shop-pdb(한 번에 하나까지만 내려감, maxUnavailable: 1)를 걸고, kx-incident probe-start 로 탐침을 켜세요. 탐침은 0.2초마다 서비스 주소로 요청을 보내 /var/lib/labhub/kx/probe.log 에 <시각ms> <HTTP 코드> 를 남깁니다. shop 의 두 파드는 서로 다른 노드에 있어야 합니다.

노드를 비울 때 drain 은 PDB 가 허락하는 만큼만 파드를 내보냅니다. 파드 둘이 한 노드에 몰려 있으면 그 노드를 비우는 순간 둘 다 사라집니다 — kubectl -n shop get pods -o wide. 「끊기지 않았다」 는 느낌이 아니라 탐침의 기록으로 말하려는 것입니다.

되돌릴 길을 먼저 만든다

업그레이드 전에 etcd 스냅숏을 /root/kx/up/pre-upgrade.db 에, /etc/kubernetes 디렉터리 전체를 /root/kx/up/etc-kubernetes.tgz 에 남기세요.

kubeadm 도 upgrade apply 중에 /etc/kubernetes/tmp/ 에 etcd 와 매니페스트 백업을 남기지만, 그것은 「방금 그 업그레이드」 의 것이고 지우지 않으면 쌓입니다. 변경 전 스냅숏은 채용 공고들이 업그레이드 런북의 첫 줄로 요구하는 항목입니다. 스냅숏은 etcdutl snapshot status 로 읽히는지까지 확인하세요.

컨트롤 플레인을 1.36.4 로

controlplane 에서 kubeadm 을 1.36.4 로 올리고(apt-mark unhold → 설치 → hold), kubeadm upgrade plan 으로 확인한 뒤 kubeadm upgrade apply v1.36.4 로 컨트롤 플레인을 올리세요. API 서버가 v1.36.4 를 보고해야 합니다. 이 단계에서는 kubelet 패키지를 아직 올리지 않습니다.

upgrade apply 는 몇 분 걸립니다 — 새 이미지를 받고, etcd·API 서버·컨트롤러 매니저·스케줄러를 하나씩 바꾸고, CoreDNS·kube-proxy 를 새 판으로 바꿉니다. 그동안 kubectl 이 잠깐씩 응답하지 않습니다. 그사이 탐침 기록(tail -f /var/lib/labhub/kx/probe.log)을 보세요 — API 서버가 바뀌는 동안 서비스는 끊기나요? 실패해도 같은 명령을 다시 돌릴 수 있습니다(멱등).

controlplane 의 kubelet 을 비우고 올린다

controlplane 을 비우고(kubectl drain controlplane --ignore-daemonsets), kubelet·kubectl 을 1.36.4 로 올려 kubelet 을 다시 띄운 뒤 다시 스케줄 가능하게(uncordon) 하세요. controlplane 노드가 v1.36.4 를 보고해야 합니다.

공식 문서는 kubelet 의 마이너 업그레이드를 반드시 노드를 비운 뒤에 하라고 합니다 — 제자리 업그레이드는 지원하지 않습니다. 정적 파드(컨트롤 플레인)는 drain 이 건드리지 않습니다. 패키지를 바꾼 뒤 systemctl daemon-reload && systemctl restart kubelet. shop 파드 하나가 node01 로 옮겨 가는 동안 PDB 가 나머지 하나를 붙잡아 둡니다.

node01 을 올린다

node01 에서 kubeadm 을 1.36.4 로 올리고 kubeadm upgrade node 를 실행한 뒤, controlplane 에서 node01 을 비우고, node01 의 kubelet·kubectl 을 1.36.4 로 올려 kubelet 을 다시 띄우고, 다시 스케줄 가능하게 하세요.

워커의 kubeadm upgrade node 는 kubelet 설정(/var/lib/kubelet/config.yaml)을 클러스터의 새 판에 맞춰 내려받습니다. drain 은 controlplane 에서, 패키지 교체는 node01 에서 합니다. 다 끝나면 kubectl get nodes 의 VERSION 이 둘 다 같아야 합니다.

끊겼는가 — 기록으로 답한다

kx-incident probe-stop 으로 탐침을 멈추고, /var/lib/labhub/kx/probe.log 에서 전체 요청 수, 실패 수(200 이 아닌 것), 가장 길게 이어진 실패 구간(마지막 성공부터 다음 성공까지, ms)을 세어 /root/kx/up/report.txt 에 total=, failed=, longest_gap_ms= 세 줄로 적으세요. 두 노드와 kube-proxy 가 모두 새 판이어야 합니다.

실패가 하나도 없었다면 longest_gap_ms 는 0 입니다. 실패가 있었다면 언제였는지 시각을 업그레이드 단계와 맞춰 보세요 — API 서버가 바뀔 때인가요, 노드를 비울 때인가요? awk 나 python 으로 셉니다. 「가장 긴 실패 구간」 은 실패가 시작되기 직전의 성공 시각부터 실패가 끝난 뒤 첫 성공 시각까지입니다.