×

作为集群管理员,您可以通过管理应用程序内存来帮助集群高效运行,方法是:

  • 确定容器化应用程序组件的内存和风险需求,并配置合适的容器内存参数。

  • 配置容器化应用程序运行时(例如,OpenJDK),以最佳方式遵守已配置的容器内存参数。

  • 诊断和解决与在容器中运行相关的内存相关错误条件。

理解应用程序内存管理

建议您在继续操作之前,完整阅读 AWS 上 Red Hat OpenShift 服务如何管理计算资源的概述。

对于每种类型的资源(内存、CPU、存储),AWS 上的 Red Hat OpenShift 服务允许为 Pod 中的每个容器设置可选的**请求**和**限制**值。

请注意以下关于内存请求和内存限制的信息:

  • 内存请求

    • 如果指定了内存请求值,它会影响 AWS 上 Red Hat OpenShift 服务的调度程序。调度程序在将容器调度到节点时会考虑内存请求,然后为容器在选定节点上隔离请求的内存。

    • 如果节点的内存耗尽,AWS 上的 Red Hat OpenShift 服务会优先驱逐其内存使用量最超过其内存请求的容器。在严重的内存耗尽情况下,节点 OOM killer 可能会根据类似的指标选择并终止容器中的进程。

    • 集群管理员可以分配配额或为内存请求值分配默认值。

    • 集群管理员可以覆盖开发人员指定的内存请求值,以管理集群超额分配。

  • 内存限制

    • 如果指定了内存限制值,它会对容器中所有进程可以分配的内存设置硬性限制。

    • 如果容器中所有进程分配的内存超过内存限制,节点内存不足 (OOM) killer 将立即选择并终止容器中的进程。

    • 如果同时指定了内存请求和限制,则内存限制值必须大于或等于内存请求值。

    • 集群管理员可以分配配额或为内存限制值分配默认值。

    • 最小内存限制为 12 MB。如果由于无法分配内存的 Pod 事件导致容器无法启动,则内存限制太低。增加或删除内存限制。删除限制允许 Pod 使用无限的节点资源。

管理应用程序内存策略

在 AWS 上 Red Hat OpenShift 服务上调整应用程序内存大小的步骤如下:

  1. 确定预期的容器内存使用情况

    确定预期的平均和峰值容器内存使用情况,如有必要,可通过经验方式确定(例如,通过单独的负载测试)。请记住考虑所有可能在容器中并行运行的进程:例如,主应用程序是否会生成任何辅助脚本?

  2. 确定风险承受能力

    确定驱逐的风险承受能力。如果风险承受能力低,则容器应根据预期的峰值使用量加上一定的安全裕度来请求内存。如果风险承受能力较高,则可能更适合根据预期的平均使用量来请求内存。

  3. 设置容器内存请求

    根据上述内容设置容器内存请求。请求越准确地代表应用程序内存使用情况越好。如果请求过高,集群和配额使用效率会降低。如果请求过低,则应用程序被驱逐的可能性会增加。

  4. 根据需要设置容器内存限制

    根据需要设置容器内存限制。设置限制的效果是,如果容器中所有进程的组合内存使用量超过限制,则会立即终止容器进程,因此这是一个利弊兼存的方法。一方面,它可以使意外的过量内存使用尽早显现(“快速失败”);另一方面,它也会突然终止进程。

    请注意,某些 AWS 上的 Red Hat OpenShift 服务集群可能需要设置限制值;某些集群可能会根据限制覆盖请求;某些应用程序镜像依赖于设置限制值,因为这比请求值更容易检测。

    如果设置了内存限制,则不应将其设置为小于预期的峰值容器内存使用量加上一定的安全裕度。

  5. 确保应用程序已调优

    如果适用,请确保应用程序已针对已配置的请求和限制值进行调优。此步骤与池化内存的应用程序(例如 JVM)尤其相关。本页的其余部分将对此进行讨论。

了解适用于 AWS 上 Red Hat OpenShift 服务的 OpenJDK 设置

默认的 OpenJDK 设置不适用于容器化环境。因此,在容器中运行 OpenJDK 时,始终必须提供一些额外的 Java 内存设置。

JVM 内存布局复杂,依赖于版本,详细描述它超出了本文档的范围。但是,作为在容器中运行 OpenJDK 的起点,至少以下三个与内存相关的任务至关重要:

  1. 覆盖 JVM 最大堆大小。

  2. 鼓励 JVM 将未使用的内存释放给操作系统(如果适用)。

  3. 确保容器内的所有 JVM 进程都已正确配置。

本文档不涵盖在容器中运行时对 JVM 工作负载进行最佳调整的细节,这可能需要设置多个额外的 JVM 选项。

理解如何覆盖 JVM 最大堆大小

对于许多 Java 工作负载,JVM 堆是最大的单一内存消耗者。目前,OpenJDK 默认允许使用计算节点内存的最多 1/4 (1/-XX:MaxRAMFraction) 用于堆,无论 OpenJDK 是否在容器中运行。因此,必须覆盖此行为,尤其是在也设置了容器内存限制的情况下。

至少有两种方法可以实现上述目标

  • 如果设置了容器内存限制并且 JVM 支持实验性选项,请设置 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

    JDK 11 中已移除 UseCGroupMemoryLimitForHeap 选项。请改用 -XX:+UseContainerSupport

    这会将 -XX:MaxRAM 设置为容器内存限制,并将最大堆大小 (-XX:MaxHeapSize / -Xmx) 设置为 1/-XX:MaxRAMFraction (默认为 1/4)。

  • 直接覆盖 -XX:MaxRAM-XX:MaxHeapSize-Xmx 之一。

    此选项涉及硬编码值,但具有允许计算安全裕度的优点。

理解如何鼓励 JVM 将未使用的内存释放回操作系统

默认情况下,OpenJDK 不会积极地将未使用的内存返回给操作系统。这对于许多容器化的 Java 工作负载可能是合适的,但值得注意的例外包括那些在容器内与 JVM 并存的其他活动进程的工作负载,无论这些其他进程是原生进程、额外的 JVM 还是两者的组合。

基于 Java 的代理可以使用以下 JVM 参数来鼓励 JVM 将未使用的内存释放回操作系统

-XX:+UseParallelGC
-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -XX:GCTimeRatio=4
-XX:AdaptiveSizePolicyWeight=90.

这些参数旨在在分配的内存超过使用中内存的 110% (-XX:MaxHeapFreeRatio) 或垃圾收集器花费高达 20% 的 CPU 时间 (-XX:GCTimeRatio) 时,将堆内存返回给操作系统。应用程序堆分配绝不会小于初始堆分配(由 -XX:InitialHeapSize / -Xms 覆盖)。更多详细信息,请访问 调整 OpenShift 中 Java 的内存占用 (第一部分)调整 OpenShift 中 Java 的内存占用 (第二部分)OpenJDK 和容器

理解如何确保容器内所有 JVM 进程都得到适当配置

如果多个 JVM 在同一个容器中运行,则必须确保它们都得到适当的配置。对于许多工作负载,需要为每个 JVM 分配一定百分比的内存预算,并保留可能相当大的额外安全裕度。

许多 Java 工具使用不同的环境变量 (JAVA_OPTSGRADLE_OPTS 等) 来配置它们的 JVM,并且确保将正确的设置传递给正确的 JVM 可能具有挑战性。

OpenJDK 始终尊重 JAVA_TOOL_OPTIONS 环境变量,并且在 JVM 命令行上指定的其他选项将覆盖 JAVA_TOOL_OPTIONS 中指定的值。默认情况下,为确保这些选项在基于 Java 的代理映像中运行的所有 JVM 工作负载默认使用,Red Hat OpenShift Service on AWS Jenkins Maven 代理映像设置:

JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap -Dsun.zip.disableMemoryMapping=true"

JDK 11 中已移除 UseCGroupMemoryLimitForHeap 选项。请改用 -XX:+UseContainerSupport

这并不能保证不需要其他选项,但它旨在作为一个有用的起点。

在 Pod 内查找内存请求和限制

希望在 Pod 内动态发现其内存请求和限制的应用程序应该使用向下 API。

步骤
  • 配置 Pod 以添加 MEMORY_REQUESTMEMORY_LIMIT 段落

    1. 创建一个类似于以下内容的 YAML 文件

      apiVersion: v1
      kind: Pod
      metadata:
        name: test
      spec:
        securityContext:
          runAsNonRoot: true
          seccompProfile:
            type: RuntimeDefault
        containers:
        - name: test
          image: fedora:latest
          command:
          - sleep
          - "3600"
          env:
          - name: MEMORY_REQUEST (1)
            valueFrom:
              resourceFieldRef:
                containerName: test
                resource: requests.memory
          - name: MEMORY_LIMIT (2)
            valueFrom:
              resourceFieldRef:
                containerName: test
                resource: limits.memory
          resources:
            requests:
              memory: 384Mi
            limits:
              memory: 512Mi
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: [ALL]
      1 添加此段落以发现应用程序内存请求值。
      2 添加此段落以发现应用程序内存限制值。
    2. 运行以下命令创建 Pod

      $ oc create -f <file_name>.yaml
验证
  1. 使用远程 shell 访问 Pod

    $ oc rsh test
  2. 检查是否应用了请求的值

    $ env | grep MEMORY | sort
    示例输出
    MEMORY_LIMIT=536870912
    MEMORY_REQUEST=402653184

也可以通过 /sys/fs/cgroup/memory/memory.limit_in_bytes 文件在容器内部读取内存限制值。

理解 OOM 终止策略

如果容器中所有进程的总内存使用量超过内存限制,或者在严重的节点内存耗尽情况下,Red Hat OpenShift Service on AWS 可以终止容器中的进程。

当进程因内存不足 (OOM) 而被终止时,这可能会导致容器立即退出。如果容器 PID 1 进程收到 **SIGKILL** 信号,则容器将立即退出。否则,容器行为取决于其他进程的行为。

例如,容器进程以代码 137 退出,表明它收到了 SIGKILL 信号。

如果容器没有立即退出,则可以通过以下方式检测 OOM 终止:

  1. 使用远程 shell 访问 Pod

    # oc rsh test
  2. 运行以下命令查看 /sys/fs/cgroup/memory/memory.oom_control 中当前的 OOM 终止计数

    $ grep '^oom_kill ' /sys/fs/cgroup/memory/memory.oom_control
    示例输出
    oom_kill 0
  3. 运行以下命令以引发 OOM 终止

    $ sed -e '' </dev/zero
    示例输出
    Killed
  4. 运行以下命令查看 sed 命令的退出状态

    $ echo $?
    示例输出
    137

    代码 137 表示容器进程以代码 137 退出,表明它收到了 SIGKILL 信号。

  5. 运行以下命令查看 /sys/fs/cgroup/memory/memory.oom_control 中的 OOM 终止计数器是否已递增

    $ grep '^oom_kill ' /sys/fs/cgroup/memory/memory.oom_control
    示例输出
    oom_kill 1

    如果 Pod 中的一个或多个进程因 OOM 而被终止,则在 Pod 随后退出时(无论是否立即退出),其阶段将为 **Failed**,原因将为 **OOMKilled**。OOM 终止的 Pod 可能会根据 restartPolicy 的值重新启动。如果没有重新启动,则复制控制器等控制器会注意到 Pod 的失败状态,并创建一个新的 Pod 来替换旧的 Pod。

    使用以下命令获取 Pod 状态

    $ oc get pod test
    示例输出
    NAME      READY     STATUS      RESTARTS   AGE
    test      0/1       OOMKilled   0          1m
    • 如果 Pod 未重新启动,请运行以下命令查看 Pod

      $ oc get pod test -o yaml
      示例输出
      ...
      status:
        containerStatuses:
        - name: test
          ready: false
          restartCount: 0
          state:
            terminated:
              exitCode: 137
              reason: OOMKilled
        phase: Failed
    • 如果已重新启动,请运行以下命令查看 Pod

      $ oc get pod test -o yaml
      示例输出
      ...
      status:
        containerStatuses:
        - name: test
          ready: true
          restartCount: 1
          lastState:
            terminated:
              exitCode: 137
              reason: OOMKilled
          state:
            running:
        phase: Running

理解 Pod 驱逐

当节点内存耗尽时,Red Hat OpenShift Service on AWS 可能会将其节点中的 Pod 驱逐出去。根据内存耗尽的程度,驱逐可能是优雅的或非优雅的。优雅驱逐意味着每个容器的主进程 (PID 1) 会收到 SIGTERM 信号,然后如果进程尚未退出,则一段时间后会收到 SIGKILL 信号。非优雅驱逐意味着每个容器的主进程会立即收到 SIGKILL 信号。

被驱逐的 Pod 处于失败状态,原因是被驱逐。无论restartPolicy的值如何,它都不会被重新启动。但是,诸如副本控制器之类的控制器会注意到 Pod 的失败状态,并创建一个新的 Pod 来替换旧的 Pod。

$ oc get pod test
示例输出
NAME      READY     STATUS    RESTARTS   AGE
test      0/1       Evicted   0          1m
$ oc get pod test -o yaml
示例输出
...
status:
  message: 'Pod The node was low on resource: [MemoryPressure].'
  phase: Failed
  reason: Evicted