随着容器技术的成熟,愈来愈多的企业客户在企业中选择Docker和Kubernetes做为应用平台的基础。然而在实践过程当中,还会遇到不少具体问题。本系列文章会记录阿里云容器服务团队在支持客户中的一些心得体会和最佳实践。咱们也欢迎您经过邮件和钉钉群和咱们联系,分享您的思路和遇到的问题。html
有些同窗反映:本身设置了容器的资源限制,可是Java应用容器在运行中仍是会莫名奇妙地被OOM Killer干掉。java
这背后一个很是常见的缘由是:没有正确设置容器的资源限制以及对应的JVM的堆空间大小。git
咱们拿一个tomcat应用为例,其实例代码和Kubernetes部署文件能够从Github中得到。github
git clone https://github.com/denverdino/system-info cd system-info`
下面是一个Kubernetes的Pod的定义描述:web
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: {}
咱们执行以下命令来部署、测试应用docker
$ 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 命令将其转化成为文本格式。api
注意:本文是在一个 2C 4G的节点上进行的测试,在不一样环境中测试输出的结果会有所不一样tomcat
$ 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干掉了。oracle
问题的根源在于:app
相似,JVM缺省的GC、JIT编译线程数量取决于宿主机CPU核数。若是咱们在一个节点上运行多个Java应用,即便咱们设置了CPU的限制,应用之间依然有可能由于GC线程抢占切换,致使应用性能收到影响。
了解了问题的根源,咱们就能够很是简单地解决问题了
开启CGroup资源感知
Java社区也关注到这个问题,并在JavaSE8u131+和JDK9 支持了对容器资源限制的自动感知能力 https://blogs.oracle.com/java...
其用法就是添加以下参数
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等参数
在 https://yq.aliyun.com/article... 一文中已经有相应的示例和代码,本文再也不赘述
本文分析了Java应用在容器使用中一个常见Heap设置的问题。容器与虚拟机不一样,其资源限制经过CGroup来实现。而容器内部进程若是不感知CGroup的限制,就进行内存、CPU分配可能致使资源冲突和问题。
咱们能够很是简单地利用JVM的新特性和自定义脚原本正确设置资源限制。这个能够解决绝大多数资源限制的问题。
关于容器应用中资源限制还有一类问题是,一些比较老的监控工具或者free/top等系统命令,在容器中运行时依然会获取到宿主机的CPU和内存,这致使了一些监控工具在容器中运行时没法正常计算资源消耗。社区中常见的作法是利用 lxcfs 来让容器在资源可见性的行为和虚机保持一致,后续文章会介绍其在Kubernetes上的使用方案。
阿里云Kubernetes服务 全球首批经过Kubernetes一致性认证,简化了Kubernetes集群生命周期管理,内置了与阿里云产品集成,也将进一步简化Kubernetes的开发者体验,帮助用户关注云端应用价值创新。
原文做者:易立