首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
混部之殇-论云原生资源隔离技术之CPU隔离(一)
2
腾讯TencentOS 十年云原生的迭代演进之路
5
一次内核hung task分析
7
NFSv4客户端hung住的BUG,您解决了吗?
8
nfs不同版本的挂载与解析
9
(好文重发)朴英敏:用crash工具分析Linux内核死锁的一次实战
10
内核问题解决方法记录
11
blocked for more than 120 seconds
12
记一次Linux主机内存脏数据引发的NameNode故障
13
​[linux][memory]cgroup回收内存对虚拟机的影响分析
14
docker cgroup 技术之memory(首篇)
15
[linux][memory] 内存回收
16
Linux内核理解 Memory barrier(内存屏障)
17
Linux内核27-优化和内存屏障
18
谢宝友:深入理解 Linux RCU 从硬件说起之内存屏障
19
谢宝友:深入理解 RCU 之概念
20
聊聊 Linux 上软件实现的“交换机” - Bridge!
21
谈谈 Linux 假死现象
22
宋宝华: 数据库为什么有可能喜欢Linux AIO(异步I/O)?
23
深入理解Linux内核之脏页跟踪
24
Iowait的成因、对系统影响及对策
25
打通IO栈:一次编译服务器性能优化实战
26
浅谈Linux dirty data配置
27
write文件一个字节后何时发起写磁盘IO?
28
深入理解 Linux的 I/O 系统
29
深入理解Linux 的Page Cache
30
深入理解Linux文件系统之文件系统挂载(上)
31
深入理解Linux文件系统之文件系统挂载(下)
32
【线上故障】通过系统日志分析和定位
33
实战案例分享:根据 JVM crash 日志定位和分析问题
34
Linux系统安全 | Linux日志分析和管理
35
如何快速处理线上故障
36
面试-线上故障如何排查
37
Linux内核Crash分析
38
内核timer crash debug思路
39
一次解决Linux内核内存泄漏实战全过程
40
Linux Kernel模块内存泄露的一种查找思路
41
linux系统奔溃之vmcore:kdump 的亲密战友 crash
42
crash浅析tasklist_lock与进程释放
43
Linux OOM机制分析
44
cgroup oom引发Pod重建问题分析
45
workqueue相关数据结构在内核crash分析中的实战应用
46
Linux设备驱动workqueue(工作队列)案例实现
47
Linux内核中的软中断、tasklet和工作队列具体解释
48
扒开 Linux 中断的底裤之 workqueue
49
Linux系统驱动之GIC驱动程序对中断的处理流程
50
Linux系统驱动之链式中断控制器驱动程序编写

cgroup oom引发Pod重建问题分析

业务在上容器云的过程中发现容器不知原因被重建,查看message信息可以看到当 oom_score_adj配置为1,对应score值为0的进程杀完后如果系统还是触发oom时就开始杀pause进程。

为什么CGROUP OOM,剩余进程oom_score_adj都配置为-998的情况下,系统杀的不是占用内存最多的java进程而是选择杀pause进程呢?

要解答这个问题我们需要先了解linux 内核的memcgroup OOM处理机制:

当cgroup内存不足时,Linux内核会触发cgroup OOM来选择一些进程kill掉,以便能回收一些内存,尽量继续保持系统继续运行。具体选择哪个进程杀掉,这有一套算分的策略,参考因子是进程占用的内存数,进程页表占用的内存数等,不废话直接上代码更清爽。从下面也可以看出adj的值越小,进程得分越少,也就越难被杀掉,oom_score_adj的取值为[-1000,1000]

代码语言:javascript
复制
static void mem_cgroup_out_of_memory(struct mem_cgroup *memcg, gfp_t gfp_mask,
                                     int order)
{
         ....
         //遍历cgroup下的所有进程
        for_each_mem_cgroup_tree(iter, memcg) {
                struct cgroup *cgroup = iter->css.cgroup;
                struct cgroup_iter it;
                struct task_struct *task;

                cgroup_iter_start(cgroup, &it);
                while ((task = cgroup_iter_next(cgroup, &it))) {
                        switch (oom_scan_process_thread(task, totalpages, NULL,
                                                        false)) {
                        case OOM_SCAN_SELECT:
                                if (chosen)
                                        put_task_struct(chosen);
                                chosen = task;
                                chosen_points = ULONG_MAX;
                                get_task_struct(chosen);
                                /* fall through */
                        case OOM_SCAN_CONTINUE:
                                continue;
                        case OOM_SCAN_ABORT:
                                cgroup_iter_end(cgroup, &it);
                                mem_cgroup_iter_break(memcg, iter);
                                if (chosen)
                                        put_task_struct(chosen);
                                return;
                        case OOM_SCAN_OK:
                                break;
                        };
                        //计算进程oom score adj分值
                        points = oom_badness(task, memcg, NULL, totalpages);
                        if (points > chosen_points) {//得到的分值比之前遍历的进程高
                                if (chosen)
                                        put_task_struct(chosen);
                                chosen = task;//替换得分最高的进程
                                chosen_points = points;
                                get_task_struct(chosen);
                        }
                }
                cgroup_iter_end(cgroup, &it);
        }

        if (!chosen)
                return;

        //若points值很小,此处得到的points将为0,这里不影响选中kill掉的进程,
        //只是作为OOM时的输出信息score值     
        points = chosen_points * 1000 / totalpages;
        //kill掉得分最高的进程,参数chosen
        oom_kill_process(chosen, gfp_mask, order, points, totalpages, memcg,
                         NULL, "Memory cgroup out of memory");
}

代码语言:javascript
复制
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
                          const nodemask_t *nodemask, unsigned long totalpages)
{
        long points;
        long adj;
          //如果进程不能被kill,则得分为0
        if (oom_unkillable_task(p, memcg, nodemask))
                return 0;
        //确认进程是否还存在
        p = find_lock_task_mm(p);
        if (!p)
                return 0;
        //若oom_score_adj为OOM_SCORE_ADJ_MIN则得分为0,表示不选择kill该进程
        adj = (long)p->signal->oom_score_adj;
        if (adj == OOM_SCORE_ADJ_MIN) {
                task_unlock(p);
                return 0;
        }

        /*
         * The baseline for the badness score is the proportion of RAM that each
         * task's rss, pagetable and swap space use.
         */
        //分数=rss+pte数(页表占用的内存)+交换分区内存,rss也就是进程自身使用的物理内存
        points = get_mm_rss(p->mm) + atomic_long_read(&p->mm->nr_ptes) +
                 get_mm_counter(p->mm, MM_SWAPENTS);
        task_unlock(p);

        /*
         * Root processes get 3% bonus, just like the __vm_enough_memory()
         * implementation used by LSMs.
         */
         //若是root用户的进程则比其他用户进程多3% bonus
        if (has_capability_noaudit(p, CAP_SYS_ADMIN))
                points -= (points * 3) / 100;

        /* Normalize to oom_score_adj units */
        adj *= totalpages / 1000;
        points += adj;

        /*
         * Never return 0 for an eligible task regardless of the root bonus and
         * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).
         */
        return points > 0 ? points : 1;
}

根据内核代码算法推导出oom score的计算方式

points = rss + nr_ptes + swapents

points = points - (points *3) /100 //for root

//若point很小,oom_adj为负数(比如-100),则算出来的point可能是负值

adj =oom_score_adj * (total_pages/1000)

points = points + adj

points = points + (oom_score_adj * (total_pages/1000) )

//若point为负值,则此处返回1

points = points > 0 ? points : 1;

查看业务Pod的yaml文件,request和limit配置相等,也就是使用的是Guranteed模式,

在该模式下oom_score_adj会被设置为-998:

另外容器的pause进程无论采用哪种Qos模式,oom_score_adj都为-998。

内核计算oom score的实现如下:

现在我们根据message打印出的被杀容器内存的情况计算出Guaranteed模式下pause进程和java进程的score值:

Java进程63130:

points=(247047+972)=248019 pages

total_pages=1048576kB/4k=262144 pages

points = points + (oom_score_adj * (total_pages/1000) ) =248019+(-998*262)=(248019-261476) < 0

points < 0 则取值1,即points=1

score=1*1000/262144=0

pause进程59675:

points=(69+7)=86 pages

total_pages=1048576kB/4k=262144 pages

points = points + (oom_score_adj * (total_pages/1000) ) =86+(-998*262)=(248019-261476) < 0

points < 0 则取值1,即points=1

score=1*1000/262144=0

pause进程和占用内存最多的业务进程score值都为0。

由于pause是创建pod时第一个创建的进程,所以kernel在遍历pod对应的cgroup及子cgroup时会先找到pause进程,所以当容器内剩余的进程算出来的score值都是相等时,pause进程就会kill掉导致pod重建。

根据kubernetes官网介绍可知:

采用Guaranteed模式时oom_score_adj值为-998

采用Burstable模式时oom_score_adj的值根据request动态调整,范围为2-999之间

那么我们再看看如果采用Burstable模式是什么结果:

由于采用Burstable模式,oom_score_adj是一个范围在2~999的浮动值,所以这里采用最小值2来计算(oom_score_adj大于0时其值与score值成正比):

Java进程63130:

points=(247047+972)=248019 pages

total_pages=1048576kB/4k=262144 pages

points = points + (oom_score_adj * (total_pages/1000) ) =248019+(2*262)= 248543

score=248543*1000/262144=948

pause进程59675:

points=(69+7)=86 pages

total_pages=1048576kB/4k=262144 pages

points = points + (oom_score_adj * (total_pages/1000) ) =86+(-998*262) < 0

points < 0 则取值1,即points=1

score=1*1000/262144=0

通过上面的计算结果可知占用内存最多的进程java score值为948 远大于pause进程的值oom score值0,这种情况内核会优先杀掉score值更大的java进程,也就不会导致容器被杀触发pod重建。

下一篇
举报
领券