尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Kubernetes运维实战:从Pod生命周期到故障排查的避坑指南

Kubernetes运维实战:从Pod生命周期到故障排查的避坑指南 Kubernetes 详解第七篇运维视角的实战手记这个系列写到第七篇前面的内容已经把Kubernetes的核心概念、集群搭建、基础资源讲了个大概。这篇笔记换个角度不按API对象一个个念经而是站在Linux运维干活的角度把日常维护Kubernetes时最容易被绊倒的地方集中梳理一遍。包括工作负载的生命周期管理、存储挂载、网络排查、调度策略这几个大块最后放几个真实故障现场。内容适合已经能把K8s集群跑起来、正在接手测试环境或生产环境维护的运维同学也适合被各种概念绕晕的开发人员当一本“避坑手册”来翻。1. 先理解这台“分布式操作系统”的运转逻辑很多运维刚接触Kubernetes时第一感觉是组件太多看不懂谁在跟谁通信。其实换个角度想K8s本质上就是一台“分布式操作系统”Master节点是内核Node节点是用户态进程的运行环境etcd是注册表API Server是系统调用入口。理解了这层映射关系后面的运维工作会顺很多。1.1 声明式系统你要的是状态不是命令Kubernetes和传统运维最大的不同在于它是声明式系统。传统运维操作是命令式的我要启动nginx执行systemctl start nginx然后等结果。Kubernetes的玩法是我告诉它“我要3个nginx副本”剩下的交给控制器去协调至于中间某个Pod挂了怎么重启、调度到哪台机器你不需要关心。这个逻辑听起来简单但实际运维时很容易带偏。比如有同事直接kubectl delete pod去“重启”一个应用其实正确做法是修改镜像tag触发滚动更新或者用kubectl rollout restart。删除Pod只是让控制器再拉起一个新的这个过程没有版本记录也没有灰度控制出问题只能靠运气。运维同学要做的就是转变思维把“怎么执行命令”变成“怎么描述期望状态”。1.2 控制面里谁在干活控制面组件不多但每个都负责一块核心职责。API Server是唯一入口所有kubectl命令、控制器、调度器的请求都要经过它。etcd保存集群全部状态相当于大脑的记忆中枢这里的数据丢失等于集群失忆。kube-scheduler负责把Pod分配到合适的Nodekube-controller-manager则是各种控制器的总管家负责让实际状态向期望状态收敛。在实际维护中我经常看到有人分不清故障出现在哪一层。这里分享一个简单的排查思路先看API Server是否正常响应kubectl命令再看etcd健康状态然后看kubelet有没有向Master上报节点信息最后才看具体Pod的状态。这个顺序基本能覆盖绝大多数控制面问题的定位路径。1.3 运维视角的五个必懂组件速查kubectl客户端命令行工具运维日常操作的主要入口。kube-apiserver集群所有请求的网关认证、授权、准入控制都在这一层处理。etcd集群状态的唯一真源备份和恢复是运维必须掌握的技能。kubelet运行在每个节点上的“代理人”负责Pod生命周期管理和健康检查。kube-proxy维护节点上的网络规则实现Service负载均衡能力。这五个组件分别对应运维日常的“操作入口、权限控制、数据备份、节点管理、网络转发”。哪个不通就顺着这条链路去查。2. 工作负载Pod生命周期里的那些坑Pod是Kubernetes最小的调度单位但日常运维中真正直接操作Pod的场景很少大多数时候都在和Deployment、StatefulSet这些工作负载对象打交道。这一节把工作负载的常见坑集中梳理一下。2.1 探针配置readiness和liveness别搞混探针是K8s用来判断Pod健康状态的机制三个类型容易搞混。startupProbe用于慢启动应用readinessProbe决定流量是否接入livenessProbe决定容器是否重启。很多线上事故就是配置错了探针导致的。举个例子一个Java应用启动需要80秒如果你只配置了livenessProbeinitialDelaySeconds设为10秒periodSeconds设为10秒那应用还在启动时就被判定为不健康直接杀掉重启陷入CrashLoopBackOff的循环。解决办法是加上startupProbe给应用留足启动时间等启动完成后再用livenessProbe做常规健康检查。另一个常见问题是探针的检查内容太粗糙。有人用wget localhost来检查端口存活但应用实际已经不响应请求了只是TCP连接还能建立。建议探针直接请求应用的健康检查接口通过HTTP状态码来判断不要只探测端口通不通。2.2 优雅终止terminationGracePeriodSeconds与preStopPod删除时默认有30秒的优雅终止时间。这个时间不是让你傻等的而是给容器发送SIGTERM信号后等应用完成收尾工作。很多运维踩过这样的坑应用没有处理SIGTERM信号导致K8s只能等满30秒后发SIGKILL强杀服务接口大量报错。处理这个问题通常配一个preStop钩子在容器停止前执行特定命令。比如微服务架构里收到SIGTERM后应该先从注册中心下线然后sleep几秒等存量请求处理完毕最后才真正退出进程。这样配合terminationGracePeriodSeconds设置可以极大减少服务重启时的报错率。关键参数建议terminationGracePeriodSeconds设置成应用平均请求耗时的3到5倍preStop里sleep的时间不宜过长否则排障时你会觉得Pod删也删不掉半天没反应。2.3 Deployment滚动更新maxSurge和maxUnavailableDeployment滚动更新有两个关键参数maxSurge和maxUnavailable很多人在部署策略里根本不管这两个值默认配置就是25%和25%在某些场景下会出问题。比如生产环境只有2个副本默认配置下滚动更新会先启1个新Pod再停1个旧Pod整个过程最少会有一个Pod不可用。如果你的服务不能容忍可用副本降级就得显式设置maxUnavailable: 0同时maxSurge调整到一个合理的百分比。反过来如果集群资源紧张不想在更新时多占用资源可以把maxSurge设为0maxUnavailable设为1。还有一个细节Deployment更新时的版本历史默认保留10个revision如果你频繁发版旧的controller revision会占用很多存储空间。大版本迭代后可以手动清理Kubernetes的controllerrevision资源避免etcd里的资源对象膨胀。2.4 StatefulSet与DaemonSet两种经常用错的场景StatefulSet用于有状态服务最典型的就是数据库、Zookeeper、Kafka这类需要稳定网络标识和独立存储的应用。它的Pod名称是固定的比如pod-0、pod-1每个Pod对应一个独立的PVC删除Pod后重建还会挂载原来的数据卷。很多人图省事想用Deployment跑数据库这是典型的错误用法数据会随Pod一起消失。DaemonSet则是保证集群每个节点上跑一个Pod典型场景是日志采集器、监控Agent、CNI插件。维护DaemonSet时要特别注意它默认会随着节点扩容自动在新节点上创建Pod如果节点上有污点记得配合tolerations使用否则新节点加进来之后Agent不会自动部署上去。3. 存储体系PV/PVC/StorageClass从建到用存储是Kubernetes运维中翻车率最高的模块之一。很多开发同学以为挂一个PVC就和挂一块云硬盘一样简单实际上一套完整的存储方案从底层存储系统到K8s存储类配置每一个环节都可能出问题。3.1 三个角色怎么配合用生活化一点的方式理解PV、PVC和StorageClass三者的关系。PV是已经准备好的存储空间相当于仓库里的货架PVC是应用提出的存储申请相当于采购单StorageClass则是自动供货系统收到申请后自动去存储后端创建一块空间绑定成PV。运维要理解的关键点是PVC和PV的绑定是一一对应的一个PV只能被一个PVC使用。当PVC删除后PV的去留由回收策略决定。Retain策略下PV变成Released状态数据还在但不能再被其他PVC绑定Delete策略下PV会直接删除底层存储。生产环境的数据库PV建议用Retain防止误删数据。3.2 NFS StorageClass从零配置在没有云存储的裸机环境下NFS是最常用的Kubernetes共享存储方案。配置一个可用的NFS StorageClass通常需要以下步骤首先确认所有K8s节点已经安装了nfs-common包否则Pod挂载时会报exec format error或者mount: wrong fs type。然后部署一个NFS provisioner它会监听PVC的创建事件自动在NFS服务器上创建子目录。StorageClass的yaml核心配置如下需要注意几个字段。provisioner指定使用哪个存储插件创建PV这里填的是NFS provisioner的部署名称。reclaimPolicy建议设置为Retain防止PVC删除连带把NFS里的数据清掉。archiveOnDeleteNFS provisioner的一个特性删除PVC后在NFS服务器上保留一个带时间戳的备份目录这个配置强烈建议开启。实际运维中NFS的权限问题经常把人逼疯。Pod里运行的用户UID和NFS目录属主不一致时会出现Permission denied。建议在provisioner的配置里指定正确的UID或者让应用容器以root用户启动虽然不推荐至少先排除权限干扰。3.3 挂载失败排查路径Pod一直ContainerCreating大概率是存储挂载卡住了。排查路径建议按以下顺序来。第一kubectl describe pod看Event通常这里会有明确的报错信息比如failed to mount volume或timed out waiting for the condition。第二手动在Node节点上测试挂载命令如果NFS就执行mount -t nfs server:/path /mnt看宿主机能否正常挂载。第三检查Node节点的kubelet日志存储相关的报错往往会在这里出现。第四确认存储服务端的网络连通性和权限设置很多时候是防火墙或安全组规则拦住了NFS端口。4. 网络模型Service与Ingress背后的逻辑Kubernetes网络是运维学习中门槛较高的部分因为它涉及多层的网络转发。把每一层的职责拆开看其实可以整理得很清晰。4.1 Pod网络一个Pod一个IP的两个原因K8s设计了一个网络模型每个Pod都有一个独立的IP地址Pod内所有容器共享这个IP和网络命名空间。这样做的目的有两个第一Pod内的容器通过localhost就能互相通信不需要额外配置端口映射第二Pod的IP在整个集群范围内可路由不同节点上的Pod可以直接通过IP互通不需要NAT转换。这背后依赖容器网络接口CNI插件来实现常见的CNI插件有Flannel、Calico、Cilium。Flannel使用VXLAN技术封装数据包简单易用但性能开销稍大Calico使用BGP协议直接路由性能好还支持NetworkPolicy网络策略。国内很多公司同时用Flannel和Calico的场景不少见小集群用Flannel省事规模上来后迁到Calico的更常见。4.2 Service四兄弟怎么选Service是Kubernetes对一组Pod提供稳定访问入口的方式四种类型各有适用场景。ClusterIP是默认类型只能在集群内部访问常用于服务间的互相调用。NodePort在ClusterIP基础上在每个节点上开放一个静态端口外部通过任意节点的IP加端口就能访问适合临时调试或小规模暴露服务。LoadBalancer依赖云平台或硬件的负载均衡器生产环境对外服务首选。ExternalName比较特殊它不是将流量转发到Pod而是将服务名解析到集群外部的DNS名称上。日常运维中NodePort端口冲突是个需要注意的点默认端口范围是30000到32767。如果你有一堆服务要对外暴露不可能每个都占一个NodePort这时候就是Ingress出场的时候。4.3 kube-proxyiptables与IPVSService之所以能实现负载均衡靠的是kube-proxy在每个节点上维护的转发规则。kube-proxy有三种工作模式userspace模式最早但性能最差目前已经很少使用iptables模式是目前默认的模式利用Linux内核的iptables规则做DNAT转发缺点是规则数量多时性能会下降IPVS模式基于Linux内核的IP虚拟服务器模块支持更高效的负载均衡算法适合大规模集群。运维中可以通过kubectl get svc里看到的CLUSTER-IP然后到Node上执行iptables -t nat -L来查看实际的转发规则。如果发现Service的Endpoints变化了但转发规则没更新第一件事就是检查kube-proxy是否正常运行很多时候重启kube-proxy就能解决。调整kube-proxy模式的方法是修改kube-proxy的ConfigMap把mode字段改成ipvs然后滚动重启kube-proxy的Pod。4.4 Ingress与Ingress Controller的区别Ingress和Ingress Controller是两回事这个坑十个人里有八个人会踩。Ingress只是一个API对象描述了域名、路径和Service的对应关系它本身不干活。真正处理请求转发的是Ingress Controller是一个独立运行的Pod常见的有nginx-ingress-controller、traefik等。如果创建了Ingress规则但没有部署Ingress Controller那么访问域名会发现完全不通。这和K8s里控制器模式的设计理念一脉相承API对象只是期望状态的描述需要对应的控制器去实现。配Ingress时有几个容易踩的坑。path的匹配规则nginx-ingress默认用nginx的location匹配逻辑前缀匹配和正则匹配的行为不一样建议先用简单的前缀匹配rewrite-target注解如果后端服务期望的路径不包含Ingress里的前缀就需要配置这个注解来重写路径证书管理的secret要放在Ingress所在的命名空间跨命名空间引用会失败。5. 调度与节点维护别让Pending变成常态调度是控制面里比较独立的一块逻辑运维不需要深入理解调度器源码但要明白调度器的决策逻辑才能理解Pod为什么被调度到某台节点为什么一直Pending。5.1 调度器的筛选逻辑调度器将Pod分配到节点的过程分两步过滤和打分。过滤阶段主要看节点资源是否满足Pod的requests要求节点是否有对应的污点容忍端口是否冲突等。过滤之后剩下的节点进入打分阶段调度器根据各项优先级规则打一个分数分数最高的节点被选中。运维最常遇到的场景是Pod一直Pendingdescribe查看Event信息通常会看到0/3 nodes are available: 1 Insufficient cpu, 2 node(s) had taint。前者说明节点CPU资源不足检查节点的allocatable资源和已有Pod的资源占用后者说明节点上有污点Pod没有对应的容忍。建议运维在配置工作负载时合理设置resources.requests不要要么不写要么写个巨大的值吓跑调度器。requests值决定了Pod能被调度到哪台节点limits值决定了Pod能用多少资源。千万不要为了省事只设置limits不设置requests这样调度器并不知道这个Pod的真实资源需求可能出现超卖导致的节点过载。5.2 污点与容忍的使用边界污点Taint和容忍Toleration是Kubernetes里控制Pod调度范围的重要手段。污点标记在节点上容忍配置在Pod上只有带有对应容忍的Pod才能调度到有污点的节点上。常见的使用场景包括给专用数据库节点打上污点防止其他业务Pod随意调度上去给GPU节点打上污点只有声明了GPU需求的Pod才能使用给运维管理节点打上污点让普通业务Pod不占用管理资源。这里要提醒一个容易踩的坑NoSchedule污点只影响新调度的Pod不会驱逐节点上已经运行的PodNoExecute污点不仅阻止新Pod调度还会驱逐节点上不符合容忍条件的存量Pod。如果操作失误给节点打了NoExecute污点可能导致节点上全部Pod被清空影响面上非常大。执行前先数一数这个节点上跑着多少关键服务。5.3 节点维护的正确姿势cordon与drain节点需要维护时比如内核升级、硬件更换正确的操作顺序是cordon先标记节点为不可调度drain将节点上的Pod优雅驱逐到其他节点。这个操作看起来简单但有几个参数需要注意。drain命令默认不会驱逐DaemonSet管理的Pod需要加--ignore-daemonsets参数忽略它们如果节点上存在使用了emptyDir或hostPath的Pod需要加--delete-emptydir-data参数才能驱逐成功当然这会删除数据操作前要想清楚。另外还要看Pod是否有对应的PodDisruptionBudgetPDBPDB限制了自愿驱逐时最多能同时中断的Pod数量。如果drain一直卡住大概率是某个Pod的PDB限制导致无法驱逐此时应该检查具体是哪条PDB在阻止而不是强行加--force参数硬删强制驱逐有可能造成服务长时间不可用。节点维护完成后执行kubectl uncordon把节点恢复为可调度状态Pod会陆续调度回来。6. 故障排查我遇到过的几个经典现场这一节整理几个实际运维中反复出现的排查场景。没有太多理论都是实操命令和思路。6.1 一张排查速查表先把最常用的故障现象和排查思路用表格列出来节省翻文档的时间。故障现象第一步排查命令常见原因Pod一直Pendingkubectl describe pod资源不足、污点不匹配、PVC无法绑定Pod一直ContainerCreatingkubectl describe pod镜像拉取失败、存储挂载卡住、CNI网络配置异常Pod反复重启kubectl logs --previous应用启动报错、liveness探针配置不当、OOMKilled服务无法访问kubectl get endpointsEndpoints为空、kube-proxy规则未更新、安全组拦截节点NotReadykubectl get nodes -o widekubelet异常、磁盘压力、容器运行时故障这张表是排查的起点大部分问题通过describe和logs能定位到大方向剩下的就是根据报错信息往下追。6.2 高频场景深入拆解先说镜像拉取失败这是新集群最常遇到的问题。现象是Pod处于ImagePullBackOff状态describe看到Failed to pull image的Event。排查思路先确认镜像名和tag是否正确特别是自建镜像仓库的地址是否多写了或漏写了前缀再检查镜像仓库是否需要认证需要的话要配置imagePullSecrets最后确认节点是否能访问镜像仓库很多离线环境需要配置企业的私有镜像仓库。再说CrashLoopBackOff这个状态说明容器反复启动后退出。核心排查方法是kubectl logs看当前容器的输出以及kubectl logs --previous看上一次容器的错误输出。如果是应用崩溃导致日志里会有堆栈或异常信息如果是探针导致Event里会注明Liveness probe failed。一个容易被忽略的情况是应用启动时报错但立即退出还没来得及写日志这时候要用kubectl describe看退出码比如137表示OOMKilled1表示应用自身错误。节点NotReady的排查也是高频操作。先登录节点看kubelet服务状态systemctl status kubelet再用kubectl describe node 查看Conditions常见的原因包括内存或磁盘压力过大kubelet触发了驱逐动作也可能是kubelet证书过期。生产环境建议给kubelet证书配置自动续期同时监控节点的磁盘和内存使用率提前预警。最后说一下Service无法访问的排查思路。一次真实的故障现场是这样的开发反馈某个服务从集群外访问不了。我先kubectl get svc确认Service存在再kubectl get endpoints发现Endpoints列表是空的说明Service的selector没有匹配到任何Pod。进一步检查发现新版本应用上线时升级了Pod标签但Service的selector还保留着旧标签。把标签对齐后Endpoints恢复正常服务立即通了。这个案例很典型标签不一致是Service故障的头号原因。写在最后的一点心得做Kubernetes运维这几年最深的感触是这个系统的复杂度和它带来的能力提升是成正比的。它把很多原来需要人工协调的运维问题抽象成了API对象和控制器的配合但也把很多问题从单机排查变成了分布式排查。遇到故障不要急躁按照“控制面到数据面、状态到事件、上位对象到下位对象”的顺序一步步查大多数问题都能在半小时内定位到根因。给新手的建议是多用kubectl describe看Event它往往已经把答案放在你眼前了。多练几次排障之后你会慢慢建立起一套自己的应对思路。
返回列表