ARTICLE DETAIL

资讯详情

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

Kubernetes进阶核心:从yaml到原理,掌握调度网络存储与排障机制

Kubernetes进阶核心:从yaml到原理,掌握调度网络存储与排障机制 不想绕弯子直接说结论Kubernetes 进阶的核心不是“会写几个 yaml”而是“能在生产环境里看懂现象、定位问题、解释清楚背后机制”。如果只是照着教程敲过kubectl run nginx --imagenginx然后看着Pod起来了就觉得“我玩过K8s”那还远远没进门。真正拉开差距的是面对一个Pod长时间Pending、流量偶尔超时、节点磁盘写满时你能不能快速判断出是哪一层出了问题以及为什么会出这个问题。这篇文章的目标读者很明确已经会部署基础应用、能看懂大部分yaml字段但对调度、网络、存储、控制器原理还停留在“背概念”阶段的人。我会从“部署nginx”这个最经典的练手场景切入把进阶路上必须吃透的几个核心机制串起来讲最后聊聊怎么啃《深入理解Kubernetes源码》这种书才不会劝退顺便给你一份可以直接照着做的实操路线。1. 进阶前的自我审视你处于哪个阶段1.1 从“会部署”到“能排障”的跨越很多人学Kubernetes是零散地学今天看一篇文章学会Deployment明天看一个视频学会Service后天操作一下Ingress。学的时候感觉什么都懂了但一旦面对真实的故障场景大脑就一片空白。我见过不少同学能熟练地用Helm安装Chart但遇到Pod一直CrashLoopBackOff就只会kubectl logs日志要是看不出明显错误就直接懵掉。进阶和入门的分水岭恰恰就在“面对未知问题时的反应”。入门阶段是“照着文档做”进阶阶段是“没有文档也能通过原理推导出排查路径”。比如Pod Pending你能想到去查调度器日志、检查节点资源、看是否有污点或亲和性冲突吗Service访问不通你能分清是Endpoints没生成、kube-proxy规则没同步、还是CNI网络插件的问题吗这些判断的背后靠的不是经验堆积而是对Kubernetes内部组件的运作链路有完整认知。1.2 进阶学习的三条主线以我自己的经验来看Kubernetes进阶学习需要沿着三条主线同步推进调度线从Pod创建到落到节点的完整链路涉及kube-scheduler、kubelet、容器运行时之间的协作关系。网络线从Pod IP到Service ClusterIP再到Ingress对外暴露的全链路涉及CNI插件、kube-proxy、DNS解析。存储线从emptyDir到PV/PVC再到StorageClass动态供给的存储抽象层设计逻辑。这三条主线不是孤立的它们共同构成了你排查问题时的“全局地图”。我建议先吃透每条线的基本流程再通过实际排障把三条线交叉串起来这才是完整的进阶路径。2. 用“部署nginx”这个场景吃透核心概念2.1 Deployment到底帮你做了什么很多人第一次部署nginx用的都是kubectl create deployment nginx --imagenginx。命令敲下去Pod起来了一切看起来很神奇。但进阶的第一步就是拆穿这种“神奇感”。Deployment的本质是一个“控制器”它做的事情可以拆成四步先创建ReplicaSetReplicaSet再根据Pod模板创建Pod然后通过kube-apiserver把Pod写入etcdkube-scheduler watch到这个Pod对象后为其分配节点节点上的kubelet负责真正拉镜像、启动容器。整条链路上每个组件各司其职而且全部通过“声明式”的方式协作——你只声明“我要3个nginx副本”剩下的由控制器不断调谐reconcile来逼近这个期望状态。理解这一点后你再去看滚动更新就不会只停留在“改了镜像tag就自动滚动”的层面。Deployment在滚动更新时会创建新的ReplicaSet逐步扩容新RS、缩容旧RS这个过程中的maxSurge和maxUnavailable两个参数决定了滚动速度与风险控制。比如maxSurge: 25%, maxUnavailable: 25%的意思是更新过程中允许最多超出期望副本数25%同时允许最多低于期望副本数25%。理解了这个生产环境要推进上线策略时才能心里有数。2.2 Service如何把流量送到PodPod是脆弱的随时可能被重建、迁移IP地址跟着变化所以Kubernetes设计了一个稳定的访问入口——Service。这里最容易让人困惑的是ClusterIP的工作原理。Service并不是一个真实存在的网络设备它更像一个“虚拟IP规则集合”。当你创建一个Service时kube-apiserver会分配一个ClusterIP然后kube-proxy或更准确地说是节点上的代理组件会监听Service和Endpoints的变化在节点上写入iptables或ipvs规则。以iptables模式为例ClusterIP的流量实际上是被DNAT到后端Pod IP的。这里有个进阶核心Service本身不直接连接Pod它只维护一个Endpoints列表新版叫EndpointSlice真正转发是节点上的内核规则完成的。所以排障时先看Endpoints有没有Pod地址基本能判断是Service定义问题还是后端Pod问题。没跑通这个逻辑排查Service访问异常会很痛苦。2.3 从yaml看Kubernetes的声明式设计Kubernetes所有对象的一个共同特征yaml里写的是“期望状态”控制器负责把“当前状态”驱赶到“期望状态”。这也是它和传统运维工具最大的区别——传统工具告诉你“怎么做”Kubernetes告诉你“做什么”怎么做由控制器自己搞定。举一个例子。你在yaml里写上replicas: 3如果某台节点宕机导致一个Pod失联Deployment控制器会自动在另一台健康节点上创建一个新Pod来补齐数量。整个过程不需要人为干预因为控制器一直在调谐。理解了这个哲学再去分析“为什么我的Pod数量多于声明值”这种问题时就知道大概率是滚动更新期间新旧RS重叠导致的而不是系统随机行为。2.4 实战一份干净的nginx部署yaml拆解说再多理论都不如手写一份yaml记得牢。我给出一份比较完整的nginx部署配置你可以直接拿去练手apiVersion: apps/v1 kind: Deployment metadata: name: nginx-web labels: app: nginx-web spec: replicas: 3 selector: matchLabels: app: nginx-web template: metadata: labels: app: nginx-web spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: nginx-web topologyKey: kubernetes.io/hostname containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 15 periodSeconds: 10注意几个细节podAntiAffinity让副本尽量散落到不同节点避免单点故障requests和limits不能省略后面会详细讲为什么探针的initialDelaySeconds要根据应用启动速度设置设太短会导致Pod反复被重启设太长又会让滚动更新变慢。这份yaml你甚至可以改造成自己的业务模板。核心思路是每个字段背后都对应一个调度行为或生命周期管理行为写之前先问问自己“我为什么需要这个字段”。3. 进阶核心搞懂调度器与资源模型3.1 调度流程的细节kube-scheduler做的事情看起来简单——给Pod找一台合适的节点。但“合适”二字包含了一套完整的过滤Filter和打分Score逻辑。过滤阶段把不满足硬性条件的节点剔除比如资源不足、端口冲突、不满足nodeSelector、节点有不可容忍的污点等打分阶段再根据软性偏好给剩余节点排序比如资源富余度、亲和性规则、本地镜像是否存在等。这里有一个经常被忽略的知识点调度器只负责选节点不负责真正启动Pod。它选定节点后只是更新Pod对象里的nodeName字段而kubelet watch到Pod被调度到本机后才会真正调用容器运行时创建容器。所以看到Pod一直Pending可以按顺序排查先看集群里有没有合适的节点再看调度器有没有报错最后看是不是PVC、存储类等调度前置条件没满足。3.2 requests/limits为什么不能乱填资源的requests和limits是进阶理解的第一个重要节点。requests是调度依据kube-scheduler根据每个节点上已分配requests的总量来判断节点是否还有余量limits是运行时限制kubelet通过cgroups来限制容器最多能使用的CPU和内存。CPU是可压缩资源超过limits只会被throttle容器还能运行内存是不可压缩资源超过limits直接OOMKilled。这就是为什么有些Pod CPU高一点没有大碍但内存一旦超了就立刻被杀死。排障时看到OOMKilled不要只想到“加内存”先看看是不是别人把不该设的limits设得太小或者业务本身存在内存泄漏。注意requests和limits不相等时会触发一个非常隐蔽的现象——节点资源超卖。超卖在离线任务场景下没问题但在在线业务场景可能带来稳定性隐患。生产环境建议对核心业务使用requests limits的Guaranteed配置换取更高的QoS等级。3.3 服务质量QoS等级与驱逐Kubernetes把Pod分为三种QoS等级Guaranteed所有容器requests和limits都相等、Burstable至少一个容器设置了requests但limits不相等、BestEffort完全不设置requests和limits。等级越高节点资源紧张时被驱逐的优先级越低。这个机制的残酷之处在于节点内存不足时kubelet会按照QoS从低到高、实际使用超出requests比例从高到低的顺序驱逐Pod。BestEffort的Pod是最先被清理的Guaranteed的最后才会被动。这也是为什么我一直强调生产环境不要嫌麻烦至少把requests写清楚——你好不容易设计的高可用架构可能就因为一个Pod没有设置requests在节点抖动时被第一个杀掉导致雪崩。3.4 Node亲和性与调度策略进阶阶段你要搞清楚三类调度策略的适用场景nodeSelector是简单的标签匹配nodeAffinity支持更丰富的表达式比如In、NotIn、ExistspodAffinity/podAntiAffinity则控制Pod之间的聚合与分散关系。实际运维中最常用的场景是把有状态服务固定在特定机型节点上比如需要SSD的数据库或者把同一业务的多副本分散到不同可用区。注意podAntiAffinity有requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution两种前者是硬性约束后者是软偏好。硬约束用得太多可能造成调度失败慎用。4. 网络插件与Pod通信最容易踩坑的区域4.1 CNI模型的本质Kubernetes本身不实现Pod网络它只定义了CNI接口规范——“给我一个能把Pod接入网络的方法”。CNI插件的任务就是为每个Pod创建网络命名空间、配置veth接口、分配IP地址并确保不同节点上的Pod可以互相通信。理解CNI的关键在于明白Pod网络和宿主机网络是隔离的。每个Pod有自己的网络命名空间里面有独立的eth0数据包通过veth对进入宿主机网络栈再由CNI组件完成路由或封装转发。不同插件实现方式不同Calico走BGP路由三层通过主机路由转发Flannel的VXLAN模式走UDP隧道封装Cilium通过eBPF在数据路径上做转发。选型时不要盲目追求新看你集群规模和网络拓扑复杂度来定。4.2 Service的iptables/ipvs实现ClusterIP的实现细节是进阶阶段必须吃透的硬骨头。iptables模式下的转发链大概长这样数据包进入KUBE-SERVICES链根据目的IP和端口匹配到对应的KUBE-SVC-XXX链再随机或轮询跳到某个KUBE-SEP-XXX链完成DNAT后送到Pod IP。规则数量随着Service和Endpoints数量线性增长几千条规则时iptables的更新延迟和匹配开销会成为问题。ipvs模式本质上是把LB逻辑下沉到内核的IPVS模块用哈希表代替线性链匹配性能和扩展性都更好。大多数云厂商的Kubernetes集群默认已经用ipvs模式。排障时你可以通过iptables-save | grep Service或ipvsadm -Ln来确认规则是否正常生成。这条命令应该刻进DNA里。4.3 从Service到IngressService解决的是集群内部的稳定访问而Ingress解决的是外部流量如何按域名/路径转发到不同Service。Ingress本身只是一个API对象真正干活的是Ingress Controller比如nginx-ingress、traefik。进阶容易忽略的一点Ingress Controller是运行在集群里的Pod它的Service通常是LoadBalancer类型或NodePort类型。外部流量先打到Ingress Controller再由它根据Ingress规则转发到后端Service。所以排查Ingress不通时链路是域名解析 - LB/NodePort - Ingress Controller - Service - Endpoints - Pod每一环都有可能出问题不要一上来就怀疑Ingress Controller挂了。4.4 网络排查方法论我自己在排网络问题时习惯按“从外到内、逐层验证”的顺序来先用DNS解析确认域名指向正确再ping或telnet LB地址看端口通不通。进入Ingress Controller的Pod内部curl一下后端Service的ClusterIP确认集群内访问是否正常。检查Service的Endpoints是否有Pod地址没有就查Pod是否Ready。进入后端Pod检查应用监听端口是否正确、容器内网络是否正常。我曾经排查过一个“每次发布后短时间有大量502”的诡异问题最后发现是Ingress Controller连接Pod的keepalive时间过长而滚动更新时旧Pod退出太急连接还没来得及断开就被杀了。解决办法是给Deployment配置terminationGracePeriodSeconds加长优雅退出时间同时在Ingress侧调短上游keepalive。这种问题不拆链路根本定位不到。5. 存储进阶从emptyDir到PV/PVC5.1 卷的本质变化容器里的文件系统是临时的容器一重建数据就没了。emptyDir卷解决了同一Pod内多容器共享文件的问题但Pod被删除后数据依然丢失。要想数据持久化就必须引入外部存储——这就是PV/PVC存在的意义。很多初学者搞不清PV和PVC的关系。用一句话说PV是管理员预先准备好的存储资源就像硬盘PVC是用户提出的存储需求申请就像“我要一块100G的SSD”。用户不关心PV具体是NFS还是云盘实现他只写PVC描述需求系统负责把合适的PV绑定给PVC。这个解耦设计让应用层的yaml不依赖具体存储实现换存储厂商时不需要改动应用定义。5.2 PV/PVC/StorageClass三者关系StorageClass把“动态供给”引入存储体系。以前管理员要预先创建一堆PV等着被绑定有了StorageClass后用户创建PVC时系统会根据PVC里声明的storageClassName自动调用存储插件创建PV并绑定到PVC。你写的是“我要一块高速存储”底层是云厂商的SSD云盘还是本地的Ceph RBD全由StorageClass决定。这里有一个排障高频点PVC一直Pending。最常见的原因是StorageClass不存在或名字写错或存储插件没有正确配置。kubectl describe pvc里通常会给出具体Event先看这个再去找存储端配置不要盲目重装插件。注意使用local-path这类本地存储动态供给时Pod会被调度到PV所在的节点。如果PV所在节点宕机数据将无法漂移这在有状态服务场景意味着必须接受停机时间。核心数据别用单节点本地存储老老实实上分布式存储或云厂商托管盘。5.3 如何选择存储方案存储选型没有银弹取决于状态化程度和数据量级Rook-Ceph适合自建集群追求开源统一存储的场景云厂商托管块存储如云盘适合需要稳定低延迟的场景对象存储S3/MinIO适合海量非结构化数据。进阶阶段建议至少亲手体验一次动态PVC创建和扩容的完整流程你会发现”存储“在Kubernetes里其实也是控制器的调谐逻辑并没有想象中那么神秘。6. 源码级学习怎样读《深入理解Kubernetes源码》才不会劝退6.1 读源码的前提与性价比分析先说句实话不是每个人都必须读源码。如果你日常工作是以部署、运维、排障为主把Kubernetes的API设计逻辑和运行机制吃透已经能解决90%的问题。但如果你想深入做二次开发比如写自定义控制器、开发Operator、定制调度器插件或者想达到“遇到任何诡异问题都能从架构层面推导原因”的水平那源码是绕不开的。《深入理解Kubernetes源码》这本书适合的读者画像有Go语言基础熟悉Kubernetes核心概念知道控制器模式的大致逻辑。零基础直接啃源码大概率会看吐因为代码量太大、函数调用链太长、很多设计细节需要配合实际行为才能理解。6.2 推荐阅读路径读源码切忌“从头到尾顺序读”要顺着核心调用链走。我建议的路径是先读kube-apiserver的启动流程理解Config、Server、APIExtensionServer这些核心结构是如何组装的以及请求是怎么经过认证、授权、准入控制、校验、存储到etcd的。再读informer机制这是Kubernetes控制器模式的基石。理解Reflector如何通过ListAndWatch从apiserver同步对象数据、DeltaFIFO如何存储增量事件、Indexer如何建立本地索引几乎所有controller的调谐循环都依赖这套东西。然后选一个controller精读比如kube-controller-manager里的DeploymentController或JobController看它如何监听事件、如何触发调谐、如何通过client-go回写状态。最后读kube-scheduler的调度框架看Filter和Score阶段如何被插件化理解framework扩展点是如何让开发者自定义调度逻辑的。每读一个部分就在本机起一个真实的kind集群或者minikube环境通过实际行为反推代码逻辑效果比死磕代码好十倍。6.3 用运行时观察验证对原理的理解读源码不是目的验证理解才是。我习惯用“行为实验”来对照原理比如创建一个Deployment然后实时watch apiserver里Pod对象的变更事件或者写一个自定义CRD和一个小型controller亲身体验从“声明状态”到“调谐完成”的整个闭环。实际操作一遍比读十遍代码都管用。7. 进阶学习实操清单与避坑经验7.1 三个月实操路线没有场景的纸上谈兵学不会Kubernetes。我整理了一条低门槛可执行的路线建议有条件的都亲手做一遍第1个月在本地用minikube或kind搭一套环境完成以下操作手写Deployment/Service/Ingress配置、修改副本数观察扩容、配置滚动更新策略并发起一次更新、故意把容器里进程kill掉观察重启。第2个月模拟故障演练。用kubectl drain排空一台节点观察Pod如何驱逐给节点打污点观察未容忍的Pod如何Pending给某个Service误写selector观察Endpoints如何变化用kubectl exec进入Pod手动制造网络延迟观察探针和流量异常。第3个月挑战一个接近实战的完整任务部署一套带持久化存储的有状态中间件配置网络策略NetworkPolicy接上Prometheus监控最后写一篇“假如这个集群发生故障我该如何定位”的思维复盘。这套路线走下来比我说的任何理论都管用。7.2 高频踩坑速查表下面这些坑是我在学习和带团队过程中反复见到的整理成一张速查表现象常见原因排查命令/做法Pod一直Pending节点资源不足、PVC未绑定、调度器异常kubectl describe pod看EventsPod反复重启探针配置过严、启动时间过长、内存OOMkubectl logs --previous看上一次日志Service访问不通Endpoints为空、kube-proxy规则未更新、selector不匹配kubectl get endpointsiptables-save更新后长时间不可用maxUnavailable设置过大、就绪探针失效检查滚动更新策略和readinessProbePVC PendingStorageClass不存在、存储插件故障kubectl describe pvc看Events节点NotReady网络插件异常、kubelet挂掉、磁盘满systemctl status kubelet 检查磁盘7.3 运维兜底经验最后分享几点我个人实操中的体会第一养成“先describe再logs”的习惯。命令kubectl describe里Events展示的系统判断信息——比如调度失败的具体原因、拉镜像失败的真实错误、探针失败的具体响应码——都是系统帮你梳理好的最有效线索。第二不要把集群当成黑盒。Pod报错时别急着在网上搜“Kubernetes Pod Pending 解决方法”。先追链路哪个组件在负责这块逻辑它的判断依据是哪些字段我能不能查到它当前的内部状态追完再搜索你会发现能搜到更多有用的东西。第三有条件就在自己的项目里多折腾。Kubernetes进阶路线没有终点我到现在碰到陌生问题第一反应还是去看源码和实际抓包分析而不是靠感觉。每一次把“为什么”弄懂都是在往一个资深实战者的方向靠拢。
返回列表