쿠버네티스 배포판 — 직접 세운다 · kubeadm 으로 표준 조립 · 실습
init 은 성공했는데 노드가 NotReady 다
목표
빈 우분투 VM 에서 kubeadm 으로 컨트롤 플레인 한 대를 세우고, NotReady 의 원인이 CNI 라는 것을 증거로 확인한 뒤
Flannel 을 붙여 DNS 까지 살립니다. 그 과정에서 정적 파드·테인트·인증서 수명·조인 토큰이 각각 무슨 역할인지 손으로 봅니다.
왜 중요한가
k3s 와 k0s 는 런타임·CNI·저장소를 배포판이 대신 골라 줍니다. kubeadm 은 그 결정을 전부 사람에게 남기는 표준 조립 키트라서,
init 이 성공했다는 말이 "클러스터가 쓸 수 있다" 는 뜻이 아닙니다. 네트워크 애드온을 고르고, 그 애드온이 요구하는 커널 설정을 맞추고,
대역이 주변 네트워크와 겹치지 않게 하는 것이 전부 운영자의 일입니다. 특히 노드가 Ready 로 바뀌었다고 끝난 것이 아니라는 점을
이 실습에서 직접 겪습니다 — CNI 설정 파일만 생겨도 Ready 가 되지만, 데몬이 죽어 있으면 CoreDNS 는 뜨지 않습니다.
인증서 1년 만료와 조인 토큰의 CA 해시는 설치 당일에는 아무 문제도 일으키지 않다가 몇 달 뒤에 사고가 되는 부분이라 미리 읽는 법을 익힙니다.
단계
1. net.ipv4.ip_forward = 1 을 /etc/sysctl.d/k8s.conf 에 적어 적용하고, containerd config default 로 /etc/containerd/config.toml 을 만든 뒤 runc 의 SystemdCgroup 을 true 로 바꿔 containerd 를 재시작하세요. 그다음 /root/kubeadm/runtime.json 에 containerd_version(containerd --version 의 세 번째 칸), systemd_cgroup(실행 중인 containerd 가 보고하는 값, 불리언), ip_forward(지금 커널 값, 숫자), swap_total_kb(/proc/meminfo 의 SwapTotal, 숫자)를 적으세요.
2. kubeadm init 을 --kubernetes-version v1.36.4 --pod-network-cidr 172.20.0.0/16 --service-cidr 172.21.0.0/16 로 실행하고 출력 전체를 /root/kubeadm/init.log 에 저장하세요. /etc/kubernetes/admin.conf 를 /root/.kube/config 로 복사해 kubectl 이 새 클러스터를 보게 합니다.
3. CNI 를 설치하기 전에 지금 상태를 /root/kubeadm/notready.json 에 기록하세요. 필드: node_uid, ready_status(Ready 조건의 status), ready_message(Ready 조건의 message), taints(노드 테인트를 키:효과 문자열로 담은 배열), coredns_phase(CoreDNS 파드 하나의 phase), cni_conf_count(/etc/cni/net.d 에서 점으로 시작하지 않는 파일 수), observed_at(기록한 UTC 시각, 2026-01-01T00:00:00Z 형식).
4. Flannel v0.28.9 릴리스의 kube-flannel.yml 을 /root/kubeadm/kube-flannel.yml 로 받아 net-conf.json 의 Network 를 이 클러스터 파드 대역으로 바꾸고 적용하세요(br_netfilter 는 아직 올리지 않습니다). 30초쯤 지켜본 뒤 /root/kubeadm/cni-symptom.json 에 node_ready(Ready 조건 status), coredns_ready_replicas(coredns Deployment 의 readyReplicas, 없으면 0), flannel_pod_uid, flannel_restarts(kube-flannel 컨테이너 restartCount), flannel_error(flannel 로그에서 원인을 말하는 한 줄)를 적으세요.
5. br_netfilter 모듈을 /etc/modules-load.d/k8s.conf 에 적어 부팅 때 올라오게 하고 지금도 올리세요. net.bridge.bridge-nf-call-iptables = 1 을 /etc/sysctl.d/k8s.conf 에 더해 적용합니다. Flannel DaemonSet 이 준비되고 CoreDNS 두 개가 Available 이 되며, 노드에서 dig @<kube-dns 서비스 IP> kubernetes.default.svc.cluster.local 이 kubernetes 서비스 IP 를 돌려줘야 합니다.
6. /root/kubeadm/static-pods.json 에 manifests(/etc/kubernetes/manifests 의 파일 이름 정렬 배열), owner_kind(kube-scheduler 파드의 ownerReferences 종류), etcd_data_dir(etcd 매니페스트가 hostPath 로 붙인 데이터 경로), service_cluster_ip_range(kube-apiserver 매니페스트의 같은 이름 플래그 값)를 적으세요. 그리고 kube-scheduler 파드를 kubectl delete 로 지운 뒤 되돌아온 것을 확인해 deleted_uid, new_uid, container_id_before, container_id_after(containerStatuses[0].containerID)를 같은 파일에 더합니다.
7. default 네임스페이스에 파드 probe(이미지 registry.k8s.io/pause:3.10.2, restartPolicy Never)를 만들고 Pending 인 것을 확인해 /root/kubeadm/pending.json 에 pod_uid, phase, message(PodScheduled 조건의 message), observed_at(UTC, 2026-01-01T00:00:00Z 형식)을 적으세요. 그다음 노드에서 node-role.kubernetes.io/control-plane:NoSchedule 테인트를 빼 probe 가 Running 이 되게 합니다. 파드는 지우고 다시 만들지 않습니다.
8. kubeadm certs check-expiration 과 openssl 로 확인해 /root/kubeadm/certs.json 에 apiserver_not_after, ca_not_after(둘 다 UTC 2026-01-01T00:00:00Z 형식), apiserver_valid_days, ca_valid_days(각 인증서 notBefore 부터 notAfter 까지 일수, 정수)를 적으세요. 그리고 kubeadm token create --ttl 3h --description worker-join 으로 새 토큰을 만들고, CA 공개키 해시를 직접 계산해 /root/kubeadm/join.txt 에 한 줄 kubeadm join <API 엔드포인트> --token <토큰> --discovery-token-ca-cert-hash sha256:<해시> 를 적으세요.
9. /root/kubeadm/report.json 에 kubernetes_version(서버 gitVersion), pod_cidr, service_cidr, dns_service_ip(kube-dns 서비스 IP), cgroup_driver(kubelet 이 쓰는 드라이버), notready_cause(3단계 NotReady 의 원인: cni · runtime · certificate 중 하나), flannel_blocker(4단계에서 flannel 을 막은 커널 모듈 이름), join_token_id(8단계 토큰의 앞 6자리)를 적으세요.
참고
- VM 에 containerd 2.3.5(
/usr/local), runc 1.5.1, crictl 1.36.0, kubeadm·kubelet·kubectl 1.36.4 가 설치돼 있고 이미지는 미리 받아 두었습니다. 클러스터는 아직 없습니다. - 한 번 init 한 뒤 다시 하려면
kubeadm reset -f가 필요합니다. 되돌리기 어려우니 대역을 먼저 확인하세요. - 흔한 실수: 4단계 전에 br_netfilter 를 미리 올리는 것. 그러면 4단계에서 볼 증상이 나타나지 않아 기록을 남길 수 없습니다.
- 흔한 실수:
containerd config dump결과만 보고 재시작을 빠뜨리는 것. 실행 중인 값은crictl info에 있습니다. - 문서: [Installing kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/) · [Creating a cluster with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/) · [Container Runtimes](https://kubernetes.io/docs/setup/production-environment/container-runtimes/) · [Certificate Management with kubeadm](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/)
단계 9개
- init 전에 런타임부터 맞춘다
- 대역을 비켜서 init 한다
- init 은 성공했는데 노드가 NotReady 다
- CNI 를 깔았더니 노드만 Ready 가 됐다
- br_netfilter 를 올려 CNI 를 살린다
- 지워도 돌아오는 컨트롤 플레인 파드
- 한 대짜리 클러스터에 파드가 안 올라간다
- 1년 뒤와 두 번째 노드를 준비한다
- 무엇을 직접 골랐는지 보고