ARTICLE DETAIL

资讯详情

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

Shell脚本一键部署Docker容器化K8s集群全流程

Shell脚本一键部署Docker容器化K8s集群全流程 简介这套一键部署脚本包面向 Docker 与 Kubernetes 运维实施场景适合需要快速搭建 K8s 集群的运维工程师、测试人员或学习容器编排的初学者。它把 kubeadm 初始化、cri-dockerd 适配、flannel 网络插件安装等原本分散的步骤集中到脚本中明显降低手动配置的出错概率。压缩包共 7 个文件、10.67MB包含 3 个 shell 脚本和 1 个 rpm 包分别承担环境检查与公共依赖安装、主安装流程、集群卸载清理等职责另有两个 yml/yaml 配置用于集群初始化参数和容器网络方案一个 txt 提供配套教程入口。脚本内默认版本为 Docker 24.0.7、cri-dockerd 0.3.9、Kubernetes v1.28.2使用时只需按实际环境修改节点规划与版本信息即可在集群各节点完成从初始化到网络插件的自动部署。已有 953 人学习下载对希望快速复现环境、避免逐项手工安装的读者来说是一份可直接落地的实战工具。1. 一键部署shell脚本Docker容器化K8s集群部署到底解决了什么第一次在新机器上装 K8s 集群时我几乎被碎片化的安装步骤逼疯先装 Docker、再配 cgroup 驱动、关闭 Swap、拉取 kubeadm 镜像、初始化控制面、部署 CNI 网络插件任何一步错位都要从头查日志。后来我把整条链路拧进一个一键部署的 shell 脚本哪怕标题里写的是“sheel”本质上还是那个能在黑盒环境里重复执行的部署流程。这篇文章要讲的就是这件事如何用 shell 脚本把 Docker 容器化 K8s 集群部署做成一条命令覆盖环境检查、Docker 安装、kubeadm 初始化、Worker 加入和重置清理全过程。网上大部分 docker 安装教程和 k8s 安装部署是分开写的照着复制一遍换个系统版本就翻车。脚本化之后至少你能把日志统一到一个文件里失败时准确知道是 Docker 安装挂了还是 kubeadm init 挂的。本文默认你有一台 2C4G 的 Linux 主机想用 Docker 作为容器运行时用 kubeadm 拉起一套可以继续加 Worker 的集群想用 Kind 玩轻量集群的读者也不用走开我会在选型里把两者的边界说清楚避免你选错方向。2. 选型先行kubeadmDocker 与 Kind 的取舍以及脚本的三个设计原则2.1 三种常见容器化 K8s 集群部署方式对比“Docker 容器化 K8s 集群部署”在不同人手里是两种意思。一种是像 Kind 那样把整个 K8s 集群跑在 Docker 容器里适合开发调试和 CI 临时环境另一种是拿 Docker 当 kubelet 的容器运行时用 kubeadm 在真机或虚拟机上拉起正式结构的集群。两种方式我都写过部署脚本但如果目标是“一键部署”且有继续扩展的可能我更倾向 kubeadm Docker。部署方式集群形态适用场景常见问题KindK8s 节点全部是 Docker 容器本地调试、CI 动态集群不适合压测和存储验证网络插件受限重启后节点 IP 变化kubeadm Docker真机或虚拟机上的独立节点开发、测试、生产预演前置条件多镜像拉取慢cgroup 驱动容易不一致k3s Docker轻量 K8s默认内置 containerd边缘计算、资源紧张机器与完整 K8s 行为有差异部分 Operator 部署方式不同我选择 kubeadm 的理由很朴素它可以从单控制面一路平滑扩展成多节点也能随时 reset 回干净状态。脚本初始化出来的集群和公司生成环境用的版本几乎一致排查问题时不需要另学一套 Kind 的容器网络。Kind 的好处是启动快几十秒就能得到一个集群但它更像玩具“一键部署”之后如果还要验证 CNI、PVC、HPA 这些真实场景Kind 经常会给出和物理集群不一样的结果这种差异本身就是坑。2.2 脚本设计三原则可重跑、日志可见、参数可变写部署脚本的教训我第一版全是顺序执行的yum install和kubeadm init中途失败后重新执行会因为重复挂载、重复 init 而把系统搞得更乱。所以现在我的脚本强制三条规矩。第一幂等。每个函数开头判断目标是否已存在比如 Docker 已安装就跳过安装步骤只做配置合并kubeadm init发现/etc/kubernetes/admin.conf已存在就直接退出。第二日志统一。所有步骤只在一个入口函数里执行每步输出带时间戳异常时能立刻定位是宿主机安装、容器运行时配置还是集群初始化。第三参数可变。版本号、网段、镜像仓库、CNI YAML 地址全部提取成变量这样同一个脚本既能装 1.28也能装 1.30不需要在代码里改几十处。下面是最小骨架我一般会把每个功能拆成独立函数主流程只做顺序调用#!/usr/bin/env bash set -euo pipefail K8S_VERSION1.28.2 POD_CIDR10.244.0.0/16 SVC_CIDR10.96.0.0/12 IMAGE_REPOregistry.aliyuncs.com/google_containers CNI_URLhttps://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml LOG_FILE/var/log/k8s-deploy.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* | tee -a $LOG_FILE } # 任何一步失败都把最近日志打出来再退出 trap log 部署流程异常结束最近日志如下; tail -20 $LOG_FILE ERR preflight() { :; } install_docker() { :; } install_kubeadm() { :; } init_cluster() { :; } main() { preflight install_docker install_kubeadm init_cluster } main $逻辑说明set -euo pipefail让脚本在命令报错或变量未定义时立刻退出避免带着坏状态继续执行。trap ... ERR会在任意函数失败时把尾部日志输出到屏幕这比一条失败就黑屏友好得多。变量区集中在顶部后续函数只引用这些变量升级集群时只需要改版本号。整个 shell 脚本的骨架就是“变量 函数 顺序调用”没有复杂的模板从业人员拿到就能改。参数说明POD_CIDR和SVC_CIDR必须根据 CNI 插件选Flannel 默认用10.244.0.0/16如果换 Calico可以改成192.168.0.0/16。IMAGE_REPO是 kubeadm 拉取控制面镜像的仓库前缀国内环境我会用阿里云的 google_containers 镜像源避免 init 时长时间卡在拉镜像阶段。这些变量在后续每个函数里都会用到所以脚本里第一步就是把它们固定住。3. 把一键部署脚本写厚环境检查、Docker 安装与 kubeadm 初始化3.1 前置检查Swap、内核模块与 sysctl集群部署里最容易被忽略的前置条件是 Swap。kubelet 检测到 swap 开启时会直接拒绝启动所以脚本必须在 init 前swapoff -a同时注释掉/etc/fstab里的挂载行否则机器重启后 swap 又会自动启用。除此之外K8s 依赖overlay和br_netfilter内核模块以及若干 IPv4 转发参数。这些不是装个 Docker 就能自动配好的必须显式写入 sysctl 配置。preflight() { log 开始环境检查与系统参数配置 # 必须用 root 执行否则后续写入 /etc、/usr/bin 会失败 [ $(id -u) -eq 0 ] || { log 请使用 root 用户执行; exit 1; } # 关闭 swap 并持久化kubelet 检测到 swap 会拒绝启动 swapoff -a sed -i / swap / s/^/#/ /etc/fstab log swap 已关闭 # 加载 K8s 需要的内核模块 modprobe overlay modprobe br_netfilter # 写入 sysctl 配置让 iptables 能处理 bridge 流量 cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system # 清掉可能残留的旧配置保证可重跑 rm -rf /etc/kubernetes || true }逻辑说明swapoff -a立即关闭所有 swap 设备sed -i / swap / s/^/#/是在 fstab 中把所有包含 swap 的行注释掉两行代码缺一不可。modprobe overlay和modprobe br_netfilter加载的是 overlay 文件系统和 bridge 防火墙模块前者是容器镜像分层的基础后者保证 Pod 间流量能正常经过 iptables 规则。sysctl --system会重新加载所有配置不需要重启机器。参数说明rm -rf /etc/kubernetes是给重跑用的第一次执行时目录不存在|| true让它不报错。如果你担心误删已有集群可以把这行移到独立的reset子命令中而不是放在 preflight 里。脚本面向的是一键部署我在这里选择“宁可重置也不要在旧配置文件上叠加”。3.2 安装 Docker 并让 cgroup 驱动对齐 systemdDocker 的安装相对简单麻烦在配置。K8s 要求容器运行时的 cgroup 驱动与 kubelet 一致当前主流 Linux 发行版都使用 systemd 作为 init 系统所以 daemon.json 里必须显式指定native.cgroupdriversystemd。否则即使集群初始化成功kubelet 也会报 “failed to validate kubelet cgroup” 或类似问题节点始终无法 Ready。install_docker() { if command -v docker /dev/null 21; then log Docker 已安装跳过安装步骤 else log 安装 Docker ... curl -fsSL https://get.docker.com -o /tmp/get-docker.sh sh /tmp/get-docker.sh systemctl enable --now docker fi # 写 Docker 配置cgroup 驱动 镜像加速 日志控制 mkdir -p /etc/docker cat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m }, registry-mirrors: [https://docker.m.daocloud.io] } EOF systemctl restart docker log Docker 配置完成cgroup 驱动为 systemd }逻辑说明command -v docker用来实现幂等已安装就不会再次执行安装脚本。registry-mirrors 只影响镜像拉取速度不影响集群内部通信。daemon.json 在重启 Docker 后生效。如果你 cloud 初始化后发现 kubelet 和 docker 的 cgroup 驱动不一致八成就是这里没配对。日志限制max-size: 100m是为了避免长时间运行把磁盘写爆这个参数在长期运行的测试集群里非常重要。参数说明registry-mirrors 的可用性会随网络环境变化建议脚本执行前先手动docker pull一个小镜像验证。如果你使用的是阿里云或其他云厂商的机器可以用云厂商提供的加速地址不要照抄我的。实际上 kubeadm 控制面镜像走的是--image-repository参数daemon.json 里的 mirror 只影响应用镜像两者不冲突。3.3 安装 kubelet/kubeadm/kubectl并初始化集群容器运行时就绪后开始安装 Kubernetes 组件。这里以 CentOS/RHEL 系的 yum 仓库为例Ubuntu 系统只需要把仓库源和包管理器换成 apt脚本结构和参数完全一样。初始化时会指定 POD 网段、Service 网段和镜像仓库这三个参数决定了集群能不能正常启动网络插件。install_kubeadm() { if [ -x /usr/bin/kubeadm ]; then log kubeadm 已安装跳过安装 return fi log 配置 Kubernetes yum 仓库 cat /etc/yum.repos.d/kubernetes.repo EOF [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck0 EOF set e yum install -y kubelet kubeadm kubectl set -e systemctl enable --now kubelet } init_cluster() { if [ -f /etc/kubernetes/admin.conf ]; then log 集群已初始化跳过 kubeadm init return fi kubeadm init \ --image-repository$IMAGE_REPO \ --kubernetes-versionv${K8S_VERSION} \ --pod-network-cidr$POD_CIDR \ --service-cidr$SVC_CIDR \ --ignore-preflight-errorsSwap }逻辑说明--image-repository让 kubeadm 从阿里云镜像仓库拉取控制面镜像避免默认仓库不可达。--ignore-preflight-errorsSwap是双保险因为前面已经 swapoff这里再忽略一次可以应对某些机器重启后又自动挂载 swap 的场景。systemctl enable --now kubelet只需要执行一次kubelet 在没有集群配置时会反复重启并报错这是正常现象等 init 成功后会恢复。参数说明--kubernetes-version必须与要安装的 kubeadm 版本匹配否则会提示找不到对应镜像 tag。如果kubeadm init失败直接执行kubeadm reset -f后修改参数重跑不需要重装系统。这一步是整个脚本中耗时最长的建议把kubeadm init的输出完整落到日志文件很多隐藏错误信息不会显示在终端。初始化成功后需要把 admin.conf 复制到当前用户 HOME 目录然后安装 CNI 网络插件。Flannel 是最省事的方案一条kubectl apply就能让节点进入 Readypost_init() { mkdir -p $HOME/.kube cp -f /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config kubectl apply -f $CNI_URL log CNI 部署完成 }这里的关键是chown。很多人 root 跑完后切到普通用户kubectl 读取 config 时报Permission denied。用chown把文件归属切给当前用户后续所有 kubectl 命令都不需要再加 sudo。kubectl apply -f的 YAML 地址是 Flannel 官方仓库的 raw 地址如果网络不通也可以把 YAML 下载到本地再 apply脚本里就需要加一个“文件存在就跳过下载”的判断。4. 部署中的 5 个高频坑从镜像拉取到 Pod 网络不通的排查顺序4.1 kubeadm init 卡在“waiting for the kubelet to be ready”现象脚本长时间停在waiting for the kubelet to be ready最后超时退出日志里看不到明显错误。原因大多是 kubelet 启动失败。常见诱因是 swap 没关干净、Docker 的 cgroup 驱动不是 systemd、或者 Docker 服务本身异常。解决不要急着重跑 init先执行journalctl -u kubelet -n 50看真实报错。我遇到最多的是 daemon.json 没生效检查/etc/docker/daemon.json里是否写了native.cgroupdriversystemd然后systemctl restart docker再重启 kubelet。如果报cgroup driver冲突就按这个配置改不要因为图省事去把 kubelet 改成 cgroupfs那样后续 Pod 资源限额会出问题。4.2 镜像拉取超时或报“manifest unknown”现象kubeadm init在 Pulling images 阶段不断重试或者提示Failed to pull image ... manifest unknown。原因默认镜像仓库registry.k8s.io在部分网络环境下连接不畅或者你指定的镜像仓库里没有对应架构的镜像。比如 kubeadm 1.28 需要kube-apiserver:v1.28.2如果镜像仓库只同步了 amd64在 arm64 机器上就会报 manifest unknown。解决在 init 命令里加--image-repositoryregistry.aliyuncs.com/google_containers并确认K8S_VERSION对应的镜像 tag 存在。还可以先单独执行kubeadm config images pull --image-repository...验证。测试集群减少等待时间的一个技巧是提前把镜像拉到本地但部署脚本里不建议维护镜像文件因为版本一升级就要重新准备。4.3 节点 Ready 但 Pod 一直 CrashLoopBackOff现象kubectl get nodes显示控制面节点 Ready但 kube-system 里的 flannel 或 kube-proxy 不断重启。原因CNI 插件的 YAML 与初始化时指定的--pod-network-cidr不一致或者宿主机防火墙挡住了 VXLAN 端口。解决执行kubectl logs -n kube-flannel -l appflannel看到no subnet或failed to create vxlan时先确认 Flannel 默认的10.244.0.0/16是否和初始化时一致。不一致就改 init 参数后 reset 重来。防火墙这边放行8472/UDP和6443/TCP如果是在内网测试环境可以直接关闭 firewalld等整套流程跑通后再重新开启。4.4 kubectl 报 Permission Denied 或无法连接 6443现象脚本结束后输入kubectl get nodes报The connection to the server localhost:8080 was refused或提示 config 文件权限不对。原因第一种是$HOME/.kube/config没有正确生成或者当前用户不是 root 且没有读取权限第二种是 apiserver 还没起来完成。解决先确认/etc/kubernetes/admin.conf存在然后执行mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config。如果是连接 6443 超时在控制面节点检查ss -lntp | grep 6443没有监听就说明 kubelet/apiserver 还在自举等两分钟再试。脚本里建议把cp和chown放在 init 成功后的同一函数里不要让用户自己去处理。4.5 Worker 节点 join 后一直 NotReady现象worker 上执行kubeadm join成功但控制面kubectl get nodes看到 worker 长时间处于 NotReady。原因token 过期、join 命令没带--discovery-token-ca-cert-hash或者 worker 节点没有安装 Docker也没有配置 cgroup 驱动。解决重新生成 join 命令时用kubeadm token create --print-join-command这条命令会把 token 和 CA 哈希一起打印出来省去手工算 hash 的麻烦。worker 上必须同样执行 preflight、安装 Docker、配置 cgroup 驱动不能只在控制面装一套。很多新手只复制 join 命令到裸机结果 worker 上连 kubelet 都没有自然永远 NotReady。5. 从单机到多节点把 Worker 加入和重置清理也做成脚本子命令5.1 用子命令区分 init、join、reset把一键部署脚本做成子命令式比单一main更实用。我的写法是让脚本接受init、join、reset三个参数每个参数走对应的函数组合这样控制面与 worker 共用同一份安装逻辑但只执行自己关联的步骤。case ${1:-init} in init) preflight install_docker install_kubeadm init_cluster post_init ;; join) preflight install_docker install_kubeadm join_cluster $2 ;; reset) reset_cluster ;; esac逻辑说明join分支需要传入控制面生成的 join 命令或 token。reset是给重装和踩坑后恢复用的这也是我后来补上的子命令否则每次失败都要手动清 iptables。脚本的main入口只做参数分发所有实际逻辑仍在独立函数里方便单独调试。参数说明${1:-init}表示不传参数时默认执行 init避免有人拿到脚本后不知所措。如果你需要支持多个 Worker可以在join分支再加一层循环或者让每次执行只加入一台更稳妥的是后者因为 join 命令里的 token 有时效限制。5.2 生成可用的 Worker 加入命令控制面初始化后最安全的做法是直接用kubeadm token create --print-join-command生成 join 命令。它会把 token 和 CA 证书哈希一并输出worker 复制执行即可。这个方法比手工拼接openssl命令更不容易出错也避开了证书文件没有权限读取的问题。join_cluster() { join_cmd$1 if [ -z $join_cmd ]; then log 在控制面节点生成 join 命令 join_cmd$(kubeadm token create --print-join-command) fi # 等待容器运行时端口可用最多等 5 分钟 timeout 300 bash -c until crictl ps /dev/null 21; do sleep 2; done $join_cmd }逻辑说明timeout 300 bash -c ...是为了确保容器运行时 CRI 端口正常返回否则 join 命令会因 kubelet 没起来而失败。$join_cmd里包含完整的kubeadm join和参数直接执行没有问题但脚本必须以 root 运行。如果之前安装的是 containerd 而不是 Dockercrictl ps仍然可用因为 crictl 面向的是 CRI 兼容运行时这个检查天然兼容两种容器运行时。参数说明如果join_cmd为空脚本会自动生成一条新 token 的 join 命令这适合第一次使用脚本、还不清楚控制面地址的情况。生产环境建议把 join 命令保存到单独文件避免 token 暴露在 shell 历史里。5.3 Reset 函数给踩坑的人留后悔药部署脚本必须提供清理能力否则一次 init 失败后整个环境就成了黑匣子。reset 函数的核心是kubeadm reset -f然后再清掉容器运行时创建的 iptables 规则和 CNI 配置。我把它理解成“后悔药”因为每次重装都要手动找还残留在机器上的旧证书和网络策略太浪费精力了。reset_cluster() { if [ -x /usr/bin/kubeadm ]; then kubeadm reset -f fi rm -rf /etc/cni/net.d iptables -F iptables -t nat -F iptables -t mangle -F rm -rf /etc/kubernetes $HOME/.kube log K8s 集群已重置可重新初始化 }逻辑说明kubeadm reset -f会停掉 kubelet 并清掉 etcd 数据。iptables三条命令放在 reset 最后因为清得太早可能导致 reset 过程本身失败。/etc/kubernetes里的证书和配置会干扰重新初始化的集群必须清掉。/root/.kube里的 config 也一并删除避免旧集群信息指向不存在控制面。参数说明这只是单节点重置如果节点已经加入集群还需要在控制面执行kubectl delete node name否则节点信息会残留在 etcd 里。把这些清理命令写进子命令后一键部署脚本才算完整。所谓“一键”不是只往一个方向跑还要能一键回退这样才敢在陌生环境里执行。6. 把脚本升级成参数化安装器并自己验证集群状态我习惯在脚本末尾加一个check_cluster函数用来回答最核心的问题到底成没成所谓一键部署不能只是命令执行完还要能确认结果。检查逻辑不复杂等节点 Ready再创建一个临时 Deployment 验证调度、镜像拉取和 CNI 网络是否真正连通。check_cluster() { kubectl wait --forconditionReady node --all --timeout300s kubectl create deployment smoke-test --imagenginx --replicas2 kubectl rollout status deployment/smoke-test --timeout120s kubectl delete deployment smoke-test }逻辑说明kubectl wait比手动 sleep 30 再get nodes精确得多节点在超时内变为 Ready 就继续。创建 nginx deployment 后等待 rollout如果调度、kubelet、CNI 有问题这一步会暴露出来错误信息比裸get nodes -o wide更直观。跑完自动删除 deployment不给环境留垃圾。参数说明nginx 镜像在测试环境拉取比较快也可以换成 busybox。如果部署在离线环境可以把验证逻辑用环境变量控制比如SKIP_CHECK1 ./deploy.sh init这样脚本就不会因为外部镜像不可用而误报失败。再往后版本变量可以提升为命令行参数。常见做法是用一个简单的 while 循环解析参数while [[ $# -gt 0 ]]; do case $1 in --version) K8S_VERSION$2; shift 2 ;; --pod-cidr) POD_CIDR$2; shift 2 ;; *) echo 未知参数: $1; exit 1 ;; esac done这样同一个脚本不需要修改文件内容就能部署不同版本、不同网段、不同镜像仓库的组合。把镜像仓库和 cgroup 驱动这类每台机器都可能不同的配置前置成变量是我写任何部署脚本的第一步。参数化之后脚本的复用范围和你的变量命名一样广而不是只能在一台机器上一个版本上跑。最后聊个个人习惯我从不把集群最终状态交给想象。无论脚本写得多完整部署之后我都会手动执行一次kubectl get nodes -o wide和kubectl get pods -A确认没有 CrashLoopBackOff 再继续下一步。我也曾因跳过验证带着一个 flannel 镜像配置错误的集群去跑业务结果后面排查了一下午。希望这篇文章里的脚本和这些血泪经验能帮你在自己的环境里少踩几个同样的坑。本文还有配套的精品资源点击获取
返回列表