K8s下Java应用一点思考

最近公司在搞降本的事,出于好奇,查看了很多服务的Pod,资源实际使用率都是非常低的,但是资源申请又非常高,于是就开始思考一些问题,今天把我认为的常见问题分享下。

个人见解,如有不对,请纠正。

K8s环境下,unused committed memory 可以被共享吗?

一般从JVM的Prometheus采集的metric里会看到有不同的memory定义,主要由Max Memory,Used Memory,Committed Memory。其中前两个比较好理解,这里主要说下Committed Memory。

Java 文档中对已提交内存的定义

represents the amount of memory (in bytes) that is guaranteed to be available for use by the Java virtual machine. The amount of committed memory may change over time (increase or decrease). The Java virtual machine may release memory to the system and committed could be less than init. committed will always be greater than or equal to used.
大意:用于保证JVM正常运行的可用内存。已提交的内存会随时发生变化(升高或降低)。JVM会释放已提交但未使用的内存给OS,同时已提交的内存可能会小于-Xms,但已提交的内存总是大于等于已使用的内存。

根据定义,基本可以知道committed memory属于Java进程独占的,不可以被其他进程使用,所以也就不存在共享。

但再使用Java 11 G1 时,常常会看到metric里committed memory 久久不能被释放,尽管文档说GC会尝试方式committed memory。
查阅相关文档,在Java 11 中G1对unused committed memory 释放的条件,在一般情况下比较难以达到
条件

  1. Full GC;(G1的设计之初,就是想通过分散成多次小GC等尽量避免触发Full GC)
  2. 执行concurrent cycle;

这情况在k8s下,其实更是不友好,因为这些unused committed memory依然会产生费用,导致资源使用率下降。
所以,日常我们选择Pod型号时,在满足应用正常启动及运行中产生对象的情况下,多冗余部分(小于50%,视成本和性能而定)的内存。

K8s里的资源配置requests与limits

K8s里这两个参数很关键,是Pod被快速schedule的关键因素之一。

requests:Pod(Container) 里进程启动所需要的最小的资源配置,它是k8s承诺给到使用者的资源(CPU和Memory);
limits:Pod(Container) 里进程运行时可向OS申请的最大资源配置,它不是k8s承诺的资源,言外之意是被schedule到的node节点可能没有limits的资源。

limits的这种设计,主要是考虑到pod在启动时应该能够被很容易的schedule,而不是因为node上的可用资源不够,导致创建新的node阶段,从而导致跟多的小块资源未被使用。

在日常配置时,可以将requests配置成 JVM的 -Xms ,而limits配置成大于-Xmx的配比值,因为要保证非堆及其他的内存使用。

K8s环境下如何合理配置JVM heap?

根据上面requests和limits的说明,理论上 requests >= -Xms,而 limits > -Xmx ,可根据实际实际的调优结果,选择更合适的资源配比。

配置合理的JVM

  1. 需要分析应用的类型
    比如有些应用启动时会加载大量的数据到内存中,那启动就需要更多的内存,一般属于内存型应用,该种应用应该配置更多的内存,以避免发生fault page,甚至是OOM;
    比如有些应用运行时,会发生大量的运算,所以需要配置更合理CPU及内存,一般属于CPU型应用,这种应用内存也不能太小;
  2. 思考K8s下heap与资源配置
    可以将-Xms 与-Xmx 配置相同,即requests = -Xms = -Xmx ,思路是
    (1) 能够保证Java应用在k8s应用上的安全运行,即不会因为内存不够问题发生OOM;
    (2)一般应用运行,会出现很快出现Committed Memory达到了-Xmx的情况,我们知道GC在向OS申请内存,会发生抖动,影响性能;

以上只是一些个人参考的思路,欢迎讨论。

K8s环境下如何看待OOM?

K8s里核心的设计理念之一 —— 容错性,就是说当发生意外后,Pod能够被快速从中恢复。
也就是说,我们应该用新的视角来看到Pod的宕机等问题,而非像使用物理机,EC2等一样,发生问题,可以快速重启恢复,这是一种思路上的转变。

但这不意味着,OOM就变得正常了,OOM对于Java应用来说,依然是比较关键的问题。一个应用经常发生OOM,可能是因为内存使用不对,或者程序内代码有问题等等,我们依然需要去关注,去定位。但OOM在K8s下变得更不可控,也就是说概率变大了,因为limits无法保证,所以有时候,就需要重新思考这个问题。

K8s环境下是否适合使用大资源的pod?

是否可以使用大资源的Pod?当然没有问题,但是否合适,可曾想过?
我有见过8CPU_32GB,10CPU_20GB的应用跑在k8s上,资源使用率实际上不是很高,幸好这样的应用不会很多。

从K8s的schedule的角度考虑,我们应该使用小Pod,因为便于被schedule。
从服务拆分的角度考虑,当一个服务复杂到不得不用大资源运行时,应该可以考虑拆分了。
从应用运行的角度考虑,不是采用一个大资源的Pod处理所有请求,而是将大资源的Pod拆分成多个小资源Pod均衡运行。

Reference

https://openjdk.org/jeps/346
https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

你可能感兴趣的:(K8s下Java应用一点思考)