惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Webroot Blog
Webroot Blog
腾讯CDC
The Last Watchdog
The Last Watchdog
博客园 - 司徒正美
H
Hacker News: Front Page
I
InfoQ
A
Arctic Wolf
H
Hackread – Cybersecurity News, Data Breaches, AI and More
H
Heimdal Security Blog
L
LINUX DO - 最新话题
T
Threat Research - Cisco Blogs
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
Security Latest
Security Latest
D
DataBreaches.Net
C
Check Point Blog
J
Java Code Geeks
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
Lohrmann on Cybersecurity
雷峰网
雷峰网
Vercel News
Vercel News
WordPress大学
WordPress大学
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Spread Privacy
Spread Privacy
Forbes - Security
Forbes - Security
阮一峰的网络日志
阮一峰的网络日志
Hacker News: Ask HN
Hacker News: Ask HN
大猫的无限游戏
大猫的无限游戏
I
Intezer
N
News and Events Feed by Topic
小众软件
小众软件
B
Blog RSS Feed
Help Net Security
Help Net Security
Google DeepMind News
Google DeepMind News
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 三生石上(FineUI控件)
S
Security @ Cisco Blogs
美团技术团队
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main

Mohuishou

如何实现支持多集群的 Kubernetes Operator? 第三方应用如何调用我们 kubebuilder 生成的自定义资源? Kubernetes 简明教程 Go 工程化(十一) 如何优雅的写出 repo 层代码 Go 工程化(十) 如何在整洁架构中使用事务? 给博客添加章节目录 使用 Notion Database 管理静态博客文章 一个普通 Go 开发的三年 4. localhost 就一定是 localhost 么? Go可用性(七) 总结: 一张图串联可用性知识点 Go可用性(六) 熔断 10. 总结 9. kubebuilder 进阶: 源码分析 8. kubebuilder 进阶: webhook 7. kubebuilder 进阶: 测试 6. kubebuilder 实战: status & event 5. kubebuilder 实战: CRUD 4. kustomize 简明教程 3. KubeBuilder 简明教程 2. Kind: 如何快速搭建本地 K8s 开发环境? 1. Operator概述: 如何对 Kubernetes 进行扩展 Go可用性(五) 自适应限流 Go可用性(四) 漏桶算法 Go可用性(三) 令牌桶的实现 rate/limt Go可用性(二) 令牌桶原理及使用 Go可用性(一) 隔离设计 Go并发编程(十二) Singleflight Go工程化(九) 项目重构实践 Go工程化(八) 单元测试 Go工程化(七) Go Module Go工程化(六) 配置管理 Go工程化(五) API 设计下: 基于 protobuf 自动生成 gin 代码 Go工程化(四) API 设计上: 项目结构 & 设计 Go工程化(三) 依赖注入框架 wire Go工程化(二) 项目目录结构 Go工程化(一) 架构整洁之道阅读笔记 Go并发编程(十一) 总结 Go并发编程(十) 深入理解 Channel Go并发编程(九) 深入理解 Context Go并发编程(八) 深入理解 sync.Once Go并发编程(七) 深入理解 errgroup Go并发编程(六) 深入理解 WaitGroup Go并发编程(五) 深入理解 sync/atomic Go并发编程(四) 深入理解 Mutex Go并发编程(三) data race Go并发编程(二) Go 内存模型 Go并发编程(一) goroutine Go错误处理最佳实践 微服务(二) 服务发现&多租户 微服务(一) 微服务概览 5. 栈下: 深入理解 defer 4. 栈上: 如何实现一个计算器 Go Struct 初始化风格的抉择 3. 数组下: 使用 GDB 调试 Golang 代码 2. 数组上: 深入理解 slice 1. 链表: 深入理解container/list&LRU缓存的实现 Go设计模式24-总结(更新完毕) Go设计模式23-中介模式 Go设计模式22-解释器模式 Go设计模式21-命令模式 Go设计模式20-备忘录模式 Go设计模式19-访问者模式 Go设计模式18-迭代器模式 Go设计模式17-状态模式 Go设计模式16-职责链模式(Gin的中间件实现) Go设计模式15-策略模式 Go模板模式14-模板模式 Go设计模式13-观察者模式(实现简单的EventBus) Go设计模式12-享元模式 Go设计模式11-组合模式 Go设计模式10-门面模式 Go设计模式09-适配器模式 Go设计模式08-装饰器模式 Go设计模式07-桥接模式 Go设计模式06-代理模式(generate实现类似动态代理) Go设计模式05-创建型模式总结 Go设计模式04-原型模式 Go设计模式03-建造者模式 Go设计模式02-工厂模式&DI容器 笔记-让你最快速地改善代码质量的20条编程规范 Go设计模式01-单例模式 一点拙见-如何写好一个技术预研报告? Go Web小技巧(四)在单个仓库中支持多个 go mod 模块 Go Web 小技巧(三)Gin 参数绑定 Go Web 小技巧(二)GORM 使用自定义类型 Go Web 小技巧(一)简化Gin接口代码 善用工具之postman高级用法概述 go generate and ast hexo-next-algolia-search全文搜索 docker镜像瘦身&优化 GORM避坑指南之含关联关系的更新 Github Actions介绍&自动构建Github Pages博客 在blog中内嵌在线PPT 记一次net http内存泄漏 使用TravisCI自动部署Blog 使用Goland调试Go程序 一个十分边缘的gorm的bug Httprouter介绍及源码阅读 Gin源码阅读 从0.1开始
k8s job 为何迟迟不能结束?
Mohuishou · 2022-09-10 · via Mohuishou

注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用

前情提要

最近我们踩了一个之前很难遇到的坑,k8s 集群内同时并发运行的 job 2000+,一个 job 只有一个 Pod,一次运行一般 1 - 2min 就可以结束,但是线上数据统计发现每分钟新增的数据只有几十条,这个很不符合常理,关键是还没有报错。

因为我们的 job 很多,加上已完成未删除的 job 有上万个同时存在,并且 controller 会根据 cr 的需求不断地增删对应的 job,所以排查起来相对比较困难。

最后发现有一个 job 执行了 3h 都还没有执行完毕,这个成功的引起了我的注意,查看发现这个 job 的确没有完成,并且这个 job 关联的 pod 才刚刚创建,job 的信息里面也只有一个 active: 1 并没有失败的 job 出现

然后去腾讯云查看 k8s 的审计日志,离谱的事情来了

这个 job 对应的 pod 被 pod gc 删除了 50 次,然后查看上线服务的日志也发现存在大量的重复数据上报请求被丢弃掉了。

为什么 Pod 刚成功就被删除?

省流版: 来自官网的温馨提示

Job 控制器(Controller)  依靠计算集群中存在的 Pod 来跟踪作业状态。 也就是说,维持一个统计  succeeded  和  failed  的 Pod 的计数器。 然而,Pod 可以因为一些原因被移除,包括:

- 当一个节点宕机时,垃圾收集器会删除孤立(Orphan)Pod。
- 垃圾收集器在某个阈值后删除已完成的 Pod(处于 `Succeeded` 或 `Failed` 阶段)。
- 人工干预删除 Job 的 Pod。
- 一个外部控制器(不包含于 Kubernetes)来删除或取代 Pod。

为什么会触发 Pod Gc, 什么时候会触发?

看一下源码 pkg/controller/podgc/doc.go 中的说明

1
2
3
4
5
6
7
8
// Package podgc contains a very simple pod "garbage collector" implementation,
// PodGCController, that runs in the controller manager. If the number of pods
// in terminated phases (right now either Failed or Succeeded) surpasses a
// configurable threshold, the controller will delete pods in terminated state
// until the system reaches the allowed threshold again. The PodGCController
// prioritizes pods to delete by sorting by creation timestamp and deleting the
// oldest objects first. The PodGCController will not delete non-terminated
// pods.

简单翻译一下就是,当已结束状态(Failed or Succeeded)的 Pod 的数量 > 配置的值的时候,Pod Gc Controller 就会按照创建时间从远往近开始删除已结束状态的 Pod

后面单独出一篇文章分析 Pod Gc,这里简单看一下 gcTerminated 部分的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func (gcc *PodGCController) gcTerminated(pods []*v1.Pod) {
// 过滤出 terminated Pods
terminatedPods := []*v1.Pod{}
for _, pod := range pods {
if isPodTerminated(pod) {
terminatedPods = append(terminatedPods, pod)
}
}

// 非常简单的判断,需要删除多少个
terminatedPodCount := len(terminatedPods)
deleteCount := terminatedPodCount - gcc.terminatedPodThreshold

if deleteCount <= 0 {
return
}

// 按照时间进行排序,然后并发删除
sort.Sort(byCreationTimestamp(terminatedPods))
var wait sync.WaitGroup
for i := 0; i < deleteCount; i++ {
wait.Add(1)
go func(namespace string, name string) {
defer wait.Done()
if err := gcc.deletePod(namespace, name); err != nil {
// ignore not founds
defer utilruntime.HandleError(err)
}
}(terminatedPods[i].Namespace, terminatedPods[i].Name)
}
wait.Wait()
}

我们可以看到这个删除逻辑也是非常的简单粗暴了,只要超过数量然后就开始删除,但是这个有个问题是,我们现在 terminated pod 数量比 terminatedPodThreshold 这个值大,并且还有 controller 在反复创建 job,这就会导致 pod gc 频繁工作导致 job 一直无法完成了

怎么解决这个问题?

  • 最简单的方案就是根据业务的实际需求调整 terminatedPodThreshold 这个值,一般而言默认值 12500 已经足够大了,所以一般也不会触发这个问题,但是需要注意这个值也不能太大,如果太大了,job 有问题可能会直接把 master 打挂
    • 但是如果使用云厂商的服务需要注意,我们这次出问题就是因为腾讯云 tke 这个值会根据集群规模自动调整,如果像我们这样集群节点可能不算特别大,也就几百,但是 job 数量特别多就要小心了,这个值比默认值小很多,当时我们是 2000 左右的 pod 就触发了这个问题
  • 还有一个办法就是减少 pod 数量
    • 这个问题其实在开发阶段出现过,但是当时没发现是 pod gc 的问题,所以我让一个 job 只有 一个 pod,job 关联的父资源状态结束之后,这个 job 就会被删除掉,尽早的去释放到多余的已完成的 pod
    • 但是显而易见这个方案治标不治本
  • 还有一个方案是 使用 Finalizer 追踪 Job
    • k8s 1.22 开始支持 job 给 pod 添加 finalizer,这样可以确保 pod 被意外删除的时候 job controller 可以感知到
    • 这个 feature 在 1.22 版本可以手动开启,1.23 版本默认开启,但是 1.24 版本被默认关闭了,因为存在未解决的 bug 可能会导致 job 和 pod 一直处于 terminating 的状态。具体可以看这个 PR

小结

  • 回顾一下这个坑,其实早有预兆,但是机会给了一次次没有及时抓住最后还是跳了下去

    • 开发阶段就发现了这个问题,但是不够重视,没有追根溯源,当时解决了就以为解决了
    • 测试阶段没有压测,上线前的就已经预料到了最多的时候需要短时间内跑 100W+ 的 job,但是觉得是上线初期感觉压力应该没那么大,把压测时间滞后了
    • 生产阶段排查问题没有第一时间去看 k8s 审计以及事件日志,导致走了弯路
  • 最后一个 github 查看源码的 tips,现在只要把 github.com 改成 github.dev 就能直接用 vscode web 版本查看仓库代码,并且还支持代码跳转,文中的所有源码链接都是 github.dev 的链接,可以体验一下

关注我获取更新

猜你喜欢