本系列文章探讨了企业客户在使用Kubernetes时遇到的一些常见问题。
随着容器技术变得越来越复杂,越来越多的企业客户选择Docker和Kubernetes作为其应用平台的基础。但是,这些客户在实践中遇到许多问题。本系列文章介绍了阿里云集装箱服务团队帮助客户完成此流程的经验中的一些见解和最佳实践。
关于Java应用程序的容器化部署,一些用户报告说,尽管他们设置了容器资源限制,但是他们的活动Java应用程序容器被OOM Killer莫名其妙地杀死了。
此问题是一个非常常见的错误的结果:无法正确设置容器资源限制和相应的JVM堆大小。
在这里,我们使用Tomcat应用程序作为示例。您可以从GitHub获取其实例代码和Kubernetes部署文件。
git clone https://github.com/denverdino/system-info
cd system-info`
我们使用以下Kubernetes pod定义:
Tomcat
容器的“webapps”目录。注意:在映像中,JSP应用程序index.jsp用于显示JVM和系统资源信息。Tomcat
容器保持活跃,我们限制了最大内存使用量为256 MB。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,内存和其他信息。我们可以使用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)
我们可以看到,容器中的系统内存为3,951 MB,但最大JVM堆大小为878 MB。为什么会这样?我们没有将容器资源容量设置为256 MB吗?在这种情况下,应用程序内存使用量超过256 MB,但JVM尚未实现垃圾收集(GC)。相反,JVM进程直接被系统的OOM杀手杀死。
问题的根本原因:
同样,默认的JVM GC和JIT编译器线程计数由主机CPU核心数决定。如果我们在单个节点上运行多个Java应用程序,即使我们设置了CPU限制,GC线程仍有可能抢占应用程序之间的切换,从而影响应用程序性能。
现在我们了解了问题的根本原因,很容易解决它。
Java社区也意识到了这个问题,现在支持在Java SE 8u131 +和JDK 9中自动感知容器资源限制。要使用此方法,请添加以下参数:
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堆大小已更改为112 MB,确保应用程序不会被OOM杀手杀死。但这引出了另一个问题:如果我们将最大容器内存限制设置为256 MB,为什么我们只将最大JVM堆内存设置为112 MB?
答案涉及JVM内存管理的细节。JVM中的内存消耗包括堆内存和非堆内存。类元数据,JIT编译代码,线程堆栈,GC和其他此类进程所需的内存取自非堆内存。因此,基于cgroup资源限制,JVM会为非堆使用保留一部分内存,以确保系统稳定性。(在前面的示例中,我们可以看到,在启动Tomcat之后,非堆内存占用了近32 MB。)
在最新版本JDK 10中,对容器中的JVM操作进行了进一步的优化和增强。
如果我们不能使用JDK 8和9的新功能(例如,如果我们仍在使用旧的JDK 6应用程序),我们可以使用容器中的脚本来获取容器的cgroup资源限制并使用它来设置JVM堆尺寸。
从Docker 1.7开始,容器cgroup信息被安装在容器中,允许应用程序从/sys/fs/cgroup/memory/memory.limit_in_bytes
和其他文件中获取内存,CPU和其他设置。因此,容器中应用程序的启动命令包含-Xmx,-XX:ParallelGCThreads的正确资源设置,以及基于cgroup配置的其他参数。
本文着眼于在容器中运行Java应用程序时出现的常见堆设置问题。容器与虚拟机的不同之处在于,它们的资源限制是使用cgroup实现的。此外,如果内部容器进程不了解cgroup限制,则内存和CPU分配可能会产生资源冲突和问题。
通过使用新的JVM功能或自定义脚本来正确设置资源限制,可以很容易地解决此问题。这些解决方案解决了绝大多数资源限制问题。
但是,这些解决方案留下了未解决的影响容器应用程序的资源限制问题。某些较旧的监视工具和系统命令(如“free”和“top”)在容器中运行时仍会获取主机的CPU和内存设置。这意味着某些监视工具在容器中运行时无法准确计算资源消耗。社区中提出的此问题的常见解决方案是使用LXCFS来维护容器的资源可见性行为与虚拟机之间的一致性。随后的文章将讨论这种方法在Kubernetes上的使用。
原文标题《Kubernetes Demystified: Restrictions on Java Application Resources》
作者:Leona Zhang
译者:February
不代表云加社区观点,更多详情请查看原文链接
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。