ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

K8s一键部署脚本:从环境检测到节点加入的幂等实践

K8s一键部署脚本:从环境检测到节点加入的幂等实践 简介一套面向运维与开发人员的Kubernetes一键部署脚本基于Docker容器化环境把K8s集群搭建中各插件的安装、配置与联调封装成自动化流程省去逐一手动操作的繁琐。脚本内置明确的软件版本组合Docker 24.0.7、cri-dockerd 0.3.9、Kubernetes v1.28.2下载后按需修改节点规划与版本信息分别上传至Master和Node节点执行install-k8s.sh即可完成环境搭建特别适合需要快速验证或反复重建集群的工程师。压缩包共7个文件体积约10.67MB主要包含shell安装与卸载脚本、kubeadm配置文件、kube-flannel网络插件声明文件以及cri-dockerd的rpm组件并附带教程地址说明方便对照完整部署流程。目前已有952人学习下载这份脚本不仅能显著缩短集群交付时间还能通过统一的版本控制减少手工配置带来的差异与差错可直接作为日常环境搭建与维护的基础工具链。1. 一键部署脚本容器化 K8s 集群部署难的不是 init而是环境一致性在测试机上手工敲 kubeadm init 的工程师大概率都想过同一件事能不能把一套 K8s 集群的安装收拢成一条命令。这份一键部署 shell 脚本就是冲着这个需求去的——它把 Docker 或 containerd 运行时准备、常用内核参数调整、swap 关闭、K8s 组件镜像拉取、控制面初始化、工作节点加入全部折进同一个脚本目标是一台干净 CentOS 或 Ubuntu 主机上跑完就能用kubectl get nodes看到 Ready。适合三类人内网搭测试集群的运维、在本地机器反复练手的 K8s 新手、以及要批量交付多套环境时不想靠手速和记忆硬扛的工程师。它不负责高可用设计只负责一件事让同一套脚本在不同机器上跑出同样结果把部署里的玄学因素压到最低。2. 脚本骨架设计从环境检测到节点加入的五段式编排脚本的编排方式比某个命令本身更值得讲。很多部署脚本翻车不是某个参数写错而是没有分层环境检测、运行时准备、集群初始化、节点加入、状态验收全糊在一坨命令里一旦中途失败重跑就是灾难。比较稳妥的做法是把脚本拆成五个阶段函数每个阶段只做一件事且阶段之间用标记文件记录进度。2.1 脚本定位它解决“重复劳动”不解决“高可用设计”这份脚本先要摆正定位。它能保证你在干净机器上快速得到一个可用集群但不会替你设计 etcd 备份、控制面高可用、多租户权限。换句话说它把“从零到 kubectl get nodes 全 Ready”这段重复劳动自动化至于集群跑起来之后的运维动作是另一套工作。我一般会用函数式结构组织脚本而不是顺序堆命令。顺序堆命令的坏处是第 30 行失败前面 29 行已经产生副作用你很难判断当前机器处于什么状态函数式结构配合标记文件可以让脚本在任意失败点停下来后重跑时只执行未完成的部分。STAGE_FILE/etc/k8s-deploy/.deploy_stage run_stage() { local stage$1 shift if grep -q ^${stage}$ $STAGE_FILE 2/dev/null; then log skip already-done stage: ${stage} return 0 fi $ echo ${stage} $STAGE_FILE log stage done: ${stage} }这段代码的逻辑是run_stage接受一个阶段名和一个函数名先检查标记文件里有没有这个阶段有就跳过没有就执行函数执行成功后才把阶段名追加进标记文件。关键参数是STAGE_FILE的路径我习惯放在/etc/k8s-deploy/下而不是脚本所在目录避免脚本被移动或目录被清理之后状态丢失。2.2 环境检测模块操作系统、swap 与内核参数一次扫清环境检测是脚本的第一道闸门。常见做法是检查四件事是否 root、swap 是否关闭、br_netfilter内核模块是否加载、时间同步是否开启。这四件事里最容易漏的是 swap——很多机器默认开着 swapkubelet 启动后反复退出日志里全是Failed to run kubelet排查半天发现只是 swap 没关。check_env() { [ $(id -u) ! 0 ] die please run as root grep -q swap /proc/swaps die swap must be disabled before install lsmod | grep -q br_netfilter || { modprobe br_netfilter echo br_netfilter loaded } command -v timedatectl /dev/null 21 timedatectl set-ntp true }这段检测用命令输出判断而不是依赖返回值是为了避免“命令执行成功但实际没生效”的假阳性。比如modprobe在确认内核模块存在时会返回 0但模块可能已经被编译进内核而不是可加载模块此时lsmod看不到先modprobe再做一次lsmod校验比只看返回值可靠。时间同步这里用timedatectl set-ntp true是给联网环境用的内网机器建议在脚本外单独配置 chrony 源。2.3 运行时准备Docker 与 containerd 的兼容取舍容器化 K8s 部署的运行时选型比想象中更看版本。K8s 1.24 之前自带 dockershimkubelet 可以直接连 Docker1.24 起官方移除了 dockershim新集群再走 Docker 就得额外装 cri-dockerd属于给自己找活。脚本里我给的默认策略是检测到 containerd 就用 containerd只有 K8s 版本小于等于 1.23 且机器上已经装好 Docker 时才把 Docker 当 CRI 用。runtime_prepare() { if command -v containerd /dev/null 21; then echo containerd detected, keep as runtime systemctl enable --now containerd elif command -v docker /dev/null 21 [[ $K8S_VERSION ~ ^1\.(2[0-3])\. ]]; then echo docker detected, use as CRI (k8s 1.23) systemctl enable --now docker else echo no supported runtime found, install containerd first install_containerd fi }这里的分支逻辑是三个出口已有 containerd 直接启用已有 Docker 且版本匹配才兼容否则装 containerd。如果你要部署 K8s 1.24 以上的版本并且机器上已经装了 Docker不要试图省事让脚本兼容 —— 直接把脚本的运行时分支改成“仅 containerd”然后把 Docker 当普通容器工具用各管各的避免后文提到的 cgroup 驱动冲突。2.4 集群初始化与节点加入init、join 和标记文件配合控制面初始化的参数我会固定写成变量而不是每次手输。这样后续重跑或复制到第二套环境时只需改四五个变量不用担心命令被改坏。kubeadm init的--image-repository必须和后续镜像拉取保持一致否则kubeadm config images pull拉了一套镜像init 又按另一个仓库重新拉等于白做。init_control_plane() { kubeadm init \ --kubernetes-versionv${K8S_VERSION} \ --image-repository${IMAGE_REPOSITORY} \ --apiserver-advertise-address${MASTER_IP} \ --control-plane-endpoint${MASTER_IP}:6443 \ --pod-network-cidr${POD_NETWORK_CIDR} \ --ignore-preflight-errorsSwap kubeadm token create --print-join-command /etc/k8s-deploy/join.txt }参数说明--kubernetes-version锁死小版本避免拉到一个意外的新版导致配置漂移--control-plane-endpoint指定为MASTER_IP:6443这样生成的 kubeconfig 和 join 命令都指向同一个稳定地址--ignore-preflight-errorsSwap是兜底开关但我不建议把它当成习惯——前面check_env已经要求关闭 swap这个参数只是防止个别云镜像在 fstab 里残留 swap 条目时卡住整个流程。init 结束后立刻用kubeadm token create生成新的 join 命令并写入 join.txt这个文件会被第 3 章的节点加入流程直接使用。2.5 幂等与重跑失败之后不会留下脏状态标记文件之外的第二个保险是日志。脚本里每个阶段入口都要带一条时间戳日志写入/var/log/k8s-deploy.log。失败重跑时先看最后几条日志基本能判断是环境残留还是偶发超时偶发超时直接重跑环境残留则需要先kubeadm reset再跑硬着头皮重跑只会把状态越搞越脏。log() { echo [$(date %F %T)] $* | tee -a /var/log/k8s-deploy.log } fail_and_retry() { log $* kubeadm reset -f /dev/null 21 || true exit 1 }log函数把输出同时送到终端和日志文件方便远程排查时只靠日志文件就能还原现场。fail_and_retry在失败时自动执行一次kubeadm reset -f把控制面残留清掉再退出给下次重跑留一条干净的路。|| true是防止机器上还没有 kubeadm 时reset报错导致原始错误信息被淹没。3. 动手复现把参数改成你的拓扑再从主控机推到全集群这章进入实操。把这份脚本拿到测试环境后第一件事不是执行而是改变量区。脚本里的主机清单、版本号、镜像仓库、Pod 网段都集中在文件头部改完这一段后面的逻辑基本不用动。3.1 修改主机清单与版本变量脚本头部的变量区是复现的第一站。常见拓扑是 1 个主节点加 2 个工作节点主节点也可以兼任工作节点但测试集群我并不建议这么做控制面负载和业务 Pod 混在一起出问题时不方便定位。变量示例值说明MASTER_IP192.168.10.10控制面节点地址也是 kubeconfig 里写死的 endpointWORKER_IPS(192.168.10.11 192.168.10.12)工作节点地址列表脚本会逐个 push join 命令K8S_VERSION1.23.16指定小版本不要写 latestIMAGE_REPOSITORYregistry.aliyuncs.com/google_containers镜像仓库地址内网环境可换自建仓库POD_NETWORK_CIDR10.244.0.0/16Pod 网段要和 CNI 默认网段一致SSH_USERroot节点加入阶段用来免密登录工作节点实际使用场景里我一般会建议在脚本落地后先把变量区完整读一遍再改而不是只改 IP。最容易翻车的是POD_NETWORK_CIDRflannel 默认用10.244.0.0/16Calico 默认用192.168.0.0/16如果你沿用脚本默认值却换了 CNIPod 网络大概率起不来。3.2 在主控机上执行初始化脚本一般会提供init、join、reset几个子命令而不是一个脚本从头跑到尾。执行顺序是先在主控节点初始化再把脚本分发到工作节点最后在工作节点上执行 join。# 主控节点 chmod x deploy.sh ./deploy.sh init # 工作节点脚本分发到 /opt/k8s-deploy/ 后 bash /opt/k8s-deploy/deploy.sh joininit子命令内部依次执行环境检测、运行时准备、镜像拉取、控制面 init、生成 join.txt。join子命令会先做同样的环境检测再用 SSH 从主控机拉取 join.txt 并执行kubeadm join。这里的工作节点如果没有配置免密登录脚本会在 join 阶段停下来提示你输入密码或手动执行 join 命令。3.3 观察关键输出从 kubeconfig 到 join 指令init 阶段结束后终端会打出几段关键信息Your Kubernetes control-plane has initialized successfully、kubeconfig 的配置路径、以及一行kubeadm join ... --token ... --discovery-token-ca-cert-hash sha256:...。脚本会把最后这行 join 命令存到/etc/k8s-deploy/join.txt因为 token 默认有效期为 24 小时及时保存比事后回忆更靠谱。export KUBECONFIG/etc/kubernetes/admin.conf kubectl get nodes kubectl get pods -Akubectl默认读~/.kube/config但 root 用户在主控机上并没有自动生成这个文件设置KUBECONFIG/etc/kubernetes/admin.conf是最快的让 kubectl 可用的方式。如果想让普通用户也能执行 kubectl按 kubeadm 输出的提示把 admin.conf 复制到对应家目录的.kube/config并改属主即可。3.4 节点加入后的集群状态验收节点加入阶段脚本会遍历WORKER_IPS数组逐个 SSH 到工作节点执行 join。比较保守的做法是每加入一个节点就检查一次kubectl get nodes而不是批量加完再统一看——批量加完再检查一旦某个节点证书有问题日志会被其他节点的正常输出淹没。for node in ${WORKER_IPS[]}; do echo join worker: ${node} ssh ${SSH_USER}${node} bash /opt/k8s-deploy/deploy.sh join done kubectl wait --forconditionReady node --all --timeout300s kubectl get node -o widekubectl wait是状态验收的方便工具它等待所有节点变成 Ready超时 300 秒就返回非零。如果超时不要急着看 K8s 侧先回工作节点执行journalctl -u kubelet -f八成是运行时没起来或 CNI Pod 没调度成功。4. 参数与选型拆解镜像源、资源预留、CNI 与版本锁定的取舍这章把脚本里涉及的关键参数摊开讲一遍。很多人在网上抄到脚本后第一件事是改 IP第二件事是跑结果跑到一半发现镜像拉不下来、Pod 网段冲突回过头来才研究参数浪费时间。参数选型应该在执行前定清楚。4.1 镜像仓库与版本锁定镜像源决定能不能跑通K8s 组件镜像默认从k8s.gcr.io拉取但部分网络环境访问这个仓库并不稳定所以脚本里默认换成了registry.aliyuncs.com/google_containers这是国内使用较普遍的替代仓库。执行 init 之前先手动确认镜像能拉下来能省掉 init 卡住的等待时间。kubeadm config images list \ --image-repositoryregistry.aliyuncs.com/google_containers \ --kubernetes-versionv1.23.16 kubeadm config images pull \ --image-repositoryregistry.aliyuncs.com/google_containers \ --kubernetes-versionv1.23.16images list只列出需要的镜像名和 tag不做实际拉取images pull才真正拉镜像。这里锁定--kubernetes-version参数的意义在于K8s 版本每差一个小版本镜像 tag 都会变不锁版本的话init 和 pull 可能各拉各的浪费流量还容易工作节点加不进来。4.2 资源预留与 sysctl内存和文件句柄的预设部署脚本通常会向/etc/sysctl.d/k8s.conf写入一组内核参数让 kubelet 和容器网络有个合理的系统环境。常见配置如下net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 vm.swappiness 0 fs.file-max 1048576bridge-nf-call-iptables保证桥接流量走 iptables 规则容器间通信才能被 NetworkPolicy 管住ip_forward是 Pod 间和 Service 转发的硬前提vm.swappiness 0配合 swap 关闭避免 kubelet 因为内存压力触发系统换页导致延迟抖动。资源预留方面小规格测试机2C4G不用刻意调4C8G 以上的机器建议给 kubelet 留出--system-reserved和--kube-reserved否则节点内存打满时 kubelet 会先被系统 OOM 杀掉。4.3 CNI 选型flannel 与 Calico 在这个场景下的差异部署脚本默认配 flannel 还是 Calico直接决定POD_NETWORK_CIDR取值。两者我都实际跑过做一张对比表维度flannelCalico网络模型VXLAN 或 host-gwBGP 或 IPIP本环境默认网段10.244.0.0/16192.168.0.0/16网络策略不支持支持适用规模小集群、测试环境需要网络策略的生产集群排查难度简单中等如果只是搭一套练手集群果断选 flannel如果要模拟生产环境的网络策略选 Calico但要把第 3.1 节的POD_NETWORK_CIDR改成192.168.0.0/16或你自己的私网段避免和现有物理网络重叠。安装 CNI 的动作放在 init 之后、节点加入之前kubectl apply -f ./kube-flannel.yml这条命令会把 flannel 的 DaemonSet 和 RBAC 一次性建好。注意 YAML 文件要先下载到内网机器上避免在 init 过程中临时拉取外部文件导致网络超时。4.4 控制面节点的健康检查参数脚本里--control-plane-endpoint的值会影响 kubeconfig 和后续 join 的目标地址。单控制面部署时直接填MASTER_IP:6443如果想给多主节点留扩展余地填一个负载均衡地址更好但这个 L4 负载均衡需要另行部署脚本本身不负责这块。控制面的健康检查依赖 kubelet 的存活端口10248和 apiserver 的6443脚本在环境检测阶段应该顺手ss -lnt检查这两个端口是否被占否则 kubeadm 的 preflight 会直接报错。5. 避坑排查容器化部署 K8s 最容易翻车的五个现场这章写实操中反复出现的故障。每一条都是我或身边的同事实际踩过的场景现象、原因、解决凑齐能帮你把排查时间从几小时压到几分钟。5.1 swap 未关闭kubelet 反复重启现象kubelet 启动后不到一分钟就退出journalctl -u kubelet -f里反复出现Failed to run kubelet相关错误。原因K8s 对 swap 的校验很严格节点开着 swap 时 kubelet 会拒绝正常工作。很多云镜像默认开了 swap而且/etc/fstab里有残留条目重启后又回来了。解决先swapoff -a让本次生效再注释/etc/fstab里的 swap 行swapoff -a sed -i /swap/s/^/#/ /etc/fstabs/^/#/是给匹配行行首加注释比自己手改 fstab 更不容易漏。之后重启机器验证free -h里 Swap 行全部为 0。5.2 cgroup 驱动不一致kubelet 直接拒绝启动现象kubelet 启动时报cgroup-driversystemd but kubelet cgroup-drivercgroupfs进程直接退出。原因容器运行时和 kubelet 的 cgroup 驱动必须一致。Docker 默认是cgroupfs而 kubelet 在 systemd 系统上通常被配置为systemd两者对不上就拒收。解决改 Docker 的 daemon.json强制统一成 systemdcat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd] } EOF systemctl restart docker改完配置后必须kubeadm reset -f再重新 init只重启 kubelet 不会让已经注册节点的配置刷新。5.3 token 过期join 命令无法二次使用现象把 init 时输出的 join 命令保存下来隔天执行kubeadm join报 token 无效或过期。原因kubeadm 生成的 token 默认有效期为 24 小时保存的 join 命令只是把 token 写死在命令里而已它不会自动续期。解决不再依赖旧命令重新生成一份kubeadm token create --print-join-command这条命令会在主控节点生成新的 token 并直接打印完整 join 命令把它复制到工作节点执行即可。如果工作节点已经执行过一次 join 但失败先在工作节点上kubeadm reset -f再重试。5.4 CoreDNS CrashLoopBackOffPod 网段与物理网段冲突现象集群 Ready但kubectl get pods -A里 coredns 一直 CrashLoopBackOffdescribe 显示 liveness 探针失败。原因最常见是 flannel 没起来或--pod-network-cidr与 flannel 默认10.244.0.0/16不一致。改过网段的人如果只在 init 参数里改、没同步改 flannel 的 YAMLCNI 就会分配出错误的网段。解决确认 init 参数和 CNI YAML 里的网段完全一致。用 flannel 就用默认值不要乱改用 Calico 就把POD_NETWORK_CIDR改成192.168.0.0/16并确认这个网段没有和现有物理网络重叠。排查时先看网络插件 Pod 日志而不是直接怀疑 CoreDNS。5.5 镜像仓库不通init 阶段拉取超时现象kubeadm init卡在Pulling image很长时间最终报拉取超时。原因镜像仓库指向k8s.gcr.io时在部分网络环境访问不稳定。这也是一键部署脚本最容易浪费时间的一步——反复 init反复卡在同一个位置。解决统一换用registry.aliyuncs.com/google_containers并在 init 前先执行一次kubeadm config images pull验证镜像可拉。如果拉取仍然失败检查所在机器的容器运行时仓库配置是否被修改过、DNS 是否能解析仓库域名。内网环境建议把镜像提前导入到本地仓库再把IMAGE_REPOSITORY指向内网地址。6. 进阶技巧给部署脚本留后路的四个习惯部署完成不等于交付完成。脚本能跑通只是第一步后续重装、扩容、换网段才是真正考验脚本设计的地方。这里分享四个我在实际交付中沉淀下来的习惯。6.1 用标记文件 自检命令收尾脚本末尾加一段自检逻辑kubectl get nodes全部 Ready、kubectl get pods -A没有 CrashLoopBackOff、DNS 解析可用。任何一步失败就让脚本返回非零。配合第 2.1 节的标记文件这个自检在每台机器上各自执行而不是只在主控机跑一遍能避免工作节点状态漏看。6.2 把“重置”也做成幂等命令很多环境被脚本跑脏恰恰是重跑前没做干净。我给脚本统一加了reset子命令内部是kubeadm reset -f加清理运行时残留和/etc/kubernetes目录。重置也写入标记文件这样重新 init 前不需要人肉判断该清理哪些东西。这个习惯救过我至少三次——每次都是改了网段想重来直接 reset 再 init干净利落。6.3 版本变量永远写具体值K8S_VERSION这个变量只接受v1.23.16这种具体小版本不接受latest。不同时间跑出来的集群版本漂移排查时根本无法对齐。我在脚本里加过一行格式校验不符合v1.x.y就直接拒绝运行避免自己哪天手滑把 latest 当版本传进去。从那以后我每次交付部署脚本都强制自己先跑一遍从零机到集群验收的完整流程再把每一步沉淀成幂等脚本这个习惯替我挡掉了大半返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表