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