11.1 kubeadm으로 클러스터 구축
관리형 Kubernetes에서 클러스터 생성은 콘솔 버튼 하나로 끝나지만, 직접 구축에서는 control plane을 이루는 프로세스 하나하나를 운영자가 기동해야 한다. kubeadm은 이 과정을 절차로 정리한 공식 도구다. 머신 프로비저닝이나 CNI 설치까지 대신하지는 않고, 준비된 머신 위에 보안 기본값을 갖춘 최소 동작 클러스터를 만드는 것까지가 kubeadm의 범위다. 이 경계를 알아야 kubeadm init이 끝난 직후 노드가 NotReady인 것이 정상이라는 사실도 이해된다.
노드 사전 준비
kubeadm init을 실행하기 전에 control plane과 워커 공통으로 갖춰야 할 조건이 있다. preflight 검사가 상당 부분을 걸러 주지만, 각 항목이 왜 필요한지 알아야 실패 메시지를 읽고 대응한다.
첫째는 컨테이너 런타임이다. kubelet은 컨테이너를 직접 실행하지 않고 CRI(Container Runtime Interface)로 런타임을 호출한다. containerd와 CRI-O가 대표적이고, Docker Engine은 1.24에서 dockershim이 제거된 뒤로 cri-dockerd 어댑터를 별도로 설치해야 쓸 수 있다.
둘째는 커널 모듈과 네트워크 sysctl이다. br_netfilter 모듈은 리눅스 브리지를 지나는 트래픽도 iptables 규칙의 검사 대상이 되게 한다. 브리지 기반 Pod 네트워크에서 kube-proxy의 Service NAT 규칙이 적용되려면 이 설정이 전제된다.
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system셋째는 swap이다. kubelet은 기본 설정에서 swap이 켜진 노드를 발견하면 기동을 거부한다. 메모리 requests와 limits로 자원을 예약하고 부족하면 eviction으로 정리하는 모델에서, swap은 “메모리가 부족하다"는 판단 자체를 흐리기 때문이다. 1.28부터 NodeSwap 기능이 beta로 올라와 제한적으로 swap을 허용하는 구성이 지원되지만, 기본값은 여전히 거부이고 관례도 비활성 쪽이다.
sudo swapoff -a
# /etc/fstab의 swap 항목도 주석 처리해 재부팅 후에도 유지한다넷째는 cgroup 드라이버 일치다. kubelet과 컨테이너 런타임은 같은 cgroup 드라이버(cgroupfs 또는 systemd)를 써야 한다. systemd가 init인 배포판에서는 systemd 드라이버가 권장되고, kubeadm은 1.22부터 KubeletConfiguration에 별도 지정이 없으면 systemd를 기본값으로 사용한다. containerd 쪽도 맞춰 준다.
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = truesystemctl restart containerd까지 실행한 뒤 kubeadm을 진행한다.kubeadm init이 하는 일
kubeadm init은 여러 phase의 연쇄다. 각 phase는 kubeadm init phase <이름>으로 따로 실행할 수도 있어서, 전체 흐름을 알면 실패 지점만 다시 실행하는 대응이 가능하다.
preflight 검사
포트 점유(6443, 10250 등), 런타임 소켓, swap, 커널 설정, 필수 명령 존재 여부를 검사한다. [ERROR]는 진행을 중단하고 [WARNING]은 통과한다.
인증서와 kubeconfig 생성
/etc/kubernetes/pki에 클러스터 CA를 비롯한 인증서 체계를 만들고, 컴포넌트별 kubeconfig 파일(admin.conf, controller-manager.conf 등)을 생성한다. 이 인증서 체계의 구조와 만료 관리는 11.5에서 다룬다.
control plane static Pod 기동
kube-apiserver, kube-controller-manager, kube-scheduler, etcd의 매니페스트를 /etc/kubernetes/manifests에 기록한다. kubelet이 이 디렉토리를 감시하다가 static Pod로 기동한다. 아직 apiserver가 없는 시점이라 apiserver를 경유하지 않는 static Pod 방식이 필요하다. control plane 구성 요소의 역할은 Part II. 클러스터 아키텍처에서 다뤘다.
addon 설치와 join 명령 출력
apiserver가 응답하기 시작하면 CoreDNS와 kube-proxy를 addon으로 설치하고, bootstrap token이 담긴 kubeadm join 명령을 출력한다.
init이 끝나면 관리자 자격증명을 kubectl이 읽을 위치로 복사한다.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/configkubeadm join과 토큰
워커는 init이 출력한 명령으로 합류한다. 주소, token, CA hash는 각자의 클러스터에서 출력된 값을 쓴다.
kubeadm join 192.0.2.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1234...cdef두 값의 방향이 다르다는 점이 이 명령의 요지다. token은 클러스터가 노드를 확인하는 수단이고, CA hash는 노드가 클러스터를 확인하는 수단이다. 워커는 hash로 자신이 접속한 apiserver의 CA가 진짜인지 검증하고, 클러스터는 token으로 합류 요청의 자격을 검증한다. 검증이 끝나면 kubelet이 CSR을 제출하고 클라이언트 인증서를 발급받는 TLS bootstrap이 이어진다.
token의 기본 TTL은 24시간이다. 하루가 지나 join 명령을 잃어버렸다면 control plane에서 새로 만든다.
kubeadm token create --print-join-commandcontrol plane 노드를 추가로 합류시킬 때는 --control-plane 플래그와 인증서 배포용 --certificate-key가 더 필요하다. 워커 join과 달리 로컬에 인증서와 static Pod 매니페스트가 다시 만들어진다.
CNI 설치가 별도 단계인 이유
init과 join이 다 끝나도 노드는 Ready가 아니다.
NAME STATUS ROLES AGE VERSION
cp-1 NotReady control-plane 3m v1.31.0
node-1 NotReady <none> 1m v1.31.0Kubernetes는 “모든 Pod는 NAT 없이 서로 통신한다"는 네트워크 모델을 정의할 뿐, 구현은 CNI 플러그인에 위임한다. kubeadm이 특정 플러그인을 대신 골라 설치하지 않는 이유다. kubelet은 CNI 설정이 없으면 컨테이너 네트워크를 구성하지 못하므로 NetworkReady 조건이 false가 되고, 노드는 NotReady로 남는다. Pod 네트워크가 필요한 CoreDNS도 Pending에 머문다.
Calico, Flannel, Cilium 같은 플러그인 중 하나를 골라 매니페스트나 전용 CLI로 설치하면 노드가 Ready로 전환된다. 선택 기준은 NetworkPolicy 지원 여부, 캡슐화 방식, 운영 조직의 익숙함 같은 요구사항이다. init 때 지정한 --pod-network-cidr 값이 플러그인이 기대하는 대역과 일치해야 한다는 점도 이 단계에서 확인한다. 네트워크 모델 자체는 Part V. 네트워킹에서 다뤘다.
정리
- kubeadm은 준비된 머신 위에 최소 동작 클러스터를 만드는 도구다. 런타임 설치, br_netfilter와 sysctl, swap 비활성, cgroup 드라이버 정리는 그 전에 끝나 있어야 한다.
- init은 preflight, 인증서, control plane static Pod, addon 순서로 진행되고, join은 token과 CA hash로 양방향 신뢰를 확인한다.
- CNI 설치 전의 NotReady는 정상이다. 네트워크 모델의 구현이 플러그인에 위임되어 있고, kubeadm의 범위 밖이기 때문이다.