目标照着下面每一步原样执行就能在5 台全新的 Ubuntu 22.04 LTS上零误差地部署并跑起一个3 控制面master 1 工作节点worker 1 负载均衡器lb的 kubeadm 高可用集群。最终形态与本环境完全一致控制面入口192.168.1.100:6443Keepalived VIP落在 lb 上CNIFlannelpodCIDR 10.244.0.0/16容器运行时containerd 1.7.xCRI v1kubeadm / kubelet / kubectlv1.36.x文档把本环境真实踩过的坑docker.io 被墙、CNI 标准插件缺失、cgroup v1、HAProxy 长连接超时、Keepalived VIP 不漂移全部固化成「必做步骤」或「排错表」所以即使你的网络也是受限的也能跑通。0. 拓扑与 IP 规划先记下来全程照用主机名IP角色说明master1192.168.1.11控制面kube-apiserver 监听 6443master2192.168.1.12控制面同上master3192.168.1.13控制面同上worker1192.168.1.14工作节点跑业务 Podlb192.168.1.16负载均衡HAProxy(:6443)Keepalived(VIP)VIP192.168.1.100虚拟 IP由 lb 上的 Keepalived 持有端口 6443所有机器的网卡名假设为ens33VMware 默认。请先用ip -br a确认你的网卡名若不同下文所有ens33换成你的真实网卡名。所有机器建议2 vCPU / 4 GB 内存起步master 至少 2C2Gworker 视负载。本环境 master 给到 2C4G。物理/虚拟网络5 台机器 VIP 必须在同一二层网段且彼此能互 ping。操作系统Ubuntu 22.04.4 LTSServer 或 Desktop 均可下文命令通用。内核 5.15 6.6。⚠️网络受限环境专项提醒本环境就是docker.io镜像仓库被防火墙拦截直接pull会超时。本手册通过两条手段解决① 给 containerd 配certs.d镜像加速② kubeadm 用阿里云google_containers镜像库③ Flannel 改用ghcr.io本环境可达。如果你在普通能直连 docker.io 的机器上部署可跳过第 2.4 节的镜像加速配置不影响结果。1. 每台机器都先做系统基础准备master1/2/3、worker1、lb 五台都执行下面命令在5 台机器上各执行一遍。为方便可先配好 SSH 互信后用pdsh/ansible批量跑本手册按单机逐条给出复制粘贴即可。1.1 设置主机名每台按上表设置别搞错# 在 192.168.1.11 上执行sudohostnamectl set-hostname master1# 在 192.168.1.12 上执行sudohostnamectl set-hostname master2# 在 192.168.1.13 上执行sudohostnamectl set-hostname master3# 在 192.168.1.14 上执行sudohostnamectl set-hostname worker1# 在 192.168.1.16 上执行sudohostnamectl set-hostname lb1.2 写 /etc/hosts5 台内容完全一致原样复制sudobash-ccat /etc/hosts EOF 127.0.0.1 localhost 192.168.1.11 master1 192.168.1.12 master2 192.168.1.13 master3 192.168.1.14 worker1 192.168.1.16 lb 192.168.1.100 k8s-vip EOF1.3 关闭 swapkubelet 要求必须做sudoswapoff-a# 永久生效注释掉 /etc/fstab 里的 swap 行sudosed-ri/\sswap\s/s/^#?/#//etc/fstabfree-h# 确认 Swap 行全为 01.4 关闭防火墙 / 放行所需端口测试环境直接关 ufw 最省事生产环境请改为按需放行6443、2379-2380、10250、10251、10252、30000-32767、8404 等。sudoufw disable2/dev/null||true# 若使用 firewalld极少见于 Ubuntusudosystemctl disable--nowfirewalld2/dev/null||true1.5 加载内核模块 设置 sysctlCNI/桥接需要catEOF|sudotee/etc/modules-load.d/k8s.confoverlay br_netfilter EOFsudomodprobe overlaysudomodprobe br_netfiltercatEOF|sudotee/etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOFsudosysctl--system# 验证下面两行都应输出 1cat/proc/sys/net/bridge/bridge-nf-call-iptablescat/proc/sys/net/ipv4/ip_forward1.6 时间同步etcd 对时钟敏感sudoapt-getupdate-ysudoapt-getinstall-ychronysudosystemctlenable--nowchrony timedatectl status|grepSystem clock# 应显示 synchronized: yes2. 安装容器运行时 containerd5 台都执行2.1 安装 containerd≥1.7必须K8s v1.26 移除了 CRIv1alpha2containerd 必须 ≥1.7。Ubuntu 自带仓库的containerd常是 1.6会报unknown service runtime.v1.RuntimeService。所以走 Docker 官方源装 1.7.x。# 装依赖sudoapt-getinstall-yca-certificatescurlgnupgsudoinstall-m0755-d/etc/apt/keyrings# 添加 Docker 官方源download.docker.com 与 docker.io 镜像仓库是不同主机通常可达sudocurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg-o/etc/apt/keyrings/docker.ascsudochmodar /etc/apt/keyrings/docker.ascechodeb [arch$(dpkg --print-architecture)signed-by/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu$(./etc/os-releaseecho$VERSION_CODENAME)stable|sudotee/etc/apt/sources.list.d/docker.list/dev/nullsudoapt-getupdate-ysudoapt-getinstall-ycontainerd.io containerd--version# 应输出 1.7.x如 1.7.18 或 1.7.27若download.docker.com也不可达改用官方静态包——从能联网的机器下载https://github.com/containerd/containerd/releases/download/v1.7.27/containerd-1.7.27-linux-amd64.tar.gzscp 到目标机后tar -C /usr/local -xzf containerd-*.tar.gz再systemctl daemon-reload systemctl enable --now containerd。2.2 生成并修改 containerd 配置开 systemd cgroupsudomkdir-p/etc/containerd containerd config default|sudotee/etc/containerd/config.toml/dev/null# 把 CRI 的 cgroup 驱动改成 systemd与 kubelet 一致避免 cgroup 驱动不一致告警sudosed-is/SystemdCgroup false/SystemdCgroup true//etc/containerd/config.tomlgrepSystemdCgroup /etc/containerd/config.toml# 应显示 true2.3 ★ 配置镜像加速受限网络必做直连 docker.io 的机器可跳过解决docker.io/registry.k8s.io被墙导致镜像拉不下来的问题。containerd 通过certs.d做仓库重写。sudomkdir-p/etc/containerd/certs.d/docker.iosudobash-ccat /etc/containerd/certs.d/docker.io/hosts.toml EOF server https://docker.io [host.https://docker.m.daocloud.io] capabilities [pull, resolve] [host.https://docker.1ms.run] capabilities [pull, resolve] EOFsudomkdir-p/etc/containerd/certs.d/registry.k8s.iosudobash-ccat /etc/containerd/certs.d/registry.k8s.io/hosts.toml EOF server https://registry.k8s.io [host.https://k8s.m.daocloud.io] capabilities [pull, resolve] EOF2.4 ★ 安装 CNI 标准插件本环境最隐蔽的坑必做为什么必须手工装Flannel 的flannel-cni-plugin镜像里/opt/cni/bin是空的不会自带 bridge/loopback/host-local/portmap。不装这些标准插件coredns / 业务 Pod 会永远卡在ContainerCreatingfailed to find plugin loopback/bridge in path [/opt/cni/bin]。所以必须先把标准 CNI 插件放进/opt/cni/bin。# 方法 A推荐需能访问 GitHub releases下载官方静态包CNI_VERSIONv1.5.1sudomkdir-p/opt/cni/bincurl-sLhttps://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-amd64-${CNI_VERSION}.tgz\|sudotar-xz-C/opt/cni/binls/opt/cni/bin# 应看到 bridge host-local loopback portmap tuning 等受限网络备选方案GitHub 不可达时备选 A从ghcr.io拉一个带插件的镜像再拷出来本环境 ghcr.io 可达sudo ctr -n k8s.io images pull ghcr.io/flannel-io/flannel-cni-plugin:latest后从 snapshot 拷/opt/cni/bin注意某些镜像内该目录也为空需确认。备选 B本环境最终验证可用最稳在 Alpine 容器里静态编译 glibc 版插件避免 musl 在 Ubuntu 上 ENOENT# 在一台能跑 docker/ctr 的节点上 chroot 进 Alpine rootfs 后执行apkaddgoexportGOPROXYhttps://mirrors.aliyun.com/goproxy/CGO_ENABLED0GO111MODULEonGOBIN/tmp/cnistatic goinstallgithub.com/containernetworking/plugins/plugins/{main/bridge,main/loopback,ipam/host-local,meta/portmap,meta/tuning}v1.5.1# 把 /tmp/cnistatic/* 拷到各节点 /opt/cni/bin 并 chmod 7552.5 重启 containerd 并设开机自启sudosystemctl daemon-reloadsudosystemctlenable--nowcontainerdsudosystemctl restart containerdsudoctr version# 能正常输出版本即 OK3. 安装 kubeadm / kubelet / kubectl5 台都执行# 添加 Kubernetes 官方 apt 源pkgs.k8s.io 国内直连可用境外机用同一地址即可sudocurl-fsSLhttps://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key-o/etc/apt/keyrings/kubernetes-apt-keyring.gpgsudochmodar /etc/apt/keyrings/kubernetes-apt-keyring.gpgechodeb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /|sudotee/etc/apt/sources.list.d/kubernetes.list/dev/nullsudoapt-getupdate-ysudoapt-getinstall-ykubelet kubeadm kubectlsudoapt-mark hold kubelet kubeadm kubectl# 锁定版本避免 apt upgrade 误升kubeadm version# 应显示 v1.36.x4. 在 lb192.168.1.16上部署负载均衡器 HAProxy Keepalived只在这1 台lb上操作。它提供 VIP192.168.1.100:6443把 API 请求转发到 3 个 master。4.1 安装sudoapt-getinstall-yhaproxy keepalived4.2 配置 HAProxy★ timeout 必须设长sudobash-ccat /etc/haproxy/haproxy.cfg EOFglobal log /dev/log local0 maxconn4096defaults log global mode tcp option tcplog# ★ 必须 4h默认 1m 会周期性切断 kubelet-apiserver 的 watch 长连接集群每分钟抖一次timeoutclient 4htimeoutserver 4htimeoutconnect 10s frontend k8s-apibind*:6443 default_backend k8s-masters backend k8s-masters balance roundrobin server master1192.168.1.11:6443 check server master2192.168.1.12:6443 check server master3192.168.1.13:6443 check# 观测面板可选frontend statsbind*:8404 statsenablestats uri / stats refresh 5s EOFsudosystemctlenable--nowhaproxysudosystemctl restart haproxysudohaproxy-c-f/etc/haproxy/haproxy.cfg# 应输出 Configuration file is valid4.3 配置 KeepalivedVIP 漂移sudobash-ccat /etc/keepalived/keepalived.conf EOFglobal_defs{router_id LVS_DEVEL}# 健康检查HAProxy 挂了就让出 VIP权重必须足够大使降权后 备机优先级vrrp_script chk_haproxy{scriptkillall -0 haproxyinterval2weight-60}vrrp_instance VI_1{state MASTER interface ens33# ★ 换成你的真实网卡名ip -br a 查看virtual_router_id51# 同一 VRRP 组必须相同priority150# 单机 MASTER若以后加第二台 lb备机用 100advert_int1# ★ 优先用 unicast不依赖组播 224.0.0.18桥接到物理网更可靠unicast_src_ip192.168.1.16 unicast_peer{192.168.1.11# 单机时这行可留空或删掉多 lb 时填对端}authentication{auth_type PASS auth_pass1111}virtual_ipaddress{192.168.1.100# VIP}track_script{chk_haproxy}}EOFsudosystemctlenable--nowkeepalivedsudosystemctl restart keepalived4.4 验证 VIP 已生效ip-4addr show ens33|grep192.168.1.100# 应能看到 inet 192.168.1.100/32 scope global secondarycurl-skhttps://192.168.1.100:6443/healthz# 此时可能返回空或报错master 还没起能连通即可说明本环境为单台 lb。若要做 lb 双机高可用按第 6 节「HAProxyKeepalived 双机」要点再加一台差异仅state/priority/router_id/unicast_peervirtual_router_id必须一致weight -60必须让降权后(100)低于备机。5. 初始化第一个控制面 master1只在 master1 上执行5.1 写 kubeadm 配置文件sudobash-ccat /root/kubeadm-config.yaml EOFapiVersion: kubeadm.k8s.io/v1.30.0 kind: ClusterConfiguration kubernetesVersion: v1.36.3 controlPlaneEndpoint:192.168.1.100:6443# VIP:端口imageRepository:registry.aliyuncs.com/google_containers# 受限网络用阿里云库networking: podSubnet:10.244.0.0/16# Flannel 默认serviceSubnet:10.96.0.0/12apiServer: certSANs: -192.168.1.100-k8s-vip-192.168.1.11-master1--- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration# Ubuntu 22.04 默认 cgroup v2一般无需此项写上作为保险兼容老内核failCgroupV1:falseEOF5.2 执行 initsudokubeadm init\--config/root/kubeadm-config.yaml\--upload-certs\--ignore-preflight-errorsSystemVerification成功末尾会打印两段命令务必保存kubeadm join 192.168.1.100:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy—— 这是worker加入命令master1 上可用kubeadm token create --print-join-command重新生成。kubeadm join 192.168.1.100:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy \ --control-plane --certificate-key zzzz—— 这是其他 master加入命令–certificate-key 2 小时内有效过期用kubeadm init phase upload-certs --upload-certs重新获取。5.3 配置当前用户的 kubectlmkdir-p$HOME/.kubesudocp-i/etc/kubernetes/admin.conf$HOME/.kube/configsudochown$(id-u):$(id-g)$HOME/.kube/config kubectl get nodes# 此时应只有 master1状态 NotReadyCNI 未装6. 安装 Flannel CNI只在 master1 上执行# 拉官方 manifestcurl-fsSLhttps://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml-okube-flannel.yml★ 镜像来源处理受限网络必看新版 Flannel manifest 镜像为ghcr.io/flannel-io/flannel*与docker.io/flannel*。本环境ghcr.io可达、docker.io被墙。若你的网络 docker.io 可达可跳过下面 sed否则把 docker.io 的 flannel 镜像改到 ghcr.iosed-is#docker.io/flannel#ghcr.io/flannel-io/flannel#gkube-flannel.ymlgrep-nimage:kube-flannel.yml# 确认镜像都指向可达源kubectl apply-fkube-flannel.yml# 等 1~2 分钟kubectl get nodes# master1 应变 Readykubectl get pods-nkube-flannel-owide# 全部 Running若 master1 仍是 NotReady大概率是/opt/cni/bin缺标准插件回看 2.4 节或 containerd 的 CNI 状态卡住——sudo systemctl restart containerd sudo systemctl restart kubelet后再看。7. 加入 master2、master3控制面扩容分别在 master2 / master3 上执行用第 5.2 节保存的带--control-plane --certificate-key的命令。若已过期在 master1 上执行# 在 master1 上重新拿 certificate-key24h 有效sudokubeadm init phase upload-certs --upload-certs然后在master2上sudokubeadmjoin192.168.1.100:6443\--tokentoken\--discovery-token-ca-cert-hash sha256:hash\--control-plane\--certificate-keykey\--ignore-preflight-errorsSystemVerification在master3上执行同样的命令用同一个 token / hash / key 即可。完成后在 master2/master3 上也配置 kubectl可选便于本地排错mkdir-p$HOME/.kubesudocp-i/etc/kubernetes/admin.conf$HOME/.kube/configsudochown$(id-u):$(id-g)$HOME/.kube/config8. 加入 worker1工作节点在 worker1 上执行用第 5.2 节保存的不带 --control-plane的 worker 命令。若丢失在 master1 上重新生成kubeadm token create --print-join-command在worker1上执行输出的命令形如sudokubeadmjoin192.168.1.100:6443\--tokentoken\--discovery-token-ca-cert-hash sha256:hash\--ignore-preflight-errorsSystemVerification9. 验证整个集群在 master1 上kubectl get nodes-owide# 期望5 台全 Ready3 master worker1lb 不跑 kubelet不在此列# NAME STATUS ROLES AGE VERSION# master1 Ready control-plane 15m v1.36.3# master2 Ready control-plane 10m v1.36.3# master3 Ready control-plane 10m v1.36.3# worker1 Ready none 8m v1.36.3kubectl get pods-A# 期望所有 Pod含 kube-system 的 coredns x2、kube-apiserver x3、etcd x3、kube-controller-manager x3、# kube-scheduler x3、kube-proxy x4、kube-flannel x4状态均为 Runningcurl-skhttps://192.168.1.100:6443/healthz# 应返回 ok# HA 验证停掉 master1 的 apiserverVIP 应仍可用sshmaster1sudo kubectl --kubeconfig /etc/kubernetes/admin.conf delete pod -n kube-system \$(ssh master1 sudo docker ... )# 不必手动测重启 master1 虚拟机后集群应自动恢复9.1 部署一个测试应用验证调度与网络kubectl create deployment nginx--imageghcr.io/flannel-io/flannel:latest2/dev/null||true# 用正规 nginxghcr.io 可达docker.io 被墙时改用镜像加速kubectl create deployment nginx--imagenginx --dry-runclient-oyaml|seds#image: nginx#image: docker.m.daocloud.io/library/nginx:latest#|kubectl apply-f- kubectl scale deployment nginx--replicas3kubectl expose deployment nginx--port80--typeNodePort kubectl get pods-owide# 3 个 nginx 分布到不同节点状态 Runningkubectl get svc nginx# 记下 NodePort如 30080curl-shttp://192.168.1.11:30080/|head-n5# 能返回 nginx 欢迎页即网络/调度全通curl-shttp://192.168.1.14:30080/|head-n5# 经 worker1 也能访问跨节点 Service 通镜像拉取提示本环境 docker.io 被墙nginx 用docker.m.daocloud.io/library/nginx:latest你的网络若 docker.io 可达直接用nginx:latest即可。10. 排错速查表本环境实测过的坑现象根因解法unknown service runtime.v1.RuntimeServicecontainerd 1.6仅 CRI v1alpha2升级 containerd ≥1.7第 2.1 节Pod 永远ContainerCreatingfailed to find plugin loopback/bridge/opt/cni/bin缺标准插件Flannel 镜像内为空按第 2.4 节装 containernetworking-pluginspreflightcgroups v1 support is deprecatedK8s≥1.35 默认拒 cgroup v1Ubuntu 18.04 才遇failCgroupV1:false--ignore-preflight-errorsSystemVerification已固化cant mask dir /proc/acpi: invalid argumentrunc≥1.2 配老内核(6.6)runc 降到 1.1.xcontainerd 1.7.x 自带 1.1.x正常不会遇集群每分钟抖一次 / kubelet 报 watch 超时HAProxy 默认timeout 1m切断长连接HAProxytimeout client/server 4h第 4.2 节已设HAProxy 挂了 VIP 不漂vrrp_script weight降权后仍 ≥ 备机优先级weight -60150-6090 备机 100第 4.3 节已设VIP 起不来网卡名写错 / 组播被交换机拦确认interface ens33真实存在改用unicast_src_ip/unicast_peer已设镜像ErrImagePull/pull timeoutdocker.io 被墙certs.d 加速2.3 阿里云 imageRepository5.1 Flannel 改 ghcr.io6 节节点装好 CNI 仍 NotReadycni plugin not initializedcontainerd CNI 状态卡住sudo systemctl restart containerd sudo systemctl restart kubeletFileAvailable--etc-kubernetes-manifests-*冲突上次 init 失败残留sudo kubeadm reset -f后重来控制面 join 报证书错 / key 过期--certificate-key24h 过期master1 上kubeadm init phase upload-certs --upload-certs重拿11. 附录把集群控制权交给「看板」做日常管理可选本环境做法本环境的 5 台 VM 跑在 VMware Workstation 上配了一个本地看板http://127.0.0.1:8765/做主机卡片开机/重启/停止/挂起集群概览节点 Ready 数、Pod Running 数、VIP 状态已部署应用面板 日志查看 / 扩缩容 / 删除应用如果你也是 VMware 环境可参考这个思路做一键开关机纯物理机/云主机则直接用kubeadm命令与kubectl即可。一句话复盘照此手册5 台 Ubuntu 22.04 → 关 swap/桥接 → containerd(1.7systemd cgroupCNI 插件镜像加速) → kubeadm/kubelet/kubectl(1.36) →lb 上 HAProxy(6443)Keepalived(VIP 100) → master1kubeadm init --upload-certs→ Flannel → master2/3--control-planejoin → worker1 join → 验证。凡是本环境踩过的坑都已变成上面的「★ 必做」或第 10 节速查表照做即可零误差跑通。