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。。。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐