ARTICLE DETAIL

资讯详情

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

Kubernetes集群部署实战:从环境准备到网络配置的完整踩坑指南

Kubernetes集群部署实战:从环境准备到网络配置的完整踩坑指南 1. 项目概述与核心痛点最近在给团队搭建一套新的KubernetesK8s测试集群从零开始走了一遍部署流程。虽然官方文档和各种教程满天飞但真到自己动手从环境准备到组件拉起再到集群可用每一步都可能遇到意想不到的“坑”。这次部署的目标是一个三节点的生产级测试集群1 Master 2 Worker使用containerd作为容器运行时Calico作为网络插件。整个过程下来最大的感触就是K8s部署的成功往往不在于你记住了多少命令而在于你是否理解每个命令背后的原理以及当命令失败时你能否快速定位到那个“捣鬼”的配置项。这篇文章我就把这次部署中遇到的关键问题、排查思路和最终解决方案以“踩坑记录”的形式整理出来希望能帮你绕过我走过的弯路。对于刚接触K8s的朋友来说部署集群是第一个“下马威”。它不像安装一个单机软件那么简单涉及到操作系统配置、容器运行时、网络、存储等多个层面的协调。常见的痛点集中在几个方面系统参数配置不当如关闭swap、配置内核模块、容器运行时与kubelet的cgroup驱动不一致导致节点无法注册、网络插件安装后Pod之间无法通信、以及证书相关的问题导致组件间认证失败。这些问题往往相互关联一个环节出错表象可能出现在另一个完全不同的地方排查起来非常考验对K8s架构的理解。2. 环境准备与前置条件梳理部署K8s集群第一步不是急着运行kubeadm init而是要把基础环境打磨好。这就像盖房子前要打好地基地基不稳后面砌再高的墙也容易塌。2.1 操作系统与内核要求我们选用的是Ubuntu 22.04 LTS。选择稳定的、K8s社区支持良好的Linux发行版至关重要。首先必须确保所有节点Master和Worker的系统主机名、/etc/hosts文件配置正确且能够互相解析。我遇到过因为主机名带下划线而导致kubelet启动失败的问题所以最好使用标准的DNS命名规则仅包含字母、数字、连字符和点。接下来是内核参数的调整。K8s对Linux内核有一些硬性要求比如必须关闭swap。这不仅仅是为了性能更是因为kubelet的默认行为假设节点有充足的非交换内存。关闭swap可以通过sudo swapoff -a临时生效并编辑/etc/fstab永久注释掉swap分区行。此外还需要加载一些必需的内核模块如br_netfilter、ip_vs等并配置sysctl参数以启用IP转发和桥接流量。# 加载内核模块 sudo modprobe br_netfilter sudo modprobe ip_vs sudo modprobe ip_vs_rr sudo modprobe ip_vs_wrr sudo modprobe ip_vs_sh sudo modprobe nf_conntrack # 配置sysctl参数 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注意sysctl --system会重新加载所有配置文件确保上述配置生效。如果遇到bridge-nf-call参数报“文件不存在”的错误通常是因为br_netfilter模块没有成功加载请先用lsmod | grep br_netfilter检查。2.2 容器运行时选型与安装Containerd实战K8s早已不再绑定Docker事实上从1.24版本开始Dockershim已被移除你需要直接选择一种容器运行时接口CRI兼容的运行时。主流的选项是containerd和CRI-O。我们选择containerd因为它性能优秀、资源占用少且是Docker的底层运行时生态成熟。安装containerd推荐使用官方二进制包或从发行版仓库安装。这里以官方二进制为例。下载解压后需要生成默认配置文件/etc/containerd/config.toml。这里藏着第一个大坑cgroup驱动。# 安装containerd wget https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-amd64.tar.gz sudo tar Cxzvf /usr/local containerd-1.7.13-linux-amd64.tar.gz sudo systemctl enable --now containerd # 生成默认配置 sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml生成配置后关键一步是修改cgroup驱动。K8s的kubelet默认使用systemd作为cgroup驱动而containerd默认生成的配置中SystemdCgroup参数是false。如果不一致kubelet在启动时会报错节点状态一直是NotReady。# 编辑 /etc/containerd/config.toml [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] ... [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true # 将这里改为 true修改后重启containerdsudo systemctl restart containerd。务必使用sudo systemctl status containerd检查服务状态确保没有错误日志。2.3 K8s组件安装与版本锁定安装kubeadm, kubelet和kubectl。建议使用阿里云或谷歌的镜像源来加速下载。这里又一个细节在安装kubelet后不要立即启动它。因为此时集群还未初始化kubelet会不断尝试连接一个不存在的API Server产生大量错误日志。# 添加阿里云镜像源 sudo apt-get update sudo apt-get install -y apt-transport-https curl curl 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.28.0-00 kubeadm1.28.0-00 kubectl1.28.0-00 sudo apt-mark hold kubelet kubeadm kubectl # 锁定版本防止意外升级apt-mark hold命令非常重要它能防止系统自动更新时升级K8s组件避免因版本不一致导致集群故障。在所有节点上重复上述环境准备步骤确保配置完全一致。3. 集群初始化与首个“拦路虎”基础环境就绪后就可以在Master节点上执行初始化命令了。这是最激动人心也最容易出错的一步。3.1 kubeadm init 命令参数解析不要直接运行sudo kubeadm init建议使用配置文件来初始化这样参数清晰、可重复。创建一个kubeadm-config.yaml文件apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.100 # 替换为Master节点的实际IP bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent taints: [] --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.0 controlPlaneEndpoint: 192.168.1.100:6443 # 高可用时可配置负载均衡器VIP networking: podSubnet: 192.168.0.0/16 # 必须与后续安装的CNI插件如Calico的默认网段匹配 serviceSubnet: 10.96.0.0/12 apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s imageRepository: registry.aliyuncs.com/google_containers # 使用国内镜像源加速 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd # 明确指定kubelet使用systemd cgroup驱动重点关注几个参数advertiseAddressMaster节点的IP其他节点将通过这个地址连接API Server。podSubnet这是Pod的IP地址范围。这个值必须与你将要安装的网络插件CNI的默认网段一致。例如Calico的默认IPPool是192.168.0.0/16。如果不一致网络插件将无法正确分配IP导致Pod无法启动。imageRepository墙内必备。指向阿里云镜像仓库可以极大加快镜像拉取速度避免初始化卡在Pull镜像阶段。cgroupDriver: systemd在KubeletConfiguration中显式声明与containerd的配置遥相呼应双重保险。3.2 初始化过程详解与常见报错执行初始化sudo kubeadm init --configkubeadm-config.yaml --upload-certs | tee kubeadm-init.log。使用tee命令将输出同时保存到文件方便后续排查。初始化过程会依次进行预检、拉取镜像、生成证书和静态Pod清单等。这里最容易卡住的地方是镜像拉取。即使配置了国内镜像源也可能因为网络波动导致某个镜像拉取失败。观察输出日志如果卡在某个镜像上可以手动去其他节点docker pull或crictl pull对应的镜像然后打tag。另一个常见错误是[ERROR Port-6443]或[ERROR Port-10259]等端口占用。这通常是因为之前部署失败后相关组件没有清理干净或者有其他服务占用了K8s默认端口6443, 10250, 10259等。使用sudo netstat -tlnp | grep 端口号检查并解决冲突。如果初始化失败可以使用sudo kubeadm reset -f进行清理它会重置kubeadm所做的更改。但注意kubeadm reset不会清除iptables规则和CNI插件创建的网桥设备这些需要手动清理。# 清理网络残留在重置后执行 sudo iptables -F sudo iptables -t nat -F sudo iptables -t mangle -F sudo iptables -X sudo ip link delete cni0 2/dev/null sudo ip link delete flannel.1 2/dev/null sudo ip link delete cali* 2/dev/null 2/dev/null # 如果用了Calico初始化成功后会输出加入集群的命令类似于kubeadm join ...。务必把这段信息保存好。按照提示配置kubectlmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config此时运行kubectl get nodes应该能看到Master节点但状态是NotReady因为网络插件CNI还没有安装。4. 网络插件部署与跨节点通信难题CNI插件是K8s集群的“神经系统”负责Pod之间的网络通信。没有它集群就是瘫痪的。我们选择Calico功能强大且文档完善。4.1 Calico安装与关键配置核对安装Calico通常很简单一条命令即可kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/calico.yaml但是魔鬼在细节里。直接应用这个官方yaml很可能导致Pod网络与你的kubeadm配置冲突。最大的坑就是Pod子网CIDR不匹配。在初始化时我们设置了podSubnet: 192.168.0.0/16而Calico默认的IP池IPPool可能也是这个网段这看起来没问题。但如果你的物理网络恰好也是192.168.0.0/16就会产生路由冲突。或者你之前初始化时用了别的网段那Calico Pod将无法分配IP。解决方案在安装前先下载Calico的yaml文件检查并修改其IP池配置。wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/calico.yaml用编辑器打开calico.yaml搜索CIDR或IPPOOL。你会找到一个Calico自定义资源定义CRD的配置部分。确保其cidr字段与kubeadm配置中的podSubnet完全一致。apiVersion: crd.projectcalico.org/v1 kind: IPPool metadata: name: default-ipv4-ippool spec: blockSize: 26 cidr: 192.168.0.0/16 # 确认这里与 podSubnet 一致 ipipMode: Never natOutgoing: true nodeSelector: all() vxlanMode: Always修改保存后再应用kubectl apply -f calico.yaml。应用后使用kubectl get pods -n kube-system观察calico-node-xxx和calico-kube-controllers-xxx这些Pod的状态直到全部变为Running。4.2 节点NotReady问题深度排查即使Calico Pod跑起来了kubectl get nodes可能依然显示NotReady。这是最让人头疼的阶段需要系统化排查。检查kubelet状态在问题节点上运行sudo systemctl status kubelet -l。查看是否有红色错误日志。常见错误是“cgroup driver mismatch”或“failed to run kubelet”。检查容器运行时运行sudo crictl ps看pause容器和k8s核心组件如etcd, apiserver的容器是否正常运行。如果crictl命令报错或没有容器说明containerd没有正确服务kubelet。检查CNI插件在节点上查看CNI配置和日志。ls -la /etc/cni/net.d/ # 应该能看到Calico的配置文件 sudo journalctl -u kubelet -f | grep -i cni # 查看kubelet日志中关于CNI的错误检查Calico Node DaemonSetkubectl describe pod calico-node-xxxx -n kube-system。关注Events部分和容器状态。常见问题是镜像拉取失败、权限不足需要挂载/var/run/calico等目录或节点标签不匹配。检查IP分配Calico为每个节点分配一个Pod网段块。在Master节点上运行kubectl get ipamblock -o yaml可以查看分配情况。如果某个节点的块没有分配成功该节点上的Pod就无法获取IP。我遇到的一个典型问题是Worker节点一直NotReadykubelet日志显示network plugin is not ready: cni config uninitialized。检查/etc/cni/net.d/发现是空的。原因是Calico的calico-nodePod在该节点上启动失败。进一步describe该Pod发现事件是Failed to create pod sandbox: rpc error: code Unknown desc failed to setup network for sandbox ...。这通常指向更底层的网络问题比如主机iptables规则冲突、内核模块缺失或者节点防火墙如ufw没有关闭。实操心得遇到节点NotReady不要慌按照“kubelet日志 - 容器运行时状态 - CNI配置与日志 - 网络插件Pod状态”这条链路自上而下或自下而上进行排查总能定位到问题根源。善用journalctl和kubectl describe、kubectl logs这三个命令。5. Worker节点加入与认证故障Master节点Ready后就可以让Worker节点加入了。在每个Worker节点上运行Master初始化成功后给出的kubeadm join命令。5.1 join命令执行与令牌管理kubeadm join命令包含API Server地址、令牌和CA证书哈希。令牌默认24小时有效。如果令牌过期可以在Master节点上重新生成# 生成新的令牌 kubeadm token create --print-join-command # 获取CA证书哈希 openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2/dev/null | openssl dgst -sha256 -hex | sed s/^.* //将新令牌和哈希值组合成新的kubeadm join命令。Worker节点执行join后使用kubectl get nodes在Master上观察新节点会先处于NotReady状态待Calico等Pod调度到该节点并运行后状态会变为Ready。5.2 节点加入失败的认证与网络排查Worker节点加入失败常见原因有网络不通Worker节点无法访问Master节点的6443端口。用telnet master-ip 6443或curl -k https://master-ip:6443测试。令牌或证书哈希无效确保复制的命令完整无误特别是超长的证书哈希。时间不同步所有节点的时间必须基本同步否则TLS握手会失败。使用chronyd或ntpd服务确保时间同步。Hostname冲突集群内节点主机名必须唯一。如果join命令看似成功但节点一直不出现检查Master节点上kube-apiserver的日志sudo journalctl -u kube-apiserver -f看是否有来自Worker节点的连接请求和认证错误。6. 核心组件状态监控与问题修复集群初步就绪后需要确保所有核心系统Pod都健康运行。这些Pod都在kube-system命名空间下。6.1 系统Pod健康检查清单运行kubectl get pods -n kube-system你应该看到以下核心组件具体名字前缀可能不同coredns-xxxDNS服务通常有两个副本。如果处于Pending或CrashLoopBackOff通常是网络问题。etcd-master-hostname键值存储数据库Master节点独有。如果异常整个集群控制面将瘫痪。kube-apiserver-master-hostnameAPI服务器。kube-controller-manager-master-hostname控制器管理器。kube-scheduler-master-hostname调度器。calico-node-xxx每个节点上都有一个负责本节点网络。calico-kube-controllers-xxxCalico的控制器通常一个副本。任何一个组件异常都需要立即排查。使用kubectl describe pod pod-name -n kube-system和kubectl logs pod-name -n kube-system查看详细原因。6.2 DNS与CoreDNS排错实战CoreDNS是最容易出问题的地方之一。症状是Pod内无法解析集群内服务名如kubernetes.default.svc.cluster.local或外部域名。诊断步骤在任意Pod内或临时运行一个busyboxPod执行nslookup kubernetes.default。如果解析失败检查CoreDNS Pod日志kubectl logs -l k8s-appkube-dns -n kube-system。检查CoreDNS的ConfigMapkubectl get configmap coredns -n kube-system -o yaml。默认配置通常没问题但如果你自定义了上游DNS或域名需要仔细核对。检查节点上的/etc/resolv.conf。kubelet会使用该文件中的nameserver作为Pod的默认上游DNS。如果这里面是127.0.0.1而节点本地没有运行DNS服务就会导致解析失败。解决方法是在kubelet启动参数中配置--resolv-conf指向正确的文件或者确保本地有可用的DNS转发服务。我遇到过一个经典案例所有Pod内部DNS解析超时。检查CoreDNS Pod日志发现大量read udp i/o timeout错误。原因是节点防火墙规则丢弃了UDP 53端口DNS查询端口的流量。虽然Calico管理了Pod网络但Pod访问节点本地DNS服务器如/etc/resolv.conf中的8.8.8.8的流量仍然会经过主机的网络栈受主机防火墙规则影响。解决方案是调整主机防火墙允许DNS查询流量。7. 存储、Ingress与后续组件部署考量基础集群稳定后就可以考虑部署有状态应用了这涉及到存储和外部访问。7.1 存储类StorageClass与本地路径供给器测试环境中最简单的存储方案是使用local-path-provisioner。它能为每个节点提供基于本地路径的动态持久卷PV。# 部署 local-path-provisioner kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml # 将其设为默认StorageClass kubectl patch storageclass local-path -p {metadata: {annotations:{storageclass.kubernetes.io/is-default-class:true}}}部署后创建PVCPersistentVolumeClaim时如果不指定storageClassName就会自动使用local-path来动态创建PV。注意这种存储是节点亲和的Pod被调度到哪个节点存储卷就在该节点的本地目录创建。如果Pod被重新调度到其他节点将无法访问原有数据因此仅适用于测试或可丢失的数据。7.2 Ingress Controller选型与部署要让外部流量访问集群内的服务需要Ingress Controller。我们选择Nginx Ingress Controller它功能全面、社区活跃。# 使用Helm安装需先安装Helm helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace部署后需要确定Ingress Controller的Service类型。在云环境通常用LoadBalancer在裸机环境则常用NodePort。如果使用NodePort可以通过任意节点的IP和分配的端口如http://node-ip:30080来访问Ingress。为了有一个固定的访问入口我们可以在前面再部署一个负载均衡器如HAProxy或者使用MetalLB一个裸机负载均衡器实现。部署Ingress时常见的问题是404或503错误。排查思路kubectl get ingress检查Ingress资源是否被正确创建ADDRESS字段是否为空。kubectl describe ingress name查看Events是否有配置错误。kubectl get pods -n ingress-nginx检查Ingress Controller Pod是否Running。查看Ingress Controller Pod的日志kubectl logs -l app.kubernetes.io/nameingress-nginx -n ingress-nginx里面会有详细的转发规则和错误信息。检查Ingress中定义的backendserviceName和servicePort是否与集群内真实的Service完全匹配包括命名空间。8. 日常维护与故障排查工具箱集群跑起来不是终点日常维护和故障排查能力同样重要。8.1 必备的kubectl诊断命令掌握以下命令组合能解决80%的日常问题资源概览kubectl get all -A查看所有命名空间所有资源Pod详情kubectl describe pod pod-name -n namespace看事件、状态详情Pod日志kubectl logs -f pod-name -n namespace实时查看日志进入Podkubectl exec -it pod-name -n namespace -- /bin/sh用于调试资源YAMLkubectl get resource-type resource-name -n namespace -o yaml获取配置节点资源kubectl describe node node-name查看节点资源使用、事件、污点服务端点kubectl get endpoints service-name查看Service背后真实的Pod IP和端口8.2 集群重置与安全清理指南当集群彻底混乱需要推倒重来时请按顺序执行驱逐节点可选kubectl drain node-name --ignore-daemonsets --delete-emptydir-data重置节点在每个节点上执行sudo kubeadm reset -f清理网络每节点sudo iptables -F sudo iptables -t nat -F sudo iptables -t mangle -F sudo iptables -X sudo ip link delete cni0 2/dev/null sudo ip link delete flannel.1 2/dev/null # 清理CNI配置和二进制文件 sudo rm -rf /etc/cni/net.d sudo rm -rf /opt/cni/bin清理配置文件rm -rf $HOME/.kube sudo rm -rf /etc/kubernetes卸载软件包如需要sudo apt-get purge kubeadm kubectl kubelet kubernetes-cni重要提醒kubeadm reset和清理操作会永久删除所有集群数据和应用仅用于测试环境或确定要销毁集群时。生产环境务必先备份ETCD和数据卷。部署Kubernetes集群就像完成一个精密的拼图每一块组件、配置都必须严丝合缝。这次踩坑经历让我深刻体会到理解其架构原理如kubelet与容器运行时的交互、CNI插件的工作机制、kube-apiserver的认证流程远比死记硬背命令更重要。当遇到问题时顺着日志和事件这条“线索”结合对原理的理解总能找到问题的根源。最后保持耐心善用社区和文档每一个坑踩过去都是对这套强大系统更深入的认识。
返回列表