CKA — 쿠버네티스 관리자 · kubeadm 앞뒤 — 인프라 준비, 업그레이드, 확장 인터페이스 · 이론
kubeadm 이 해 주지 않는 것 — 준비, 스큐, 인터페이스
한 줄 요약
kubeadm 은 컨트롤 플레인을 조립하는 도구이지 노드를 준비하는 도구가 아닙니다. swap·IP 포워딩·컨테이너 런타임·cgroup 드라이버·포트는 사람이 맞춰 두어야 하고, 업그레이드는 버전 스큐 정책이 정한 순서(kube-apiserver 가 먼저)를 따라야 하며, CNI·CSI·CRI 는 네트워크·스토리지·런타임을 코어에서 떼어 내 교체 가능하게 만든 인터페이스입니다.
왜 이게 필요했나
kubeadm init 은 시작하자마자 preflight 검사를 돌립니다. [kubeadm 설치](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/) 문서는 요구사항을 이렇게 적습니다 — 머신당 2GB 이상의 RAM, 컨트롤 플레인 머신은 2 CPU 이상, 모든 머신 사이의 완전한 네트워크 연결, 노드마다 고유한 hostname·MAC 주소·product_uuid, 그리고 특정 포트의 개방. 바이너리는 glibc 에 동적 링크되어 있어 Alpine 처럼 glibc 가 없는 배포판에서는 호환 계층이 필요하고, kubeadm 은 지원하는 커널 버전인지 SystemVerification 검사로 확인합니다.
이 검사가 실패하면 kubeadm 은 멈추지만, 통과하도록 고쳐 주지는 않습니다. 그래서 준비 항목을 모르면 "왜 안 되지" 에서 시작해 검색으로 하나씩 메우게 되고, 시험장에서는 그 시간이 없습니다. 업그레이드도 마찬가지입니다. kubeadm 은 스큐 정책을 강제하지만 어느 순서로 어느 노드를 올려야 하는지는 사람이 알고 있어야 합니다.
어떻게 동작하나
설치 전 인프라 준비
swap. kubelet 의 기본 동작은 노드에 swap 이 감지되면 시작을 거부하는 것입니다. 그러니 swapoff -a 로 끄고, 재부팅 뒤에도 유지되도록 /etc/fstab 이나 systemd.swap 설정에서 빼야 합니다. 굳이 swap 을 두려면 kubelet 설정에 failSwapOn: false 를 주어야 하고, 그래도 워크로드는 기본 swapBehavior 인 NoSwap 때문에 swap 을 쓰지 않습니다.
커널 네트워크 설정. [컨테이너 런타임](https://kubernetes.io/docs/setup/production-environment/container-runtimes/) 문서에 따르면 리눅스 커널은 기본적으로 인터페이스 사이의 IPv4 패킷 라우팅을 허용하지 않습니다. 그래서 /etc/sysctl.d/k8s.conf 에 net.ipv4.ip_forward = 1 을 적고 sysctl --system 으로 적용합니다. 문서는 대부분의 네트워크 구현체가 필요하면 이 값을 스스로 바꾸지만 일부는 관리자에게 맡기며, "다른 sysctl 값이나 커널 모듈 적재를 기대하는 구현체도 있으니 네트워크 구현체의 문서를 보라" 고 덧붙입니다. 예전 안내서에 있던 overlay·br_netfilter 모듈 적재와 bridge-nf-call 계열 sysctl 은 현재 공식 페이지의 필수 목록에는 없고 CNI 구현체가 요구하는지에 따라 결정합니다. 시험이나 현장에서 옛 절차를 그대로 외우기보다, 지금 문서가 요구하는 것(ip_forward)과 구현체가 요구하는 것을 구분해 두는 편이 안전합니다.
컨테이너 런타임. 쿠버네티스는 CRI 로 런타임과 대화합니다. 런타임을 지정하지 않으면 kubeadm 이 알려진 소켓 경로를 훑어 자동 감지하고, 여러 개가 있거나 하나도 없으면 오류를 내며 지정을 요구합니다. Docker Engine 은 CRI 를 구현하지 않으므로 cri-dockerd 를 따로 설치해야 하고, kubelet 의 Docker 내장 지원(dockershim)은 1.24 에서 제거됐습니다.
| 런타임 | 유닉스 소켓 |
| --- | --- |
| containerd | unix:///var/run/containerd/containerd.sock |
| CRI-O | unix:///var/run/crio/crio.sock |
| Docker Engine (cri-dockerd) | unix:///var/run/cri-dockerd.sock |
cgroup 드라이버. kubelet 과 런타임은 둘 다 cgroup 으로 자원을 제한하고, 그때 쓰는 드라이버가 cgroupfs 와 systemd 둘입니다. 문서가 "critical" 이라고까지 말하는 규칙은 kubelet 과 런타임이 같은 드라이버를 써야 한다는 것입니다. kubelet 의 기본은 cgroupfs 이지만, init 시스템이 systemd 인 배포판에서는 cgroup 관리자가 둘이 되어 자원 압박 아래에서 노드가 불안정해질 수 있으므로 systemd 드라이버를 씁니다. cgroup v2 를 쓴다면 systemd 드라이버가 답입니다. kubelet 쪽은 KubeletConfiguration 의 cgroupDriver: systemd, 런타임 쪽은 각 런타임 문서의 설정입니다. 쿠버네티스 1.37 에서는 KubeletCgroupDriverFromCRI 기능 게이트가 켜져 있고 런타임이 RuntimeConfig CRI RPC 를 지원하면 kubelet 이 런타임에서 드라이버를 자동으로 알아내지만, containerd 1.y 처럼 그 RPC 가 없는 런타임에서는 여전히 kubelet 자신의 설정을 씁니다.
포트. [포트와 프로토콜](https://kubernetes.io/docs/reference/networking/ports-and-protocols/) 문서의 표를 그대로 옮기면 이렇습니다.
| 어디 | 포트 | 용도 |
| --- | --- | --- |
| 컨트롤 플레인 | 6443 | kube-apiserver |
| 컨트롤 플레인 | 2379-2380 | etcd 클라이언트 API |
| 컨트롤 플레인 | 10250 | kubelet API |
| 컨트롤 플레인 | 10259 | kube-scheduler |
| 컨트롤 플레인 | 10257 | kube-controller-manager |
| 워커 | 10250 | kubelet API |
| 워커 | 10256 | kube-proxy |
| 워커 | 30000-32767 | NodePort Service (TCP·UDP) |
클러스터 생애주기 — 스큐가 순서를 정한다
[버전 스큐 정책](https://kubernetes.io/releases/version-skew-policy/) 은 프로젝트가 최근 세 개의 minor 릴리스 브랜치를 유지하고, 1.19 이후 버전은 약 1년의 패치 지원을 받는다고 적습니다. 구성요소 사이의 허용 차이는 이렇습니다.
- HA 클러스터의 kube-apiserver 들은 서로 1 minor 이내여야 합니다.
- kubelet 은 kube-apiserver 보다 새로울 수 없고, 최대 3 minor 까지 오래되어도 됩니다. kube-proxy 도 같습니다.
- kube-controller-manager·kube-scheduler·cloud-controller-manager 는 kube-apiserver 보다 새로울 수 없습니다.
- kubectl 은 kube-apiserver 의 ±1 minor 안에서 지원됩니다.
이 규칙에서 업그레이드 순서가 나옵니다. kube-apiserver 를 먼저 올리고(minor 를 건너뛸 수 없습니다), 그다음 controller-manager 와 scheduler, 마지막에 kubelet 과 kube-proxy 를 올립니다. 반대로 하면 "kubelet 이 apiserver 보다 새롭다" 는 금지 상태를 지나게 됩니다. 문서는 업그레이드 전에 현재 minor 의 최신 패치로 올리고, 목표 minor 의 최신 패치로 가라고 권합니다.
[kubeadm 클러스터 업그레이드](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/) 문서가 정한 절차는 세 단계입니다 — 첫 컨트롤 플레인 노드, 추가 컨트롤 플레인 노드, 워커 노드.
1. 첫 컨트롤 플레인에서 kubeadm upgrade plan 으로 올릴 수 있는 버전과 구성요소 설정 상태를 확인한 뒤 kubeadm upgrade apply v1.X.y 를 실행합니다. 이 명령은 API 서버 도달 여부·모든 노드 Ready·컨트롤 플레인 상태를 검사하고, 스큐 정책을 강제하며, 이미지를 준비하고, 컨트롤 플레인 구성요소를 교체하되 하나라도 올라오지 않으면 되돌리고, 새 CoreDNS·kube-proxy 매니페스트를 적용합니다. kubeadm 이 관리하는 인증서도 이때 갱신됩니다.
2. 추가 컨트롤 플레인 노드에서는 kubeadm upgrade node 를 씁니다. 클러스터의 ClusterConfiguration 을 가져와 static Pod 매니페스트와 kubelet 설정을 올립니다.
3. 각 노드의 kubelet 을 minor 업그레이드할 때는 먼저 drain 합니다. 그다음 kubelet·kubectl 패키지를 올리고, 워커에서는 kubeadm upgrade node 로 kubelet 설정을 갱신한 뒤 kubelet 을 재시작하고, kubectl uncordon 으로 되돌립니다.
문서는 컨테이너 스펙 해시가 바뀌므로 업그레이드 뒤 모든 컨테이너가 재시작된다고 미리 알려 줍니다. 실패했을 때는 같은 명령을 다시 실행해도 됩니다(멱등), 버전을 바꾸지 않은 채 kubeadm upgrade apply --force 로 상태를 맞출 수도 있고, /etc/kubernetes/tmp 아래에 etcd 와 매니페스트 백업이 남습니다.
[노드를 안전하게 비우기](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/) 문서에 따르면 kubectl drain 은 파드를 우아하게 종료시키며 PodDisruptionBudget 을 존중하고, DaemonSet 파드가 있으면 --ignore-daemonsets 가 필요합니다. drain 이 성공적으로 끝났다는 것은 (제외된 시스템 파드를 빼고) 모든 파드가 안전하게 쫓겨났다는 뜻이고, 그 뒤에 kubectl uncordon 으로 스케줄링을 다시 엽니다. 한 번에 한 노드씩 실행하되 여러 터미널에서 병렬로 돌려도 PDB 는 함께 지켜집니다.
CNI·CSI·CRI — 무엇을 떼어 냈나
세 인터페이스는 이름이 비슷하지만 떼어 낸 층이 다릅니다.
| 인터페이스 | 떼어 낸 것 | 누가 누구를 부르나 |
| --- | --- | --- |
| CRI | 컨테이너 런타임 | kubelet 이 gRPC 클라이언트로 런타임을 부른다 |
| CNI | 파드 네트워크 구현 | 컨테이너 런타임이 CNI 플러그인을 적재해 파드 네트워크 모델을 구현한다 |
| CSI | 스토리지 시스템 | CSI 드라이버가 스토리지를 표준 인터페이스로 노출하고, 파드는 csi 볼륨 타입으로 쓴다 |
[CRI](https://kubernetes.io/docs/concepts/architecture/cri/) 문서는 CRI 가 kubelet 과 런타임 사이의 주 gRPC 프로토콜이며 v1.23 에서 안정화됐고, 1.26 부터 kubelet 이 v1 CRI API 를 요구하므로 지원하지 않는 런타임에서는 노드가 등록되지 않는다고 적습니다. 엔드포인트는 kubelet 의 --container-runtime-endpoint 로 지정합니다. [네트워크 플러그인](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) 문서는 CNI 플러그인이 쿠버네티스 네트워크 모델을 구현하는 데 필수이고, CNI 스펙 v0.4.0 이상(권장 v1.0.0)과 호환되어야 하며, 1.24 에서 kubelet 의 cni-bin-dir·network-plugin 플래그가 제거되어 CNI 관리는 kubelet 이 아니라 런타임의 몫이 되었다고 설명합니다. [볼륨](https://kubernetes.io/docs/concepts/storage/volumes/) 문서의 CSI 절은 CSI 가 임의의 스토리지 시스템을 컨테이너 워크로드에 노출하는 표준 인터페이스이며, PVC 참조·generic ephemeral 볼륨·CSI ephemeral 볼륨 세 방식으로 쓸 수 있고, 이전 방식인 FlexVolume 은 1.23 부터 폐기됐다고 적습니다.
현장에서 만나는 모습
자원 압박에서만 노드가 흔들린다. 평소에는 멀쩡한데 메모리가 차면 kubelet 이 재시작되거나 파드가 이상하게 죽는 노드는 cgroup 드라이버 불일치를 의심합니다. 문서가 정확히 이 증상을 적어 두었습니다 — systemd 가 init 인데 kubelet 과 런타임이 cgroupfs 를 쓰면 cgroup 관리자가 둘이 되어 자원 뷰가 갈립니다. 문서는 클러스터에 이미 합류한 노드의 드라이버를 바꾸는 것도 민감한 작업이라고 경고하니, 합류 전에 맞추는 편이 훨씬 쌉니다.
drain 없이 kubelet 을 올렸다. 패키지만 갈아 끼우고 kubelet 을 재시작하면 그 노드의 컨테이너가 전부 재시작되는데, drain 을 하지 않았으니 PDB 도 우아한 종료도 지켜지지 않습니다. 문서가 "minor 업그레이드는 먼저 drain" 이라고 못 박은 이유입니다.
다음 퀴즈에서 확인할 것
퀴즈에서는 swap 에 대한 kubelet 의 기본 동작, cgroup 드라이버를 systemd 로 맞추는 이유, 현재 공식 문서가 요구하는 커널 설정, 업그레이드 순서, kubelet 의 허용 스큐, 그리고 CNI 플러그인을 적재하는 주체가 누구인지 묻습니다.