2026/6/20 9:30:48
网站建设
项目流程
做网站用jsp还是html,网页设计app软件,建设工程立项在哪个网站查询,广州网址大全前言
本文基于对 Hidden Kubernetes Bad Practices Learned the Hard Way During Incidents 的阅读后#xff0c;在尊重原文内容的基础上进行了本地化翻译与整理#xff0c;在语义表达上相较于机翻#xff0c;更加贴近中文母语者的阅读习惯#xff0c;提升整体的可读性与理…前言本文基于对 Hidden Kubernetes Bad Practices Learned the Hard Way During Incidents 的阅读后在尊重原文内容的基础上进行了本地化翻译与整理在语义表达上相较于机翻更加贴近中文母语者的阅读习惯提升整体的可读性与理解成本。在每个事件的原文翻译之后都会补充我的个人注解也算是对原作者没有提到的内容进行补充。所有个人注解均会明确标识并独立于原文翻译部分呈现不会穿插在原文内容之中以避免对原意造成干扰。原文一共 7 个故障本文去掉了故障二的翻译解析因为我没用过 ArgoCD...Kube-proxy 默认负载均衡机制导致 HTTP/2 流量热点问题原文翻译本次事故发生在首次把 HTTP/2 流量引入 Kubernetes 时。我们发现 HTTP/2 流量在后端 Pod 之间的分布出现了异常负载。其表现为某个 Pod 持续出现 CPU 使用率和延迟都很高但监控指标中同一服务下的其他 Pod 副本几乎完全处于空闲状态。我们错误地认为 HTTP/2 流量在 kube-proxy 中的行为会和 HTTP/1.x 一样但事实并非如此。kube-proxy 对这两种协议的处理方式并不一样。默认情况下kube-proxy 在四层TCP 层进行负载均衡它是通过连接级别而不是请求级别分发流量当客户端向 Kubernetes Service 建立一个 TCP 连接时kube-proxy 会使用轮询算法选择一个后端 Pod。该 TCP 连接上的所有请求都会被转发到同一个 Pod 上。对于 HTTP/1.x 客户端来说这种方式通常没有问题因为客户端一般会建立多个短连接生命周期较短流量因此可以较为均匀地分布。但对于单个连接多路复用处理所有请求的 HTTP/2 客户端来说这种机制会导致所有请求都被转发到同一个 Pod其余 Pod 大多处于空闲状态。最终该 Pod 上的 CPU 使用率和请求延迟显著上升成为性能瓶颈。由于对 kube-proxy 连接处理机制理解有限这个问题在最初阶只能通过调整应用层的 keep-alive 配置暂时缓解。但从长期来看引入支持 HTTP/2 的服务网格并通过七层负载均衡来解决才是最终方案。关键结论默认情况下通过 Service 的流量是由 kube-proxy 在 TCP 连接级别进行负载均衡的在与 HTTP/2 的多路复用机制结合使用时容易导致流量分布不均。如果没有七层路由能力单个 Pod 可能会成为瓶颈而其他 Pod 却处于空闲状态从而引发性能下降。个人注解在注解前需要先明确几点k8s svc 只是一个转发规则kube-proxy 才是将访问 svc 的流量转发到 Pod 的执行者以上都发生在 TCP 层L4这个问题的核心原因在于kube-proxy 默认是四层负载均衡只在 TCP 连接建立SYN时选择一个 PodHTTP/2 一般使用单个连接多路复用处理所有请求flowchart TB Client[Clientbr/(HTTP/2 长连接)] Service[k8s svcbr/(虚拟 IP)] KubeProxy[kube-proxybr/(L4 / TCPbr/连接建立时选择 Pod)] PodA[Pod Abr/(导致高 CPU / 高延迟)] PodB[Pod Bbr/(几乎空闲)] PodC[Pod Cbr/(几乎空闲)] Client --|1 个 TCP 连接| Service Service -- KubeProxy KubeProxy --|连接绑定| PodA KubeProxy -.- PodB KubeProxy -.- PodC所以他的正确解法应该是引入支持 HTTP/2 的七层负载均衡或服务网格Ingress ControllerIstioflowchart TB Client[Clientbr/(HTTP/2)] L7[Ingress / Service Meshbr/(L7 按请求负载均衡)] PodA[Pod A] PodB[Pod B] PodC[Pod C] Client --|HTTP/2| L7 L7 --|请求 1| PodA L7 --|请求 2| PodB L7 --|请求 3| PodCCPU 限制导致不必要的降级原文翻译这是我在资源管理中遇到过最困惑的事件之一。乍一看集群状态完全正常节点监控面板显示 CPU 仍然充足也没有任何资源耗尽的告警。然而从用户体验来看应用却频繁出现延迟飙升和请求超时。最终定位到的根因是 Pod 级别配置了 CPU limit这导致 CPU 被限流从而拖慢了应用的执行。原因在于与内存不同CPU 并不是一种可被消耗殆尽的资源它是可压缩的并且在每一个调度周期都会重新分配。每个调度周期CFS 调度周期中CPU 时间都会被分配、回收并重新分发。在配置了 CPU limit 的情况下即使节点上仍然有空闲 CPU但如果 Pod 达到了配置的 CPU limit 上限也无法继续使用这些空闲的 CPU。当容器触及 CPU limit 时内核会对容器进行限流throttling进程会被暂停直到下一个 CPU 调度周期即使节点上存在空闲 CPU容器也无法突破其限制进行突发使用CPU 限流相关的指标是定位该问题根因的关键信号尤其是container_cpu_cfs_throttled_seconds_total和container_cpu_cfs_throttled_periods_total。将这些指标与延迟曲线进行关联分析可以清晰地看到延迟峰值与 CPU 限流之间的高度一致性。移除 CPU limit仅保留 CPU request就足以避免限流问题。这样既可以保证 Pod 按比例获得 CPU 资源又能防止某些 Pod 过度抢占 CPU。关键结论配置 CPU limit 可能会出现节点 CPU 空闲的情况下让 Pod 遇到性能瓶颈使得延迟问题难以诊断。在大多数场景下合理配置 CPU request 就已经足够既允许工作负载在需要时进行突发使用又能避免 CPU 限制问题。个人注解需要注意的是当 Pod 达到了配置的 mem limit 上限时会触发 oomkill 导致 Pod 重启。但 CPU limit 的本质是一旦容器在某个 CFS 周期内用完了分配的 CPU 时间片就会被内核直接暂停这个暂停行为与节点是否还有空闲 CPU 无关我的观点与作者结论一致我认为 CPU limit 仅能作为隔离工具而不是性能优化工具。除非你明确希望某些业务不希望占用过多 CPU。关键业务未设置 PriorityClass原文翻译试想一下核心业务 Pod 在集群中被驱逐且在短时间内无法重新调度而一些不那么重要的工作负载却仍在正常运行。当你没有为关键部署设置 PriorityClass 时在集群资源充足的情况下一切看起来都很正常。但一旦集群遭遇资源压力(mem、cpu、diskk8s 就会在节点中驱逐某些 Pod。Kubelet 会基于服务质量QoSQuality Of Service来驱逐 Pod。如果多个 Pod 属于同一种 QoS 类别这种情况非常常见那么其中任何一个 Pod 都可能被驱逐。更糟糕的问题出现在后续的重新调度阶段。被驱逐的 Pod 可能会卡在 Pending 状态并输出如下信息/* by yours.tools - online tools website : yours.tools/zh/formatxml.html */ preemption: No victims found for incoming pod由于所有 Pod 拥有相同的优先级抢占preemption机制无法生效而此时所有节点又都被占满。关键结论仅设置 resource requests 和 limits 并不足以在资源压力下保证存活。它们只能延缓 Pod 被驱逐但并不能保证调度优先级。对于关键工作负载必须始终定义 PriorityClass。个人注解在集群资源紧张时涉及两种不同阶段的决策驱逐通过 kubelet 根据不同服务质量QoSBestEffort/Burstable/Guaranteed进行驱逐不同服务质量的定义可参考官网文档调度通过 scheduler 根据不同 PriorityClass / priority 资源值决定简单来说 QoS 解决的是 谁能活下来而 PriorityClass 则是 谁更重要。由于 Pod 卡在 Terminating 状态导致的 EKS IP 耗尽问题原文翻译想象一下有 100 多个 Pod 卡在 Terminating 状态而新的 Pod 因为 IP 耗尽而无法被调度。在删除一个自定义 Operator 后该 Operator 下所有带 finalizer 的 Pod 都卡在了 Terminating 状态。由于 EKS 会为 Pod 分配真实的 VPC IP 地址这些正在终止的 Pod 仍然持续占用 IP最终耗尽了子网中的 IP 资源导致新的 Pod 无法被调度。通过定位被 finalizer 阻塞的 Pod并谨慎地移除这些 finalizer使 Pod 得以真正删除并释放 IP从而缓解了这次事故。当子网的 IP 容量恢复后调度重新开始集群也恢复了稳定。批量移除 Pod 中 finalizer 的方法如下/* by yours.tools - online tools website : yours.tools/zh/formatxml.html */ kubectl get pods -n $NAMESPACE -o json | jq -r .items[] | select(.metadata.deletionTimestamp ! null) | select(.metadata.finalizers | index($FINALIZER_NAME)) | kubectl patch pod \(.metadata.name) -n $NAMESPACE -p \{\metadata\:{\finalizers\:[]}}\ --typemerge | sh关键结论Finalizer 一旦添加就会一直存在除非被 operator 显式移除operator 在其管理的资源被完全清理之前绝不能被删除在 EKS 中处于 Terminating 状态的 Pod 仍然会消耗真实的集群资源。个人注解Finalizer 可以理解为 k8s 中的 拦截删除器在资源真正被删除之前需要强制完成某些清理动作只要他还存在k8s 就不能将这个资源真正删除。在集群中删除资源时k8s 的工作流程是这样的标记删除添加deletionTimestamp等标签资源状态变为Terminating检查 Finalizer如果有则等待完成后再删除如果没有则直接删除某个 Operator 清理后将 finalizer 从 metadata 中删掉真正删除资源从 etcd 中移除 需要注意的是k8s 本身不会完成 finalizer 操作真正执行的是某个 operator简单来讲本次故障的根因在于资源清理顺序不当。在删除 Operator 后其管理的 Pod 中 Finalizer 无人处理导致这些 Pod 长期卡在 Terminating 状态无法被真正删除。处于该状态的 Pod 仍会占用 IP 资源最终造成子网 IP 耗尽影响新 Pod 调度。## 脚本解析 ## 通过命令查看 Pod 中有哪些 Finalizer ## kubectl get pod pod-name -n ns -o yaml index($FINALIZER_NAME)本地临时日志导致的磁盘压力原文翻译我记得有次事故是在某业务部署失败后出现的调度过程中出现错误同时触发了 Pod 异常驱逐并且多个节点上出现磁盘压力Disk Pressure告警。通过磁盘空间用量的突增与各 Pod 资源消耗情况进行关联分析最终发现一个 Pod 产生了异常高的日志量很快耗尽了节点的磁盘从而触发了 DiskPressure。随后 kubelet 开始驱逐无关的 Pod并将节点状态设置为 NodeHasDiskPressure True新工作负载无法再被调度报错如下0/12 nodes are available: 12 node(s) had disk pressure.最终的解决方式是下线这个 Pod在集群中新增 Node 来承载处于 Pending 的 Pod并通过清理容器日志来释放受影响节点的磁盘空间。由于这些日志并非关键数据因此 30 分钟的停机时间是可以接受的。永久性修复方案是在 Helm Chart 中将本地存储切换为持久化存储确保日志写入外部存储而不是节点本地磁盘。关键结论永远不要使用 emptyDir 卷在节点本地存储 Pod 数据即便只是临时用途。应该使用外部的持久化存储来避免节点磁盘压力事件或者必须设置 ephemeral-storage 限制防止 Pod 无限制地消耗磁盘空间。个人注解这个没啥说的Pod 数据默认也都是存在本地/var/lib/目录下。使用 emptyDir 或 stdout/stderr 本质上用的都是本地磁盘使用时限制 limit 就好了。缺失 ConfigMap 重载器Reloader原文翻译该事故始于一次数据库连接配置变更这些配置是通过 ConfigMap 管理。但应用 Pod 并没有感知到这些新配置原因很简单在该 Deployment 中并没有配置 ConfigMap Reloader。Reloader 会监听 ConfigMap 和 Secret 变化。检测到变更时会触发受影响 Pod 的滚动重启。如果缺少重载器就需要手动重启 Pod 才能让其加载最新配置。当应用尝试使用过期的数据库连接配置运行时故障发生了最终导致数据库连接失败ERROR Database connection failed: could not connect to server ERROR Connection timeout while attempting to reach database endpoint ERROR Failed to initialize database pool: invalid connection parameters ERROR Database unavailable after retrying 5 times在所有 Pod 被手动重启并加载了最新配置之前该问题一直未能得到解决。关键结论ConfigMap 的更新不会自动应用到正在运行的 Pod依赖手动重启 Pod 存在较大的运维风险也无法规模化。应始终在 Deployment 中添加 ConfigMap 重载器容器例如 reloader。个人注解这个问题本质上并不是 k8s 自身原因。文中提到的 Reloader 重载器是 k8s 生态中的一个开源项目其作用是监听 ConfigMap 变更检测到更新后自动触发相关 Pod 重启让 Pod 重新加载最新的配置。实际上k8s 早已支持 ConfigMap 的热更新机制。ConfigMap 更新后是否能够真正生效关键在于 Pod 内部应用是否支持热加载如果支持就用不上该项目如果不支持则可以使用该项目或者手动重启业务 Pod 也一样