如何优雅安全地最大化分配给Kubernetes中的Java应用程序的堆空间量?

我有一个Kubernetes部署,它基于anapsix / alpine-java映像部署Java应用程序。 容器中没有其他任何东西可以用于Java应用程序和容器开销。

我想最大化Java进程在docker容器中可以使用的内存量,并最小化将保留但从未使用过的ram数量。

例如我有:

  1. 两个Kubernetes节点,每个节点有8个ram,没有交换
  2. 运行Java进程的Kubernetes部署,最多消耗1 gig堆以实现最佳运行

如何安全地最大化在两个节点上运行的pod的数量,而由于内存限制,从不让Kubernetes终止我的POD?

apiVersion: extensions/v1beta1 kind: Deployment metadata: name: my-deployment spec: replicas: 1 template: metadata: labels: app: my-deployment spec: containers: - name: my-deployment image: myreg:5000/my-deployment:0.0.1-SNAPSHOT ports: - containerPort: 8080 name: http resources: requests: memory: 1024Mi limits: memory: 1024Mi 

Java 8 update 131+有一个标志-XX:+ UseCGroupMemoryLimitForHeap来使用来自Kubernetes部署的Docker限制。

我的Docker实验向我展示了Kubernetes正在发生的事情

如果我在Docker中运行以下代码:

 docker run -m 1024m anapsix/alpine-java:8_server-jre_unlimited java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -version 

我明白了:

 VM settings: Max. Heap Size (Estimated): 228.00M 

这个低值是因为Java默认情况下将-XX:MaxRAMFraction设置为4并且我得到了大约1/4的ram分配…

如果我在Docker中使用-XX:MaxRAMFraction = 2运行相同的命令:

 docker run -m 1024m anapsix/alpine-java:8_server-jre_unlimited java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -XX:MaxRAMFraction=2 -version 

我明白了:

 VM settings: Max. Heap Size (Estimated): 455.50M 

最后设置MaxRAMFraction = 1会导致Kubernetes杀死我的容器。

 docker run -m 1024m anapsix/alpine-java:8_server-jre_unlimited java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -XX:MaxRAMFraction=1 -version 

我明白了:

 VM settings: Max. Heap Size (Estimated): 910.50M 

Kubernetes杀死你的豆荚的原因是资源限制 。 由于容器开销和内存使用规范中十进制和​​二进制前缀之间通常不匹配,因此很难计算。 我的解决方案是完全放弃限制并且只保留要求(如果计划的话,任何情况下你的pod都可用)。 依靠JVM通过静态规范限制其堆,让Kubernetes根据资源需求管理在单个节点上安排的pod数量。

首先,您需要确定在使用所需堆大小运行时容器的实际内存使用情况。 使用-Xmx1024m -Xms1024m运行pod并连接到计划安装的主机-Xmx1024m -Xms1024m守护程序。 运行docker ps以查找您的pod和docker stats 以查看其当前内存使用情况,它是JVM堆的总和,其他静态JVM使用情况,如直接内存和容器开销(alpine with glibc)。 由于在JVM外部处理的某些网络使用,此值应仅在kibibytes内波动。 将此值作为内存要求添加到pod模板。

计算或估计节点上其他组件需要多少内存才能正常运行。 至少会有Kubernetes kubelet,Linux内核,它的用户空间,可能是一个SSH守护进程,在你的情况下是一个在它们上运行的docker守护进程。 如果你可以节省额外的几个字节,你可以选择一个宽大的默认值,如1 Gibibyte,不包括kubelet。 在你的kubelets标志中指定--system-reserved=1Gi--kube-reserved=100Mi并重新启动它。 这将在确定节点上可以运行多少个pod时将这些保留资源添加到Kubernetes调度程序计算中。 有关更多信息,请参阅官方Kubernetes文档 。

这样,在具有8 GB RAM的节点上可能会安排五到七个pod,具体取决于上面选择的和测量的值。 它们将保证在内存要求中指定的RAM,并且不会被终止。 通过Allocated resources下的kubectl describe nodevalidation内存使用情况。 至于优雅/灵活性,如果要增加应用程序可用的RAM,则只需调整内存要求和JVM堆大小。

这种方法只能假设pod的内存使用不会爆炸,如果它不受JVM的限制,一个rouge pod可能会导致驱逐,请参阅资源处理 。

我们在这种情况下所做的是在kubernetes上启动高内存限制,在负载下观察一段时间,并使用-Xmx将内存使用量调整到我们想要达到的水平,或者将内存限制(和请求)调整到实际内存消耗。 说实话,我们通常使用两种方法的组合。 这种方法的关键是在群集上启用适当的监控(在我们的例子中是Prometheus),如果你想要高水平的微调,你可能还想添加类似JMX prometheus导出器的东西,以便详细了解指标。调整你的设置。

我认为这里的问题是kubernetes内存限制是针对容器而MaxRAMFraction是针对jvm的。 因此,如果jvm堆与kubernetes限制相同,那么就没有足够的内存留给容器本身。

你可以尝试的一件事是增加

 limits: memory: 2048Mi 

保持requests限制相同。 请求和限制之间的根本区别在于,如果节点级别有可用内存,请求将超过限制,而limits是硬限制。 这可能不是理想的解决方案,你必须弄清楚你的pod在jvm之上消耗了多少内存,但是作为快速修复,增加limits应该有效。