
1. 项目概述为什么“参考掘金 K8s 部署教程”反而最容易翻车你是不是也经历过——打开掘金搜“K8s 部署教程”点开一篇高赞文章照着命令一行行敲结果卡在第3步systemd has not been booted with systemd as init system (pid 1). cant 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-dockerdDocker 和 K8s 到底什么关系2.1 从 dockershim 移除说起一场被严重低估的架构地震很多初学者看到“K8s 不再支持 Docker”就慌了以为 Docker 彻底不能用了。这是最大的误解。真相是K8s 从未“支持”过 Docker Engine它只支持符合 CRIContainer 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 的升级版其实完全相反——它是一个降级适配层。生产环境强烈推荐直接使用 containerdK8s 官方默认运行时因为它更轻量、更稳定、更少中间环节。但如果你的团队重度依赖 Docker CLI 生态如docker buildx、docker compose或者需要复用大量 Dockerfile 构建脚本cri-dockerd就是你绕不开的过渡方案。2.2 为什么 Ubuntu 24.04 是“雷区”cgroup v2 的硬性约束Ubuntu 24.04Jammy是第一个将 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_enablememory swapaccount1在 24.04 上不仅无效还可能引发冲突。Ubuntu 24.04 的内核6.8默认已启用 v2你无需额外配置但必须彻底禁用 cgroup v1 的遗留影响。最稳妥的做法是检查/proc/1/cgroup# 正确状态第一行显示 0::/表示纯 cgroup v2 $ cat /proc/1/cgroup 0::/ # 错误状态出现大量 1:namesystemd:/ 或 2:cpu,cpuacct:/说明 v1 未关闭 $ cat /proc/1/cgroup 1:namesystemd:/init.scope 2:cpu,cpuacct:/init.scope ...如果看到 v1 的痕迹必须修改 GRUB 配置强制内核只启用 v2# 编辑 GRUB 配置 sudo nano /etc/default/grub # 找到 GRUB_CMDLINE_LINUX 行改为 GRUB_CMDLINE_LINUXsystemd.unified_cgroup_hierarchy1 # 更新 GRUB 并重启 sudo update-grub sudo reboot这个细节99% 的掘金教程都不会提但它决定了你的集群能否启动。2.3 systemd 的角色不只是“启动服务”而是整个生命周期的仲裁者systemd在 K8s 部署中常被当作“启动 kubelet 的工具”这是巨大认知偏差。实际上systemd是 K8s 组件的守护神与仲裁者。它决定了kubelet 进程的启动顺序是否等cri-dockerd.socket就绪后再启动进程崩溃后的自动拉起策略Restartalways资源限制MemoryLimit、CPUQuota工作目录权限WorkingDirectory/var/lib/kubelet直接影响证书生成路径那个著名的报错system has not been booted with systemd as init system (pid 1). cant operate根本原因不是你没装 systemd而是你在非 systemd 环境下强行调用 systemctl。典型场景包括在 WSL1init 系统是 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与cgroupDriverkubeadm 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] EnvironmentKUBELET_KUBECONFIG_ARGS--bootstrap-kubeconfig/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig/etc/kubernetes/kubelet.conf EnvironmentKUBELET_CONFIG_ARGS--config/var/lib/kubelet/config.yaml EnvironmentKUBELET_KUBEADM_ARGS--container-runtimeremote --container-runtime-endpointunix:///var/run/cri-dockerd.sock EnvironmentKUBELET_SYSTEM_PODS_ARGS--pod-manifest-path/etc/kubernetes/manifests --event-record-qps0 EnvironmentKUBELET_NETWORK_ARGS--network-plugincni --cni-conf-dir/etc/cni/net.d --cni-bin-dir/opt/cni/bin EnvironmentKUBELET_DNS_ARGS--cluster-dns10.96.0.10 --cluster-domaincluster.local EnvironmentKUBELET_AUTHZ_ARGS--authorization-modeWebhook --client-ca-file/etc/kubernetes/pki/ca.crt EnvironmentKUBELET_CADVISOR_ARGS--cadvisor-port0 EnvironmentKUBELET_CGROUP_ARGS--cgroup-driversystemd --cgroup-root/ EnvironmentKUBELET_CERTIFICATE_ARGS--rotate-certificatestrue --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-cidr10.244.0.0/16 \ # Flannel 网络段必须与后续 CNI 配置严格一致 --cri-socketunix:///var/run/cri-dockerd.sock \ # 强制指定 CRI socketv1.24 必填 --kubernetes-versionv1.31.0 \ # 显式指定版本避免 kubeadm 自动拉取失败 --upload-certs \ # 生成证书并上传便于后续节点加入 --v5 # 开启详细日志失败时第一手线索其中--v5是最被低估的参数。当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文件不存在。没有-v5你永远不知道问题出在“找不到 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_API3 etcdctl --endpointshttps://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 healthykube-proxy 工作模式网络层# 查看 kube-proxy 配置 kubectl get cm kube-proxy -n kube-system -o yaml | grep mode # 应为 mode: iptables 或 mode: nftablesv1.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 --imagebusybox:1.36 --rm -it --restartNever -- 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 $(loginctlgrep $(whoami)Unit cri-docker.service could not be foundcri-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 codecri-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-dockerd4.2 kubeadm init 预检失败高频场景预检失败项日志关键词根本原因解决方案swap should be disabledpreflight] [ERROR Swap]: running with swap on is not supported/etc/fstab中 swap 行未注释或sudo swapoff -a未持久化sudo swapoff -a sudo sed -i /swap/d /etc/fstabcgroup driverpreflight] [ERROR SystemVerification]: cgroup driver mismatchkubelet 配置的cgroupDriver与cri-dockerd的 driver 不一致检查/var/lib/kubelet/config.yaml中cgroupDriver: systemd并确认cri-dockerd启动时无--cgroup-driver参数默认即 systemdcri socketpreflight] [ERROR FileExisting-criSocket]: /var/run/cri-dockerd.sock does not existcri-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-appkube-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-appkube-dns -c coredns # 如果输出为空说明容器根本没启动需检查 Init Container kubectl logs -n kube-system -l k8s-appkube-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 kubelet4.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 NATsudo iptables -t nat -L POSTROUTING | grep MASQUERADE→ 必须存在否则 Pod 出向流量无法 SNAT。这个故障树我画在笔记本上贴在显示器边每次网络问题都按序号敲命令10 分钟内必定位。5. 进阶避坑指南那些教程绝不会告诉你的“潜规则”5.1 Ubuntu 24.04 的 systemd 特殊行为WorkingDirectory的陷阱Ubuntu 24.04 的 systemd v255 引入了一个新特性ProtectHomeread-only默认启用。这意味着任何 systemd 服务包括kubelet.service默认无法写入/home目录。而kubeadm init默认将证书存放在/etc/kubernetes/pki/这没问题但如果你在kubeadm init时指定了--cert-dir/home/user/k8s-certskubelet会因权限不足无法读取证书报错open /home/user/k8s-certs/ca.crt: permission denied。解决方案有两个推荐永远使用默认证书路径/etc/kubernetes/pki/不要自定义--cert-dir。备选在kubelet.service的[Service]段添加ProtectHomefalse但这会降低安全性仅用于测试。同样WorkingDirectory参数在 24.04 中更严格。kubelet的WorkingDirectory必须设为/var/lib/kubelet且该目录属主必须是root:root。如果误设为/home/user/kubeletkubelet启动时会因无法创建pki/子目录而失败。5.2kubeadm join的 token 过期与续期kubeadm init输出的kubeadm join命令中token 默认有效期为 24 小时。超过时间worker 节点加入会失败报错couldnt 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 YAMLapiserver、controller-manager 等将 YAML 渲染为/etc/kubernetes/manifests/下的文件启动kubelet由