K8s集群部署实战:从cgroup驱动冲突到网络插件配置的完整避坑指南

K8s集群部署实战:从cgroup驱动冲突到网络插件配置的完整避坑指南 1. 项目概述一次真实的K8s集群部署历险记最近在给团队搭建一套新的K8s测试集群本以为凭着以往的经验能快速搞定结果从环境准备到组件拉起一路磕磕绊绊踩了不少坑。这次部署的目标是一个三节点的生产就绪型集群一个Master节点两个Worker节点操作系统统一用Ubuntu 20.04 LTS。虽然官方文档和网络教程一大堆但真正自己动手才会发现那些教程里轻描淡写的一句话可能就是让你折腾半天的“天坑”。这篇文章不是什么官方指南的复述而是我这次实战部署的完整记录重点会放在那些容易出错、配置复杂、以及网上资料说法不一的地方。如果你也正准备搭建自己的K8s集群无论是用于学习、开发还是测试希望我这些踩坑经验和排查思路能帮你少走弯路特别是关于kubelet和docker的cgroup driver冲突、网络插件选择、证书问题这些高频雷区我会掰开揉碎了讲清楚。2. 集群架构设计与基础环境准备2.1 节点规划与系统配置我采用了最经典的一主两从架构。Master节点hostname: k8s-master承担控制平面的职责运行kube-apiserver、kube-controller-manager、kube-scheduler以及etcd。两个Worker节点hostname: k8s-node1, k8s-node2用来运行实际的工作负载。所有机器配置为2核4GB这对于一个基础测试集群来说足够了。第一坑往往从系统配置开始。很多教程会告诉你关闭swap但未必会解释清楚为什么。Kubernetes设计上假设节点有充足的内存内存不足时它希望通过Pod的驱逐机制来优雅处理而不是被操作系统的swap行为干扰这可能导致调度和性能评估的不确定性。所以这一步必须做# 临时关闭 sudo swapoff -a # 永久关闭注释掉/etc/fstab中swap相关的行 sudo sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab接下来是配置网络和主机名。确保每个节点有固定的内网IP并且在/etc/hosts文件中做好主机名解析这能避免后续证书和节点发现时出现奇怪的域名错误。然后需要加载内核模块并修改sysctl参数这是为网络插件比如Calico、Flannel准备的它们需要内核支持网络桥接和流量转发。# 加载模块 sudo modprobe br_netfilter sudo modprobe overlay # 使sysctl配置生效 cat EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter overlay EOF cat EOF | sudo tee /etc/sysctl.d/k99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --system这里有个细节sysctl --system会重新加载所有配置文件比单独用-p参数更彻底能避免因为加载顺序导致某些配置不生效。2.2 容器运行时安装与关键配置Docker的“陷阱”我选择了Docker作为容器运行时因为它最成熟生态工具也多。安装过程本身很简单但配置才是重头戏直接关系到后面kubelet能否正常启动。首先需要配置Docker使用systemd作为cgroup driver。这是Kubernetes官方推荐的做法因为kubelet默认也使用systemd作为cgroup driver。如果两者不匹配kubelet就会启动失败并报出“detected cgroupfs as cgroup driver”之类的错误。很多旧教程或默认安装的Docker可能使用的是cgroupfs所以我们必须显式配置。# 创建或修改Docker的daemon配置文件 sudo cat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m }, storage-driver: overlay2, registry-mirrors: [https://your-mirror.mirror.aliyuncs.com] # 建议替换为国内镜像加速地址 } EOF配置完成后重启Docker并设置开机自启sudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl enable docker。注意/etc/docker/daemon.json这个文件如果格式有误比如多了个逗号会导致Docker服务完全无法启动。每次修改后最好用sudo docker info | grep -i cgroup命令验证一下Cgroup Driver是否已经变成了systemd。这是我踩的第一个坑因为一个多余的逗号排查了半小时。3. Kubernetes组件安装与初始化3.1 安装kubeadm, kubelet和kubectl这三个组件是搭建集群的核心工具。kubeadm是官方的集群引导工具kubelet是运行在每个节点上的代理负责管理Pod和容器kubectl是命令行工具用于管理集群。由于网络原因直接从Google的仓库安装可能会很慢甚至失败。通常我们会使用国内镜像源比如阿里云的。但这里又有一个小坑不仅要替换软件源有时连镜像仓库地址也需要替换。以下是标准的安装步骤# 添加阿里云Kubernetes软件源 sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - echo deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main | sudo tee /etc/apt/sources.list.d/kubernetes.list # 安装指定版本建议指定版本避免最新版带来未知问题 sudo apt-get update sudo apt-get install -y kubelet1.23.0-00 kubeadm1.23.0-00 kubectl1.23.0-00 # 锁定版本防止意外升级 sudo apt-mark hold kubelet kubeadm kubectl我选择了1.23.0这个相对稳定的版本。安装后kubelet服务会处于failed状态这是正常的因为它需要等集群初始化完成后由kubeadm来配置它。3.2 使用kubeadm初始化Master节点这是最激动人心也最容易出错的一步。kubeadm init命令会拉取镜像、生成证书、启动控制平面组件。命令的参数配置至关重要。sudo kubeadm init \ --apiserver-advertise-address192.168.1.100 \ # Master节点的IP --image-repository registry.aliyuncs.com/google_containers \ # 使用国内镜像仓库 --kubernetes-version v1.23.0 \ --service-cidr10.96.0.0/12 \ --pod-network-cidr192.168.0.0/16 \ # 需要与后续安装的网络插件匹配 --ignore-preflight-errorsSwap # 如果确认swap已关闭可以不加这个参数这里有几个关键点--image-repository这是加速成功的关键。默认的k8s.gcr.io在国内基本无法访问必须替换为国内镜像源如阿里云或中科大源。--pod-network-cidr这个网段是给Pod分配IP用的必须与你将要安装的网络插件CNI的默认网段一致否则网络插件会无法工作。比如Calico的默认CIDR就是192.168.0.0/16如果你这里填了10.244.0.0/16后面Calico就会报错。--ignore-preflight-errors预检错误。如果系统检查出你有swap未关闭等问题初始化会失败。在测试环境如果你确认影响不大可以用这个参数忽略。但在生产环境建议解决所有预检错误。初始化成功后控制台会输出几行非常重要的信息务必完整保存下来其中包括如何使用kubectl的配置命令mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config用于将Worker节点加入集群的kubeadm join命令令牌。实操心得初始化过程可能会因为镜像拉取失败而卡住。一个有效的排查方法是在另一个终端用sudo docker images查看正在拉取的镜像或者用sudo kubeadm config images list和sudo kubeadm config images pull预先拉取镜像。如果拉取失败可以手动从国内仓库docker pull下来然后重新打标签。4. 网络插件部署与节点加入4.1 安装Calico网络插件一个“裸”的Kubernetes集群是没有网络能力的Pod之间无法通信。必须安装一个CNI容器网络接口插件。我选择了Calico因为它性能不错支持网络策略文档也全。根据初始化时指定的--pod-network-cidr192.168.0.0/16我们安装对应的Calico manifests文件。# 下载Calico的部署清单 curl https://docs.projectcalico.org/manifests/calico.yaml -O # 如果你的pod网段不是192.168.0.0/16需要修改calico.yaml中的CALICO_IPV4POOL_CIDR # 使用sed命令修改或者直接用编辑器打开yaml文件 # sed -i s|192.168.0.0/16|你的pod-cidr|g calico.yaml # 部署Calico kubectl apply -f calico.yaml部署后使用kubectl get pods -n kube-system命令查看等待所有Pod的状态都变为Running。特别是calico-node-xxx这个DaemonSet它需要在每个节点上都运行起来。如果Pod一直处于Pending或CrashLoopBackOff状态通常有几个原因节点资源不足检查内存和CPU。镜像拉取失败Calico的镜像可能在国外需要配置Docker镜像加速或提前拉取。网络配置冲突检查pod-network-cidr是否与主机网络或Service网段重叠。4.2 将Worker节点加入集群在Master节点初始化成功并保存好join命令后到每个Worker节点上以root或sudo权限执行那条命令。命令格式类似sudo kubeadm join 192.168.1.100:6443 --token token \ --discovery-token-ca-cert-hash sha256:hash执行后在Master节点上运行kubectl get nodes应该能看到新加入的节点。但状态可能先是NotReady等Calico网络插件在该节点上的Pod启动完成后才会变成Ready。这里有一个巨坑kubeadm join命令中的token默认24小时有效。如果过期了就需要在Master节点上重新生成# 生成新的token kubeadm token create # 获取discovery-token-ca-cert-hash openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2/dev/null | openssl dgst -sha256 -hex | sed s/^.* //然后将新的token和hash组合成新的join命令。更稳妥的做法是在初始化Master后直接创建一个永不过期的token虽然不推荐用于生产环境kubeadm token create --ttl 0。5. 核心问题排查与修复实录5.1 kubelet与Docker的cgroup driver不匹配这是最经典的问题。症状是Worker节点加入集群后在Master上执行kubectl get nodes该节点状态一直是NotReady。用journalctl -u kubelet -f查看该节点的kubelet日志会发现大量类似以下的错误failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs解决方案就是确保两者一致。按照我们之前的做法已经将Docker的cgroup driver配置为systemd。如果问题依旧请按以下步骤排查在Worker节点上确认Docker的配置已生效sudo docker info | grep -i cgroup。修改Kubernetes的kubelet配置指定cgroup driver。编辑/etc/default/kubelet文件如果没有就创建添加一行KUBELET_EXTRA_ARGS--cgroup-driversystemd。重启服务sudo systemctl daemon-reload sudo systemctl restart kubelet。排查技巧不要只看错误信息的第一行。journalctl -u kubelet --no-pager -r可以倒序查看完整日志往往真正的根因藏在后面。另外systemctl status kubelet -l命令也能给出清晰的服务状态和最后几条日志。5.2 网络插件故障导致Pod无法通信现象节点状态Ready但自己部署的Pod无法启动或无法跨节点通信。kubectl describe pod pod-name查看Pod事件可能会看到NetworkPluginNotReady或FailedCreatePodSandBox等错误。排查思路检查Calico Pod状态kubectl get pods -n kube-system -l k8s-appcalico-node。如果有Pod不是Running用kubectl describe pod -n kube-system calico-pod-name和kubectl logs -n kube-system calico-pod-name查看详情。检查节点网络接口在出问题的节点上执行ip addr show应该能看到一个名为calixxx或tunl0的接口。如果没有说明Calico的node组件没在该节点成功运行。检查路由在节点上执行route -n或ip route。正常情况下到其他节点Pod网段如192.168.1.0/24的路由应该指向对端节点的IP。如果路由缺失或错误Pod网络就不通。常见原因镜像拉取失败手动拉取并重试。内核模块缺失确保ip_tables、iptable_nat、ip6_tables等模块已加载。网络策略或防火墙检查主机防火墙如ufw, firewalld是否屏蔽了Calico需要的端口如TCP的179、5473UDP的4789等。在测试环境可以先暂时关闭防火墙sudo ufw disable。5.3 证书过期或配置错误Kubernetes集群严重依赖TLS证书进行组件间通信。证书问题通常表现为kubectl命令执行失败或者kubelet无法连接apiserver。典型症状kubectl get nodes报错The connection to the server x.x.x.x:6443 was refused或certificate has expired or is not yet valid。排查与解决检查证书有效期在Master节点上sudo kubeadm certs check-expiration可以列出所有证书的过期时间。Kubernetes的证书默认有效期是一年。更新证书如果证书即将过期或已过期可以使用sudo kubeadm certs renew all来更新所有证书然后重启控制平面组件sudo systemctl restart kubelet。检查kubeconfig文件确保$HOME/.kube/config文件中的server地址指向正确的apiserverIP和端口并且证书数据正确。有时从/etc/kubernetes/admin.conf复制配置时可能出错。Worker节点证书问题如果Worker节点突然失联可能是其/etc/kubernetes/kubelet.conf文件中的客户端证书过期。需要从Master节点重新生成join令牌并在Worker节点上执行kubeadm reset后重新加入集群注意这会清理该节点上所有Pod。6. 集群稳定性调优与日常维护建议集群跑起来只是第一步要稳定运行还需要一些调优和维护。6.1 关键组件配置调优kubelet 内存与垃圾回收默认配置下kubelet可能不会及时清理已退出的容器镜像导致磁盘空间被占满。可以编辑/var/lib/kubelet/config.yaml或通过kubelet的启动参数调整apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration ... imageGCHighThresholdPercent: 85 # 当磁盘使用率达到85%时开始垃圾回收 imageGCLowThresholdPercent: 80 # 垃圾回收直到使用率降到80%修改后需要重启kubeletsudo systemctl restart kubelet。Docker日志限制如果不加限制容器日志可能撑爆磁盘。我们之前在/etc/docker/daemon.json中配置的max-size就是做这个的。还可以通过Kubernetes的Pod Spec设置日志轮转策略。6.2 日常监控与命令速查部署完成后建立一些基本的监控和检查习惯核心组件状态kubectl get componentstatuses(kubectl get cs) 查看控制平面组件健康状态。节点资源kubectl describe nodes查看每个节点的资源分配和使用情况关注Conditions部分是否有内存压力、磁盘压力等警告。Pod状态kubectl get pods --all-namespaces -o wide查看所有Pod的运行状态和所在节点。事件查看kubectl get events --sort-by.lastTimestamp -w可以实时查看集群事件帮助快速定位问题。6.3 备份与恢复关键配置对于生产环境备份以下内容至关重要/etc/kubernetes/包含所有证书和静态Pod的manifest文件。/var/lib/etcd/如果etcd是单独部署的备份其数据目录。集群应用配置使用kubectl get all --all-namespaces -o yaml cluster-backup.yaml可以导出一个粗略的备份但对于有状态应用这远远不够需要结合Velero等专业工具。最后关于学习资源官方文档kubernetes.io永远是最新最权威的但遇到具体错误时在GitHub的Issues里搜索错误信息往往能找到更直接的解决方案。这次部署让我深刻体会到搭建K8s集群就像拼装一台精密仪器每个步骤都必须到位任何一个螺丝没拧紧都可能让整个系统运行异常。耐心查看日志理解每个组件的作用和它们之间的协作关系是解决问题的根本。