摘要: 随着容器技术的成熟,越来越多的企业客户在企业中选择Docker和Kubernetes作为应用平台的基础。然而在实践过程中,还会遇到很多具体问题。本文分析并解决了Java应用在容器使用过程中关于Heap大小设置的一个常见问题。
随着容器技术的成熟,越来越多的企业客户在企业中选择Docker和Kubernetes作为应用平台的基础。然而在实践过程中,还会遇到很多具体问题。本系列文章会记录阿里云容器服务团队在支持客户中的一些心得体会和最佳实践。我们也欢迎您通过邮件和钉钉群和我们联系,分享您的思路和遇到的问题。
问题
有些同学反映:自己设置了容器的资源限制,但是Java应用容器在运行中还是会莫名奇妙地被OOM Killer干掉。
这背后一个非常常见的原因是:没有正确设置容器的资源限制以及对应的JVM的堆空间大小。
我们拿一个tomcat应用为例,其实例代码和Kubernetes部署文件可以从Github中获得。
git clone https://github.com/denverdino/system-info cd system-info`
下面是一个Kubernetes的Pod的定义描述:
  1. 1.Pod中的app是一个初始化容器,负责把一个JSP应用拷贝到 tomcat 容器的 “webapps”目录下。注: 镜像中JSP应用index.jsp用于显示JVM和系统资源信息。
2.tomcat 容器会保持运行,而且我们限制了容器最大的内存用量为256MB内存。
apiVersion: v1 kind: Pod metadata: name: test spec: initContainers: - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info name: app imagePullPolicy: IfNotPresent command: - "cp" - "-r" - "/system-info" - "/app" volumeMounts: - mountPath: /app name: app-volume containers: - image: tomcat:9-jre8 name: tomcat imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /usr/local/tomcat/webapps name: app-volume ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "256Mi" cpu: "500m" volumes: - name: app-volume emptyDir: {}
我们执行如下命令来部署、测试应用
$ kubectl create -f test.yaml pod "test" created $ kubectl get pods test NAME READY STATUS RESTARTS AGE test 1/1 Running 0 28s $ kubectl exec test curl http://localhost:8080/system-info/ ...
我们可以看到HTML格式的系统CPU/Memory等信息,我们也可以用 html2text 命令将其转化成为文本格式。
注意:本文是在一个 2C 4G的节点上进行的测试,在不同环境中测试输出的结果会有所不同
$ kubectl exec test curl http://localhost:8080/system-info/ | html2text Java version Oracle Corporation 1.8.0_162 Operating system Linux 4.9.64 Server Apache Tomcat/9.0.6 Memory Used 29 of 57 MB, Max 878 MB Physica Memory 3951 MB CPU Cores 2 **** Memory MXBean **** Heap Memory Usage init = 65011712(63488K) used = 19873704(19407K) committed = 65536000(64000K) max = 921174016(899584K) Non-Heap Memory Usage init = 2555904(2496K) used = 32944912(32172K) committed = 33882112(33088K) max = -1(-1K)
我们可以发现,容器中看到的系统内存是 3951MB,而JVM Heap Size最大是 878MB。纳尼?!我们不是设置容器资源的容量为256MB了吗?如果这样,当应用内存的用量超出了256MB,JVM还没对其进行GC,而JVM进程就会被系统直接OOM干掉了。
问题的根源在于:
  • 对于JVM而言,如果没有设置Heap Size,就会按照宿主机环境的内存大小缺省设置自己的最大堆大小。
  • Docker容器利用CGroup对进程使用的资源进行限制,而在容器中的JVM依然会利用宿主机环境的内存大小和CPU核数进行缺省设置,这导致了JVM Heap的错误计算。
类似,JVM缺省的GC、JIT编译线程数量取决于宿主机CPU核数。如果我们在一个节点上运行多个Java应用,即使我们设置了CPU的限制,应用之间依然有可能因为GC线程抢占切换,导致应用性能收到影响。
了解了问题的根源,我们就可以非常简单地解决问题了
解决思路
开启CGroup资源感知
Java社区也关注到这个问题,并在JavaSE8u131+和JDK9 支持了对容器资源限制的自动感知能力 blogs.oracle.com/java-platfo…
其用法就是添加如下参数
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap …
我们在上文示例的tomcat容器添加环境变量 “JAVA_OPTS”参数
apiVersion: v1 kind: Pod metadata: name: cgrouptest spec: initContainers: - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info name: app imagePullPolicy: IfNotPresent command: - "cp" - "-r" - "/system-info" - "/app" volumeMounts: - mountPath: /app name: app-volume containers: - image: tomcat:9-jre8 name: tomcat imagePullPolicy: IfNotPresent env: - name: JAVA_OPTS value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" volumeMounts: - mountPath: /usr/local/tomcat/webapps name: app-volume ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "256Mi" cpu: "500m" volumes: - name: app-volume emptyDir: {}
我们部署一个新的Pod,并重复相应的测试
$ kubectl create -f cgroup_test.yaml pod "cgrouptest" created $ kubectl exec cgrouptest curl http://localhost:8080/system-info/ | html2txt Java version Oracle Corporation 1.8.0_162 Operating system Linux 4.9.64 Server Apache Tomcat/9.0.6 Memory Used 23 of 44 MB, Max 112 MB Physica Memory 3951 MB CPU Cores 2 **** Memory MXBean **** Heap Memory Usage init = 8388608(8192K) used = 25280928(24688K) committed = 46661632(45568K) max = 117440512(114688K) Non-Heap Memory Usage init = 2555904(2496K) used = 31970840(31221K) committed = 32768000(32000K) max = -1(-1K)
我们看到JVM最大的Heap大小变成了112MB,这很不错,这样就能保证我们的应用不会轻易被OOM了。随后问题又来了,为什么我们设置了容器最大内存限制是256MB,而JVM只给Heap设置了112MB的最大值呢?
这就涉及到JVM的内存管理的细节了,JVM中的内存消耗包含Heap和Non-Heap两类;类似Class的元信息,JIT编译过的代码,线程堆栈(thread stack),GC需要的内存空间等都属于Non-Heap内存,所以JVM还会根据CGroup的资源限制预留出部分内存给Non Heap,来保障系统的稳定。(在上面的示例中我们可以看到,tomcat启动后Non Heap占用了近32MB的内存)
在最新的JDK 10中,又对JVM在容器中运行做了进一步的优化和增强。
容器内部感知CGroup资源限制
如果无法利用JDK 8/9的新特性,比如还在使用JDK6的老应用,我们还可以在容器内部利用脚本来获取容器的CGroup资源限制,并通过设置JVM的Heap大小。
Docker1.7开始将容器cgroup信息挂载到容器中,所以应用可以从 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件获取内存、 CPU等设置,在容器的应用启动命令中根据Cgroup配置正确的资源设置 -Xmx, -XX:ParallelGCThreads等参数
在 yq.aliyun.com/articles/18… 一文中已经有相应的示例和代码,本文不再赘述
总结
本文分析了Java应用在容器使用中一个常见Heap设置的问题。容器与虚拟机不同,其资源限制通过CGroup来实现。而容器内部进程如果不感知CGroup的限制,就进行内存、CPU分配可能导致资源冲突和问题。
我们可以非常简单地利用JVM的新特性和自定义脚本来正确设置资源限制。这个可以解决绝大多数资源限制的问题。
关于容器应用中资源限制还有一类问题是,一些比较老的监控工具或者free/top等系统命令,在容器中运行时依然会获取到宿主机的CPU和内存,这导致了一些监控工具在容器中运行时无法正常计算资源消耗。社区中常见的做法是利用 lxcfs 来让容器在资源可见性的行为和虚机保持一致,后续文章会介绍其在Kubernetes上的使用方案。
阿里云Kubernetes服务 全球首批通过Kubernetes一致性认证,简化了Kubernetes集群生命周期管理,内置了与阿里云产品集成,也将进一步简化Kubernetes的开发者体验,帮助用户关注云端应用价值创新。
原文链接
干货好文,请关注扫描以下二维码:

Kubernetes之路 1 - Java应用资源限制的迷思相关推荐

  1. Kubernetes之路 2 - 利用LXCFS提升容器资源可见性

    本系列文章记录了企业客户在应用Kubernetes时的一些常见问题 第一篇:Java应用资源限制的迷思 第二篇:利用LXCFS提升容器资源可见性 第三篇:解决服务依赖 这是本系列的第2篇内容,将介绍在 ...

  2. Kubernetes之路 3 - 解决服务依赖

    摘要: 在容器服务的客户群中,一个经常被问起的问题就是如何处理服务间依赖.本文介绍了常见的解决方法来实现服务的依赖检查,还进一步用示例展示了如何利用init container, liveness/r ...

  3. 获取日志的等级_进阶之路:Java 日志框架全画传(中)

    导读:随着互联网和大数据的蓬勃发展,分布式日志系统以及日志分析系统得到了广泛地应用.目前,几乎在所有应用程序中,都会用到各种各样的日志框架来记录程序的运行信息.鉴于此,工程师十分有必要熟悉主流的日志记 ...

  4. Java学习资源整理(超级全面),java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  5. java国际化——资源包

    [0]README 1) 本文部分文字描述转自 core java volume 2 , 旨在理解 java国际化--资源包 的基础知识 : 2) 本文源代码idea 转自: (利用propertie ...

  6. java实现资源监视器_实现Java监视的12个步骤程序存在缺陷

    java实现资源监视器 Java监视的当前状态最大的问题是什么? 生产中的错误很像喝醉的短信. 您只有在事情已经发生之后才意识到出了点问题. 发短信日志通常比应用程序错误日志更有趣,但是--两者可能同 ...

  7. java架构师之路:JAVA程序员必看的15本书的电子版下载地址

    java架构师之路:JAVA程序员必看的15本书的电子版下载地址 作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从.我想就我自己读过的技术书籍中挑选出来一 ...

  8. java log4j logback jcl_进阶之路:Java 日志框架全画传(下)

    导读:随着互联网和大数据的蓬勃发展,分布式日志系统以及日志分析系统得到了广泛地应用.目前,几乎在所有应用程序中,都会用到各种各样的日志框架来记录程序的运行信息.鉴于此,工程师十分有必要熟悉主流的日志记 ...

  9. Java学习资源、视频教程汇总

    Java学习资源汇总 -(持续更新...) 最高端的JAVA架构师资源(来自龙果学院 价值¥1399元).JAVA互联网分布式架构(龙果学院 价值¥899元).Spring Boot(2017年最新 ...

最新文章

  1. linux ftp随机端口,linuxFTP生产环境配置
  2. 我写了一个面向源码阅读者的 UI 框架(基于 Vue)
  3. 4-1 图像特效介绍
  4. office2016word 每次打开都有进度条问题 解决方式
  5. linux 网络地址
  6. adf.test_在ADF 12.2.1.3中使用基于JSON的REST Web服务
  7. oracle 查询结果升序,Oracle学习日志-8(查询结果排序)
  8. 数据结构之图的存储结构:十字链表法
  9. flume + kafka
  10. 为什么C++构造函数不能是虚函数
  11. 自适应和响应式区别以及写法
  12. Gris 游戏开发-day04
  13. 【笔记】C++ 命令行小游戏 节奏大师(别踩白块) 的制作
  14. 高数 微分的几何意义
  15. javamail 读取邮箱邮件并下载附件
  16. 我学习上的一个小插曲
  17. pagehelper mybatis yml文件配置
  18. 新建网站提升曝光率设置集合(边使用边更新)(包括:SEO优化,Robots设置,CDN加速,防盗链)
  19. 海康威视视频监控demo 源码+库文件
  20. dubbox发布http的注意事项

热门文章

  1. linux查看内存、cpu等硬件信息
  2. 浙大计算机 在职博士,浙江大学在职博士含金量高吗?
  3. 【转载】linux服务器下非root权限安装anaconda
  4. 在ubuntu16.04中一键创建LAMP环境 新系统
  5. 剑指offer 算法 (知识迁移能力2)
  6. 面向对象的三大特性之继承
  7. 【bzoj5082】弗拉格 矩阵乘法
  8. Unable to locate package update
  9. 使用.Net平台构建企业应用系统场(下)
  10. 【CCAI大咖秀】AlphaGo/Mobileye教父:智能科学需要融合机器学习、计算神经科学与认知科学