资讯动态

Ubuntu 18.04 上 kubeadm 部署 Kubernetes 实战与排障

发布时间:2026/10/19 11:24:22 来源:尧图企业网站定制
最近给某项目组搭一套内部测试环境翻出几台闲置的物理机系统清一色是 Ubuntu 18.04 LTS。这个版本虽然已经过了标准支持期但业务暂时迁移不动还是得在这套系统上把 Kubernetes 集群跑起来。折腾了几天从环境初始化到 Flannel 网络恢复中间踩了不少坑。这篇就把我基于 Ubuntu 18.04 部署 Kubernetes 的完整过程、参数选择和排障链路整理出来给准备在存量 18.04 机器上建集群的同学提供一个可参考的落地方案。如果你只是想用虚拟机快速搭一套单节点集群做学习这套流程同样适用。区别只在于资源分配和是否需要多节点 join。文中涉及的版本组合我会明确标注实际操作时请以官方支持矩阵为准但整体思路不会变。1. 动手前先把环境想清楚版本组合与主机清单1.1 为什么我选了 1.23.x Docker 19.03 这套组合很多人上来就装最新版但 Ubuntu 18.04 的系统内核默认是 4.15cgroups 还停留在 v1 体系容器运行时和 Kubernetes 版本选择需要额外谨慎。我自己试过在这套系统上直接跑 1.24 以上的 Kubernetes问题倒不是装不上而是 1.24 开始 kubelet 移除了对 Docker 的 dockershim 支持必须用 containerd 或 CRI-O 作为运行时配置链路又长了一截。最终我选定的组合是 Kubernetes v1.23.17 Docker 19.03.15。选择 v1.23 的原因很实际它仍然支持 Docker 作为直接运行时kubeadm 初始化流程最简单不需要额外处理 containerd 的 CRI socket 配置正好覆盖目标场景。v1.23 虽然是较早的版本但作为内部测试和工具链验证已经足够稳定性经过了大版本验证资料也最全。下面这张表可以帮你快速做选择Kubernetes 版本推荐运行时踩坑难度适用场景1.23.xDocker 19.03低存量 18.04 系统最快落地1.24.x ~ 1.26.xcontainerd中需要较新 API 特性的场景1.27containerd中高新系统、长期维护为主如果你完全不受系统版本限制我当然建议直接上新版。但如果你手里就是一堆 18.04 的存量机器又想快速看到集群跑起来1.23 Docker 是最省心的一条路。1.2 双节点资源规划与网络拓扑建议我这次用的是最经典的拓扑一个控制节点加两个工作节点。控制节点单独占用一台机器不让业务 Pod 调度上去。原因后面会讲控制面上跑着 etcd 和 kube-apiserver一旦被业务负载挤爆内存整个集群都会震荡。资源方面控制节点建议至少 2 核 2G工作节点根据业务量往 4G 以上给。磁盘是另一个容易被忽略的点Docker 镜像、容器日志、etcd 数据都会不断增长我给每台机器都分了 80G 以上的独立数据盘专门挂给/var/lib/docker和/var/lib/kubelet。网络拓扑规划要提前做因为 kubeadm init 时指定的 IP 后续很难改。我用的内网网段是192.168.1.0/24规划如下主机名IP 地址角色master-1192.168.1.10控制节点Control Planeworker-1192.168.1.11工作节点worker-2192.168.1.12工作节点所有节点都需要配置/etc/hosts确保彼此可以通过主机名解析我见过不少集群故障是因为节点之间走内网再绕了一层路由器导致的延迟和权限问题。时间同步也要提前做Ubuntu 18.04 自带 systemd-timesyncd确认它正常工作即可etcd 对节点间时钟偏差非常敏感。1.3 系统初始化脚本这几行配置必须做网上很多教程直接告诉你sudo swapoff -a但没说清楚为什么。Kubernetes 从设计上就不希望在节点上看到 swap 存在因为 kubelet 的资源管理模型假设内存是固定的一旦内存不够调度器会优先调度新 Pod而不是让旧 Pod 被 swap 拖到性能悬崖。所以不仅是当前关闭 swap还要在/etc/fstab里把 swap 那一行注释掉避免重启后又回来。其次是加载overlay和br_netfilter两个内核模块。前者是 Docker 和 kubelet 使用的存储驱动基础后者让 Linux 内核能够处理桥接网络上的 IPv4/IPv6 流量过滤。这一步不做后面集群内部的 Service 和 Pod 通信会莫名其妙超时。下面是我在每台节点上执行的初始化脚本你可以直接抄# 设置主机名每台节点改成对应名字 sudo hostnamectl set-hostname master-1 # 关闭 swap并注释 /etc/fstab 中的 swap 行 sudo swapoff -a sudo sed -i / swap / s/^/#/ /etc/fstab # 加载内核模块 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 --systemnet.ipv4.ip_forward这个参数很容易漏。它控制 Linux 是否转发 IP 包Kubernetes 的 Service 转发和 Pod 间跨节点通信都依赖它。有一次我在一台工作节点上把这行漏了结果所有跨节点的 Pod 访问都失败Pod 内 curl Service IP 完全没有响应排查很久才发现问题。2. 安装容器运行时与 kubeadm 三件套2.1 Docker 安装与 daemon.json 的那点关键配置Ubuntu 18.04 自带的软件源里 Docker 版本偏老我直接用的 Docker 官方 apt 源安装。安装本身不是重点关键在于/etc/docker/daemon.json的配置。Kubernetes 要求 kubelet 的 cgroup 驱动和容器运行时的 cgroup 驱动保持一致官方推荐两者都用systemd。默认情况下 Docker 的 cgroup 驱动是cgroupfs而 kubelet 在 systemd 系统上默认使用systemd两者不一致会直接导致 kubelet 启动失败。这个坑我放在最后一节详细讲这里先说正确配置{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2 }日志配置也很重要。不限制日志大小时/var/lib/docker/containers会无限膨胀我一个项目就碰到一块 40G 磁盘被容器日志写满Pod 直接进入 Evicted 状态。max-size: 100m配合max-file: 3基本够用更精细的日志收集你后续可以用采集器去处理。配置完成后执行sudo systemctl restart docker sudo docker info | grep Cgroup Driver看到输出Cgroup Driver: systemd才算通过。2.2 安装 kubeadm、kubelet、kubectl 并把版本锁死官方 apt 源的地址在 Kubernetes 版本迭代后会变化这里我用的是当时可用的官方源。注意 Ubuntu 18.04 的 apt 版本较老签名机制需要gpg --dearmor把公钥转换一下。curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.23/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo deb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.23/deb / | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt update安装时需要明确指定版本号不要直接apt install kubeadm拿最新版sudo apt install -y kubelet1.23.17-00 kubeadm1.23.17-00 kubectl1.23.17-00装完立刻执行版本锁定sudo apt-mark hold kubelet kubeadm kubectlapt-mark hold的意义在于防止后续随手apt upgrade把三件套升级到不兼容版本。kubeadm 的升级逻辑要求版本号阶梯式前进跨越太多版本会触发 unsupported version 错误。我可以负责任地说这个坑我在另一个集群上踩过锁版本是我现在每次搭建的固定动作。2.3 提前把镜像拉好kubeadm config images 的用法kubeadm init 的时候会从镜像仓库拉取一大堆控制平面镜像包括 kube-apiserver、kube-controller-manager、kube-scheduler、etcd 等。如果你所在的网络访问官方镜像仓库比较慢init 过程会卡住报错还特别不直观。建议初始化之前先用下面的命令看看需要哪些镜像kubeadm config images list --kubernetes-versionv1.23.17输出会包含类似这样的列表k8s.gcr.io/kube-apiserver:v1.23.17 k8s.gcr.io/kube-controller-manager:v1.23.17 k8s.gcr.io/kube-scheduler:v1.23.17 k8s.gcr.io/kube-proxy:v1.23.17 k8s.gcr.io/pause:3.6 k8s.gcr.io/etcd:3.5.1-0 k8s.gcr.io/coredns/coredns:v1.8.6如果你的网络访问k8s.gcr.io不太顺畅有两种处理方式。一是通过--image-repository参数在 init 时指定一个可访问的公共镜像仓库地址二是先把镜像拉到本地再通过kubeadm init --image-repository或者修改配置来对齐。我这边实际使用的是公共镜像仓库加速方案init 时统一指定即可具体仓库地址不展开你根据自己网络环境选择即可。提前拉取验证kubeadm config images pull --kubernetes-versionv1.23.17看到所有镜像都拉取成功再继续下一步。不要跳过这步我的经验是如果镜像拉取有问题init 会先创建一堆静态 Pod 然后反复重启最后整个/etc/kubernetes/manifests目录下的配置全乱了。3. kubeadm init 初始化控制平面参数不能乱填3.1 控制节点初始化之前的最后检查在控制节点上执行 init 之前我会习惯性地做一轮快速检查# 确认 IP 地址没有变化 ip addr show # 确认 kubelet 服务是 stopped 状态而不是 failed systemctl status kubelet # 确认端口没被占用 ss -lntp | grep -E :6443|:10250|:2379如果 kubelet 处于 failed 状态不用慌这很正常。kubeadm 初始化完成之前 kubelet 本来就没有配置文件所以会反复尝试启动失败只要服务没有被 mask 掉就行。另外检查一下/etc/kubernetes目录是否存在残留文件。如果之前跑过 init 又中途失败需要先执行sudo kubeadm reset清理环境否则新配置和旧配置混合在一起错误信息会让你无从下手。3.2 init 命令逐参数拆解我的初始化命令如下sudo kubeadm init \ --control-plane-endpoint192.168.1.10 \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16 \ --kubernetes-versionv1.23.17四个核心参数逐个说清楚--control-plane-endpoint控制面的统一入口地址。单控制节点场景下就是本机 IP但如果你以后要做高可用这个位置应该填 VIP 或负载均衡器地址这样工作节点 join 时就不需要因为控制节点 IP 变更而重新配置。--apiserver-advertise-address告诉 kube-apiserver 对外广播哪个 IP 地址。通常和control-plane-endpoint一致但如果你有多网卡必须显式指定否则 kubeadm 可能选偏。--pod-network-cidrPod 网络 CIDR这个参数必须和后面安装的 CNI 插件要求匹配。我用 FlannelFlannel 默认使用10.244.0.0/16所以这里必须写这个网段。如果后面你改装 CalicoCalico 默认网段是192.168.0.0/16两者不能混用。--kubernetes-version显式指定版本避免 kubeadm 去远端查最新版导致和本地安装的 1.23.17 不一致。关于--image-repository如果你不需要自定义镜像仓库可以不写。但如果前面拉镜像时用了加速仓库这里一定要同步指定否则 kubeadm 会用默认的k8s.gcr.io去拉前面做的提前拉镜像工作会白费。执行 init 后如果一切顺利末尾会输出两段关键信息一段是配置 kubectl 的命令另一段是工作节点 join 的命令。这两段输出一定要复制保存join 命令里的 token 和 ca-cert-hash 都是临时生成的有效凭证关掉终端就找不回来了。3.3 初始化完成后的目录结构与 kubectl 配置init 成功之后控制节点上的/etc/kubernetes目录会多出这么几个关键文件/etc/kubernetes/ ├── admin.conf # 管理员 kubeconfig ├── kubelet.conf # 本节点 kubelet 使用的 kubeconfig ├── controller-manager.conf ├── scheduler.conf ├── manifests/ # kube-apiserver 等静态 Pod 的 yaml └── pki/ # CA 证书、证书、私钥admin.conf是管理整个集群的凭证默认情况下 kubectl 不会自动使用它需要手动配置mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config配置好后验证kubectl get nodes正常会看到控制节点状态为NotReady这是预期状态因为还没有安装 CNI 网络插件。接下来就需要加入工作节点并让网络插件把集群的 Pod 网络打通。4. 工作节点 join 与网络插件 Flannel 的安装4.1 token 的生成与 join 命令保存如果 init 完成时没有保存 join 命令或者 token 丢失可以在控制节点上随时重新生成kubeadm token create --print-join-command输出类似kubeadm join 192.168.1.10:6443 --token abcdef.0123456789abcdef --discovery-token-ca-cert-hash sha256:xxxxxxxx--discovery-token-ca-cert-hash的作用是让工作节点校验控制节点的 CA 证书哈希防止中间人攻击。不用担心它泄露的问题token 默认 24 小时过期。如果你需要长期保存 token可以加--ttl0生成不过期的 token但仅限测试环境生产环境不建议。4.2 worker 节点的重复配置与 join工作节点不需要安装 kubeadm 之外的复杂组件但前面章节的初始化步骤一样不能少关闭 swap、加载内核模块、配置 sysctl、安装 Docker、安装 kubeadm kubelet kubectl、锁定版本。不要试图跳过我在 worker 节点上漏掉br_netfilter之后那个节点上的 Pod 通信一直是坏的。全部配置完成后在 worker 节点上执行 join 命令sudo kubeadm join 192.168.1.10:6443 \ --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxjoin 成功后会提示This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received.回到控制节点查看kubectl get nodes工作节点大概率还是NotReady原因同前Pod 网络还没有就绪。4.3 Flannel 安装与三个常见疑问Flannel 是最简单的 CNI 插件之一适合快速搭建和测试。安装前需要下载 manifest 文件wget https://github.com/flannel-io/flannel/releases/download/v0.25.7/kube-flannel.yml kubectl apply -f kube-flannel.yml这里有几个常见疑问我一次说清为什么必须先装网络插件才能变 Readykubelet 启动时会检查节点的 CNI 网络配置没有配置网络插件时kubelet 无法为 Pod 分配网络命名空间节点也就无法进入 Ready 状态。Flannel 的默认网段为什么是 10.244.0.0/16这是 Flannel 内置的默认 subnet如果 init 时用了其他 CIDRFlannel Pod 会不断 CrashLoopBackOff报错说找不到合适的 subnet。所以--pod-network-cidr10.244.0.0/16必须与 Flannel 匹配。Flannel 和 Calico 选哪个简单场景用 Flannel实现简单、底层是 VXLAN/主机路由性能损耗小需要 NetworkPolicy 隔离、多租户网络策略的场景用 Calico它支持 BGP 路由分发和更细粒度的策略控制。Calico 的安装通常也是一句话kubectl apply -f calico.yaml但 Calico 默认 CIDR 要求是192.168.0.0/16所以 init 时如果打算用 Calico--pod-network-cidr要改掉。这个选择要在 init 前决定装完再改极其痛苦。4.4 验证节点 Ready 的完整输出安装 Flannel 后等待镜像拉取和 Pod 启动执行kubectl get pods -n kube-system -o wide重点关注kube-flannel-ds-*和coredns-*两个系列 Pod。Flannel 的 DaemonSet 会在每个节点上启动一个 Pod全部Running之后再查看节点状态kubectl get nodes -o wide输出类似NAME STATUS ROLES AGE VERSION INTERNAL-IP master-1 Ready control-plane,master 1h v1.23.17 192.168.1.10 worker-1 Ready none 1h v1.23.17 192.168.1.11 worker-2 Ready none 1h v1.23.17 192.168.1.12如果某个节点长时间停在NotReady多半是 Flannel Pod 没有起来看日志kubectl logs -n kube-system -l appflannel --tail50常见的失败原因包括/etc/hosts解析异常、内核模块缺失、MTU 不匹配。MTU 的问题我会在最后一节展开。5. 上生产前该做的验证清单5.1 核心组件与控制面健康检查节点 Ready 只是第一步控制面各组件是否工作正常需要单独验证。先看静态 Pod 状态kubectl get pods -n kube-system确认kube-apiserver、kube-controller-manager、kube-scheduler、etcd都处于Running状态。然后执行kubectl get cs这个命令会显示控制面组件的健康状态1.23 版本还能正常输出新版可能被移除。如果看到任何组件异常优先查看对应 Pod 的日志kubectl logs -n kube-system kube-apiserver-master-1 --tail50etcd 的检查稍微特殊它是集群的数据库绝对不能随意处理。我一般只看它是否稳定运行以及日志里有没有apply request took too long之类的高延迟警告。5.2 部署一个测试应用验证调度与 DNS控制面正常后部署一个简单的 Nginx 验证端到端链路kubectl create deployment nginx-test --imagenginx:1.21 kubectl expose deployment nginx-test --port80 --typeNodePort kubectl get svc nginx-test通过任一节点 IP 加 NodePort 访问 Nginx能返回欢迎页说明 Pod 网络、Service 转发、kube-proxy 都正常。这一步不能省我就遇到过节点全部 Ready、但 Service 跨节点访问不通的情况。DNS 验证同样重要。CoreDNS 是集群内部的域名解析服务如果它有问题Service 间的调用会大量失败。用一个临时 Pod 测试kubectl run busybox --imagebusybox:1.28 --restartNever --rm -it -- nslookup kubernetes.default正常情况下会解析出kubernetes.default.svc.cluster.local对应的 ClusterIP。如果解析失败排查 CoreDNS 的日志这是集群 DNS 问题的第一现场。5.3 给 Master 节点加污点并配置资源上限kubeadm init 默认会给控制节点打上污点node-role.kubernetes.io/master:NoSchedule这意味着普通业务 Pod 不会调度到控制节点上这正是我想要的。你可以通过以下命令确认kubectl describe node master-1 | grep Taints如果出于某些原因你希望控制节点也能跑一些系统级 DaemonSet通常是监控、日志采集需要给这些 Pod 加上对应的容忍度配置。但不要轻易移除控制节点的污点否则业务 Pod 和集群管理组件争抢资源整个集群的稳定性都会下降。为工作节点上的 Pod 设置资源限制也很关键。很多同学部署应用不给resources字段导致 OOM 时节点上所有 Pod 一起遭殃。一个典型的配置示例resources: requests: memory: 256Mi cpu: 250m limits: memory: 512Mi cpu: 500mrequests是调度时的依据limits是运行时的硬上限。设置合理的 limits 能有效避免单容器把节点内存耗尽。5.4 etcd 定期备份命令etcd 是整个集群状态的核心一旦数据损坏控制面就算活过来集群也会处于脑裂状态。Kubernetes 没有自动备份机制需要你自己定时执行快照。我用的备份命令sudo 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 \ snapshot save /backup/etcd-snapshot.db注意这里的证书路径是 kubeadm 默认生成的。测试环境可以手动执行生产环境请配合 cron 定期执行并把备份文件同步到其他机器。6. 跑了一周实际踩过的坑排查过程完整复盘6.1 kubelet 起不来的根因cgroup 驱动不一致集群跑了一周后我为了调内核参数重启了一台 worker 节点结果 kubelet 怎么都起不来节点状态直接从 Ready 变成 NotReady。第一反应是看服务状态systemctl status kubelet journalctl -u kubelet -f --tail50日志里出现了关键报错kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs问题很清楚我重启容器服务时Docker 读到了旧的/etc/docker/daemon.json而这份配置是在系统初始化之前生成的里面没有native.cgroupdriversystemd。kubelet 默认按 systemd 驱动启动Docker 却是 cgroupfs两者不匹配直接拒绝启动。修复方式很直接统一 Docker 的 cgroup 驱动为 systemd然后重启 Dockersudo systemctl restart docker sudo systemctl restart kubelet如果不想 Docker 用 systemd也可以修改 kubelet 的启动参数让它使用 cgroupfs。但我建议统一到 systemd因为 systemd 本身对 cgroup 的托管更完善这也是 Kubernetes 官方推荐的做法。排查这类问题有个固定思路先看 kubelet 日志再对比容器运行时和 kubelet 的配置报错信息一般不会说谎问题往往出在你忽略的基础配置上。6.2 CoreDNS CrashLoopBackOffresolv.conf 的循环解析另一个印象深刻的坑是 CoreDNS Pod 反复重启状态一直是CrashLoopBackOff。查看日志kubectl logs -n kube-system coredns-pod-name --tail50日志中间出现了 loop 相关错误提示指向 CoreDNS 的 loop 插件检测到 DNS 解析循环。根因是 Ubuntu 18.04 默认使用 systemd-resolved 管理 DNS它在/etc/resolv.conf里把 nameserver 指向了127.0.0.53。CoreDNS 容器启动时继承了这个配置当它尝试解析外部域名时请求又回到了本机监听 53 端口的 CoreDNS 自身形成循环。解决方案有两个。要么在 kubelet 的配置中指定正确的 resolvConf# 在 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 中添加 EnvironmentKUBELET_EXTRA_ARGS--resolv-conf/run/systemd/resolve/resolv.conf然后重启 kubelet。/run/systemd/resolve/resolv.conf文件里包含的是真实的 DNS 服务器地址而不是 self 引用。另一种方案是修改 CoreDNS 的 ConfigMap给它指定上游 DNS。但第一种方案更干净问题出在节点而非 CoreDNS 内部修好节点配置才是根治。6.3 Flannel 网络下大包不通与 MTU 调整第三个坑是在两个不同节点上的 Pod 之间做大数据传输时频繁超时小包通、大包不通。排查时先 ping 对端 Pod IPping -M do -s 1472 10.244.1.2加上-M do是禁止分片-s 1472对应的正好是 1500 字节不加 IP 头的情况。结果发现超过 1472 字节就会丢包说明底层网络的 MTU 不是 1500或者中间设备不支持这么大的帧。Flannel 默认使用 VXLAN 模式这会额外封装一层 UDP 头实际有效的 MTU 只有 1450 字节。当底层物理网络 MTU 是 1500 时1450 本来不会出问题但如果集群跑在云平台或虚拟化环境里宿主机的网络 MTU 被调低了Flannel 的默认值就会超过链路的承载能力。查看 Flannel 的配置和 Pod 中的 MTUkubectl exec -n kube-system flannel-pod-name -- ip link show flannel.1如果确实需要调整可以在 Flannel 的 ConfigMap 中显式指定 MTU 值。这个操作建议提前做好网络规划时确认而不是等集群跑起来再改因为修改后需要重启所有 Flannel Pod 和可能受影响的业务 Pod。6.4 证书与 token 过期一年后的隐性问题Kubernetes 集群中的证书有效期是一个“慢性”问题。kubeadm 生成的证书默认一年到期到时 kube-apiserver 会拒绝接收新连接集群只是看起来正常实际上已经无法变更资源。检查证书有效期kubeadm certs check-expiration输出会列出/etc/kubernetes/pki下所有证书的过期时间。如果临近过期执行sudo kubeadm certs renew all然后重启相关组件。更稳妥的做法是配置 kubelet 证书自动轮换在 kubelet 配置文件或启动参数中加上--rotate-certificatestrue这个配置让 kubelet 自动向 apiserver 请求新证书不用你手动干预。但控制面组件apiserver、etcd的证书还是需要你定期检查。token 的问题也是一样如果集群里后续还有新节点要加入提前生成一个长期 token 保存好避免节点下线重装后因为 token 过期而无法 joinkubeadm token create --ttl0 --print-join-command这套环境最终稳定运行了几个月期间我最深的体会是Kubernetes 部署不难难的是安装前后的基础配置是否真的想明白了。cgroup 驱动、MTU、DNS 循环每一个坑背后都是 Linux 系统机制和 Kubernetes 设计理念的交叉点。最后分享一个我自己的小习惯每次执行kubeadm init或kubeadm join之前先跑一遍sudo kubeadm reset。别嫌多此一举它能清掉上一次残留的配置和证书让整个集群从一套干净状态开始。尤其是多次失败后这条命令能省下你大量排查时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑