目录

1. 背景

2. 问题

3. 解决方案

3.1. 注意

4. 参考


1. 背景

        在使用Kubernetes部署业务应用的实际操作中,由于k8s集群的资源是有限的,为了防止部分应用无节制地使用资源,我们会在Deployment.yaml文件中设置request,limit的资源限制大小。

        在 Kubernetes 中,你可以通过设置 pod 的 spec.containers[].resources.requests 和 spec.containers[].resources.limits 来配置容器的资源需求和限制。以下是一个样例:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: my-springboot-jdk11-demo-image
    resources:
      requests:
        cpu: "0.5"
        memory: "250Mi"
      limits:
        cpu: "2"
        memory: "8192Mi"

在这个例子中:

  • requests:这部分设置了容器启动所需要的资源。在这个例子中,容器启动所需的 CPU 是 0.5 个核心,内存是 250 MiB。
  • limits:这部分设置了容器所能使用的资源的最大值。在这个例子中,容器所能使用的最多的 CPU 是 2 个核心,内存是 8192 MiB(8Gib)。

        如果容器使用的资源超过了所设置的 limits,那么这个容器可能会被 Kubernetes 杀掉并重启。

2. 问题

        在现实中,我们有很多应用使用java开发,那必然会使用JDK基础镜像,在这个实际操作中,我们可能会遇到明明设置了Limit,但是容器的最大Memory就是上不去,卡在2.1Gib左右,这个和使用的基础镜像有关系,部分JDK基础镜像有的存在默认的Xmx和Xms值。或者说,JDK运行在容器中,它无法确定Pod的limit是多少。

  • -Xmx<size>: 这个参数可以设置程序的最大内存分配。比如,如果你想要设置最大内存为1GB,你可以写成-Xmx1024m或者-Xmx1g。
  • -Xms<size>: 这个参数可以设置程序的初始化(最小)内存分配。比如,如果你想要设置最小内存为256MB,你可以写成-Xms256m。

3. 解决方案

        对于运行在容器中的Java应用,于JDK 10及以后的版本,JVM可以自动识别并根据容器的内存限制动态设置堆大小,无需再额外手动设置JVM的-Xmx和-Xms参数。

        JDK 10及以后的版本默认开启了-XX:+UseContainerSupport选项,默认使用容器的cgroup限制作为JVM的内存限制。如果你使用的是JDK 8,可以使用-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap选项来让JVM使用容器cgroup的内存限制。

        此外,你还可以通过使用比例设置JVM的内存分配,例如,你可以使用-XX:MaxRAMPercentage和-XX:MinRAMPercentage参数来限制JVM使用的最大和最小内存占比。这两个参数设置的是JVM可用内存占容器可用内存的最大和最小百分比。

-XX:MaxRAMPercentage=80.0 -XX:MinRAMPercentage=5.0

# 注意
# -XX:+UseContainerSupport参数在JDK 10及以后的版本是默认开启的
# 完整命令:-XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 -XX:MinRAMPercentage=5.0

# JDK8完整命令:-XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 -XX:MinRAMPercentage=5.0

        上述命令就设置了JVM使用最大内存为容器内存的80%,最小内存为容器内存的20%。这样,无论你的pod限制如何变动,JVM都会按照这个比例动态调整。

        在上述例子中,因为设置的容器内存上限是8GB,所以JVM会设置最大堆大小(-Xmx)为8GB的80%,即6.4GB。最小堆大小(-Xms)为8GB的5%,即400MB

完整的Dockerfile如下:

FROM openjdk-11

ADD target/my-app-name.jar /app/app.jar

WORKDIR /app

ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 -XX:MinRAMPercentage=5.0"

ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Dspring.profiles.active=$PROJECT_ENV -jar app.jar"]

EXPOSE 8080

3.1. 注意

        在实际使用过程中,请注意,XX:MaxRAMPercentage=80.0是限制堆内存最大值。我遇到一个项目出现这个问题,当时就提出这个问题,此处也记录一下

:我的container limit 是3000M, 设置-XX:MaxRAMPercentage=80.0之后,在压力测试之后,从监控上看,内存还是能飚高到3000M,为何没有限制住?

        这主要是因为Java程序的内存使用不仅仅包括堆内存(Heap),还包括了非堆内存(Non-Heap),其中包括MetaSpace(元数据区),JVM堆栈,JVM本身代码等等。 MaxRAMPercentage 这个参数只是设置了堆内存(Heap)的大小,但并没有限制非堆内存(Non-Heap)。

        如果你看到的内存占用达到了3000M,可能的情况是,堆内存占用了你设置的比例(80%,即2400M),剩下的600M被非堆内存占用了。

另外一个需要注意的地方,3000M的limit是设置给了整个容器,容器里不仅仅有这一个Java进程。也可能是其他进程占用了一部分内存。

        所以这意味着,如果你希望Java进程的最大内存使用不超过一个确定的值,那么你需要将MaxRAMPercentage设置的更小一些,比如50%或60%等,以便为非堆内存和其他进程留出足够的空间。

        注意,一定要结合实际的应用情况去做这个调整和选择,避免设置过小影响了Java程序的运行性能。

4. 参考

ChatGPT

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐