kubeadm 故意留空的部分
一句话总结
kubeadm 不是“帮你安装 Kubernetes 的工具”,而是按最佳实践组装控制平面的工具;运行时、网络、网段、内核配置,始终都是运维人员自己的决定。
为什么需要它
先用过 k3s 或 k0s 的人,在 kubeadm init 刚成功之后会感到困惑。绿色的成功提示出现了,节点却是 NotReady,CoreDNS 是 Pending。这不是故障。kubeadm 是有意不选择网络附加组件的。Creating a cluster with kubeadm 文档用粗体写下的那句话就是:“必须部署基于 CNI 的 Pod 网络,在安装网络之前,集群 DNS 不会启动。”
这样设计的原因是,kubeadm 的定位是“其他安装工具的零部件”。文档说,kubeadm 被期望用作 Ansible、Terraform 这类预配系统或更大的安装工具的构建块。如果零部件擅自选定了 CNI、存储、Ingress,那么建在它之上的工具就得重新拆掉。所以 kubeadm 只创建证书、kubeconfig、静态 Pod 清单、引导令牌这类在任何环境中都应该相同的东西,把随环境而异的部分留空。代价是,人需要了解的东西变多了。
工作原理
安装分为三层。
호스트 준비 swap · ip_forward · 컨테이너 런타임(CRI) · cgroup 드라이버
kubeadm init preflight → 인증서(/etc/kubernetes/pki) → kubeconfig → 정적 파드 매니페스트
→ kubelet 이 매니페스트를 보고 etcd·apiserver·controller-manager·scheduler 를 띄움
→ CoreDNS·kube-proxy 애드온, 부트스트랩 토큰, control-plane 테인트
사람의 몫 CNI 설치(대역을 init 과 맞춰서) · 테인트 정리 · 워커 조인 · 인증서 갱신 계획
该代码块中的韩文说明依次为:主机准备包括 swap、ip_forward、容器运行时(CRI)和 cgroup 驱动;kubeadm init 依次经过 preflight、证书(/etc/kubernetes/pki)、kubeconfig、静态 Pod 清单,再由 kubelet 根据清单启动 etcd、apiserver、controller-manager、scheduler,最后是 CoreDNS 和 kube-proxy 附加组件、引导令牌以及 control-plane 污点;属于人的部分包括安装 CNI(网段要与 init 时一致)、清理污点、让工作节点加入,以及证书续期计划。
主机准备。 根据 Installing kubeadm,如果存在 swap,kubelet 默认会拒绝启动。软件包仓库位于 pkgs.k8s.io,每个次版本各有一个,文档要求安装之后用 apt-mark hold 把 kubelet、kubeadm、kubectl 排除在常规升级之外,因为升级必须遵循 kubeadm 的流程。Container Runtimes 文档说明了两点:Linux 内核默认会阻止接口之间的 IPv4 转发;在以 systemd 作为 init 的主机上使用 cgroupfs 驱动,会出现两个 cgroup 管理器,资源紧张时节点可能变得不稳定。在 containerd 2.x 中,plugins.'io.containerd.cri.v1.runtime' 之下 runc 选项中的 SystemdCgroup = true 就是这个开关。
这里版本很重要。KubeletCgroupDriverFromCRI 功能在 1.34 中成为 stable,如果 CRI 支持 RuntimeConfig 调用,kubelet 就会忽略自身配置中的 cgroupDriver,使用运行时告知的值。containerd 从 2.0 开始支持。在本实验 VM(containerd 2.3.5、kubelet 1.36.4)上实测,kubelet 日志中会打印 Using cgroup driver setting received from the CRI runtime。也就是说,现在 containerd 的配置实际上是唯一的事实来源。
init。 preflight 中既有会阻止的,也有只警告的。在 1.36.4 上实测,ip_forward 为 0 时会以 [ERROR FileContent--proc-sys-net-ipv4-ip_forward] 阻止,但不会检查 br_netfilter。通过之后,kubeadm 会在 /etc/kubernetes/manifests 中写入四个清单,由 kubelet 直接读取该目录来启动控制平面。API 服务器还不存在,却要先启动 API 服务器,这个鸡生蛋蛋生鸡的问题就是用静态 Pod 解决的。在 API 服务器中看到的控制平面 Pod 是 kubelet 注册的镜像 Pod(mirror Pod),所有者是 Node,即使删除,kubelet 也会重新注册。
网段。 --pod-network-cidr 让 controller-manager 为每个节点分配 podCIDR,--service-cidr 则成为 API 服务器的 --service-cluster-ip-range。文档说,Pod 网络与主机网络重叠会出问题,所以要选择不重叠的网段,并把同样的值同时填入 init 和网络插件 YAML 两处。
在现场相遇的样子
这是在本实验 VM 上原样复现的场景。应用 Flannel v0.28.9 的清单后,8 秒内节点就变成了 Ready。然而 CoreDNS 很长时间都处于 ContainerCreating。原因在于顺序。Flannel Pod 的初始化容器先复制了 /etc/cni/net.d/10-flannel.conflist,kubelet 只看到配置文件出现了,就判断网络已经就绪。而 flanneld 本体却这样崩溃并反复重启。
E0914 22:51:43.944688 1 main.go:292] Failed to check br_netfilter:
stat /proc/sys/net/bridge/bridge-nf-call-iptables: no such file or directory
Flannel README 中也写道,Flannel 需要 br_netfilter 才能启动,而 kubeadm 从 1.30 起不再检查这个模块。即使加载了模块,由于 CrashLoopBackOff 的重启间隔,实测还多等了 74 秒。Ready 只是“有配置文件”,并不是“数据路径在运转”。 判断标准应该是 CoreDNS 是否真的能返回域名解析结果。
第二个场景是网段。这台 VM 运行在宿主 Kubernetes 之内,VM Pod 的地址是宿主 Pod 网段 10.244.x。Flannel 清单中默认的 Network 恰好是 10.244.0.0/16,所以不修改清单直接应用,内层 Pod 网段就会与外层重叠。因此本实验使用 172.20.0.0/16 和 172.21.0.0/16,并把清单中的一行改成与 init 的值一致。
第三个是 containerd 的 drop-in。在作者的 GPU 节点(containerd 1.7.27)上,曾经出现一个 conf.d drop-in 整体覆盖 CRI 插件配置,导致运行时配置回到默认值的事。在本 VM 的 containerd 2.3.5 上做同样的实验,drop-in 是按字段合并的,SystemdCgroup = true 得以保留。合并规则可能随版本而变,所以不要只靠肉眼读文件就相信它,应养成用 containerd config dump 和 crictl info 确认的习惯。
实际工作中真正重要的事
证书有效期是一年。 根据 Certificate Management with kubeadm,kubeadm 创建的客户端证书一年后过期,默认值是叶子证书(leaf certificate)8760h、CA 87600h。在这台 VM 上,kubeadm certs check-expiration 也显示叶子证书 364 天、CA 9 年。kubelet 证书会自动轮换,所以不在列表中。一整年都没有升级过的集群,某天所有 API 调用都被拒绝,这类事故就出在这里。
join 命令中的哈希是安全机制。 --discovery-token-ca-cert-hash 是 CA 公钥的 sha256,是加入集群的节点用来确认“这个 API 服务器是不是真的由我们的 CA 签名”的值。令牌以 kube-system 中 bootstrap-token-<id> 的 Secret 形式保存,BootstrapSigner 会为 kube-public 的 cluster-info ConfigMap 加上按令牌计算的 HMAC 签名。虽然也有关闭哈希验证的选项,但文档建议尽量使用其他方式。
如果是单节点集群,要记住污点。 出于安全考虑,kubeadm 会给控制平面节点加上 node-role.kubernetes.io/control-plane:NoSchedule,使其不接收普通 Pod。如果只有一个节点,所有工作负载都会变成 Pending。
要固定版本。 如果省略 --kubernetes-version,kubeadm 会先从互联网上查找最新版本号(实测:remote version is much newer: v1.37.0; falling back to: stable-1.36)。如果是隔离网络,就会卡在这一行。
下一项实验要做什么
从 containerd 配置开始,用 kubeadm init 搭建控制平面,把 NotReady 作为证据留下来,然后接入 Flannel。亲身经历节点已经 Ready 但 DNS 不通的状态,并通过 br_netfilter 使其恢复。最后尝试删除静态 Pod,去掉污点,亲手计算证书到期日和 join 命令,并整理成报告。