前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >cgroup oom引发Pod重建问题分析

cgroup oom引发Pod重建问题分析

原创
作者头像
cdh
修改2020-07-02 17:31:34
3K0
修改2020-07-02 17:31:34
举报
文章被收录于专栏:笔记+笔记+

业务在上容器云的过程中发现容器不知原因被重建,查看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重建。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com