K8s集群一键部署实战:Shell脚本、Docker与CNI避坑指南

发布时间:2026/10/8 2:17:18
K8s集群一键部署实战:Shell脚本、Docker与CNI避坑指南
简介面向容器化集群快速部署的Shell脚本压缩包适合运维工程师、开发人员以及正在搭建基础架构的团队。脚本将Docker环境初始化、Kubernetes控制面与工作节点配置整合为一键命令免去手动安装各类插件、编辑多处配置的繁琐步骤使用前可根据实际环境修改集群节点规划和软件版本再分别上传至Master节点与Node节点后执行安装即可。资源包共7个文件压缩后大小约10.67MB类型包括3个Shell脚本安装、卸载与公共配置、1个YAML集群初始化清单、1个YML网络插件配置、1个RPM安装包和1个说明文本覆盖K8s集群搭建的关键环节。脚本内置Docker 24.0.7、cri-dockerd 0.3.9与Kubernetes v1.28.2并提供Flannel容器网络方案适合快速验证、离线部署等场景。目前已有953人学习下载对希望缩短集群环境准备时间的中级运维人员具有直接参考价值。1. 一键部署K8s集群shell脚本的价值不是省那几条命令很多第一次接触K8s集群部署的人都被网上那种“二十分钟从零到集群”的教程误导过。真正跑过一遍就会发现卡住你的从来不是kubeadm init这一条命令而是它前面的环境准备和它后面的网络插件。Docker容器化、K8s组件初始化、节点加入、CNI配置每一步都有独立的坑而这些坑在交互式操作里会被反复踩。这套一键部署shell脚本就是把“环境检查→容器运行时→kubeadm初始化→CNI部署→worker加入”这条链路固化成可重复执行的步骤适合那些不想跟图形化面板黑匣子打交道的运维和开发也适合需要在多套环境里反复建集群的从业者。2. 先理清部署链路K8s集群里的Docker和你想的不一样2.1 容器运行时的选型为什么不一定直接装Docker很多人看到“Docker容器化K8s集群”这个标题第一反应是“那我先装个Docker”。这个理解不算错但不完全对。K8s本身不直接运行容器它需要一个容器运行时Container Runtime InterfaceCRI来干活。K8s 1.24之后的版本已经移除了内置的dockershim这意味着如果你非要用Docker Engine作为运行时需要额外装一个cri-dockerd适配器等于多了一层维护成本。所以在生产环境或者正经测试环境里我更倾向于用containerd作为运行时它本身就是CNCF的容器运行时和K8s同源配置干净资源占用也更低。那这套脚本里为什么还叫“Docker容器化”因为宿主机上的镜像管理、本地调试、日志查看你仍然会用docker命令行或者crictl来操作。部署脚本里保留Docker的安装主要是为了让运维同学在排查问题时能直接用docker ps、docker exec进容器看状态。运行时用containerd命令行用docker这两者不冲突。各组件与网段在K8s集群中的职责如下表所示组件职责部署位置常见坑kubeadm集群初始化与节点加入Master/Workertoken过期、证书有效期kubelet管理Pod生命周期所有节点cgroup driver不一致kubectl集群操作入口Masterkubeconfig权限containerd容器运行时所有节点sandbox_image拉取失败Calico/FlannelPod网络通信集群内网段冲突、MTU问题2.2 三节点最小架构资源不够时怎么缩如果你只有两台机器甚至一台机器这套脚本也能用但我不建议把控制平面和工作节点压缩到一台物理机上做“学习之外的用途”。最少三台一台Master、两台Worker这样能验证Pod调度、节点故障转移这些核心机制。如果是纯本机学习可以用kubeadm的single-node模式但脚本里默认初始化的apiserver地址和网络参数需要改成单机自包含的配置。我在脚本里习惯把Master节点的IP、Pod网段、Service网段抽成变量放在脚本开头。这样你换环境时不用翻遍脚本找参数只改一个配置段落就行。后面第3章贴出来的就是这套变量的实际写法。如果你只有两台物理节点可以把etcd、kube-apiserver都跑在Master上Worker单独一台集群能正常调度只是etcd容错性为零重启Master时集群会短暂不可用这个代价在测试环境可以接受。3. 脚本拆解从环境初始化到kubeadm集群拉起3.1 环境基础准备内核参数、swap、模块加载kubeadm对环境有一个硬性要求关闭swap否则kubelet会拒绝启动。另外还需要加载overlay和br_netfilter两个内核模块并调整net.bridge.bridge-nf-call-iptables等内核参数让iptables能正确处理桥接流量。这个环节最容易翻车因为很多发行版默认不会自动加载这些模块重启后还会丢。我一般把这段写成独立脚本单独执行方便反复跑。#!/bin/bash # env_init.sh —— 集群节点基础环境初始化 HOST_IP$(hostname -I | awk {print $1}) # 1. 关闭swap。kubeadm检测到swap开启会直接报错 swapoff -a sed -i / swap / s/^/#/ /etc/fstab # 2. 加载内核模块并写入启动配置让重启后仍生效 cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 3. 桥接流量走iptables并开启ip_forward cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables 1 net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 EOF sysctl --system /dev/null echo env_init done on $HOST_IP这段逻辑不复杂但每一行都不能省。第一处坑是sed -i / swap / s/^/#/ /etc/fstab有些系统里swap记录的字段和注释状态不同如果你之前已经注释过一次再跑就会把#重复加上不影响功能但看着乱。我会在脚本里用grep先判断是否已注释。第二处是sysctl --system执行后最好再跑一次sysctl net.bridge.bridge-nf-call-iptables确认输出为1因为个别云服务器镜像会把某些内核参数锁死。3.2 containerd配置pause镜像和SystemdCgroup两个关键点containerd安装完成后默认配置是不满足K8s要求的。必须改两处一是把cgroup driver从cgroupfs换成systemd和kubelet保持一致否则节点启动后kubelet会一直报cgroup driver不一致二是把sandbox_image指向一个能拉到的pause镜像仓库。pause镜像就是每个Pod里最先启动的“占位容器”拉不到它Pod永远起不来。# install_containerd.sh —— 安装并配置containerd运行时 yum install -y containerd.io # 以CentOS/RHEL系为例 mkdir -p /etc/containerd containerd config default /etc/containerd/config.toml # 将cgroup驱动改为systemd与kubelet保持一致 sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml # 将sandbox镜像指向国内可拉取的pause镜像仓库 sed -i s#sandbox_image .*#sandbox_image registry.aliyuncs.com/google_containers/pause:3.9# /etc/containerd/config.toml systemctl restart containerd systemctl enable containerd这里有个细节值得注意containerd config default生成的配置里SystemdCgroup默认是false但你如果搜网上的教程很多是让你直接改成true实际上在新版本里配置结构可能略有变化直接sed匹配SystemdCgroup false有时候匹配不到。我建议改完以后跑containerd config dump | grep -A 5 SystemdCgroup看一眼确认修改生效。另外pause镜像的版本号必须和K8s版本匹配K8s 1.28对pause 3.9没问题如果是更老的1.23pause 3.6可能更合适选错版本虽然不至于起不来但会有兼容性告警。3.3 kubeadm初始化镜像仓库、Pod网段、apiserver地址kubeadm init是集群初始化的核心动作。默认它会从registry.k8s.io拉取所有控制平面组件镜像在国内网络环境下大概率超时。我的方案是把镜像仓库参数指向阿里云的google_containers镜像仓库Pod网段采用Calico默认使用的192.168.0.0/16避免后面装网络插件时还要回头改网段。#!/bin/bash # init_cluster.sh —— 当前节点初始化为Master MASTER_IP192.168.1.10 POD_CIDR192.168.0.0/16 SERVICE_CIDR10.96.0.0/12 kubeadm init \ --apiserver-advertise-address$MASTER_IP \ --image-repositoryregistry.aliyuncs.com/google_containers \ --pod-network-cidr$POD_CIDR \ --service-cidr$SERVICE_CIDR \ --kubernetes-versionv1.28.2 mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config--apiserver-advertise-address必须填Master节点的内网IP不能填127.0.0.1否则Worker节点加入后连不上apiserver。--pod-network-cidr要和后面安装的Calico的IPPool网段一致这是新手最容易忽略的坑。--image-repository是镜像仓库前缀替换后会从registry.aliyuncs.com/google_containers/kube-apiserver:v1.28.2这个路径拉镜像。kubeadm init执行成功后会输出一段kubeadm join命令包含token和ca-cert-hash这段输出我一般会重定向到文件里存起来防止终端滚动丢失。如果init失败了不要直接重跑先kubeadm reset清理现场再把/etc/cni/net.d、/var/lib/kubelet里残留的旧配置清干净否则重跑会碰到端口占用或者etcd数据不一致的诡异问题。3.4 Worker节点加入从join命令到节点状态确认Worker节点的加入步骤很简单本质就是执行Master上kubeadm init输出的那条join命令。但它有隐藏前提Worker节点上的containerd、kubelet必须已经装好并启动环境初始化脚本也要跑过一遍尤其是swap和内核参数。如果join之后节点状态一直是NotReady八成是网络插件没装好而不是join动作本身出了问题。# join_node.sh —— Worker节点加入集群 # 在Master上执行: kubeadm token create --print-join-command # 把输出的完整join命令粘贴到下面执行 kubeadm join 192.168.1.10:6443 \ --token token \ --discovery-token-ca-cert-hash sha256:hashtoken默认24小时过期如果你在Master上执行join命令时报“token expired”就去Master上重新生成一条。如果提示ca-cert-hash不匹配用openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2/dev/null | openssl dgst -sha256 -hex重新计算hash再拼接成完整命令。这些命令看着繁琐但放进脚本以后你只需要维护一次。Worker节点加入后在Master上执行kubectl get nodes能看到该节点状态为NotReady这是正常的因为还没部署网络插件。4. 网络插件与Pod调度把集群从NotReady拉到Ready4.1 CNI选型Calico和Flannel怎么挑节点加入后所有节点都是NotReady根因是Pod网络没通。K8s集群必须有CNI插件来给每个Pod分配集群内IP并打通跨节点通信。Flannel简单、资源占用低但网络策略的支持几乎为零适合内网纯互通场景。Calico功能更强支持NetworkPolicy、BGP路由生产环境用得最多但要求内核支持IP转发和特定模块且默认配置里包含了网络策略相关的组件资源占用比Flannel高。如果你只是搭一套测试环境Flannel就够了如果后面要练网络策略、做生产模拟直接上Calico。维度FlannelCalico数据面VXLAN/OverlayBGP/路由NetworkPolicy不支持支持资源占用低中高Pod网段冲突风险低需要显式配置IPPoolCalico安装时会读取kubeadm init时指定的--pod-network-cidr所以两者必须一致。如果你用了Calico默认的192.168.0.0/16但同时有其他业务网段也在这个范围里就必须改Calico的IPPool配置这个改动要在部署Calico之前完成否则节点状态虽然Ready了但跨节点Pod通信会间歇性失败玄学问题从此开始。4.2 部署Calico版本选择与配置清单部署Calico最简单的方式是直接用官方manifest文件。需要注意的是K8s版本和Calico版本有兼容矩阵我一般根据kubeadm初始化的K8s版本来选Calico的release分支。manifest里最重要的配置就是CALICO_IPV4POOL_CIDR这个环境变量它必须和kubeadm init的pod-network-cidr一致。# install_calico.sh —— 部署Calico网络插件到集群 CALICO_VERSIONv3.27.0 POD_CIDR192.168.0.0/16 # 下载预生成manifest curl -L https://raw.githubusercontent.com/projectcalico/calico/$CALICO_VERSION/manifests/calico.yaml -o calico.yaml # 修改IPPool网段确保与kubeadm init参数一致 sed -i s#192.168.0.0/16#192.168.0.0/16#g calico.yaml kubectl apply -f calico.yaml这里有个很隐蔽的坑新版本Calico的manifest里同时存在IPv4和IPv6两套配置如果你只改IPv4的IPPoolIPv6那套会使用默认的disable配置一般不碍事。但如果你下载的是老版本的manifest结构不同sed命令的匹配可能落空。我一般apply之后会执行kubectl get pods -n kube-system | grep calico观察状态calico-node的Pod起来以后再等两分钟然后kubectl get nodes确认所有节点都变成Ready。如果calico-node反复CrashLoopBackOff看日志里是否有“BIRD is not ready”或者“failed to open /sys/fs/bpf”这说明宿主机内核开启了一些安全特性Calico的数据面组件启动受限需要加环境变量禁用BPF模式或检查内核模块。4.3 验证调度用nginx和busybox做最小连通性测试集群Ready之后很多人急着部署业务我建议先做一个最小调度验证。在Master上创建一个nginx deployment指定replicas为2然后观察Pod是否被调度到不同节点。这一步测试的不只是调度器还包括镜像拉取、Pod网络、kubelet正常工作一整条链路。# test_schedule.sh —— 验证Pod调度与跨节点通信 kubectl create deployment nginx --imagenginx:alpine --replicas2 kubectl get pods -o wide # 进入其中一个Podping另一个节点的PodIP kubectl exec -it nginx-xxxxx -- ping 192.168.0.12如果nginx Pod的IMAGE拉取失败先检查containerd配置的registry mirror或者看Pod事件的ErrImagePull原因。如果Pod一直Pendingkubectl describe pod看事件最常见的是节点资源不足或者存在污点。如果两个Pod能互相ping通说明CNI工作正常。你再创建一个busybox Pod执行nslookup kubernetes.default.svc.cluster.local验证DNS组件CoreDNS是否正常很多集群就是卡在这一步——节点Ready但服务发现完全不可用。5. 避坑合集K8s集群部署最常见的五个翻车现场5.1 kubeadm init一直卡在拉镜像最后超时失败现象kubeadm init执行到拉取镜像步骤长时间无响应最终报unable to fetch image类似错误。原因默认镜像仓库registry.k8s.io在国内网络环境下拉取极慢而且部分组件镜像比较大。另外containerd的sandbox_image如果没改即使你指定了镜像仓库pause镜像还是会走默认地址。解决两条路并行。第一kubeadm init加--image-repositoryregistry.aliyuncs.com/google_containers。第二提前把containerd的sandbox_image改成同一个Google镜像仓库下的pause版本并重启containerd。还有一个后悔药kubeadm config images pull --image-repositoryregistry.aliyuncs.com/google_containers可以单独测试镜像拉取不用等init走到那一步才发现。5.2 Worker节点join后状态永远是NotReady现象join命令执行成功但kubectl get nodes看到节点一直NotReadykubelet日志里报CNI相关错误。原因节点上网络插件没有部署或者部署时间晚于节点加入时间。另一个常见原因是Pod网段和节点所在局域网网段重叠导致路由冲突。解决先确认CNI是否已经部署kubectl get pods -n kube-system查看calico相关Pod是否Running。如果CNI已经部署还NotReady大概率是网段重叠把kubeadm init的pod-network-cidr改成172.16.0.0/16这种不冲突的段重新初始化。5.3 重启机器后kubelet起不来docker权限报permission denied现象宿主机重启后systemctl status kubelet显示failed手动启动报Permission denied while trying to connect to the Docker daemon socket或者kubelet日志里大量cgroup相关错误。原因在混合使用Docker和containerd的环境里用户权限、cgroup driver配置在重启后没有恢复到期望状态。更常见的是docker用户组权限当前用户不在docker组里。还有一部分情况是containerd启动晚于kubeletkubelet连不上运行时。解决先把用户加进docker组usermod -aG docker your_user然后确认containerd先启动、kubelet后启动两者都设为enable。cgroup driver必须是systemd检查/etc/containerd/config.toml里的SystemdCgroup值两边的driver不一致重启后必翻车。5.4 kubectl命令报错“The connection to the server was refused”现象在Master上执行任何kubectl命令报连接api server被拒绝或者提示找不到kubeconfig文件。原因新手操作时没有把admin.conf复制到$HOME/.kube/config或者当前用户对kubeconfig没有读写权限。还有些场景是KUBECONFIG这个环境变量指向了错误文件。解决重新执行mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config。然后kubectl cluster-info验证连接。如果这条命令之前跑过还报错检查一下环境变量env | grep KUBECONFIG有就unset。5.5 集群跑了两三个月证书突然过期现象某天执行kubectl get nodes报证书过期错误查看api server日志发现certificate has expired or is not yet valid。原因K8s控制平面组件证书默认有效期一年除非你手动配置过自动续期否则到期后整个集群API不可用这是最典型的“黑匣子”翻车。解决在Master上执行kubeadm certs renew all然后重启kube-apiserver、kube-controller-manager、kube-scheduler这几个静态Pod容器最后更新admin.confcp /etc/kubernetes/admin.conf $HOME/.kube/config。执行完kubectl get nodes确认恢复。从那以后我习惯把证书到期时间记在监控提醒里每季度看一次。6. 部署后的验证链路从能用到敢用的三道检查集群能起来只是第一步真正的问题在于你有没有验证到“敢用”的程度。我通常把验证拆成三道。第一道是基础设施检查kubectl get nodes -o wide看所有节点Ready状态、内核版本、容器运行时版本是否一致kubectl get pods -n kube-system确认所有系统组件Running注意kube-system里的Pod如果产生了Evicted状态的说明之前有节点资源不足清理掉再观察。第二道是业务链路验证部署带状态的服务最好是一个有PV的mysql或者redis验证Pod创建、svc暴露、数据写入、节点重启后数据是否还在。第三道才是性能压测这对大多数刚搭好集群的人还太早但至少要把etcd的备份方案落地。# verify_cluster.sh —— 部署后的完整检查清单 # 1. 节点状态与系统组件 kubectl get nodes -o wide kubectl get pods -n kube-system -o wide # 2. DNS与Pod间通信 kubectl run test-dns --imagebusybox --restartNever -- nslookup kubernetes.default.svc.cluster.local kubectl logs test-dns # 3. 控制器恢复能力删掉一个nginx Pod观察控制器是否重新拉起 kubectl delete pod nginx-xxxxx kubectl get pods -o wide --watch这三条命令看似基础但每一条背后都有对应的失败场景。第一条对应节点或系统组件隐藏问题第二条对应CoreDNS和网络插件是否真的工作第三条对应控制器循环和调度器是否正常。我记得有一次集群各节点看起来都Ready但部署的服务就是访问不通最后排查发现kube-proxy的IPVS模式在某个节点上没生效kubectl get pods -n kube-system里kube-proxy虽然是Running但宿主机上ipvsadm -L -n看不到任何规则。这种问题只有用实际流量验证才能暴露出来看Pod状态都是绿的。证书续期这块每次续期完我还会执行kubeadm certs check-expiration看一眼剩余时间明细。从那次证书过期导致集群瘫了半天之后我给自己定的规矩是每次部署完成强制走一遍上面的检查链路然后立刻把证书和etcd快照的备份脚本放进crontab两周一跑。etcd备份用的就是一条命令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这个命令里三个证书路径不能写错写错了备份出来的快照是坏的恢复时才会发现。这套脚本和流程迭代了几轮之后我现在搭一套三节点集群的时间基本控制在20分钟以内大部分时间都花在等待组件启动上而不是排错上。希望帮到你。本文还有配套的精品资源点击获取