资讯动态

Kubernetes Pod静态机制详解:配置、生命周期、资源与探针

发布时间:2026/10/26 3:19:44 来源:尧图企业网站定制
写这个系列写到第五篇绕不开一个东西Pod。身边不少刚上手Kubernetes的朋友对Pod的理解停在一个很模糊的位置——知道它是最小的调度单元写YAML的时候照模板抄一份跑起来就算完事。等真到线上出问题Pod卡在Pending半天不动、容器反复CrashLoopBackOff、节点资源看着还挺空却调度不上去、日志打到一半突然断——回头一查八成是对Pod这一层的东西没吃透。这一篇就正面拆Pod。上篇只聊静态的Pod它到底是个什么东西、清单怎么写才不埋雷、生命周期怎么走、多容器怎么协作、资源怎么算、探针怎么配。下篇再讲控制器怎么管它、亲和性怎么排、污点容忍怎么绕、拓扑分布怎么散这些动态的部分。这里说的Pod是Kubernetes里被调度、被扩缩、被回收的最小单位一个Pod里可以只跑一个容器也可以跑多个共享网络与存储的容器。它解决的核心问题是一组关系紧密、必须同生共死的进程如何作为一个整体被安置到某台机器上。适合谁看如果你能照着文档跑通Deployment但说不清Pod内部机制或者你正在维护集群、被各种Pod状态和驱逐事件搞得有点懵这篇应该能给你一些能直接拿去用的东西。1. 先把Pod这个概念掰开揉碎1.1 Pod不是高级容器它更像一套合租房我见过太多人把Pod当成容器的另一种写法这个认知偏差是所有后续踩坑的源头。Pod是一个逻辑主机不是容器的别名。你可以把它理解成一套合租房里面的容器是合租的室友大家共享同一个门牌号Pod IP、同一个客厅网络命名空间、同一个储物间Volume但各自有独立的卧室根文件系统。落到内核层面看同一个Pod内的所有容器共享 Network Namespace、IPC Namespace、UTS Namespace可选共享 PID Namespace。网络命名空间共享意味着什么意味着它们看到的是同一张网卡、同一个IP、同一个端口空间。这就是为什么同一个Pod里两个容器不能同时监听8080——不是Kubernetes拦着你是内核层面就冲突了。UTS共享意味着它们看到同一个主机名容器里执行 hostname 拿到的是Pod的名字而不是容器名。但文件系统是真不共享的。每个容器有自己的rootfs镜像里有什么就是什么。想让两个容器看到同一份文件必须显式声明一个Volume挂到两边。这个区别在实际排查时特别关键很多人发现日志容器读不到应用日志第一反应是共享没生效其实是根本没挂Volume两个容器各自在各自的rootfs里找文件当然找不到。1.2 为什么Kubernetes不直接调度容器这是个值得停下来想清楚的设计取舍。既然容器是运行进程的实体为什么不让调度器直接管容器非要加一层Pod答案藏在超亲密关系这个词里。设想一个典型的Web服务主进程处理请求旁边挂一个日志转发进程把写盘的日志实时推走。如果这两个东西分到两台机器上日志进程就得跨网络去读远端磁盘延迟高、丢数据的概率大、还得处理网络分区。把它们绑在一起同一个节点、同一个IP、同一个Volume、同一个生命周期——这就是Pod存在的意义。代价是灵活性。一个Pod里的容器没法单独扩缩不能一个跑在A节点一个跑在B节点资源是打包计算的。Kubernetes的设计者认为这个代价可以接受因为真正需要超亲密的场景本来就不该拆开。我在实际项目里见过有人把一个服务的三个副本拆成三个单容器Pod又想共享缓存目录最后只能上网络存储性能和复杂度都很难看。这种时候老老实实承认它们是一个整体用多容器Pod或者干脆合并成一个容器反而更省事。1.3 Pod的几种典型形态先建立整体地图在深入细节之前先把Pod能长成什么样摆出来后面所有的配置细节才有归属。形态结构典型场景生命周期特点单容器Pod一个容器绝大多数无状态服务最简单Deployment的标准形态多容器PodSidecar主容器 旁路容器日志采集、配置热加载、流量转发同生共死一起创建一起销毁多容器PodAmbassador主容器 出站转发主容器只连本地端口由旁路容器连外部主容器代码不需要知道外部地址多容器PodAdapter主容器 格式转换把主容器暴露的指标转成统一格式对上层监控系统屏蔽差异Init容器形态若干一次性容器 主容器等待依赖就绪、初始化目录权限、拉取配置Init按顺序跑完才启动主容器这张表里单容器占实际使用的九成以上。多容器不是高级玩法它是为特定耦合场景准备的工具用错了比不用更糟——比如把两个可以独立扩缩的服务塞进一个Pod扩容时只能整体扩资源利用率会很难看。2. Pod的配置骨架从最小清单到生产模板2.1 一份最小可用的Pod清单逐行拆先看最短能跑起来的样子apiVersion: v1 kind: Pod metadata: name: demo-web namespace: default labels: app: demo-web tier: frontend spec: containers: - name: web image: nginx:1.25-alpine ports: - containerPort: 80 name: httpapiVersion 写 v1因为Pod属于核心组没有组名前缀。kind 是 Pod。metadata 下面的 name 在同一个命名空间内必须唯一namespace 不写就落到 default。labels 是键值对后面Service选Pod、Deployment管Pod全靠它。真正容易误解的是 ports 这一段。containerPort 这个字段不改变任何运行时行为它纯粹是文档性质的声明。容器实际监听哪个端口完全由镜像里的进程决定。我见过有人把 containerPort 从80改成8080然后疑惑为什么nginx还在80上响应——因为改的只是一行注释。真正要改端口得改镜像里的配置或者用 args 覆盖启动参数。只有当你想给端口起名字、然后在Service里用名字引用时这个字段才有实际价值。比如上面写的 name: httpService里就可以写 targetPort: http将来端口号变了只要名字不变Service不用动。这是个小技巧但迁移的时候能省不少事。2.2 生产环境里我常备的Pod清单模板最小清单只能证明能跑生产环境要考虑的东西多得多。下面这份是我自己项目里反复用的骨架字段不算全但覆盖了最容易出事的那些点apiVersion: v1 kind: Pod metadata: name: demo-api namespace: prod labels: app.kubernetes.io/name: demo-api app.kubernetes.io/version: 1.4.2 app.kubernetes.io/component: backend annotations: owner: team-backend change-ticket: OPS-2317 spec: terminationGracePeriodSeconds: 30 serviceAccountName: demo-api-sa securityContext: runAsNonRoot: true runAsUser: 10001 fsGroup: 10001 containers: - name: api image: registry.internal.example/demo-api:1.4.2 imagePullPolicy: IfNotPresent command: [/app/server] args: [--port8080, --config/etc/demo/config.yaml] env: - name: APP_ENV value: prod - name: DB_PASSWORD valueFrom: secretKeyRef: name: demo-db key: password resources: requests: cpu: 500m memory: 512Mi limits: cpu: 2000m memory: 1Gi readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: path: /livez port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 volumeMounts: - name: config mountPath: /etc/demo readOnly: true - name: logs mountPath: /var/log/app volumes: - name: config configMap: name: demo-api-config - name: logs emptyDir: sizeLimit: 2Gi几个字段值得单独说说。imagePullPolicy 设成 IfNotPresent 配合明确版本号是为了避免每次启动都去拉镜像也避免 latest 标签带来的版本漂移。真正上线我建议把镜像摘要digest也考虑进去标签可以被人覆盖摘要是不可变的。terminationGracePeriodSeconds 默认30秒很多人不改。但如果你的应用处理长连接或者要做数据落盘30秒可能不够进程会被SIGKILL直接砍掉正在处理的事务就丢了。我的经验是设成比应用最大请求超时时间再多10到15秒比较稳妥。securityContext 这几行看着琐碎其实是过安全审计的必答题。runAsNonRoot 让容器不以root启动runAsUser 指定UIDfsGroup 决定挂载卷的组归属。这三行不加很多集群的准入策略会直接拒掉你的Pod。2.3 标签和注解不是一回事别混着用labels 和 annotations 都是键值对但用途完全不同。labels 会被索引、会被选择器查询所以它必须短、必须是有限的枚举值。annotations 不进索引用来存一些描述性信息比如负责人、变更单号、构建时间、配置校验和。我踩过的坑是把一大段JSON配置塞进label结果超过长度限制提交时直接报错还有个更隐蔽的问题label一旦变动如果Deployment的selector和它绑定了会导致Pod被认为不属于这个Deployment引发一轮意外的重建。所以记住一条能放annotations的绝不放labels。3. Pod的生命周期状态机其实没那么玄3.1 五个阶段状态各自意味着什么kubectl get pod 输出的 STATUS 那一列反映的是Pod的 phase。这个字段只有五个合法值理解它们的边界能省掉很多猜测。Phase含义常见触发原因PendingPod已被API Server接收但还没完成调度或镜像未就绪资源不足、节点亲和性不满足、镜像拉取中RunningPod已绑定到节点至少一个容器在运行或正在启动正常状态但不代表业务可用Succeeded所有容器正常退出且不会重启一次性任务完成Job的终点Failed所有容器都已终止至少一个异常退出启动失败、退出码非0、被驱逐Unknown无法获取Pod状态节点失联、kubelet通信中断这里有个特别容易误解的点Running 不等于业务可用。一个Java应用可能容器进程起来了但Spring上下文还在初始化端口还没监听这时候 phase 就是 Running可流量打进来全是连接拒绝。判断业务可用要看 readinessProbe 的结果或者看 READY 列的 x/x 是不是全绿。把 phase 和 ready 这两个概念分开是排查问题的第一课。3.2 容器状态比Pod状态更细排查要往下钻一层Pod的phase是粗粒度的汇总真正的细节在容器状态里。kubectl get pod 加 -o yaml看 status.containerStatuses每个容器有 state 字段取值是 waiting、running、terminated 三选一。waiting 里最有信息量的是 reason。ImagePullBackOff 说明镜像拉不到可能是仓库地址错、凭证没配、网络不通CrashLoopBackOff 说明容器起来了又挂了反复重试CreateContainerConfigError 通常是ConfigMap或Secret引用了不存在的键。这几个词看着像黑话其实每个都对应一条明确的排查路径。terminated 里的 exitCode 是金矿。退出码137意味着被SIGKILL最常见的原因是内存超限被OOMKiller干掉或者liveness探针失败触发重启。退出码143是SIGTERM说明是正常终止流程。退出码1是应用自己报错退出。记住137和143这两个数能少走很多弯路。restartPolicy 这个字段作用在整个Pod上不是单个容器。取值 Always、OnFailure、Never。Deployment管的Pod必须是 AlwaysJob一般用 OnFailure 或 Never。有人想在Deployment里设 OnFailure 让失败容器不重启这是无效的——控制器层面会覆盖这个行为。3.3 用describe和events把卡点定位到具体环节排查Pod问题我的固定顺序是四步几乎能覆盖八成场景。第一步看概览kubectl get pod demo-api -n prod -o wide-o wide 会多给节点名和Pod IP能立刻判断是不是调度到了预期节点。如果 STATUS 是 Pending 且 NODE 列是 none问题就在调度环节跟应用代码一点关系没有。第二步看事件这是信息密度最高的地方kubectl describe pod demo-api -n prod输出末尾的 Events 段落会按时间倒序列出这个Pod经历的所有事件。FailedScheduling 会告诉你调度器为什么拒绝比如 0/5 nodes are available: 3 Insufficient cpu, 2 node(s) had taint。FailedMount 会告诉你卷挂不上。Unhealthy 会告诉你探针失败了。我个人的习惯是直接看 Events比翻 YAML 快得多。第三步如果事件里提到节点层面的问题把视角切到节点kubectl describe node node-name | grep -A 20 Non-terminated Pods能看到这个节点上所有Pod的资源请求总和判断是不是真的被占满了。第四步看日志尤其是上一个容器实例的日志kubectl logs demo-api -n prod --previous--previous 这个参数是排查 CrashLoopBackOff 的关键。容器重启后kubectl logs 默认给的是新实例的日志可能一片空白而真正的死因在上一个实例里。这个参数我用得极频繁值得记牢。4. 多容器Pod的实战共享边界在哪4.1 一张表说清共享与隔离多容器Pod出问题绝大多数是因为没搞清楚哪些东西共享、哪些不共享。资源类型是否共享说明网络命名空间共享同一Pod IP同一端口空间可用localhost互访IPC命名空间共享SysV IPC、POSIX消息队列可跨容器使用UTS命名空间共享同一主机名容器内hostname返回Pod名PID命名空间默认隔离可开启设 shareProcessNamespace: true 后可见彼此进程根文件系统隔离每个容器独立rootfsVolume按需挂载必须显式在两个容器里都声明volumeMounts环境变量隔离各自独立不互相传递用生活类比合租房的室友共用一个门牌号和客厅但各自卧室是私密的想共用冰箱就得一起出钱买一台放客厅——这个一起出钱买就是显式声明Volume。不声明谁都别想用。localhost互访这一条特别实用。主容器监听127.0.0.1:8080旁路容器直接连localhost:8080就能通不需要知道Pod IP。这让配置变得非常简单也让主容器完全不需要感知自己在Kubernetes里跑。4.2 Sidecar落地日志旁路转发的完整写法日志收集是最经典的多容器场景。应用只管往文件写旁路容器负责读取并推走应用代码零侵入。apiVersion: v1 kind: Pod metadata: name: demo-order labels: app: demo-order spec: containers: - name: app image: registry.internal.example/demo-order:2.1.0 volumeMounts: - name: app-logs mountPath: /var/log/app - name: log-shipper image: registry.internal.example/log-shipper:0.9.3 args: [--source/var/log/app, --targetcollector.internal:5044] volumeMounts: - name: app-logs mountPath: /var/log/app readOnly: true resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 128Mi volumes: - name: app-logs emptyDir: sizeLimit: 1Gi这段配置里有三个细节值得强调。第一旁路容器挂载时加了 readOnly: true防止它误删应用日志这是个顺手就做的小防护。第二旁路容器给了很小的 resources因为它的活儿很轻不设的话会按BestEffort处理节点压力大时最先被驱逐反而在最需要日志的时候丢日志。第三emptyDir 设了 sizeLimit。第三点特别重要我踩过坑。emptyDir 默认落在节点的磁盘上不设上限的话如果应用日志写得疯或者转发端出故障导致文件堆积会一路把节点磁盘写满然后节点上所有Pod一起遭殃触发节点级驱逐。设了 sizeLimit 之后超过限制时kubelet会驱逐这个Pod影响范围限制在单个Pod内。这个参数不写等于给了应用一个把整台机器拖垮的能力。4.3 Init容器把等依赖这件事从应用代码里拿出去Init容器是一类特殊容器它在所有主容器启动之前按顺序执行全部成功后才启动主容器。失败就按restartPolicy重试主容器一直等着。spec: initContainers: - name: wait-for-db image: busybox:1.36 command: - sh - -c - | until nc -z demo-db 3306; do echo waiting for demo-db... sleep 2 done - name: fix-permissions image: busybox:1.36 command: [sh, -c, chown -R 10001:10001 /data] volumeMounts: - name: data mountPath: /data containers: - name: app image: registry.internal.example/demo-order:2.1.0 volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {}这个用法解决了两个长期困扰应用开发的问题一是启动时的依赖等待以前要在代码里写重试循环现在交给Init容器应用代码干净了二是挂载卷的权限问题emptyDir挂载后默认属主是root非root容器写不进去用一个Init容器chown一下就解决了。几个硬约束要记牢。Init容器严格串行第一个跑完才跑第二个没有并行。Init容器不支持livenessProbe、readinessProbe、startupProbe因为它们本来就是一次性任务成功即退出。Init容器失败时会按Pod的restartPolicy重试如果设的是NeverPod就直接进入Failed。还有一个容易忽略的点Pod的资源请求计算里Init容器的资源是按取所有Init容器中最大的那个来算的不是累加因为它跑完就释放了。这个规则在做资源规划时很关键。5. 资源请求与限制Pod规格里最容易埋雷的地方5.1 requests和limits一个是订座一个是饭量这两个概念经常被混着说但它们作用在完全不同的环节。requests 是给调度器看的。调度器在选节点时会把节点上所有Pod的requests加起来看还剩多少。你写 cpu: 500m意思是我至少要半个核调度器就按这个给你找位置。这个数字跟实际用了多少没关系写大了浪费写小了容易把节点塞爆。limits 是给内核看的。cgroup按这个值设上限超了就动手。CPU和内存的处理方式完全不同这是最关键的一点CPU是压缩型资源。超过limit不会被杀只会被限流throttle表现是响应变慢。cgroup的cpu quota机制会让进程在每个周期内跑满配额后被强制挂起等到下个周期。短期看只是慢长期看P99延迟会明显恶化。内存是非压缩型资源。超过limit没有慢一点这种中间态内核的OOMKiller直接杀进程容器退出码137然后被重启。这个差异决定了配置策略。CPU的limit可以适当放宽因为最坏情况只是变慢内存的limit必须精确设小了就是定时炸弹设大了浪费不说还容易掩盖内存泄漏。我的习惯是内存的requests和limits设成相等让QoS等级至少落在Burstable偏上的位置减少被驱逐的概率。5.2 QoS等级决定驱逐顺序的隐形标签Kubernetes根据requests和limits的配置自动给每个Pod打一个QoS等级这个等级直接决定节点资源紧张时谁先被赶走。QoS等级判定条件驱逐优先级适用场景Guaranteed所有容器都设了requests和limits且CPU和内存的requests等于limits最低最后被驱逐核心服务、数据库类Burstable至少一个容器设了requests但不满足Guaranteed条件中等大多数业务服务BestEffort所有容器都没设requests和limits最高最先被驱逐临时任务、批处理判定规则比想象中严格。要拿到Guaranteed必须每个容器、每个资源维度CPU和内存都设了值而且requests和limits完全相等。少设一个内存limit哪怕CPU配得再完美也只能是Burstable。这个等级不需要你手写但你必须知道自己的Pod落在哪一档。生产环境里我看到过一批核心服务因为漏配内存limit掉到Burstable节点一紧张就被优先驱逐业务方还以为是平台不稳定。查了半天问题出在一行没写的limit上。5.3 参数怎么算一个Java服务的完整估算过程抽象讲原理没什么用拿个具体例子走一遍。假设一个Java服务压测下来QPS峰值500单请求平均处理时间20毫秒。先算并发度500 × 0.02 10也就是平均有10个请求同时在处理。考虑到突发和GC停顿线程池开到20比较稳妥。CPU估算每个请求是IO密集型查库调下游CPU时间大概占处理时间的20%也就是4毫秒。500 QPS × 4ms 2秒CPU时间需要2个核才能打平。留50%余量requests设 2000mlimits设 4000m 允许突发。内存估算JVM堆设2Gi堆外部分包括元空间约256Mi、直接内存Netty的DirectBuffer约256Mi、线程栈20线程 × 1Mi 20Mi、JIT代码缓存约240Mi、GC自身开销。加起来大约 2Gi 0.8Gi ≈ 2.8Gi。再留20%给系统调用和页缓存最终 requests 和 limits 都设 3.5Gi。资源requestslimits计算依据CPU2000m4000m500 QPS × 4ms 2核留50%余量limit允许突发内存3584Mi3584Mi堆2Gi 堆外0.8Gi 20%余量两者相等保证Guaranteed这里有个JVM特有的坑必须提。容器里的JVM在早期版本默认读的是宿主机的内存而不是cgroup限制导致堆开得过大一启动就被OOMKilled。现在的JDK版本基本都支持容器感知了但如果你跑的是老版本务必显式加 -XX:MaxRAMPercentage75 或者 -Xmx 固定堆大小。我见过一个服务limit设4GiJVM却按64Gi的宿主机内存算堆直接开到16Gi容器起来20秒必挂排查了两天才定位到。6. 探针体系三个探针各管一段6.1 职责边界配错一个就够喝一壶三种探针名字很像作用完全不同配错是新手最常见的翻车点。livenessProbe 回答这个容器还活着吗。失败达到阈值kubelet会杀掉容器并按restartPolicy重启。注意它不会新建Pod只是重启容器。readinessProbe 回答这个容器能接流量吗。失败时不会重启只是把这个Pod从Service的Endpoints里摘掉流量不再打进来。等探针恢复自动加回去。startupProbe 回答这个容器启动完了吗。它在启动阶段生效成功之前liveness和readiness都不执行。这是给慢启动应用准备的保护伞。三者的关系可以用一句话概括startup管开头readiness管过程liveness管兜底。一个应用启动要40秒、运行中偶尔因为连不上数据库短暂不可用正确配法是startup给足60秒readiness灵敏一点快速摘流量liveness放宽避免误杀。如果只配了liveness且initialDelaySeconds给10秒那必然陷入启动超时被杀、重启、再超时的死循环日志里什么都看不到因为容器还没跑到打日志的地方就被砍了。6.2 参数配置的口诀和一组可用值参数有五个initialDelaySeconds、periodSeconds、timeoutSeconds、successThreshold、failureThreshold。我的经验口诀是liveness宽松、readiness灵敏、startup兜底。探针initialDelayperiodtimeoutfailureThreshold说明startupProbe05324最多给120秒启动窗口readinessProbe552315秒内连续失败就摘流量livenessProbe0由startup保护103660秒持续异常才重启这套值在我维护的几个服务上跑了很久基本不用调。要注意的是readiness的timeout不能设得太小如果探针接口本身要查数据库2秒可能不够会误判。探针接口的设计原则是轻量、不依赖外部组件最好是纯内存判断别在探针里做重操作。我见过有人在 /healthz 里查一次全表统计结果探针把数据库打挂了这个教训值得记。还有一个反直觉的点探针的failureThreshold次数乘以periodSeconds才是真正的容忍时长。liveness设period 10秒、failureThreshold 6意味着要连续60秒失败才重启。这个乘积决定了你的服务对瞬时抖动的容忍度比单看任何一个参数都有意义。7. 常见问题与排查技巧实录7.1 高频问题速查表下面这些是我这几年反复遇到的按现象归类方便直接对号入座。现象最可能的原因第一条命令处理方向Pending节点列为none资源不足或亲和性不满足kubectl describe pod看FailedScheduling事件的具体原因ImagePullBackOff镜像地址错、私有仓库凭证缺失kubectl describe pod检查imagePullSecrets和镜像tag是否存在CrashLoopBackOff应用启动报错或探针误杀kubectl logs --previous先看上一个实例日志再核对探针参数Running但READY 0/1readinessProbe失败kubectl describe pod看Unhealthy事件exec进去手动curl探针路径退出码137内存超limit被OOMKilledkubectl describe pod看Last State的Reason是否为OOMKilled调大内存limit退出码143收到SIGTERM正常终止看终止原因通常是缩容或滚动更新检查preStop钩子是否够用Pod频繁被驱逐QoS等级低或节点压力kubectl get events补全requests/limits提升QoS等级多容器读不到文件未挂载共享Volumekubectl exec 进入查看两个容器都要声明volumeMounts7.2 我自己的排障顺序和一些不那么常规的技巧遇到Pod异常我的动作基本是固定的先 get 看状态和节点再 describe 看事件然后 logs --previous 看死因最后 exec 进去手动验证。这四步能解决八成问题剩下两成再往节点层和网络层钻。分享几个不太写进文档的技巧。第一个describe 输出很长我习惯用 grep 直接抽事件段kubectl describe pod demo-api -n prod | sed -n /Events:/,$p第二个事件有时效性默认只保留一小时。如果问题发生得早用这个查历史kubectl get events -n prod \ --field-selector involvedObject.namedemo-api \ --sort-by.lastTimestamp第三个判断是不是探针误杀最直接的办法是临时把liveness去掉重跑一次。如果去掉后应用能正常起来那百分之百是探针参数问题接着调 initialDelaySeconds 或者换成 startupProbe 就行。这个手法虽然粗暴但定位效率极高。第四个CrashLoopBackOff 的时候容器活得太短exec 根本来不及进去。这时候可以临时把 command 覆盖成 sleep让容器挂在那儿再exec进去手工执行启动脚本一步步看卡在哪kubectl run debug-pod --imageregistry.internal.example/demo-api:1.4.2 \ --restartNever --command -- sleep 3600这个调试Pod的思路是从原Pod的清单里扒下镜像、环境变量、Volume挂载去掉探针改成一个不会退出的命令。我一般会直接 copy 原YAML改比手敲靠谱。第五个心得关于 emptyDir 的 sizeLimit。前面提过一次这里再强调凡是用 emptyDir 的地方一律显式设 sizeLimit。这不是可选项是防护栏。没设的Pod在大流量或者下游故障时会把节点磁盘写满然后整个节点上的Pod一起遭殃故障半径从一个Pod扩大到一台机器。这个参数就一行加不加的差别可能是半小时故障和半天故障。7.3 关于资源超卖的一个实测观察最后说个实测出来的体会。很多团队为了提升集群利用率会把 requests 设得很低、limits 设得很高靠超卖来塞更多Pod。短期看资源利用率上去了长期看会积累两个隐患。一是CPU throttling会在高峰期集中爆发。节点上所有Pod都以为自己能用高limit实际物理核就那么多cgroup调度器只能轮流掐结果每个Pod都在被throttle延迟全线恶化。这个现象很隐蔽因为CPU使用率指标看着不高但容器的nr_throttled计数在飙升。查这个要看容器的cpu.stat文件throttled_time 持续增长就是信号。二是内存的OOM风险在节点层面会被放大。单个Pod超限只是自己被杀但如果多个Pod同时冲高节点的可用内存会被迅速吃光触发节点级驱逐一口气赶走一批Pod。这种连锁反应排查起来很痛苦因为每个Pod的单点日志都看不出问题。我现在的做法是CPU的requests按实际P50用量设limits给到requests的1.5到2倍允许合理突发内存的requests和limits全部相等杜绝超卖。这么配之后集群利用率大概在60%左右看着不极致但线上抖动和驱逐事件基本消失了。资源利用率这个数字好看不好看和业务体验之间没有必然联系别被它牵着走。至于Pod的调度约束、亲和性、污点容忍、拓扑分布这些让Pod落到哪台机器的规则牵扯到节点标签体系和集群拓扑设计展开讲篇幅不小留到下一篇单独拆。这一篇把Pod本身的静态部分吃透后面那些动态规则才有落脚点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑