问题描述:

2019.9.16下午2:40左右发现环境出现故障,功能无法正常运行。

马上进行排查
1、基础服务端口运行都是正常的
2、查看环境上最近有新发版的三个微服务,发现都在不同频率的打印这句日志:

2019-09-16 14:42:41,626  INFO [DubboMonitor.java:80] :  [DUBBO] Send statistics to monitor zookeeper://192.168.1.101:2181/com.alibaba.dubbo.monitor.MonitorService?anyhost=true&application=dubbo-monitor&check=false&delay=-1&dubbo=crud&generic=false&interface=com.alibaba.dubbo.monitor.MonitorService&methods=lookup,collect&pid=11&revision=monitors&side=provider×tamp=1568598922300, dubbo version: crud, current host: 10.42.91.223

因为之前有一个微服务出现OutOfMemoryError的时候,就有一直打印这些日志,因此将三个容器日志导出来查看,刚刚导了两个日志,正在导第三个日志的时候,发现docker命令无法执行,docker挂了???

先重新启动了docker服务,恢复了业务,然后查看docker挂掉的原因。

原因分析:

1、查看/var/log/messages日志

将messages文件中跟docker有关的内容过滤出来,发现了这样的信息(部分日志):

Sep 16 14:43:07 rancher-node dockerd-current: time="2019-09-16T14:43:07.982713104+08:00" level=error msg="collecting stats for 587cf4938bed5e3172868d85ae41db3af37e9c1a6cd8192f1cfa22a4e969d53b: rpc error: code = 2 desc = fork/exec /usr/libexec/docker/docker-runc-current: cannot allocate memory: \"\""
Sep 16 14:45:04 rancher-node journal: Suppressed 1116 messages from /system.slice/docker.service
Sep 16 14:45:05 rancher-node dockerd-current: time="2019-09-16T14:45:05.410928493+08:00" level=info msg="Processing signal 'terminated'"
Sep 16 14:45:05 rancher-node journal: time="2019-09-16T06:45:05Z" level=error msg="Error processing event &events.Message{Status:\"kill\", ID:\"af42628b1354b74d08b195c0064d8c5d760c826626a3ad36501a85c824d2204d\", From:\"prod.locmn.cn/prod/locmn-drols-query-chq:latest\", Type:\"container\", Action:\"kill\", ..... Error: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?" 

就是说在14:45的时候,docker就已经无法分配到内存了
然后信号终止,docker进程被杀掉,因此docker的命令无法运行,所有docker容器也都一起挂掉了:

Sep 16 14:45:05 rancher-node dockerd-current: time="2019-09-16T14:45:05.410928493+08:00" level=info msg="Processing signal 'terminated(处理信号的终止)'"
Sep 16 14:45:05 rancher-node journal: time="2019-09-16T06:45:05Z" level=error msg="Error processing event &events.Message{Status:\"kill\", ID:\"af42628b1354b74d08b195c0064d8c5d760c826626a3ad36501a85c824d2204d\", From:\"registry.locman.cn/sefon-online/locman-drools-query-chq:latest\", Type:\"container\", Action:\"kill\", ......Error: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?" 

可是为什么分配不到内存呢?
查看了主机,内存还有10G的。

2、查看业务日志

后面细查新发版的三个微服务的业务的日志,发现在14:03分的时候,有一个叫cud的服务有“java.lang.OutOfMemoryError”的报错:

2019-09-16 14:03:10,554 ERROR [ExceptionFilter.java:87] :  [DUBBO] Got unchecked and undeclared exception which called by 10.42.83.124. service: com.run.locman.api.crud.service.AlarmInfoCrudService, method: add, exception: java.lang.OutOfMemoryError: unable to create new native thread, dubbo version: crud, current host: 10.42.91.223
java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
......(省略部分日志内容)
Exception in thread "pool-1-thread-3" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:714)

原来是这个叫cud服务的内存溢出了引发的故障,导致了docker服务被kill掉,所有的docker容器瞬间全部挂掉!

故障原因总结

和开发一起对故障进行了分析,发现有两个原因:
1、这个服务有一个线程池,在代码里面设置的最小是8,最大限制是2147483647 ,用完的线程要1分钟之后才能回收。这就存在两个问题:
一、业务在持续不断的发送请求,这个服务就会一直创建线程,而因为给定的线程最大值过大,相当于可以无限制的创建线程了,会一直消耗资源;
二、用完的线程1分钟之后才会回收,时间过长。
在这两点的影响下,程序跑一段时间,就会出现创建大量的线程,过度的消耗内存资源.

2、由于docker容器在最初的时候没有做容器的内存限制,所以默认情况下容器使用的资源是不受限制的。
也就是可以使用主机内核调度器所允许的最大资源,因此当主机发现内存不够用的时候,也会抛出内存溢出的错误。而且会开始杀死一些进程用于释放内存空间。可怕的是任何进程都可能成为内核猎杀的对象,包括 docker daemon 和宿主机上的其它一些重要的程序。更危险的是如果某个支持系统运行的重要进程被kill掉了,整个系统也就宕掉了。
这次的docker服务进程就被杀掉了。

解决方案

1、开发优化代码,包括限制线程池的最大线程数量和线程回收的时间,重新发布代码打补丁,后面观察到目前,没有再出现类这个问题了;
2、限制docker内存。重新优化了docker容器,限制了docker内存的使用量,减少docker容器过度占用宿主机资源的风险;
3、加强对docker容器的监控与告警;

总结

1、docker限制内存,非常重要!
2、限制内存的方式(放一个别人写的修改内存的步骤):

方法一:静态修改 -m
-m参数:限制docker容器最大使用内存

例如:$ docker run -it -m 300M --memory-swap -1 --name con1 u-stress /bin/bash
上面的 docker run 命令中通过 -m 选项限制容器使用的内存上限为 300M。
同时设置 memory-swap 值为 -1,它表示容器程序使用内存的受限,而可以使用的 swap 空间使用不受限制(宿主机有多少 swap 容器就可以使用多少)。

方法二:动态修改 docker update
docker update 动态修改docker容器内存

例如:把一个运行着gitlab 的容器内存限制在2048M以内
docker update --memory 2048m --memory-swap -1 gitlab