背景

前段时间业务研发反馈说是他的利用内存使用率很高,导致频繁的重启,让我排查下是怎么回事;

在这之前我也没怎么在意过这个问题,正好这次排查剖析的过程做一个记录。

首先我查看了监控面板里的 Pod 监控:

发现的确是快满了,而此时去查看利用的 JVM 占用状况却只有30%左右;阐明并不是利用内存满了导致 JVM 的 OOM,而是 Pod 的内存满了,导致 Pod 的内存溢出,从而被 k8s 杀掉了。

k8s 为了维持利用的正本数量就得重启一个 Pod,所以看起来就是利用运行一段时间后就被重启。



而这个利用配置的是 JVM 8G,容器申请的内存是16G,所以 Pod 的内存占用看起来也就 50% 左右。

容器的原理

在解决这个问题之前还是先简略理解下容器的运行原理,因为在 k8s 中所有的利用都是运行在容器中的,而容器实质上也是运行在宿主机上的一个个常常而已。

但咱们应用 Docker 的时候会感觉每个容器启动的利用之间互不烦扰,从文件系统、网络、CPU、内存这些都能齐全隔离开来,就像两个运行在不同的服务器中的利用。

其实这一点也不是啥黑科技,Linux 早就反对 2.6.x 的版本就曾经反对 namespace 隔离了,应用 namespace 能够将两个过程齐全隔离。

仅仅将资源隔离还不够,还须要限度对资源的应用,比方 CPU、内存、磁盘、带宽这些也得做限度;这点也能够应用 cgroups 进行配置。

它能够限度某个过程的资源,比方宿主机是 4 核 CPU,8G 内存,为了爱护其余容器,必须给这个容器配置应用下限:1核 CPU,2G内存。

这张图就很清晰的示意了 namespacecgroups 在容器技术中的作用,简略来说就是:

  • namespace 负责隔离
  • cgroups 负责限度

在 k8s 中也有对应的提现:

  resources:    requests:      memory: 1024Mi      cpu: 0.1    limits:      memory: 1024Mi      cpu: 4

这个资源清单示意该利用至多须要为一个容器调配一个 0.1 核和 1024M 的资源,CPU 的最高下限为 4 个外围。

不同的OOM

回到本次的问题,能够确认是容器产生了 OOM 从而导致被 k8s 重启,这也是咱们配置 limits 的作用。

k8s 内存溢出导致容器退出会呈现 exit code 137 的一个 event 日志。

因为该利用的 JVM 内存配置和容器的配置大小是一样的,都是8GB,但 Java 利用还有一些非 JVM 治理的内存,比方堆外内存之类的,这样很容易就导致容器内存大小超过了限度的 8G 了,也就导致了容器内存溢出。

云原生背景的优化

因为这个利用自身应用的内存不多,所以倡议将堆内存限度到 4GB,这样就防止了容器内存超限,从而解决了问题。

当然之后咱们也会在利用配置栏里加上倡议:举荐 JVM 的配置小于容器限度的 2/3,预留一些内存。

其实实质上还是开发模式没有转变过去,以传统的 Java 利用开发模式甚至都不会去理解容器的内存大小,因为以前大家的利用都是部署在一个内存较大的虚拟机上,所以感知不到容器内存的限度。

从而误以为将两者画了等号,这一点可能在 Java 利用中尤为显著,毕竟多了一个 JVM;甚至在老版本的 JDK 中如果没有设置堆内存大小,无奈感知到容器的内存限度,从而主动生成的 Xmx 大于了容器的内存大小,以致于 OOM。