回收手表的网站,网页版梦幻西游能交易吗,国内自助建站,建设网站电话摘要#xff1a; 随着容器技术的成熟#xff0c;越来越多的企业客户在企业中选择Docker和Kubernetes作为应用平台的基础。然而在实践过程中#xff0c;还会遇到很多具体问题。本文分析并解决了Java应用在容器使用过程中关于Heap大小设置的一个常见问题。随着容器技术的成熟 随着容器技术的成熟越来越多的企业客户在企业中选择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.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-infoname: appimagePullPolicy: IfNotPresentcommand:- cp- -r- /system-info- /appvolumeMounts:- mountPath: /appname: app-volumecontainers:- image: tomcat:9-jre8name: tomcatimagePullPolicy: IfNotPresentvolumeMounts:- mountPath: /usr/local/tomcat/webappsname: app-volumeports:- containerPort: 8080resources:requests:memory: 256Micpu: 500mlimits:memory: 256Micpu: 500mvolumes:- name: app-volumeemptyDir: {}
我们执行如下命令来部署、测试应用
$ 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/ | html2textJava 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了吗如果这样当应用内存的用量超出了256MBJVM还没对其进行GC而JVM进程就会被系统直接OOM干掉了。
问题的根源在于
对于JVM而言如果没有设置Heap Size就会按照宿主机环境的内存大小缺省设置自己的最大堆大小。Docker容器利用CGroup对进程使用的资源进行限制而在容器中的JVM依然会利用宿主机环境的内存大小和CPU核数进行缺省设置这导致了JVM Heap的错误计算。
类似JVM缺省的GC、JIT编译线程数量取决于宿主机CPU核数。如果我们在一个节点上运行多个Java应用即使我们设置了CPU的限制应用之间依然有可能因为GC线程抢占切换导致应用性能收到影响。
了解了问题的根源我们就可以非常简单地解决问题了
解决思路
开启CGroup资源感知
Java社区也关注到这个问题并在JavaSE8u131和JDK9 支持了对容器资源限制的自动感知能力 https://blogs.oracle.com/java-platform-group/java-se-support-for-docker-cpu-and-memory-limits
其用法就是添加如下参数
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-infoname: appimagePullPolicy: IfNotPresentcommand:- cp- -r- /system-info- /appvolumeMounts:- mountPath: /appname: app-volumecontainers:- image: tomcat:9-jre8name: tomcatimagePullPolicy: IfNotPresentenv:- name: JAVA_OPTSvalue: -XX:UnlockExperimentalVMOptions -XX:UseCGroupMemoryLimitForHeapvolumeMounts:- mountPath: /usr/local/tomcat/webappsname: app-volumeports:- containerPort: 8080resources:requests:memory: 256Micpu: 500mlimits:memory: 256Micpu: 500mvolumes:- name: app-volumeemptyDir: {}
我们部署一个新的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等参数
在 https://yq.aliyun.com/articles/18037 一文中已经有相应的示例和代码本文不再赘述
总结
本文分析了Java应用在容器使用中一个常见Heap设置的问题。容器与虚拟机不同其资源限制通过CGroup来实现。而容器内部进程如果不感知CGroup的限制就进行内存、CPU分配可能导致资源冲突和问题。
我们可以非常简单地利用JVM的新特性和自定义脚本来正确设置资源限制。这个可以解决绝大多数资源限制的问题。
关于容器应用中资源限制还有一类问题是一些比较老的监控工具或者free/top等系统命令在容器中运行时依然会获取到宿主机的CPU和内存这导致了一些监控工具在容器中运行时无法正常计算资源消耗。社区中常见的做法是利用 lxcfs 来让容器在资源可见性的行为和虚机保持一致后续文章会介绍其在Kubernetes上的使用方案。
阿里云Kubernetes服务 全球首批通过Kubernetes一致性认证简化了Kubernetes集群生命周期管理内置了与阿里云产品集成也将进一步简化Kubernetes的开发者体验帮助用户关注云端应用价值创新。
原文链接 干货好文请关注扫描以下二维码