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

rjd*_*olb 5 java java-8 kubernetes

我有一个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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我明白了:

VM settings:
Max. Heap Size (Estimated): 228.00M
Run Code Online (Sandbox Code Playgroud)

这个值很低是因为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
Run Code Online (Sandbox Code Playgroud)

我明白了:

VM settings:
Max. Heap Size (Estimated): 455.50M
Run Code Online (Sandbox Code Playgroud)

最后设置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
Run Code Online (Sandbox Code Playgroud)

我明白了:

VM settings:
Max. Heap Size (Estimated): 910.50M
Run Code Online (Sandbox Code Playgroud)

Sim*_*sar 7

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

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

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

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

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


rjd*_*olb 6

重要概念

  • 内存请求主要在(Kubernetes)Pod调度时使用。
  • 内存限制定义了该 cgroup 的内存限制。

根据文章容器化您的 Java 应用程序,配置 JVM 的最佳方法是使用以下 JVM 参数:

-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0
Run Code Online (Sandbox Code Playgroud)

除此之外,您应该始终将 JVM 设置为在内存不足时崩溃。没有什么比健康端点认为自己健康但 JVM 内存不足更糟糕的了!

-XX:+CrashOnOutOfMemoryError
Run Code Online (Sandbox Code Playgroud)

请注意,有一个错误,您需要指定 75.0 而不是 75

要在 Linux 容器中模拟 Kubernetes 中发生的情况并进行限制,请运行:

docker run --memory="300m" eclipse-temurin:17-jdk java -XX:+UseContainerSupport -XX:MinRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -XX:+CrashOnOutOfMemoryError -XshowSettings:vm -version
Run Code Online (Sandbox Code Playgroud)

结果:

VM settings:
    Max. Heap Size (Estimated): 218.50M
    Using VM: OpenJDK 64-Bit Server VM
Run Code Online (Sandbox Code Playgroud)

它也适用于老式 Java 8:

docker run --memory="300m" eclipse-temurin:8-jdk java -XX:+UseContainerSupport -XX:MinRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0  -XX:+CrashOnOutOfMemoryError  -XshowSettings:vm -version
Run Code Online (Sandbox Code Playgroud)

这样,容器将从 cgroup(cgroups v1 或 cgroups v2)读取您的请求。限制对于防止驱逐和吵闹的邻居非常重要。我个人将限制设置为超出请求的 10%。

旧版本的 Java(如 Java 8)不读取 cgroups v2,而 Docker 桌面使用 cgroups v2。 强制 Docker Desktop 使用旧版{"deprecatedCgroupv1": true}cgroups1设置~/Library/Group\ Containers/group.com.docker/settings.json