序言
又是一个冬季,在这寒冷的冬季,总是让人心动。。。迷雾之城
外界的刁难,挑战。。。其实并不是最难的,最难的总是内部难以安抚,OOM。。。内存泄漏,OOM killer了解一下。。。攘外必先安内。。。我可能要死在内部了。。。
抛出一个问题:容器是否需要限制内存的使用,限制CPU的使用呢?
OOM排查
背景:
微服务架构,几百个服务,运行在不同的容器上,总是莫名的同时出现十几个服务不可用,伴随着各个容器的状态异常,无法ping通,无法ssh上去,大量告警。。。总是莫名的有物理机宕机,每次查的时候总是无疾而终。。。
验尸报告:
Emmm,故障现场不够新鲜,检查的力度不够。。。
故障之间总是有关联的,查出根本的问题之后,就发现,莫名的物理机宕机和这次发生的问题是一样的,只是原来从来没有想过,内存泄漏导致物理机重启,未曾进行关联,当查出每次都是OOM之后,那么问题就可以联系在一起,其实两者的问题的本质是一样的。
收到告警,大量服务出现单点,查看相关的告警信息,大量的容器无法ping通,伴随着load值告警,而且这些所有的容器都分布在一台物理机上,有部门的服务在慢慢的恢复。。。经常看到这种情况的发生,也麻木了,等一会儿,慢慢就会自动恢复的。。。
等了两个多小时,还没有恢复,依据以往的经验,这个时候应该已经恢复了。。。Emmm,经验往往是不可靠的,所谓的黑天鹅事件了解一下。。。
大量的容器无法ping通,登录上主机,查看资源抢占情况*(示例图):
在以上的图中,主要看四个指标,一个是load,Emmm,假设CPU是56个,实际的load值达到了3K,那么这个时候,系统已经不堪重负了;一个是Tasks,主要看任务的运行数量,在图中,才2个在运行,实际也就几百个,但是sleeping状态的有几万个,而zombie状态的有300多个;一个就是内存的使用量,在实际情况中,内存基本使用完毕;最后一个就是使用交换空间,图中的未使用,实际上已经全部使用完毕。
系统资源不足,要不扩容试试。。。向上?还是水平扩展。。。Emm,当然是不可能的。。。
CPU的load太高,那么说起来其实也就两个队列,一个是运行的队列,一个block的队列,从而需要收集相关的进程信息,从而可以使用ps来查看进程的状态信息:
以上主要是为了统计:一个是进程的数量,一个是线程的数量。。。(需要多次执行,从而进行对比,可以知道哪些进程造成了相关的阻塞,数量庞大的必有蹊跷。。),统计的结果是大量状态为D的进程。。。在线程多的结果中,可以看到相关的PID,从而可以知道是哪个进程产生了大量的阻塞。。。
统计容器的数量,从容器的内存限制来查看是否容器的内存都达到了限制。
在查看结果的时候,发现很多容器使用的内存值和限制值差不多一致,而且falcnt也就是分页中断有几万次。。。当然缺页异常几万次,可能或许maybe属于正常情况。。
保存进程列表(主要是保存进程的树形结构,可以用来追查父进程):
突然出现。。。fork,Can't allocate memory!。。至此已经无法查看相关信息,只能。。。重启服务器了。。。所有的内存已经耗尽。。。。
重启之后查看相关的日志:对。。。重启是万能的。。。
在日志里面查看到大量的OOM信息,也就可以看到相关的进程被杀死,从而可以找到是哪些进程导致了内存泄漏,从而进行整改。。。在kern日志中可以看到被杀死的进程名,在dmesg中可以看到OOM,在message中,能追查到相关的容器id。。。
排查思路:根据load值偏高,查询进程的数量和线程的数量,从而从增加的数量查看到是什么样的进程阻塞了CPU的调度,查看系统日志,主要查看oom,从而对比两者的结果进程是否一致,从而看哪个进程OOM需要整改。。。可能你会说,查看单独进程的内存占用量,Emmm,这也是一个排查思路。。。
风言风语
在以上的问题追踪中,可以产生两个疑点:第一既然oom都杀死了进程,为什么内存还会溢出,杀死了进程应该已经将相关的内存进行回收了;第二:是什么导致了那么高的load值。。。
回答第一个问题就是:在oom killer进行杀死进程的时候,使用的是kill -9 ,从而能强行杀死进程,但是在进行oom的时候,oom的分值是给占用内存大的进程,而这个进程在等待IO,也就是等待分配内存,Emm。。。读取内存也是一种IO,所谓的缺页中断。。。在杀死这个进程的时候,这个进程的状态为D,也就是表示这个进程是不可中断睡眠,在等待分配内存。。。从而杀死这个进程可能根本就无法杀死。。。
还有一种情况是,进程已经变成了僵尸进程,从而在oom killer在进行杀死进程的时候,根据当前的进程号id杀,而僵尸进程要想杀死,必须杀掉其父进程,而当僵尸进程的父进程为1的时候,这个时候就相当于服务器重启了。
回答第二个问题就是:将一个进程杀死,变成了僵尸进程,自动拉起进程并不能识别变成了僵尸进程,从而会自动拉起服务,然后又内存溢出,再次杀,再次变成僵尸进程,死循环,最终导致内存耗尽。。。
还有一种情况就是,在容器中运行了很多进程,而oom分值高的进程是其中的一个子进程,而不是容器的根进程,也就是pid为1的进程,如果恰好是1的进程,那么很完美,相当于将容器进行重启,那么这种情况下会慢慢的恢复,不会增加load值;而当是一个普通进程之后,杀掉,有很大的概率变成僵尸进程。。。
容器也是一个进程,在其中又有很多进程,资源隔离还不是那么好。。。原因之一也在于使用不当。
最后解答开篇的问题:要不要设置cpu和容器的最高使用值。。。要
如果在容器的层面进行限制了内存的使用,那么就只有容器出现OOM,而不会影响这台机器上其他的容器,不会出现资源竞争的情况。。。在查看容器oom的时候,也可以通过docker inspect id来查看容器的OOM。。。
风言风语
有些东西,明明知道是对的,却不能说。。。。因为绝对的对并不是对的。。。
然而,并不能忍住不能说,所以,很多事。。。雪上加霜。。。明明知道前面是坑,不说,你会掉下去,说了,你就怪我。。。怪我咯。。。
看过了那么多的算法,却还不能算好这一生。。。。
看过了那么多数据结构,却还不能组织好生活,工作。。。
看过了那么多设计模式,却还不能八面玲珑。。。
技术一途,要么是0,要么是1.。。。还不能将所有的组织好,是因为暴露了不该暴露的接口,暴露了太多的内部实现。。。这也就是为什么类也要有那么多标准,为什么要各种模块的使用。。。
入门不难,坚持最难。。。懂了不难,执行下去才难。。。嘴上说了不难,心里记住才难。。。那一丝风。。。
无论你懂得与否。。。不能回头。。。放弃是一种心魔。。。别样的风景,别样的修行。。。
僵尸进程最难管,太轻狂。。。哈哈。。。最近有朋友说,瞎逼逼个啥。。。Emmm,你们吓得我都不敢写了。。。哈哈。。。心里委屈,心酸,还没人安慰。。。看了那么多算法,我都没算好你们喜欢看啥。。。伤悲的时候莫要听慢歌。。。