Kubespray离线部署K8S高可用集群实战指南
简介本资源是一套面向运维工程师与云原生初学者的高可用Kubernetes集群部署实战方案聚焦国内网络环境下的离线部署痛点基于Docker容器化方式集成KubeSpray工具链解决企业级K8S集群快速搭建、组件版本对齐与网络插件Calico适配等关键问题。压缩包共72个文件包含47个RPM依赖包覆盖内核模块、IPVS、基础工具、13个Tar/Gz镜像缓存与二进制分发包如etcd、CNI插件、kubeadm/kubelet/kubectl v1.22.8、4个Shell自动化脚本环境初始化、SSH密钥配置、内核升级、IPVS加载及Calicoctl等核心管理工具整体体积达554.46MB结构完整、开箱即用。目前已有938人学习下载用户可直接获取经验证的v1.22.8版本全栈部署资产包括KubeSpray 2.18.1源码、预拉取容器镜像缓存、AMD64架构专用二进制、ZooKeeper兼容性适配说明及基础环境标准化脚本显著降低国内网络下K8S集群部署门槛与调试成本。1. 为什么国内互联网团队还在用 Kubespray 部署高可用 K8S不是因为“怀旧”而是它真能扛住双十一流量洪峰你可能刚在某招聘 JD 里看到“熟悉 Kubespray 部署高可用 K8S 集群”心里嘀咕都 2025 年了不是该用 Rancher RKE2 或 OpenShift 了吗但现实是——国内头部电商、支付、内容平台的 SRE 团队仍在持续迭代自己的 Kubespray Docker 定制化部署包。原因很实在Kubespray 不依赖公有云 API不强耦合特定 CNI 插件所有组件版本、镜像源、证书策略、节点角色分配全由 Ansible 变量控制更重要的是它能把 control plane 的 etcd、kube-apiserver、kube-controller-manager 全部跑在 Docker 容器里而非 systemd 服务配合--pod-infra-container-image和cri_dockerd模式完美兼容国内企业级 Linux 发行版如 CentOS 7.9、Rocky 8.10、Anolis OS 8.8上长期运行的 Docker 20.10.x 稳定分支。这不是“老派方案”而是经过千万级 QPS 场景锤炼的确定性路径。本文聚焦一个可立即复现的落地切口基于 Docker 运行 Kubespray在离线/弱网/国产化环境里30 分钟内完成三 master 两 worker 的高可用 K8S 1.28 集群部署——所有资源包已适配国内镜像源阿里云、华为云、清华源、规避the api server is not healthy after 4m0.00747357s类超时问题并预置了k8s redis 集群所需的 RBAC 和 StorageClass 基础能力。2. 准备工作5 个必须确认的硬性前提少一个都会卡在k8s控制节点master初始化阶段Kubespray 对底层环境极其“较真”。很多团队翻车不是因为 Ansible 脚本写错而是跳过了这 5 项物理层校验。我见过太多人卡在Waiting for api server to be ready...后直接重装系统其实只差一条swapoff -a。2.1 确认操作系统与内核版本满足最低要求Kubespray v2.23对应 K8S 1.28明确要求OS 发行版CentOS 7.9 / Rocky 8.6 / Ubuntu 20.04 / Anolis OS 8.8内核版本≥ 3.10CentOS 7 默认 3.10.0-1160但强烈建议 ≥ 4.18解决 cgroup v2 下 kubelet 启动异常验证命令# 查看内核版本重点看是否启用 cgroup v1 uname -r cat /proc/cgroups | head -5 # 若输出中 subsystem memory 的 enabled 列为 0说明启用了 cgroup v2 → 需强制回退 # 解决方案在 /etc/default/grub 中添加 systemd.unified_cgroup_hierarchy0然后 grub2-mkconfig -o /boot/grub2/grub.cfg reboot提示国内多数生产环境仍用 cgroup v1。Kubespray 默认不兼容 cgroup v2强行启用会导致 kubelet 无法注册节点报错failed to run Kubelet: unable to load bootstrap kubeconfig。2.2 关闭 swap、firewalld 与 SELinux三者缺一不可这是国内服务器最常被忽略的“玄学”环节。K8S 官方文档写“建议关闭”而 Kubespray 是强制要求# 1. 永久关闭 swap临时关掉没用 sudo swapoff -a sudo sed -i / swap / s/^/#/ /etc/fstab # 2. 停止并禁用 firewalldKubespray 自带 iptables 规则管理 sudo systemctl stop firewalld sudo systemctl disable firewalld # 3. 关闭 SELinux设为 permissive 仍会拦截 mount 操作 sudo setenforce 0 sudo sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config注意若 SELinux 未彻底禁用后续kubeadm init会卡在creating directory /var/lib/kubelet/pki报错Permission denied—— 因为/var/lib/kubelet目录被 SELinux 标签锁定。2.3 配置免密 SSH 登录与时间同步Kubespray 通过 Ansible 控制所有节点必须确保控制机运行 ansible 的机器能免密登录所有 target 节点包括自己所有节点时间误差 1setcd 对时钟偏移极度敏感# 在控制机生成密钥不要用 root 密钥用普通用户 ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_kubespray -N ssh-copy-id -i ~/.ssh/id_rsa_kubespray.pub usernode1 ssh-copy-id -i ~/.ssh/id_rsa_kubespray.pub usernode2 # ... 以此类推 # 所有节点安装 chrony 并同步阿里云 NTP sudo yum install -y chrony sudo sed -i s/^pool.*/pool ntp1.aliyun.com iburst/ /etc/chrony.conf sudo systemctl enable chronyd sudo systemctl start chronyd sudo chronyc sources -v # 确认输出中有 ^* 表示已同步2.4 预装 Docker 20.10.24非最新版并配置国内镜像加速Kubespray v2.23 经测试最稳定的 Docker 版本是20.10.24非 24.x 或 25.x。新版 Docker 的 containerd 默认启用 cgroup v2与 K8S 1.28 的 kubelet 存在兼容性问题。# 卸载旧版 Docker如有 sudo yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine # 安装指定版本以 Rocky 8 为例 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo yum install -y docker-ce-20.10.24 docker-ce-cli-20.10.24 containerd.io # 配置国内镜像加速与 cgroup driver关键 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://your-aliyun-accelerator.mirror.aliyuncs.com], exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m }, storage-driver: overlay2 } EOF sudo systemctl daemon-reload sudo systemctl enable docker sudo systemctl start docker提示“exec-opts: native.cgroupdriversystemd” 是 K8S 1.28 的硬性要求。若此处写成cgroupfs后续kubeadm init会报错failed to run Kubelet: misconfiguration: cgroup driver: cgroupfs。2.5 下载并解压 Kubespray 部署资源包国内镜像优化版标题中提到的“国内互联网方案一部署资源包”核心是做了三件事替换全部k8s.gcr.io为registry.cn-hangzhou.aliyuncs.com/google_containers将quay.io/cephcsi/cephcsi、quay.io/k8scsi/csi-provisioner等 CSI 镜像映射到华为云镜像站预置inventory/sample/group_vars/all/all.yml中的docker_dns_servers为[223.5.5.5, 114.114.114.114]阿里 DNS 114 DNS# 创建工作目录 mkdir -p ~/kubespray-deploy cd ~/kubespray-deploy # 下载国内优化版假设已上传至内网 Nexus 或私有 Git # 实际使用时请替换为你的内部地址如https://git.internal.company.com/kubespray-cn-v2.23.tgz wget https://mirrors.huaweicloud.com/kubespray/kubespray-cn-v2.23.tgz tar -xzf kubespray-cn-v2.23.tgz cd kubespray # 验证镜像列表是否已替换关键检查点 grep -r k8s.gcr.io inventory/sample/group_vars/ # 应无任何输出 grep -r registry.cn-hangzhou.aliyuncs.com inventory/sample/group_vars/ # 应有多处命中3. 部署执行用 7 行命令完成三 master 高可用集群初始化Kubespray 的核心是cluster.ymlplaybook但它默认不启用高可用模式。要实现真正的 multi-master非 stacked etcd必须显式开启etcd_kubeadm_enabled: true并正确划分etcd、kube_control_plane、node组。3.1 编辑 inventory 文件定义三 master 两 worker 的拓扑结构进入inventory/sample目录修改inventory.ini[all] node1 ansible_host192.168.10.11 ip192.168.10.11 node2 ansible_host192.168.10.12 ip192.168.10.12 node3 ansible_host192.168.10.13 ip192.168.10.13 node4 ansible_host192.168.10.14 ip192.168.10.14 node5 ansible_host192.168.10.15 ip192.168.10.15 [kube_control_plane] node1 node2 node3 [etcd] node1 node2 node3 [kube_node] node1 node2 node3 node4 node5 [k8s_cluster:children] kube_control_plane kube_node逻辑说明kube_control_plane组决定哪些节点运行kube-apiserver、kube-scheduler、kube-controller-manageretcd组决定哪些节点运行独立 etcd 集群非 stackedkube_node组决定哪些节点运行kubelet和kube-proxy关键约束etcd组必须是kube_control_plane的子集且至少 3 节点奇数以保证 raft 投票3.2 覆盖关键变量解决the api server is not healthy after 4m0.00747357s该错误 90% 由以下三个变量未正确设置导致apiserver_advertise_address必须是节点实际监听的 IP非 127.0.0.1loadbalancer_apiserver必须配置 VIP 或 DNS 名称国内常用lb.k8s.local keepalivedkubeadm_extra_args必须显式指定--upload-certs编辑inventory/sample/group_vars/all/all.yml# --- 必须修改项 --- apiserver_advertise_address: {{ hostvars[inventory_hostname][ip] }} loadbalancer_apiserver: address: 192.168.10.100 # 这是 VIP需提前在 lb 节点可复用 node1上配置 keepalived port: 6443 # --- 必须追加项在文件末尾--- kubeadm_extra_args: --upload-certs参数说明apiserver_advertise_address若写成0.0.0.0kubelet 无法反向连接 apiserver导致NotReadyloadbalancer_apiserver.address这个 VIP 必须真实存在。若无 LB 设备可在任意 master 节点如 node1部署 keepalived# 在 node1 上安装 keepalived sudo yum install -y keepalived sudo tee /etc/keepalived/keepalived.conf EOF vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.10.100/24 } } EOF sudo systemctl enable keepalived sudo systemctl start keepalived3.3 执行部署从拉取镜像到kubectl get nodes成功在kubespray目录下运行# 1. 安装 Python 依赖Ansible 2.14 需要 pip3 install -r requirements.txt # 2. 预检检查 SSH、Docker、内核等 ansible-playbook -i inventory/sample/inventory.ini cluster.yml --limitnode1,node2,node3 --tagsverify-all -b -v # 3. 执行完整部署耗时约 12~18 分钟 ansible-playbook -i inventory/sample/inventory.ini cluster.yml -b -v \ --private-key~/.ssh/id_rsa_kubespray \ --useruser \ --extra-vars ansible_ssh_common_args-o StrictHostKeyCheckingno \ --flush-cache # 4. 部署完成后拷贝 admin.conf 到本地 scp user192.168.10.11:/etc/kubernetes/admin.conf ~/.kube/config # 5. 验证集群状态 kubectl get nodes -o wide kubectl get cs # 应显示 scheduler/controller-manager 为 Healthy kubectl get pods -A | grep -E (coredns|etcd|apiserver) # 所有核心组件应为 Running逻辑说明--limitnode1,node2,node3确保只在三台 master 上执行初始化避免 worker 节点干扰 etcd 启动顺序--flush-cache强制刷新 Ansible 的 facts 缓存防止因内核版本变更导致docker_version变量误判若kubectl get nodes显示NotReady先执行journalctl -u kubelet -n 100 --no-pager90% 是cgroup driver mismatch或certificate has expired4. 避坑指南5 条血泪经验专治k8s生产环境中常见的故障影响到用户这些坑我都亲手踩过修复时间从 2 小时到 3 天不等。列在这里是为了让你绕开它们。4.1 现象kubeadm init卡在waiting for the api server to be ready超过 4 分钟最终失败原因loadbalancer_apiserver.address配置的 VIP如192.168.10.100在所有节点上均不可达。Kubespray 的check_apiserver任务会尝试curl -k https://192.168.10.100:6443/healthz若失败则重试直至超时。解决在任意 master 节点执行ping 192.168.10.100确认 VIP 已生效在 node1 上执行ip a | grep 192.168.10.100确认 keepalived 正常接管若用 DNS如lb.k8s.local需确保所有节点/etc/resolv.conf中的 nameserver 能解析该域名4.2 现象kubectl get nodes显示NotReadyjournalctl -u kubelet报错failed to load KubeConfig原因/etc/kubernetes/kubelet.conf中的client-certificate-data指向的证书文件被覆盖或权限错误。常见于多次执行cluster.yml未清理残留。解决# 在每个 NotReady 节点上执行 sudo rm -f /etc/kubernetes/kubelet.conf /var/lib/kubelet/pki/* sudo systemctl restart kubelet # 然后重新运行 kubespray 的 node.yml playbook ansible-playbook -i inventory/sample/inventory.ini scale.yml -b -v --limitnode4,node54.3 现象kubectl get pods -A中coredns一直ContainerCreatingdescribe pod显示FailedCreatePodSandBox原因Docker 的cgroup driver与 kubelet 不一致见 2.4 节。docker info | grep Cgroup Driver输出cgroupfs而 kubelet 要求systemd。解决# 修改 Docker daemon.json见 2.4 节然后 sudo systemctl restart docker sudo systemctl restart kubelet # 再删除 coredns pod 让其重建 kubectl delete pod -n kube-system -l k8s-appkube-dns4.4 现象etcdPod 日志出现read-only file systemetcdctl member list显示节点状态为unstarted原因/var/lib/etcd目录所在磁盘被挂载为ro只读。常见于某些国产化发行版如 Anolis OS的/var分区默认启用strictatime导致 inode 耗尽。解决# 检查挂载选项 mount | grep /var # 临时修复重启后失效 sudo mount -o remount,rw /var # 永久修复编辑 /etc/fstab将 /var 行的 options 改为 defaults,noatime sudo sed -i s/defaults/defaults,noatime/g /etc/fstab sudo mount -o remount /var4.5 现象k8s redis 集群部署后Pod 无法挂载 PVCdescribe pvc显示no persistent volumes available for this claim原因Kubespray 默认不部署任何 StorageClass。国内方案包虽预置了local-path但需手动启用。解决# 启用 local-path-provisionerKubespray 内置但默认禁用 cd ~/kubespray-deploy/kubespray ansible-playbook -i inventory/sample/inventory.ini addons.yml -b -v --tagslocal_volume_provisioner # 验证 StorageClass 是否就绪 kubectl get sc # 应输出local-path (default) rancher.io/local-path ...5. 进阶验证用 3 个真实场景检验集群是否真正“高可用”部署完成只是起点。真正的高可用必须经受住节点宕机、网络分区、证书轮换三重考验。下面给出可直接执行的验证脚本和判断标准。5.1 场景一模拟 master 节点宕机验证 control plane 自愈能力目标kill 掉 node2 的kube-apiserver容器确认 30 秒内kubectl get nodes仍能返回全部 5 个节点且coredns不中断。# 在 node2 上执行模拟硬件故障 sudo docker ps | grep kube-apiserver | awk {print $1} | xargs sudo docker kill # 在控制机上实时监控执行 60 秒 watch -n 1 kubectl get nodes -o wide | head -6; echo ---; kubectl get pods -n kube-system -l componentkube-apiserver | grep -v NAME✅成功标志kubectl get nodes始终显示 5 个节点node2 状态变为NotReady但其他节点不受影响corednsPod 未重建说明 service mesh 流量未中断30 秒内kube-apiserver容器自动重启由 kubelet 的 restartPolicy 控制技巧若想更严格可iptables -A INPUT -p tcp --dport 6443 -s 192.168.10.12 -j DROP模拟网络隔离效果等同于物理断网。5.2 场景二验证 etcd 集群容灾能力三节点中容忍一节点永久离线目标停止 node3 的 etcd 容器确认etcdctl endpoint health仍显示 node1、node2 为healthy且kubectl get ns可正常返回。# 在 node3 上停止 etcd sudo docker ps | grep etcd | awk {print $1} | xargs sudo docker stop # 在 node1 上检查 etcd 健康状态 export ETCDCTL_API3 export ETCDCTL_CACERT/etc/kubernetes/ssl/etcd/ca.crt export ETCDCTL_CERT/etc/kubernetes/ssl/etcd/client.crt export ETCDCTL_KEY/etc/kubernetes/ssl/etcd/client.key etcdctl --endpointshttps://127.0.0.1:2379 endpoint health --cluster✅成功标志输出包含https://192.168.10.11:2379 is healthy和https://192.168.10.12:2379 is healthykubectl get ns返回default,kube-system等命名空间证明 etcd 读写正常etcdctl member list中 node3 状态为unstarted非down说明集群已将其剔除注意Kubespray 的 etcd 默认配置--initial-cluster-statenew节点离线后不会自动 rejoin需手动etcdctl member remove清理。5.3 场景三为k8s redis 集群部署一个带 PV 的 StatefulSet并验证滚动更新不丢数据这是国内互联网最典型的有状态应用。我们用官方 Redis Helm Chart 部署一个 3 节点集群并触发一次主从切换。# 1. 添加 Bitnami Helm 仓库国内加速 helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update # 2. 部署 Redis 集群自动创建 3 个 PVC helm install my-redis bitnami/redis-cluster \ --set global.storageClasslocal-path \ --set cluster.nodes3 \ --set auth.enabledfalse \ --set persistence.enabledtrue \ -n default # 3. 等待所有 Pod Running 后写入测试数据 kubectl exec -it my-redis-redis-cluster-0 -- redis-cli set testkey testvalue kubectl exec -it my-redis-redis-cluster-0 -- redis-cli get testkey # 应返回 testvalue # 4. 模拟主节点故障kill 当前 master kubectl get pods -l app.kubernetes.io/instancemy-redis -o wide # 找到 master通常为 my-redis-redis-cluster-0执行 kubectl delete pod my-redis-redis-cluster-0 # 5. 观察新 master 选举及数据一致性 kubectl exec -it $(kubectl get pods -l app.kubernetes.io/instancemy-redis | grep -v NAME | head -1 | awk {print $1}) -- redis-cli get testkey✅成功标志新 master 选举在 15 秒内完成redis-cli cluster nodes显示master角色转移get testkey仍返回testvalue证明 PVC 数据未丢失kubectl get pvc中所有 PVC 状态保持Bound未被重建教训我在某次大促前做此验证发现local-pathProvisioner 在 Rocky 8.10 上存在chown权限 bug导致 Redis 启动失败。最终切换为openebs-hostpath方案。永远不要假设存储插件在所有发行版上行为一致——这就是为什么标题强调“国内互联网方案一”它必须经过你自己的 OS 组合实测。希望帮到你。本文还有配套的精品资源点击获取