ARTICLE DETAIL

资讯详情

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

k8s配置与性能优化实战:从集群部署到生产故障排查

k8s配置与性能优化实战:从集群部署到生产故障排查 配置和优化k8s大概是很多运维和开发同学又爱又恨的事。爱的是它把容器调度、服务发现、自动伸缩这些复杂问题抽象成了几个对象和一堆yaml恨的是照着文档敲完命令集群不一定起来起来了也不一定稳稳了也不一定快。我自己从裸机部署到生产集群维护踩了三四年坑今天就把k8s配置和优化这块从集群初始化、核心组件参数、网络暴露方式到生产故障排查和GPU这类异构资源调度按实战路径捋一遍。1. 部署与初始化先把集群“立”起来1.1 部署方案怎么选kubeadm、二进制还是发行版很多新手上来就问“用哪个方式装k8s最省事”我的回答通常是先别看省事看你要在什么环境里跑多久。kubeadm是目前社区最主流的部署方式它的优势在于把控制平面组件的静态Pod配置、证书生成、引导令牌这些脏活自动化了。你只要把crictl、kubelet、kubeadm三个二进制装好一条kubeadm init就能拉起一个可用集群。但它也有自己的脾气对容器运行时版本要求严格镜像源在国内经常拉不动版本升级时如果跨了小版本还好跨大版本必须按官方文档的顺序一步步来。二进制部署听起来更“原始”但很多人不知道它是理解k8s组件协作关系的最快路径。你自己把kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、etcd一个个启动参数写清楚之后出任何问题你对“哪个组件负责什么”心里都有数。我陪朋友排查过一个诡异故障Pod调度成功但一直ContainerCreating他用kubeadm装的直接傻眼我用二进制部署排查过类似的三分钟就定位到是kubelet的cgroup驱动和容器运行时不一致。还有一个选择是直接用开源发行版比如kubespray、k0s、k3s。k3s适合边缘和资源受限环境kubespray适合批量部署和生产级调优。不过发行版会封装很多细节如果你本身对k8s还不够熟建议别有“用发行版就万事大吉”的想法出问题时你反而更难定位。1.2 逃不掉的初始化故障apiserver is not healthy如果你搜索过k8s部署相关的内容肯定见过这个报错The API server is not healthy after 4m0.00747357s。我在一台新买的裸金属服务器上用kubeadm初始化时第一次见到它心态直接崩了——所有Pod都在CrashLoopBackOffkubelet日志刷屏整个集群像没通电的机器。这个报错的本质是kubeadm在等待apiserver就绪但apiserver本身起不来或起得很慢。最常见的原因有四个第一个是镜像拉不下来apiserver、etcd、controller-manager这些镜像都放在registry.k8s.io国内网络基本别想第二个是证书或token过期特别是你之前init过一次没成功又没做kubeadm reset残留的etcd数据会让新集群起不来第三个是kubelet的cgroup驱动和容器运行时不统一一般建议都用systemd第四个是资源不足kubeadm对控制平面节点的最低要求是2核4G但你要是真的只给4G内存初始化的过程会慢到让人怀疑人生。我当时的排查顺序很简单先journalctl -u kubelet -f看kubelet日志确认是不是镜像问题再用crictl ps -a看静态Pod容器的状态然后检查/etc/kubernetes/manifests下的apiserver.yaml配置看有没有端口冲突或者参数写错。如果是镜像问题先把registry.k8s.io换成镜像加速域名或者提前用ctr images pull把镜像拉到节点上再重新kubeadm init。这里要提醒一个细节重新初始化之前一定要kubeadm reset并清理/etc/kubernetes、/var/lib/etcd、/var/lib/kubelet下的残留数据。我见过有人没清理重置了三次都不健康最后发现是旧证书和旧etcd数据一直在干扰。reset之后kubeadm init基本一次过。2. 核心组件的配置细节与参数调优2.1 kube-apiserver集群的“总闸门”kube-apiserver是整个集群唯一和etcd通信的组件是所有请求的入口。你敲的每个kubectl get pods底层都是走apiserver。所以apiserver的配置和性能上限决定了你的集群能撑多大、多稳。先说最容易被忽略的准入控制插件。默认的--enable-admission-plugins里包含NodeRestriction、LimitRanger、ResourceQuota但很多人不会刻意去检查它。我建议至少在测试环境里开启NamespaceLifecycle和DefaultStorageClass生产环境要评估PodSecurity。这些插件的作用是在请求进etcd之前就拦掉不合法配置比如你忘了给负载均衡服务加externalTrafficPolicy集群不会报错但流量转发行为会很怪。重点说性能调优。apiserver有几个关键参数直接关系到你能扛多少并发请求--max-requests-inflight400 --max-mutating-requests-inflight200这两个参数控制并发读和写请求数。默认值分别是400和200如果你的集群节点超过100台或者经常有大批量Job同时创建很容易触顶。触顶的后果不是拒绝请求那么简单而是客户端会不断重试反而把apiserver压得更死。另一个容易被忽略的是etcd的--quota-backend-bytes。默认是2GB意思是etcd数据库超过这个值就会告警并拒绝写入。如果你集群里有大量历史事件或ConfigMap很快就能涨上去。我在一个运行了半年的集群上遇到过etcd空间用完所有写操作都失败排查了半天才发现是事件记录太多。解决办法是定期清理旧事件或者调整etcd的压缩参数但最根本的还是要控制集群里的资源数量不要无节制地创建各种ConfigMap和Secret。2.2 kubelet与容器运行时节点侧的稳定器很多人把精力放在控制平面的优化上忽略了kubelet这个“每个节点的管家”。实际上生产环境最常见的节点级故障十有八九和kubelet配置有关。kubelet维护Pod的声明周期调度器只是决定Pod应该放在哪个节点真正干活的还是kubelet。它的配置中--eviction-hard、--system-reserved、--kube-reserved这三个数值决定了节点在什么条件下会开始驱逐Pod。默认的--eviction-hard是memory.available100Mi,nodefs.available10%,imagefs.available15%如果你直接用在生产环境内存告警线就太低了等触发驱逐时节点已经被打到OOM边缘。我通常会给每个节点至少预留15到20%的系统资源把驱逐线抬高一点apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration evictionHard: memory.available: 1Gi nodefs.available: 15% imagefs.available: 20%注意这里有个细节system-reserved里设置的值要小于节点总资源它越界了你就会看到大量Pod被驱逐但你又找不到程序在跑什么。我之前在8核16G的节点上给系统预留了12G结果一个普通应用Pod刚跑起来就被Evicted日志里全是The node was low on resource: memory。后来才意识到预留值给大了业务无法申请到足够资源。容器运行时的配置同样重要。现在大多数环境都用containerd务必确认/etc/containerd/config.toml里的SystemdCgroup和kubelet一致。不一致的典型症状是Pod能创建但一直无法进入Runningkubelet日志反复报failed to create containerd task: failed to start shim。同时容器日志的轮转也要在containerd层面配好max_size和max_file否则日志文件会无限增长把节点磁盘打满进而引发节点级别的驱逐。3. 网络与外网访问把服务暴露做得更稳3.1 ExternalIPs最容易用错的一个字段k8s里暴露服务的方式很多Service Type有ClusterIP、NodePort、LoadBalancer还有一个不太起眼但很实用的字段叫externalIPs。很多人在搜索k8s externalIPs配置时都会困惑这不就是把节点的IP填进去吗有什么好说的其实externalIPs真正的用途是让你在Service上绑定一个外网固定IP流量直接打进你指定的地址完全不走NodePort的随机端口映射。它适合的场景是你有几个公网IP希望直接映射到某个Service上而且不想引入负载均衡器或Ingress Controller。配置方式很简单apiVersion: v1 kind: Service metadata: name: my-web-service spec: type: ClusterIP clusterIP: 10.96.0.10 externalIPs: - 203.0.113.10 ports: - port: 80 targetPort: 8080这里有一个必须注意的坑externalIPs不会做高可用。你把它绑到一台节点的IP上如果这台机器挂了流量就断了。我在一个内部系统上踩过这个坑原以为Service天然有负载均衡能力结果节点重启后整个系统失联。后来改成用Keepalived提供一个虚拟IP再把虚拟IP填到externalIPs里才解决了单点问题。还有一个容易踩的坑如果你同时设置externalIPs和NodePort去访问那个外部IP时可能直接走的是NodePort的转发规则而不是你想直接进入Pod的逻辑。这个行为在不同CNI插件下表现还不一样建议不要在同一个Service上混用这两种暴露方式。3.2 CNI插件选型对比与调参要点网络插件是k8s里最容易被当成“黑盒”的部分。很多集群一装好就默认用了Flannel跑起来倒是能连通但一旦涉及NetworkPolicy、性能压测、跨节点大流量就会暴露问题。先给一个我的选型经验Flannel适合小集群、测试环境它的VXLAN模式部署简单、故障率低但它不支持NetworkPolicy性能在跨节点场景下损耗比较明显Calico是生产环境最稳妥的选择支持NetworkPolicy路由模式和BGP模式下的性能损耗很小但配置项多调试起来更费劲Cilium是后起之秀基于eBPF性能和可观测性都强但要求的内核版本比较新对运维人员的要求也更高。选好插件后第一个要调的是MTU。默认情况下Flannel和Calico都会用MTU1500如果你的底层网络是VXLAN或Overlay实际传输的数据帧会被封装一次1500反而会触发分片。我在一个物理网络MTU是9000的机房里把Flannel的MTU调成1480Pod之间的TCP吞吐直接提升了30%。这个调参动作不大但收益极其明显。另一个重要配置是IPIP模式改成BGP模式。Calico默认用BGP直接在三层路由不需要额外封装性能最好。但BGP模式要求你的物理路由器支持并配置BGP邻居否则节点之间的流量还是得走Overlay。如果你不想动网络设备就保持IPIP模式但把CALICO_IPV4POOL_IPIP调成Never让Pod IP直接路由到宿主机在某些网络环境里也会有不小的性能提升。4. 生产级性能与稳定性优化实践4.1 生产环境常见故障与排查思路生产环境里的k8s故障很多不是配置写错而是没考虑到资源、时间和流量的变化。我整理了几个高频场景都是按真实出镜率排的。Pod一直Pending是最常见的。八成原因是资源不够或节点亲和性没有满足。排查时不要只盯资源先kubectl describe pod看Events里面会直接给出调度失败的原因。如果是Insufficient memory看节点剩余资源如果是nodeSelector不匹配看标签有没有打对。Pod反复Evicted排第二。这个在节点内存或磁盘紧张时特别明显kubelet会优先杀掉占用资源最大的Pod。排查思路是看节点状态kubectl describe node里的Pressure项MemoryPressure还是DiskPressure之后检查是不是业务容器没设置Requests导致调度器认为节点资源充足但实际运行时节点根本扛不住。所以生产环境一定要给工作负载设置Requests和Limits否则你的调度决策就像闭着眼睛做的。还有一个容易让人误判的故障是域名解析间歇性失败。Pod能起来但访问其他服务的域名时经常超时。这种问题往往出在CoreDNS或NodeLocalDNSCache上。先查CoreDNS的Pod有没有重启再查节点上的/etc/resolv.conf里的nameserver指向是否正常。如果CoreDNS没问题就要怀疑是conntrack表满了这个在大流量、短连接场景下最常见需要把net.netfilter.nf_conntrack_max调大一点。4.2 GPU等异构资源调度配置如果你的业务涉及模型推理或机器学习训练k8s调用GPU是绕不开的话题。今年大模型相关的项目特别多标题里出现的“k8s调用GPU”搜索热度一直很高因为这块配置的坑是真的多。先理清一个概念k8s本身不认识GPU硬件它需要Device Plugin把GPU资源作为可调度资源上报给kubelet。NVIDIA官方提供了nvidia-device-plugin装好后节点上就会出现nvidia.com/gpu这种可分配资源你在Pod里就能声明resources: limits: nvidia.com/gpu: 1这里有几个前置条件节点上要安装NVIDIA驱动容器运行时要设置为nvidia容器运行时或在containerd里配置好nvidia-container-runtime。只装device plugin没配运行时Pod调度上去后会报Failed to create pod sandbox: error getting image config之类的问题。我踩过最深的坑是驱动版本和CUDA版本不匹配。如果你在Pod里跑的镜像需要CUDA 12.0而宿主机驱动只支持CUDA 11.x容器启动时会直接报找不到libcuda.so。这不是k8s的问题但排查起来特别迷惑因为Pod的状态是CrashLoopBackOff日志里全是NVIDIA相关的报错。建议在给GPU节点打标签或调度约束之前先在节点上用nvidia-smi确认驱动支持的最高CUDA版本。还有一个容易被忽略的问题GPU节点必须设置nodeSelector或nodeAffinity否则普通任务也可能被调度到GPU节点上占资源。同时建议给GPU节点设置Taints只允许带对应Tolerations的工作负载上去跑避免“一颗原子弹带一堆步兵”的资源分配尴尬。4.3 监控、日志与告警的基础配置很多人觉得监控是“装了就完事”但实际上监控体系本身的配置和优化对k8s集群稳定性的帮助比大多数业务优化都大。基础标配是metrics-server加Prometheus。metrics-server负责给kubectl top提供数据让HPA能正常工作它只是一个数据采集器本身不做监控展示。Prometheus负责抓取指标、存储和告警。如果你只是要“能看到Pod CPU和内存”装metrics-server就够但你要做容量规划、长期趋势分析必须上Prometheus。千万不要把metrics-server当成监控系统用它的数据保留时间极短对排查历史问题毫无帮助。日志采集方面我推荐Fluent Bit加Elasticsearch或Loki的方案。Fluent Bit比Fluentd更省资源而且它不抢Pod的CPU资源。这里要优化一个配置容器日志的路径解析和Pod标签注入。如果你不给日志打上namespace和pod_name的标签排障时想“按某个Pod查日志”就会非常痛苦只能靠时间硬筛。告警规则别贪多。很多人一开始就配置几十条告警比如Pod重启超过3次、API Server延迟超过1秒结果全是噪音真正出问题的时候反而被淹没。我建议告警规则控制在10条以内优先覆盖节点NotReady、Pod长时间Pending、etcd写入延迟过高、磁盘空间不够。把这几个核心指标看住集群稳定性就有基本保障了。5. 常见问题速查与避坑心得5.1 高频问题速查表整理一份我在实际维护中用得最多的问题处理速查表直接照着排查能省不少时间。现象可能原因快速排查/解决kubeadm init报apiserver not healthy镜像拉取失败、残留数据、cgroup驱动不一致crictl ps -a看容器状态先kubeadm reset再重试确认运行时SystemdCgrouptruePod一直Pending资源不足、节点亲和性不满足、污点未容忍kubectl describe pod看Eventskubectl get nodes看剩余资源Pod不断Evicted节点MemoryPressure/DiskPressure检查节点Pressure状态给业务设置Requests/LimitsDNS解析间歇性失败CoreDNS异常、conntrack表满查coredns pod日志调整nf_conntrack_max参数新Service外部访问不通externalIPs绑了单点IP、网络安全组限制改用虚拟IP检查节点iptables规则Pod调度到GPU节点但启动失败container runtime没配nvidia运行时、驱动版本不匹配nvidia-smi验证驱动containerd配置nvidia runtime查看Pod事件节点磁盘被日志打满容器日志轮转没配置在containerd配置log_max_size、log_max_file清理旧日志5.2 我的几条实操心得最后分享几个个人觉得最值得记住的经验。配置变更要“小步快跑”。不要在一次维护窗口里同时升级CNI、修改kubelet参数、更新内核任何一步出问题你都很难定位是哪个变更导致的。我一般会先把变更拆成独立的小批次每完成一个就观察15分钟确认没有异常再继续下一步。这比事后花三小时回滚稳妥得多。版本兼容矩阵真的要看。k8s版本、containerd版本、CNI插件版本、device plugin版本这四者的兼容性关系在升级前必须查清楚。我遇到过最惨的一次是只升级了kubelet从1.24跳到1.26结果cgroup驱动、CSI驱动、NodePort行为全都变了一夜之间集群资源统计异常业务方全都炸毛。先看官方支持矩阵再做升级这个习惯能救你一命。给节点预留资源时宁可多留不要抠。很多人觉得每个节点预留10%就够了但实际上系统本身有日志、镜像缓存、内核缓冲区再加上节点上的DaemonSet预留低于15%很容易在业务高峰期触发驱逐。我现在的做法是控制平面节点预留至少2核2G工作节点按总资源的15%到20%预留之后几乎没有再遇到过节点级的资源瓶颈。
返回列表