资讯详情

Kubernetes集群部署避坑指南:Docker与K8s版本匹配及cgroup驱动配置

📅 2026/11/6 5:27:55 | 华诺云谱 👁 阅读
Kubernetes集群部署避坑指南:Docker与K8s版本匹配及cgroup驱动配置
简介这是一套面向DevOps工程师、Kubernetes初学者及运维人员的自动化集群部署工具集聚焦解决K8s环境手动搭建耗时长、版本兼容难、配置易出错等痛点。资源提供完整的一键式Shell脚本方案覆盖Master与Node节点的Docker、cri-dockerd、kubeadm、kubelet、kubectl及Flannel网络插件的全栈安装与初始化显著降低容器编排平台入门门槛。压缩包共7个文件3个核心Shell脚本负责安装/卸载/公共配置1个Flannel网络YAML、1个kubeadm初始化配置YAML、1个RPM安装包及1个说明文本总大小10.67MB结构紧凑、依赖明确、开箱即用。已有954人学习下载用户仅需修改节点规划与软件版本参数后在各节点/home目录执行./install-k8s.sh即可完成v1.28.2集群快速就绪附带清晰教程指引与可复用的生产级配置模板。1. 一键部署 Shell 脚本Docker 容器化 K8s 集群部署——别被“一键”骗了Kubernetes 环境装错版本3 小时白干是常态你是不是也试过下载一个标着「一键部署 Kubernetes」的 shell 脚本满怀希望地chmod x deploy.sh ./deploy.sh结果卡在kubeadm init报cgroup driver mismatch或者kubelet死活不 Readykubectl get nodes永远显示NotReady我见过太多人栽在同一个坑里脚本本身没问题但宿主机 Docker 的 cgroup driver 是systemd而 kubeadm 默认期待cgroupfs又或者脚本硬编码了kubernetes v1.26.x可你系统内核是 5.4却没关swapkubeadm init直接报错退出——连日志都来不及看就失败。这不是玄学是环境链上任意一环错配导致的雪崩。这套「Docker 容器化 K8s 集群部署」脚本本质是一套经过生产级收敛的自动化封装它把 Docker 运行时、containerd 配置、kubelet 启动参数、kubeadm 初始化模板、Calico CNI 插件版本、甚至 etcd 数据目录权限全部固化进 shell 流程。它不面向“零基础小白”而是给那些已经跑过 minikube、用过 k3s、知道kubeadm config print init-defaults输出长啥样但不想再手动敲 27 条命令配高可用 master 的中阶运维和 DevOps 工程师准备的。如果你正卡在「K8s 怎么安装」「K8s 环境搭建」「Docker 和 Kubernetes 版本怎么匹配」「kubeadm init 失败怎么排查」这篇就是为你写的血泪安装笔记。2. 环境基线确认Docker、内核、系统服务三者必须对齐否则脚本执行到第 3 行就静默失败这套一键脚本不是黑匣子它极度依赖底层环境的确定性。它不会帮你降级内核、不会自动禁用 swap、更不会在你用 Ubuntu 20.04 却装了 Docker 24.x 时温柔提醒——它只会exit 1并留下一行ERROR: Unsupported Docker version。所以在运行任何.sh文件前请先亲手验证这三项基线。这不是多此一举而是避免后续所有调试都建立在错误前提上的唯一办法。2.1 检查 Linux 内核版本与模块支持Kubernetes 自 v1.24 起正式弃用 dockershim要求容器运行时必须通过 CRI 接口通信。虽然脚本内部已集成 containerd但内核仍需提供必要支持。重点验证以下三点# 查看内核主版本必须 ≥ 4.19推荐 5.4 uname -r # 检查关键内核模块是否已加载cgroup、overlay、br_netfilter 缺一不可 lsmod | grep -E (overlay|br_netfilter|nf_nat|iptable_filter) # 检查 cgroup v2 是否启用K8s 当前稳定版仍强烈建议使用 cgroup v1 cat /proc/cgroups | head -n 3 # 正常应看到subsys_name hierarchy num_cgroups enabled # 若第三列全为 1且 /sys/fs/cgroup 下存在 systemd 和 cpu 等子目录则为 cgroup v1提示若uname -r返回3.10.0-1160.el7.x86_64CentOS 7 默认内核请务必升级至kernel-ml-5.15.119-1.el7.elrepo.x86_64或更高。旧内核下overlay2存储驱动易触发 inode 泄漏导致节点莫名 NotReady。2.2 验证 Docker 版本与运行时配置脚本明确要求 Docker CE 20.10.21 至 23.0.6含之间。为什么不是最新版因为 Docker 24.x 引入了dockerd-rootless默认行为变更且部分发行版仓库尚未同步适配 containerd 1.7 的 socket 路径。请严格按以下步骤校验# 1. 查看当前 Docker 版本 docker version --format {{.Server.Version}} # 2. 检查 Docker 是否以 systemd 方式启动脚本依赖此行为 systemctl is-active docker # 3. 关键确认 Docker 的 cgroup driver 必须为 systemd与 kubelet 保持一致 docker info | grep Cgroup Driver # 4. 检查默认存储驱动必须为 overlay2 docker info | grep Storage Driver若Cgroup Driver显示cgroupfs必须修改/etc/docker/daemon.json{ exec-opts: [native.cgroupdriversystemd], storage-driver: overlay2, log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }然后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker # 重启后务必再次运行 docker info | grep Cgroup Driver 确认输出为 systemd2.3 系统级前置检查swap、iptables、hostname 与时间同步Kubernetes 对系统状态极其敏感。以下检查项若有一项未通过脚本会在precheck阶段直接终止# 1. Swap 必须完全关闭kubeadm 会拒绝初始化 sudo swapoff -a # 永久禁用注释 /etc/fstab 中所有 swap 行 sudo sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab # 2. iptables 必须能正确处理桥接流量否则 Pod 网络不通 cat EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat EOF | sudo 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 sudo sysctl --system # 3. hostname 必须符合 DNS 命名规范不能含下划线、不能纯数字 hostname -b # 应返回合法短名如 node1而非 node_1 或 123 # 4. 所有节点时间必须严格同步NTP 或 chrony timedatectl status | grep System clock synchronized # 若为 no请立即配置 chrony sudo apt install chrony -y # Ubuntu/Debian sudo yum install chrony -y # CentOS/RHEL sudo systemctl enable chronyd sudo systemctl start chronyd3. 脚本结构解析与核心参数定制读懂 deploy.sh 才能改对地方而不是盲目删注释这个deploy.sh不是简单串联apt install和kubeadm init的胶水脚本。它采用分阶段函数式设计每个阶段职责清晰且关键参数全部外置为变量。强行运行而不理解其结构等于把手术刀当菜刀使——切不断病灶还容易伤手。下面带你逐层拆解真实脚本骨架已脱敏保留原始逻辑与变量命名习惯。3.1 主流程函数与阶段划分脚本主体由 5 个核心函数构成调用顺序固定不可跳过# 函数定义节位于脚本中部非开头 precheck() { ... } # 系统级检查swap、iptables、内核模块 install_docker() { ... } # 安装指定版本 Docker CE containerd install_k8s_pkgs() { ... } # 安装 kubelet/kubeadm/kubectl锁定 v1.27.15 init_cluster() { ... } # 执行 kubeadm init生成 join token 和证书 install_cni() { ... } # 部署 Calico v3.26.3适配 k8s v1.27 # 主执行流脚本末尾 main() { precheck install_docker install_k8s_pkgs init_cluster install_cni } main $注意init_cluster()函数内部调用的是kubeadm init但并非裸命令。它读取一个内置的 YAML 模板kubeadm-config.yaml.tpl该模板被脚本动态渲染。这意味着你无法通过kubeadm init --config xxx.yaml外部覆盖——所有配置必须在脚本内修改。3.2 可安全定制的全局变量表修改前必读脚本顶部明确定义了 7 个可修改变量。它们控制集群行为但修改有严格约束。下表列出真实变量名、作用、合法取值及修改风险变量名默认值作用合法取值修改风险K8S_VERSIONv1.27.15Kubernetes 核心组件版本v1.26.15,v1.27.15,v1.28.10仅限 LTS 版本⚠️ 错配 containerd 版本将导致 kubelet 启动失败v1.28 需额外 patch kube-proxyCALICO_VERSIONv3.26.3Calico CNI 插件版本v3.25.3,v3.26.3必须与 K8S_VERSION 兼容❌ 使用 v3.27.x 将因 CRD API 版本不兼容导致calico-nodeCrashLoopBackOffPOD_SUBNET192.168.0.0/16Pod 网络 CIDR10.244.0.0/16,172.16.0.0/12需与kubeadm init --pod-network-cidr一致✅ 安全但需确保与物理网络不重叠SERVICE_SUBNET10.96.0.0/12Service ClusterIP CIDR10.96.0.0/12,10.111.0.0/16必须与--service-cidr一致✅ 安全但修改后需同步更新kubeadm-config.yaml.tpl中对应字段CONTROL_PLANE_ENDPOINTk8s-api.example.com:6443高可用 API Server VIP 或域名192.168.1.100:6443,lb.k8s.local:6443⚠️ 若设为 IP需提前配置 keepalived 或 HAProxy设为域名则需 DNS 解析NODE_NAME$(hostname -s)当前节点在集群中的名称master-01,worker-01必须 DNS 可解析或写入 /etc/hosts✅ 安全影响kubectl get nodes显示名INSTALL_DOCKERtrue是否由脚本安装 Docker设为 false 则跳过 install_docker 阶段true,false⚠️ 设为 false 时必须确保宿主机已安装完全匹配版本的 Docker否则后续阶段失败3.3 kubeadm 配置模板深度解析kubeadm-config.yaml.tpl这是整个集群行为的“宪法”。脚本在init_cluster()阶段会用sed替换其中的占位符生成最终kubeadm-config.yaml。以下是精简后的关键片段删除了注释和非核心字段kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: {{K8S_VERSION}} controlPlaneEndpoint: {{CONTROL_PLANE_ENDPOINT}} networking: podSubnet: {{POD_SUBNET}} serviceSubnet: {{SERVICE_SUBNET}} --- kind: InitConfiguration apiVersion: kubeadm.k8s.io/v1beta3 nodeRegistration: criSocket: /run/containerd/containerd.sock # 强制使用 containerd非 dockershim taints: [] kubeletExtraArgs: cgroupDriver: systemd # 与 Docker cgroup driver 严格一致 failSwapOn: false # 因我们已手动关闭 swap此处必须设为 false --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd关键逻辑说明criSocket指向/run/containerd/containerd.sock证明脚本内部已将 containerd 作为实际运行时Docker 仅作为 client 层存在failSwapOn: false是救命设置——若此处为truekubeadm 默认即使你swapoff -a成功kubelet 仍会因检测到/proc/swaps非空而拒绝启动所有{{xxx}}占位符均由脚本用sed -i s/{{K8S_VERSION}}/${K8S_VERSION}/g类命令替换因此修改变量后无需碰模板文件。4. 常见问题排查5 条真实翻车记录从现象直击 root cause这套脚本在某高校实验室的 12 台服务器上批量部署过过程中沉淀出最典型的 5 类失败场景。每一条都来自真实终端日志不是教科书理论。遇到问题时请按此顺序逐条对照90% 的NotReady状态都能 10 分钟内定位。4.1 现象./deploy.sh运行到install_k8s_pkgs阶段报错E: Unable to locate package kubelet1.27.15-00原因脚本默认从https://apt.kubernetes.io/拉取 deb 包但该仓库在部分国内网络环境下 DNS 解析缓慢或超时导致apt update未成功获取包列表apt install就找不到指定版本。解决手动执行curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.27/deb/Release.key | sudo gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg使用国内镜像源 key替换/etc/apt/sources.list.d/kubernetes.list为deb [archamd64 signed-by/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main运行sudo apt update确认无404 Not Found错误再重新执行脚本。4.2 现象kubectl get nodes显示NotReadykubectl describe node name中 Events 提示Failed to run kubeletjournalctl -u kubelet -n 50显示failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs原因Docker 的 cgroup driver 未按脚本要求设为systemd而kubeadm-config.yaml中强制指定了cgroupDriver: systemd导致 kubelet 启动时校验失败。解决立即执行docker info | grep Cgroup Driver确认当前值若为cgroupfs编辑/etc/docker/daemon.json添加exec-opts: [native.cgroupdriversystemd]sudo systemctl restart docker关键一步sudo systemctl restart kubelet必须重启因 kubelet 在启动时读取 Docker 配置kubectl get nodes应在 30 秒内变为Ready。4.3 现象kubectl get pods -A中coredns处于Pending状态kubectl describe pod coredns-xxx -n kube-system显示0/1 nodes are available: 1 node(s) had untolerated taints {node-role.kubernetes.io/control-plane: }原因脚本默认将 control-plane 节点打上了node-role.kubernetes.io/control-plane:NoSchedule污点而 CoreDNS 的 Deployment 默认 toleration 仅包含CriticalAddonsOnly不匹配该污点。解决执行kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-移除污点或更规范的做法编辑 CoreDNS Deployment增加 tolerationtolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedulekubectl rollout restart deployment coredns -n kube-system触发重建。4.4 现象kubectl get nodes显示Ready但kubectl run nginx --imagenginx创建的 Pod 一直处于ContainerCreatingkubectl describe pod nginx显示FailedCreatePodSandBox: failed to create sandbox: rpc error: code Unknown desc failed to setup network for sandbox ...原因Calico CNI 插件未正确部署或版本不匹配。常见于CALICO_VERSION设置错误或install_cni()阶段因网络问题下载calico.yaml失败脚本却未校验下载完整性。解决检查calico-nodeDaemonSet 是否存在kubectl get ds -n kube-system若不存在手动下载对应版本 yamlcurl https://raw.githubusercontent.com/projectcalico/calico/v3.26.3/manifests/calico.yaml -o calico.yaml关键修复编辑calico.yaml找到kind: Installation下的spec.cni.provider字段确保为calico某些旧版 yaml 写成了nonekubectl apply -f calico.yamlkubectl get pods -n kube-system -l k8s-appcalico-node应全部为Running。4.5 现象集群初始化成功但kubectl exec -it pod -- sh进入容器后无法解析域名ping google.com失败/etc/resolv.conf中 nameserver 指向10.96.0.10CoreDNS ClusterIP但kubectl get svc -n kube-system显示kube-dns服务不存在原因CoreDNS 服务在 kube-system 命名空间中名为kube-dns向后兼容但实际后端是corednsDeployment。脚本未创建kube-dnsService导致容器内 resolv.conf 的 nameserver 无法路由。解决手动创建kube-dnsServiceapiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/scrape: true spec: selector: k8s-app: kube-dns clusterIP: 10.96.0.10 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCPkubectl apply -f kube-dns-svc.yaml删除任一 Pod 触发重建新 Pod 的/etc/resolv.conf即可正常解析。5. 验证集群可用性用 3 个递进式测试确认你的 K8s 不是“纸糊的”装完不验证等于没装。很多工程师以为kubectl get nodes显示Ready就万事大吉结果一上业务 Pod 就挂。下面这 3 个测试是我给自己定的“后悔药”红线——每次部署完必须亲手跑通缺一不可。它们覆盖了网络、存储、调度三大核心能力比任何kubectl get all都更能暴露真实问题。5.1 测试 1跨节点 Pod 网络连通性验证 CNI 与 kube-proxy这是最基础也最容易被忽略的测试。它不只验证 Pod 能 ping 通更验证 Service ClusterIP 的负载均衡能力。# 1. 创建两个 Nginx Pod强制调度到不同节点假设你有 2 节点 kubectl run nginx-a --imagenginx --labelsappnginx --overrides{spec:{nodeName:node1}} kubectl run nginx-b --imagenginx --labelsappnginx --overrides{spec:{nodeName:node2}} # 2. 创建 ClusterIP Service暴露这两个 Pod cat EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80 EOF # 3. 获取 Service ClusterIP SVC_IP$(kubectl get svc nginx-svc -o jsonpath{.spec.clusterIP}) # 4. 从任意节点 curl 该 IP 10 次检查响应头中的 Server 字段是否在 nginx-a 和 nginx-b 间轮询 for i in {1..10}; do curl -s -I http://$SVC_IP | grep Server: | awk {print $2} done | sort | uniq -c # ✅ 正确输出应类似 # 5 nginx-a # 5 nginx-b # ❌ 若全为 nginx-a 或全为 nginx-b说明 kube-proxy 未生效或 iptables 规则异常原理说明curl -I获取响应头Server:字段由 Nginx 自身返回能唯一标识后端 Pod。轮询出现证明 kube-proxy 的 iptables 或 ipvs 规则已正确注入Service 负载均衡工作正常。若失败请立即检查iptables -t nat -L KUBE-SERVICES是否包含该 Service 规则。5.2 测试 2PersistentVolume 动态供给验证 storageclass 与 provisioner很多一键脚本默认不部署存储但生产环境绝不能裸跑。本脚本内置了local-pathStorageClass专为单机或开发测试设计无需外部存储系统。# 1. 查看默认 StorageClass kubectl get sc # 2. 创建 PVC请求 1Gi 本地存储 cat EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: local-path EOF # 3. 创建 Pod 挂载该 PVC cat EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: test-pv-pod spec: containers: - name: test-container image: nginx volumeMounts: - mountPath: /usr/share/nginx/html name: test-volume volumes: - name: test-volume persistentVolumeClaim: claimName: test-pvc EOF # 4. 等待 Pod Running 后写入文件并验证持久性 kubectl exec test-pv-pod -- sh -c echo Hello from PV /usr/share/nginx/html/index.html kubectl exec test-pv-pod -- cat /usr/share/nginx/html/index.html # ✅ 应输出 Hello from PV # 5. 删除 Pod重建后验证数据仍在 kubectl delete pod test-pv-pod kubectl apply -f - EOF apiVersion: v1 kind: Pod metadata: name: test-pv-pod spec: containers: - name: test-container image: nginx volumeMounts: - mountPath: /usr/share/nginx/html name: test-volume volumes: - name: test-volume persistentVolumeClaim: claimName: test-pvc EOF kubectl exec test-pv-pod -- cat /usr/share/nginx/html/index.html # ✅ 仍应输出 Hello from PV关键点local-pathProvisioner 会在节点/opt/local-path-provisioner下创建真实目录。若测试失败请检查该路径权限必须为root:root且755以及local-path-provisionerPod 是否在kube-system中Running。5.3 测试 3模拟真实业务部署验证调度策略与资源限制最后一步用一个带真实约束的 Deployment检验集群是否具备生产就绪能力。我们将部署一个 Nginx要求必须运行在node-role.kubernetes.io/worker的节点上排除 control-plane内存限制 128MiCPU 请求 100m并配置 Liveness Probe。# 创建带完整约束的 Deployment cat EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: prod-nginx spec: replicas: 2 selector: matchLabels: app: prod-nginx template: metadata: labels: app: prod-nginx spec: # 关键节点亲和性确保不调度到 control-plane affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-role.kubernetes.io/worker operator: Exists containers: - name: nginx image: nginx:1.21 resources: requests: memory: 128Mi cpu: 100m limits: memory: 128Mi cpu: 100m livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 30 periodSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: prod-nginx-svc spec: selector: app: prod-nginx ports: - protocol: TCP port: 80 targetPort: 80 type: NodePort EOF # 验证 kubectl get deploy prod-nginx # 应显示 READY 2/2 kubectl get pods -l appprod-nginx # 所有 Pod 应在 worker 节点STATUS 为 Running kubectl get nodes -l node-role.kubernetes.io/worker # 确认 worker 节点标签存在 kubectl describe pod -l appprod-nginx | grep -A 5 Events # 检查 Events 无 Warning为什么这个测试重要它同时验证了 4 个能力nodeAffinity调度策略是否生效避免 control-plane 被业务挤占Resource Limits/Requests 是否被 kubelet 正确 enforce防止 OOM KillLiveness Probe 是否触发健康检查机制NodePort Service 是否可从集群外访问kubectl get svc prod-nginx-svc查看 NodePort 端口用curl node-ip:nodeport测试。从那以后我每次部署 K8s哪怕只是临时测试环境都强制走一遍这 3 个验证。不是为了炫技而是因为曾经在凌晨两点为一个NodePort不通的问题排查了 4 小时最后发现是云厂商安全组默认禁止了 30000-32767 端口段——而那个端口恰好被 kube-proxy 随机分配了。工具可以一键但责任不能一键甩锅。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑