Kubernetes Demystified:Java应用程序资源的限制

本系列文章探讨了企业客户在使用Kubernetes时遇到的一些常见问题。

随着容器技术变得越来越复杂,越来越多的企业客户选择Docker和Kubernetes作为其应用平台的基础。但是,这些客户在实践中遇到许多问题。本系列文章介绍了阿里云集装箱服务团队帮助客户完成此流程的经验中的一些见解和最佳实践。

关于Java应用程序的容器化部署,一些用户报告说,尽管他们设置了容器资源限制,但是他们的活动Java应用程序容器被OOM Killer莫名其妙地杀死了。

此问题是一个非常常见的错误的结果:无法正确设置容器资源限制和相应的JVM堆大小。

在这里,我们使用Tomcat应用程序作为示例。您可以从GitHub获取其实例代码和Kubernetes部署文件。

git clone https://github.com/denverdino/system-info
cd system-info`

我们使用以下Kubernetes pod定义:

  1. pod中的应用程序是一个初始化容器,负责将一个JSP应用程序复制到Tomcat容器的“webapps”目录。注意:在映像中,JSP应用程序index.jsp用于显示JVM和系统资源信息。
  2. 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杀手杀死。

问题的根本原因:

  1. 如果我们不设置JVM堆大小,则默认情况下会根据主机环境的内存大小设置最大堆大小。
  2. Docker容器使用cgroup来限制进程使用的资源。因此,如果容器中的JVM仍使用基于主机环境内存和CPU内核的默认设置,则会导致不正确的JVM堆计算。

同样,默认的JVM GC和JIT编译器线程计数由主机CPU核心数决定。如果我们在单个节点上运行多个Java应用程序,即使我们设置了CPU限制,GC线程仍有可能抢占应用程序之间的切换,从而影响应用程序性能。

现在我们了解了问题的根本原因,很容易解决它。

解决方案

启用cgroup资源感知

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操作进行了进一步的优化和增强。

容器中cgroup资源限制的感知

如果我们不能使用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

不代表云加社区观点,更多详情请查看原文链接

原文链接:https://dzone.com/articles/kubernetes-demystified-restrictions-on-java-applic

原文作者:Leona Zhang

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术翻译

使用Kubernetes和Ambassador API Gateway部署Java应用程序

在本文中,您将学习如何将三个简单的Java服务部署到Kubernetes(通过新的Docker for Mac / Windows集成在本地运行),并通过Kub...

7092
来自专栏散尽浮华

Docker网络解决方案-Calico部署记录

Calico简单简介 Calico是一个纯三层的协议,为OpenStack虚机和Docker容器提供多主机间通信。Calico不使用重叠网络比如flannel和...

97210
来自专栏有刻

Docker 小记 — Compose & Swarm

52411
来自专栏北京马哥教育

Linux命令的使用格式以及命令帮助信息的获取方式

1.Linux系统上的命令使用格式 ? 2.Linux系统程序文件存放位置 ? 3.Linux获取命令的帮助信息 区分内部命令和外部命令 内部命令在系统启动时就...

3506
来自专栏java一日一条

HAProxy、Nginx 配置 HTTP/2 完整指南

基于最近对HTTP/2的争论和它的优势,是时候升级底层架构了。这篇文章将会介绍如何在安装和配置HAProxy和Ngnix(使用ssl终端)。为了简化流程,我建议...

1451

什么是Docker的编排?它到底意味着什么?为什么我们需要它?

[本文由Yaron Parasol编写]

3665
来自专栏Laoqi's Linux运维专列

KVM部署篇

7504
来自专栏眯眯眼猫头鹰的小树杈

猫头鹰的深夜翻译:持久化容器存储

临时性存储是容器的一个很大的买点。“根据一个镜像启动容器,随意变更,然后停止变更重启一个容器。你看,一个全新的文件系统又诞生了。”

1605
来自专栏北京马哥教育

KVM虚拟化平台部署及管理

前言 KVM即Kernel Virtual Machine,最初是由以色列公司Qumranet开发。2007年2月被导入Linux 2.6.20核心中,成为内核...

4767
来自专栏云计算教程系列

如何在Ubuntu 18.04上安装Anaconda [快速入门]

Anaconda是专为数据科学和机器学习工作流程而设计的,是一个开源包管理器,环境管理器,以及负责Python和R编程语言的分发。

7.4K1

扫码关注云+社区

领取腾讯云代金券