1. 项目概述:为什么“参考掘金 K8s 部署教程”反而最容易翻车?
你是不是也经历过——打开掘金,搜“K8s 部署教程”,点开一篇高赞文章,照着命令一行行敲,结果卡在第3步?systemd has not been booted with systemd as init system (pid 1). can't operate这种报错弹出来时,连错误在哪都不知道;或者kubeadm init死活过不了 preflight 检查,提示 swap 没关、cgroup 版本不对、cri-socket 路径找不到……更崩溃的是,你反复核对命令,发现和教程一模一样,但就是跑不通。
这不是你手残,也不是教程写错了。这是K8s 部署领域最典型的“信息断层陷阱”:掘金上的优质教程(比如那些带图、分步骤、有踩坑总结的)绝大多数基于特定环境快照——Ubuntu 22.04 + Kubernetes v1.26 + cri-dockerd v0.3.0 + containerd 默认配置。而你本地可能是 Ubuntu 24.04(默认启用 cgroup v2 + systemd v255)、Debian 12、甚至 WSL2 或 macOS 的 Docker Desktop。更关键的是,这些教程几乎从不显式声明其隐含前提:它默认你已关闭 swap、已加载 overlay 模块、已配置 sysctl 网络参数、已确认/var/run/cri-dockerd.sock真实存在且权限正确、已理解--cri-socket参数不是可选而是强制项……这些“默认已做”的事情,恰恰是新手90%失败的根源。
我过去三年在生产环境交付过27个 K8s 集群,从单节点开发环境到百节点混合云集群,亲手重装过不下80次 kubeadm。最深的体会是:K8s 部署不是拼命令熟练度,而是拼环境诊断能力与配置因果链的还原能力。你敲下的每一行kubeadm init,背后都牵扯至少5个独立子系统(内核模块、cgroup 层、systemd 单元、CRI 接口、网络命名空间),任何一个环节版本错配或状态异常,都会导致整个流程雪崩。这篇分享不讲“怎么装”,而是聚焦于“为什么这么装”——把掘金教程里被省略的17个关键决策点、8类高频静默失败场景、以及5个必须手动验证的“信任锚点”,全部摊开给你看。适合所有正在 Ubuntu/Debian 环境下部署 K8s 的人,尤其适合刚被cri-dockerd启动失败折磨到凌晨两点的你。
2. 核心设计逻辑:为什么必须用 cri-dockerd?Docker 和 K8s 到底什么关系?
2.1 从 dockershim 移除说起:一场被严重低估的架构地震
很多初学者看到“K8s 不再支持 Docker”就慌了,以为 Docker 彻底不能用了。这是最大的误解。真相是:K8s 从未“支持”过 Docker Engine,它只支持符合 CRI(Container Runtime Interface)标准的运行时。在 v1.24 之前,Kubernetes 代码库里内置了一个叫dockershim的胶水层,它负责把 kubelet 发来的 CRI 请求(如“启动一个容器”)翻译成 Docker Engine 能听懂的 API 调用(如POST /containers/create)。这个 shim 就像一个实时翻译官,让两个原本语言不通的系统能协作。
v1.24 的移除,不是删掉了 Docker,而是删掉了这个“翻译官”。这意味着:kubelet 不再认识 Docker Engine 的原生接口,它现在只认标准 CRI socket(通常是 Unix domain socket)。如果你还想用 Docker Engine 作为底层容器引擎,就必须自己装一个第三方翻译官——cri-dockerd。它由 Mirantis 维护,本质就是一个独立进程,监听/var/run/cri-dockerd.sock,接收 kubelet 的 CRI 请求,再转发给本地的dockerd进程。所以,cri-dockerd不是 Docker 的插件,而是 K8s 和 Docker 之间的协议网关。
提示:很多人误以为
cri-dockerd是 Docker 的升级版,其实完全相反——它是一个降级适配层。生产环境强烈推荐直接使用 containerd(K8s 官方默认运行时),因为它更轻量、更稳定、更少中间环节。但如果你的团队重度依赖 Docker CLI 生态(如docker buildx、docker compose),或者需要复用大量 Dockerfile 构建脚本,cri-dockerd就是你绕不开的过渡方案。
2.2 为什么 Ubuntu 24.04 是“雷区”?cgroup v2 的硬性约束
Ubuntu 24.04(Jammy)是第一个将 cgroup v2 设为默认且强制启用的 LTS 版本。而 Kubernetes v1.30+(包括当前主流 v1.31/v1.32)要求所有节点必须运行在 cgroup v2 模式下,否则 kubelet 启动时会直接 panic 并退出,报错类似:
F0315 10:22:33.123456 12345 server.go:274] failed to run Kubelet: unsupported cgroup driver: cgroupfs这背后是 Linux 内核调度模型的根本性升级。cgroup v1 使用多层级树状结构管理资源,而 cgroup v2 采用统一的扁平化控制组,更利于精细化资源隔离。K8s 选择拥抱 v2,是因为它解决了 v1 中长期存在的资源争抢、OOM Killer 行为不可预测等顽疾。
但问题在于:很多掘金教程仍基于 Ubuntu 22.04(默认 cgroup v1),其内核参数配置(如/etc/default/grub中的cgroup_enable=memory swapaccount=1)在 24.04 上不仅无效,还可能引发冲突。Ubuntu 24.04 的内核(6.8+)默认已启用 v2,你无需额外配置,但必须彻底禁用 cgroup v1 的遗留影响。最稳妥的做法是检查/proc/1/cgroup:
# 正确状态:第一行显示 0::/,表示纯 cgroup v2 $ cat /proc/1/cgroup 0::/ # 错误状态:出现大量 1:name=systemd:/ 或 2:cpu,cpuacct:/,说明 v1 未关闭 $ cat /proc/1/cgroup 1:name=systemd:/init.scope 2:cpu,cpuacct:/init.scope ...如果看到 v1 的痕迹,必须修改 GRUB 配置,强制内核只启用 v2:
# 编辑 GRUB 配置 sudo nano /etc/default/grub # 找到 GRUB_CMDLINE_LINUX 行,改为: GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1" # 更新 GRUB 并重启 sudo update-grub && sudo reboot这个细节,99% 的掘金教程都不会提,但它决定了你的集群能否启动。
2.3 systemd 的角色:不只是“启动服务”,而是整个生命周期的仲裁者
systemd在 K8s 部署中常被当作“启动 kubelet 的工具”,这是巨大认知偏差。实际上,systemd是 K8s 组件的守护神与仲裁者。它决定了:
- kubelet 进程的启动顺序(是否等
cri-dockerd.socket就绪后再启动) - 进程崩溃后的自动拉起策略(
Restart=always) - 资源限制(
MemoryLimit=、CPUQuota=) - 工作目录权限(
WorkingDirectory=/var/lib/kubelet,直接影响证书生成路径)
那个著名的报错system has not been booted with systemd as init system (pid 1). can't operate,根本原因不是你没装 systemd,而是你在非 systemd 环境下强行调用 systemctl。典型场景包括:
- 在 WSL1(init 系统是 SysV init)中执行
sudo systemctl start kubelet - 在 Docker 容器内(PID 1 是
/bin/bash)执行 systemctl 命令 - 在某些精简版 Linux 发行版(如 Alpine)中,systemd 未被设为 PID 1
验证方法极其简单:
# 查看 PID 1 的进程名 ps -p 1 -o comm= # 输出应为 "systemd",而非 "init" 或 "bash" # 检查 systemd 是否真正运行 systemctl is-system-running # 输出应为 "running",而非 "degraded" 或 "initializing"如果你在 WSL2 中部署,WSL2 默认使用 systemd(需在/etc/wsl.conf中启用),但 WSL1 绝对不行。这个前提不满足,后面所有systemctl enable、systemctl daemon-reload都是空中楼阁。
3. 关键实操环节:5个必须亲手验证的“信任锚点”
3.1 cri-dockerd 的安装与 socket 路径校验:别信文档,要信ls
很多教程直接贴出wget下载命令,却忽略最关键的三件事:校验哈希值、确认二进制权限、验证 socket 文件真实存在。我见过太多人因为下载了损坏的 tar 包,或mv时路径写错,导致cri-dockerd进程根本没起来,但systemctl status cri-docker显示 active(因为 socket 激活机制欺骗了状态检查)。
实操步骤必须包含以下验证:
# 1. 下载并校验(以 v0.4.4 为例,务必去 GitHub Releases 页面核对最新版) cd /tmp wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.4.4/cri-dockerd-0.4.4.amd64.tgz # 获取官方发布的 SHA256 哈希值(在 Release 页面的 Assets 下) echo "a1b2c3d4... cri-dockerd-0.4.4.amd64.tgz" | sha256sum -c - # 2. 解压并移动二进制文件 tar xzvf cri-dockerd-0.4.4.amd64.tgz sudo mv cri-dockerd/cri-dockerd /usr/local/bin/ sudo chmod +x /usr/local/bin/cri-dockerd # 3. 下载 service 文件并修正路径(重点!很多教程漏掉 sed 命令) wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket # 必须替换路径,否则 service 会尝试调用 /usr/bin/cri-dockerd(不存在) sed -i 's|/usr/bin/cri-dockerd|/usr/local/bin/cri-dockerd|g' cri-docker.service sudo mv cri-docker.service cri-docker.socket /etc/systemd/system/ # 4. 最关键的验证:启动后,立刻检查 socket 文件 sudo systemctl daemon-reload sudo systemctl enable cri-docker sudo systemctl start cri-docker # 检查 socket 是否真实创建 ls -l /var/run/cri-dockerd.sock # 正确输出:srw-rw---- 1 root docker 0 Mar 15 11:00 /var/run/cri-dockerd.sock # 如果文件不存在,说明 cri-dockerd 进程根本没监听 socket,需查 journalctl sudo journalctl -u cri-docker -n 50 --no-pager注意:
/var/run/cri-dockerd.sock的权限必须是srw-rw----(s 表示 socket 类型),且属组为docker。如果属组是root,后续 kubelet 会因权限不足无法连接,报错connection refused。
3.2 kubelet 配置的“隐形开关”:failSwapOn与cgroupDriver
kubeadm init的预检(preflight)失败,80% 源于 kubelet 的配置文件未正确覆盖默认值。/var/lib/kubelet/config.yaml是 kubelet 的核心配置,但kubeadm默认不会生成它,而是依赖/etc/default/kubelet或 systemd drop-in 文件。很多教程跳过这步,直接kubeadm init,结果 kubelet 因默认配置(如failSwapOn: true)拒绝启动。
必须手动创建/var/lib/kubelet/config.yaml,内容如下:
apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd # 关键!必须与 cri-dockerd 的 driver 一致 failSwapOn: false # 关键!即使 swap 已关,此字段也需显式设为 false clusterDNS: - 10.96.0.10 resolvConf: /run/systemd/resolve/resolv.conf然后创建 systemd drop-in 文件,强制 kubelet 加载此配置:
sudo mkdir -p /etc/systemd/system/kubelet.service.d cat <<EOF | sudo tee /etc/systemd/system/kubelet.service.d/10-kubeadm.conf [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" Environment="KUBELET_KUBEADM_ARGS=--container-runtime=remote --container-runtime-endpoint=unix:///var/run/cri-dockerd.sock" Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --event-record-qps=0" Environment="KUBELET_NETWORK_ARGS=--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin" Environment="KUBELET_DNS_ARGS=--cluster-dns=10.96.0.10 --cluster-domain=cluster.local" Environment="KUBELET_AUTHZ_ARGS=--authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt" Environment="KUBELET_CADVISOR_ARGS=--cadvisor-port=0" Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=systemd --cgroup-root=/" Environment="KUBELET_CERTIFICATE_ARGS=--rotate-certificates=true --cert-dir=/var/lib/kubelet/pki" ExecStart= ExecStart=/usr/bin/kubelet \$KUBELET_KUBECONFIG_ARGS \$KUBELET_CONFIG_ARGS \$KUBELET_KUBEADM_ARGS \$KUBELET_SYSTEM_PODS_ARGS \$KUBELET_NETWORK_ARGS \$KUBELET_DNS_ARGS \$KUBELET_AUTHZ_ARGS \$KUBELET_CADVISOR_ARGS \$KUBELET_CGROUP_ARGS \$KUBELET_CERTIFICATE_ARGS EOF sudo systemctl daemon-reload sudo systemctl restart kubelet实操心得:
KUBELET_KUBEADM_ARGS中的--container-runtime-endpoint必须与cri-dockerd监听的 socket 路径完全一致。cri-dockerd默认监听/var/run/cri-dockerd.sock,但有些旧版教程写成/var/run/cri-docker.sock(少了个d),这种拼写错误会导致kubeadm init报failed to run Kubelet: failed to create kubelet: unable to load client CA file,因为 kubelet 根本连不上运行时,无法完成初始化握手。
3.3 kubeadm init 的参数精解:每个 flag 都是救命稻草
kubeadm init命令看似简单,但每个参数都是针对特定环境问题的“急救包”。盲目复制粘贴,等于放弃所有调试线索。
sudo kubeadm init \ --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \ # 阿里云镜像加速,国内必加 --pod-network-cidr=10.244.0.0/16 \ # Flannel 网络段,必须与后续 CNI 配置严格一致 --cri-socket=unix:///var/run/cri-dockerd.sock \ # 强制指定 CRI socket,v1.24+ 必填 --kubernetes-version=v1.31.0 \ # 显式指定版本,避免 kubeadm 自动拉取失败 --upload-certs \ # 生成证书并上传,便于后续节点加入 --v=5 # 开启详细日志,失败时第一手线索其中--v=5是最被低估的参数。当kubeadm init卡住或报错时,不加-v你只能看到一句模糊的error execution phase preflight,而加上后,你会看到完整的执行栈,例如:
I0315 11:22:33.456789 12345 preflight.go:123] [preflight] Running pre-flight checks I0315 11:22:33.456890 12345 preflight.go:123] [preflight] The system verification failed, but the error was ignored. I0315 11:22:33.456901 12345 preflight.go:123] [preflight] checking if the container runtime is configured correctly I0315 11:22:33.456912 12345 preflight.go:123] [preflight] checking if the container runtime socket path exists I0315 11:22:33.456923 12345 preflight.go:123] [preflight] socket path: unix:///var/run/cri-dockerd.sock I0315 11:22:33.456934 12345 preflight.go:123] [preflight] dialing: unix:///var/run/cri-dockerd.sock E0315 11:22:33.456945 12345 preflight.go:123] [preflight] fatal error: dial unix /var/run/cri-dockerd.sock: connect: no such file or directory这段日志直接告诉你:cri-dockerd.sock文件不存在。没有-v=5,你永远不知道问题出在“找不到 socket”,还是“socket 权限不对”,或是“cri-dockerd 进程崩溃”。
3.4 CNI 插件部署的“时间窗口”:CoreDNS 启动失败的真正原因
kubeadm init成功后,kubectl get pods -A会显示coredns处于Pending状态。几乎所有教程都说“这是因为还没部署 CNI 插件”,然后让你kubectl apply -f flannel.yml。但没人告诉你:CNI 插件的部署必须在kubeadm init完成后的 5 分钟内完成,否则 CoreDNS 会因超时而进入 CrashLoopBackOff。
Flannel 的kube-flannel.yml文件中,有一个关键字段host-gw模式依赖宿主机路由表。如果在kubeadm init后立即部署,Flannel DaemonSet 会为每个节点创建flannel.1网卡,并注入路由规则。但如果延迟太久,kubelet 可能已为 CoreDNS Pod 分配了 IP,但该 IP 对应的网络尚未建立,导致 Pod 无法通信,进而触发 kubelet 的健康检查失败,反复重启。
正确做法是:kubeadm init输出Your Kubernetes control-plane has initialized successfully!后,立刻执行 CNI 部署,且必须验证 Flannel Pod 全部 Running:
# 部署 Flannel(注意:必须用与 Kubernetes 版本兼容的 Flannel 版本) kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.24.2/Documentation/kube-flannel.yml # 等待所有 flannel Pod Running(通常 30 秒内) watch -n 1 'kubectl get pods -n kube-flannel' # 确认 CoreDNS 状态变为 Running kubectl get pods -n kube-system # 如果 CoreDNS 仍是 Pending,检查节点 taint kubectl describe node | grep Taints # 如有 NoSchedule taint,需解除(单节点测试环境必需) kubectl taint nodes --all node-role.kubernetes.io/control-plane-实操心得:
kubectl describe node输出中的Taints字段是另一个隐藏杀手。kubeadm init默认会给 master 节点打上node-role.kubernetes.io/control-plane:NoSchedule污点,防止普通 Pod 调度到 master。但 CoreDNS 是系统组件,它需要容忍这个污点。如果 Flannel 部署太慢,CoreDNS Pod 可能已在污点生效后被调度,但因污点不匹配而卡在 Pending。此时kubectl describe pod coredns-xxx -n kube-system会显示Tolerations: node-role.kubernetes.io/control-plane:NoSchedule,但事件里却是0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/control-plane: }. No taints matching.—— 这说明污点配置有冲突,必须手动kubectl taint清除。
3.5kubectl get all -A的真相:你看到的只是冰山一角
很多教程以kubectl get all -A输出全是 Running 作为部署成功的标志。这是危险的幻觉。get all只查询pods, services, replicationcontrollers, deployments, statefulsets, daemonsets, jobs, cronjobs这几类资源,但 K8s 的核心组件远不止这些。
必须手动验证的 4 个关键指标:
etcd 健康状态(存储层):
# 进入 etcd 容器(etcd 通常以静态 Pod 运行) sudo crictl ps | grep etcd sudo crictl exec -it <etcd-container-id> sh # 在容器内执行 ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health # 输出应为 "https://127.0.0.1:2379 is healthy"kube-proxy 工作模式(网络层):
# 查看 kube-proxy 配置 kubectl get cm kube-proxy -n kube-system -o yaml | grep mode # 应为 "mode: iptables" 或 "mode: nftables"(v1.33+ 推荐) # 验证 iptables 规则是否注入 sudo iptables -t nat -L KUBE-SERVICES | head -10CNI 插件路由表(网络连通性):
# 查看节点路由表,确认 flannel.1 网卡已添加 ip route show | grep flannel # 正确输出示例:10.244.0.0/24 via 10.0.2.15 dev eth0 src 10.0.2.15Pod 网络连通性(端到端):
# 创建一个临时 busybox Pod 测试 DNS 和网络 kubectl run test-pod --image=busybox:1.36 --rm -it --restart=Never -- sh -c "nslookup kubernetes.default.svc.cluster.local" # 应返回正确的 ClusterIP(如 10.96.0.1)
这四步验证,缺一不可。我曾遇到一个案例:get all -A全绿,但etcdctl endpoint health返回unhealthy,原因是磁盘 I/O 过高导致 etcd 写入超时。表面一切正常,实则集群随时可能脑裂。
4. 常见问题排查速查表:从报错日志直击根因
4.1 systemd 相关报错全解析
| 报错信息 | 根本原因 | 排查命令 | 解决方案 |
|---|---|---|---|
Failed to connect to bus: No such file or directory | 当前 shell 未连接到 systemd 用户总线(常见于非登录 shell 或 su 切换) | `loginctl show-session $(loginctl | grep $(whoami) |
Unit cri-docker.service could not be found | cri-docker.service文件未正确复制到/etc/systemd/system/ | ls /etc/systemd/system/cri-docker* | 重新执行sudo mv cri-docker.service cri-docker.socket /etc/systemd/system/,确认文件存在 |
Job for cri-docker.service failed because the control process exited with error code | cri-dockerd二进制文件路径错误或权限不足 | sudo journalctl -u cri-docker -n 50 --no-pager | grep -i "failed|error" | 检查cri-docker.service中ExecStart=路径是否为/usr/local/bin/cri-dockerd,并执行sudo chmod +x /usr/local/bin/cri-dockerd |
4.2 kubeadm init 预检失败高频场景
| 预检失败项 | 日志关键词 | 根本原因 | 解决方案 |
|---|---|---|---|
swap should be disabled | preflight] [ERROR Swap]: running with swap on is not supported | /etc/fstab中 swap 行未注释,或sudo swapoff -a未持久化 | sudo swapoff -a && sudo sed -i '/swap/d' /etc/fstab |
cgroup driver | preflight] [ERROR SystemVerification]: cgroup driver mismatch | kubelet 配置的cgroupDriver与cri-dockerd的 driver 不一致 | 检查/var/lib/kubelet/config.yaml中cgroupDriver: systemd,并确认cri-dockerd启动时无--cgroup-driver参数(默认即 systemd) |
cri socket | preflight] [ERROR FileExisting-criSocket]: /var/run/cri-dockerd.sock does not exist | cri-dockerd进程未启动,或 socket 路径配置错误 | sudo systemctl status cri-docker查看状态,sudo ls -l /var/run/cri-dockerd.sock确认文件存在 |
4.3 CoreDNS 启动失败深度诊断
当kubectl get pods -n kube-system显示coredns处于CrashLoopBackOff,不要急着删 Pod。按顺序执行以下诊断:
查看 Pod 事件:
kubectl describe pod -n kube-system -l k8s-app=kube-dns # 重点关注 Events 部分,常见错误: # Warning FailedCreatePodSandBox 2m kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "xxx": failed to find plugin "loopback" in path [/opt/cni/bin] # → 表明 CNI 插件未正确安装,/opt/cni/bin 下缺少二进制文件进入 Pod 查看日志:
kubectl logs -n kube-system -l k8s-app=kube-dns -c coredns # 如果输出为空,说明容器根本没启动,需检查 Init Container kubectl logs -n kube-system -l k8s-app=kube-dns -c install-cni # 此容器负责下载 CNI 插件,失败日志会显示 "curl: (7) Failed to connect to raw.githubusercontent.com port 443" → 网络代理问题验证 DNS 解析:
# 在 master 节点上,用 CoreDNS 的 ClusterIP 测试 echo 'nameserver 10.96.0.10' > /tmp/resolv.conf nslookup kubernetes.default.svc.cluster.local - /tmp/resolv.conf # 如果失败,说明 CoreDNS 服务未监听或 iptables 规则未生效
实操心得:
install-cniInit Container 失败是国产网络环境下最高频问题。解决方案不是改 hosts,而是提前下载 CNI 插件二进制到节点:# 下载 flannel 依赖的 CNI 插件 mkdir -p /opt/cni/bin curl -L "https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz" | sudo tar -C /opt/cni/bin -xz # 然后重启 kubelet sudo systemctl restart kubelet
4.4 网络连通性故障树
当 Pod 无法访问外部网络(如ping google.com失败),按此顺序排查:
- 节点本身网络:
ping 8.8.8.8→ 失败则查宿主机网络配置。 - Pod 网络栈:
kubectl exec -it <pod-name> -- ip a→ 确认有eth0且分配了10.244.x.x地址。 - CNI 路由:
kubectl exec -it <pod-name> -- ip route→ 应有default via 10.244.0.1 dev eth0。 - Node 路由:
ip route show | grep flannel→ 确认10.244.0.0/24网段路由指向flannel.1。 - iptables NAT:
sudo iptables -t nat -L POSTROUTING | grep MASQUERADE→ 必须存在,否则 Pod 出向流量无法 SNAT。
这个故障树,我画在笔记本上贴在显示器边,每次网络问题都按序号敲命令,10 分钟内必定位。
5. 进阶避坑指南:那些教程绝不会告诉你的“潜规则”
5.1 Ubuntu 24.04 的 systemd 特殊行为:WorkingDirectory的陷阱
Ubuntu 24.04 的 systemd v255 引入了一个新特性:ProtectHome=read-only默认启用。这意味着,任何 systemd 服务(包括kubelet.service)默认无法写入/home目录。而kubeadm init默认将证书存放在/etc/kubernetes/pki/,这没问题;但如果你在kubeadm init时指定了--cert-dir=/home/user/k8s-certs,kubelet会因权限不足无法读取证书,报错open /home/user/k8s-certs/ca.crt: permission denied。
解决方案有两个:
- 推荐:永远使用默认证书路径
/etc/kubernetes/pki/,不要自定义--cert-dir。 - 备选:在
kubelet.service的[Service]段添加ProtectHome=false,但这会降低安全性,仅用于测试。
同样,WorkingDirectory参数在 24.04 中更严格。kubelet的WorkingDirectory必须设为/var/lib/kubelet,且该目录属主必须是root:root。如果误设为/home/user/kubelet,kubelet启动时会因无法创建pki/子目录而失败。
5.2kubeadm join的 token 过期与续期
kubeadm init输出的kubeadm join命令中,token 默认有效期为 24 小时。超过时间,worker 节点加入会失败,报错couldn't validate the identity of the API Server。教程从不提如何续期。
续期命令很简单,但必须在 control-plane 节点执行:
# 生成新的 token(有效期 2 小时) kubeadm token create --ttl 2h # 获取 ca cert hash(只需执行一次,长期有效) openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //' # 组合成新 join 命令 kubeadm join <control-plane-host>:6443 --token <new-token> --discovery-token-ca-cert-hash sha256:<hash>提示:生产环境中,建议用
kubeadm token create --usages authentication,signing --groups system:bootstrappers:kubeadm:default-node-token创建专用 token,并配合 RBAC 控制权限。
5.3sealos部署的真相:它只是 kubeadm 的封装壳
最近sealos很火,号称“一条命令部署 K8s”。但它的本质是什么?sealos是一个 Go 编写的命令行工具,核心逻辑就是:
- 下载指定版本的 K8s 静态 Pod YAML(apiserver、controller-manager 等);
- 将 YAML 渲染为
/etc/kubernetes/manifests/下的文件; - 启动
kubelet,由