容器中的JVM资源该如何被安全的限制?

640?wx_fmt=jpeg


前言

640?wx_fmt=png


Java与Docker的结合,虽然更好的解决了application的封装问题。但也存在着不兼容,比如Java并不能自动的发现Docker设置的内存限制,CPU限制。
这将导致JVM不能稳定服务业务!容器会杀死你JVM进程,而健康检查又将拉起你的JVM进程,进而导致你监控你的Pod一天重启次数甚至能达到几百次。
我们希望当Java进程运行在容器中时,Java能够自动识别到容器限制,获取到正确的内存和CPU信息,而不用每次都需要在Kubernetes的yaml描述文件中显示的配置完容器,还需要配置JVM参数。
使用JVM MaxRAM参数或者解锁实验特性的JVM参数,升级JDK到10+,我们可以解决这个问题(也许吧~.~)。
首先Docker容器本质是是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器内看到的/proc/meminfo,/proc/cpuinfo与直接在宿主机上看到的一致,如下。
Host:
 
 
  1. cat /proc/meminfo

  2. MemTotal:      197869260KB

  3. MemFree:         3698100KB

  4. MemAvailable:   62230260KB


容器:
 
 
  1. docker run -it --rm alpine cat /proc/meminfo

  2. MemTotal:      197869260KB

  3. MemFree:         3677800KB

  4. MemAvailable:   62210088KB


那么Java是如何获取到Host的内存信息的呢?没错就是通过/proc/meminfo来获取到的。
默认情况下,JVM的Max Heap Size是系统内存的1/4,假如我们系统是8G,那么JVM将的默认Heap≈2G。
Docker通过CGroups完成的是对内存的限制,而/proc目录是已只读形式挂载到容器中的,由于默认情况下Java压根就看不见CGroups的限制的内存大小,而默认使用/proc/meminfo中的信息作为内存信息进行启动,这种不兼容情况会导致,如果容器分配的内存小于JVM的内存,JVM进程会被理解杀死。
内存限制不兼容

640?wx_fmt=png


我们首先来看一组测试,这里我们采用一台内存为188G的物理机。
 
 
  1. #free -g


  2.          total used free shared buff/cache available

    Mem:      188   122   1      0     64           64


以下的测试中,我们将包含OpenJDK的hotspot虚拟机,IBM的OpenJ9虚拟机。
以下测试中,我们把正确识别到限制的JDK,称之为安全(即不会超出容器限制不会被kill),反之称之为危险。
测试用例1(OpenJDK)
这一组测试我们使用最新的OpenJDK8-12,给容器限制内存为4G,看JDK默认参数下的最大堆为多少?看看我们默认参数下多少版本的JDK是安全的 命令如下,如果你也想试试看,可以用一下命令。
 
 
  1. docker run -m 4GB --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version

  2. docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version

  3. docker run -m 4GB --rm  openjdk:10-jre-slim java -XshowSettings:vm  -version

  4. docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version

  5. docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version


OpenJDK8(并没有识别容器限制,26.67G) 危险。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version


  2. VM settings:

  3.    Max. Heap Size (Estimated): 26.67G

  4.    Ergonomics Machine Class: server

  5.    Using VM: OpenJDK 64-Bit Server VM


  6. openjdk version "1.8.0_181"

  7. OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)

  8. OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)


OpenJDK8 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap (正确的识别容器限制,910.50M)安全。

 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 910.50M

  4.    Ergonomics Machine Class: server

  5.    Using VM: OpenJDK 64-Bit Server VM


  6. openjdk version "1.8.0_181"

  7. OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)

  8. OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)


OpenJDK 9(并没有识别容器限制,26.67G)危险。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 29.97G

  4.    Using VM: OpenJDK 64-Bit Server VM


  5. openjdk version "9.0.4"

  6. OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)

  7. OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)


OpenJDK 9 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap(正确的识别容器限制,1G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM


  5. openjdk version "9.0.4"

  6. OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)

  7. OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)


OpenJDK 10(正确的识别容器限制,1G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 32GB --rm  openjdk:10-jre-slim java -XshowSettings:vm -XX:MaxRAMFraction=1  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM


  5. openjdk version "10.0.2" 2018-07-17

  6. OpenJDK Runtime Environment (build 10.0.2+13-Debian-2)

  7. OpenJDK 64-Bit Server VM (build 10.0.2+13-Debian-2, mixed mode)


OpenJDK 11(正确的识别容器限制,1G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM


  5. openjdk version "11.0.1" 2018-10-16

  6. OpenJDK Runtime Environment (build 11.0.1+13-Debian-3)

  7. OpenJDK 64-Bit Server VM (build 11.0.1+13-Debian-3, mixed mode, sharing)


OpenJDK 12(正确的识别容器限制,1G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM


  5. openjdk version "12-ea" 2019-03-19

  6. OpenJDK Runtime Environment (build 12-ea+23)

  7. OpenJDK 64-Bit Server VM (build 12-ea+23, mixed mode, sharing)


测试用例2(IBM OpenJ9)
 
 
  1. docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version

  3. docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version

  4. docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java -XshowSettings:vm  -version


OpenJDK8-OpenJ9(正确的识别容器限制,3G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Ergonomics Machine Class: server

  5.    Using VM: Eclipse OpenJ9 VM


  6. openjdk version "1.8.0_192"

  7. OpenJDK Runtime Environment (build 1.8.0_192-b12_openj9)

  8. Eclipse OpenJ9 VM (build openj9-0.11.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20181107_95 (JIT enabled, AOT enabled)

  9. OpenJ9   - 090ff9dcd

  10. OMR      - ea548a66

  11. JCL      - b5a3affe73 based on jdk8u192-b12)


OpenJDK9-OpenJ9 (正确的识别容器限制,3G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Using VM: Eclipse OpenJ9 VM


  5. openjdk version "9.0.4-adoptopenjdk"

  6. OpenJDK Runtime Environment (build 9.0.4-adoptopenjdk+12)

  7. Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 9 Linux amd64-64-Bit Compressed References 20180814_248 (JIT enabled, AOT enabled)

  8. OpenJ9   - 24e53631

  9. OMR      - fad6bf6e

  10. JCL      - feec4d2ae based on jdk-9.0.4+12)


OpenJDK10-OpenJ9 (正确的识别容器限制,3G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Using VM: Eclipse OpenJ9 VM


  5. openjdk version "10.0.2-adoptopenjdk" 2018-07-17

  6. OpenJDK Runtime Environment (build 10.0.2-adoptopenjdk+13)

  7. Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 10 Linux amd64-64-Bit Compressed References 20180813_102 (JIT enabled, AOT enabled)

  8. OpenJ9   - 24e53631

  9. OMR      - fad6bf6e

  10. JCL      - 7db90eda56 based on jdk-10.0.2+13)


OpenJDK11-OpenJ9(正确的识别容器限制,3G)安全。
 
 
  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Using VM: Eclipse OpenJ9 VM


  5. openjdk version "11.0.1" 2018-10-16

  6. OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.1+13)

  7. Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.11.0, JRE 11 Linux amd64-64-Bit Compressed References 20181020_70 (JIT enabled, AOT enabled)

  8. OpenJ9   - 090ff9dc

  9. OMR      - ea548a66

  10. JCL      - f62696f378 based on jdk-11.0.1+13)


分析
分析之前我们先了解这么一个情况:
 
 
  1. JavaMemory (MaxRAM) = 元数据+线程+代码缓存+OffHeap+Heap...


一般我们都只配置Heap即使用-Xmx来指定JVM可使用的最大堆。而JVM默认会使用它获取到的最大内存的1/4作为堆的原因也是如此。
安全性(即不会超过容器限制被容器kill)
OpenJDK:
OpenJdk8-12,都能保证这个安全性的特点(8和9需要特殊参数,-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap)。
OpenJ9:
IBM OpenJ9所有的版本都能识别到容器限制。
资源利用率
OpenJDK:
自动识别到容器限制后,OpenJDK把最大堆设置为了大概容器内存的1/4,对内存的浪费不可谓不大。
当然可以配合另一个JVM参数来配置最大堆。-XX:MaxRAMFraction=int。下面是我整理的一个常见内存设置的表格,从中我们可以看到似乎JVM默认的最大堆的取值为MaxRAMFraction=4,随着内存的增加,堆的闲置空间越来越大,在16G容器内存时,Java堆只有不到4G。
 
 
  1. MaxRAMFraction取值    堆占比 容器内存=1G 容器内存=2G 容器内存=4G   容器内存=8G   容器内存=16G

  2. 1                     90%  910.50M     1.78G       3.56G      7.11G      14.22G

  3. 2                     50%  455.50M     910.50M     1.78G      3.56G      7.11G

  4. 3                     33%  304.00M     608.00M     1.19G      2.37G      4.74G

  5. 4                     25%  228.00M     455.50M     910.50M    1.78G      3.56G


OpenJ9:
关于OpenJ9的的详细介绍你可以从https://www.eclipse.org/openj9/docs/xxusecontainersupport/了解更多。
对于内存利用率OpenJ9的策略是优于OpenJDK的。以下是OpenJ9的策略表格。
 
 
  1. 容器内存<size>    最大Java堆大小

  2. 小于1GB           50%<size>

  3. 1GB-2GB          <size>-512MB

  4. 大于2GB           大于2GB


结论
注意:这里我们说的是容器内存限制,和物理机内存不同。
自动档
如果你想要的是,不显示的指定-Xmx,让Java进程自动的发现容器限制。
如果你想要的是JVM进程在容器中安全稳定的运行,不被容器kiil,并且你的JDK版本小于10(大于等于JDK10的版本不需要设置,参考前面的测试)。
你需要额外设置JVM参数-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap,即可保证你的Java进程不会因为内存问题被容器Kill。
当然这个方式使用起来简单,可靠,缺点也很明显,资源利用率过低(参考前面的表格MaxRAMFraction=4)。
如果想在基础上我还想提高一些内存资源利用率,并且容器内存为1GB - 4GB,我建议你设置-XX:MaxRAMFraction=2,在大于8G的可以尝试设置-XX:MaxRAMFraction=1(参考上表格)。
手动挡
如果你想要的是手动挡的体验,更加进一步的利用内存资源,那么你可能需要回到手动配置时代-Xmx。
手动挡部分,请可以完全忽略上面我的BB。
上面我们说到了自动挡的配置,用起来很简单很舒服,自动发现容器限制,无需担心和思考去配置-Xmx。
比如你有内存1G那么我建议你的-Xmx750M,2G建议配置-Xmx1700M,4G建议配置-Xmx3500-3700M,8G建议设置-Xmx7500-7600M,总之就是至少保留300M以上的内存留给JVM的其他内存。如果堆特别大,可以预留到1G甚至2G。
手动挡用起来就没有那么舒服了,当然资源利用率相对而言就更高了。
原文链接:https://qingmu.io/2018/12/17/How-to-securely-limit-JVM-resources-in-a-container/


Kubernetes实战培训

640?


Kubernetes实战培训将于2019年3月8日在深圳开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习 本次培训包括:云原生介绍、微服务;Docker基础、Docker工作原理、镜像、网络、存储、数据卷、安全;Kubernetes架构、核心组件、常用对象、网络、存储、认证、服务发现、调度和服务质量保证、日志、监控、告警、Helm、实践案例等。
640?wx_fmt=png
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值