-XX:+UseParallelGC
-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -XX:GCTimeRatio=4
-XX:AdaptiveSizePolicyWeight=90.
作为集群管理员,您可以通过管理应用程序内存来帮助集群高效运行,方法是:
确定容器化应用程序组件的内存和风险需求,并配置容器内存参数以满足这些需求。
配置容器化应用程序运行时(例如,OpenJDK)以最佳方式遵守已配置的容器内存参数。
诊断和解决与在容器中运行相关的内存相关错误条件。
建议在继续操作之前,完整阅读 OpenShift Dedicated 如何管理计算资源的概述。
对于每种资源(内存、CPU、存储),OpenShift Dedicated 允许对 Pod 中的每个容器设置可选的**请求**和**限制**值。
注意关于内存请求和内存限制的以下事项
内存请求
如果指定了内存请求值,它会影响 OpenShift Dedicated 调度器。调度器在将容器调度到节点时会考虑内存请求,然后为容器在所选节点上隔离请求的内存。
如果节点的内存耗尽,OpenShift Dedicated 会优先驱逐其内存使用量最超过其内存请求的容器。在严重的内存耗尽情况下,节点 OOM killer 可能会根据类似的指标选择并终止容器中的进程。
集群管理员可以分配配额或为内存请求值分配默认值。
集群管理员可以覆盖开发人员指定的内存请求值,以管理集群超额承诺。
内存限制
如果指定了内存限制值,它会对容器中所有进程可以分配的内存设置硬性限制。
如果容器中所有进程分配的内存超过了内存限制,节点内存不足 (OOM) killer 将立即选择并终止容器中的进程。
如果同时指定了内存请求和限制,则内存限制值必须大于或等于内存请求。
集群管理员可以分配配额或为内存限制值分配默认值。
最小内存限制为 12 MB。如果由于 `Cannot allocate memory` Pod 事件导致容器无法启动,则内存限制太低。增加或删除内存限制。删除限制允许 Pod 使用无限的节点资源。
在 OpenShift Dedicated 上调整应用程序内存大小的步骤如下:
确定预期的容器内存使用情况
确定预期的平均和峰值容器内存使用情况,如有必要,可通过实证方法(例如,通过单独的负载测试)确定。请记住考虑所有可能在容器中并行运行的进程:例如,主应用程序是否会产生任何辅助脚本?
确定风险承受能力
确定驱逐的风险承受能力。如果风险承受能力低,则容器应根据预期的峰值使用量加上一定百分比的安全裕度来请求内存。如果风险承受能力较高,则根据预期的平均使用量请求内存可能更合适。
设置容器内存请求
根据以上内容设置容器内存请求。请求越准确地代表应用程序内存使用情况,效果越好。如果请求过高,集群和配额使用效率会降低。如果请求过低,应用程序驱逐的可能性会增加。
根据需要设置容器内存限制
根据需要设置容器内存限制。设置限制会产生这样的效果:如果容器中所有进程的组合内存使用量超过限制,则会立即终止容器进程,因此这是一个利弊兼具的策略。一方面,它可以尽早发现意外的内存过度使用(“快速失败”);另一方面,它也会突然终止进程。
请注意,某些 OpenShift Dedicated 集群可能需要设置限制值;某些集群可能会根据限制值覆盖请求值;某些应用程序镜像依赖于设置的限制值,因为这比请求值更容易检测。
如果设置了内存限制,则该限制不应低于预期的容器内存峰值使用量加上一定的安全裕度。
确保应用程序已调优
如果合适,请确保应用程序根据配置的请求和限制值进行调优。此步骤与池化内存的应用程序(例如 JVM)尤其相关。本页的其余部分将对此进行讨论。
默认的 OpenJDK 设置在容器化环境中效果不佳。因此,在容器中运行 OpenJDK 时,始终必须提供一些其他 Java 内存设置。
JVM 内存布局复杂,依赖于版本,详细描述它超出了本文档的范围。但是,作为在容器中运行 OpenJDK 的起点,至少以下三个与内存相关的任务至关重要
覆盖 JVM 最大堆大小。
如果合适,鼓励 JVM 将未使用的内存释放回操作系统。
确保容器内所有 JVM 进程都已正确配置。
针对在容器中运行的 JVM 工作负载进行最佳调优超出了本文档的范围,并且可能涉及设置多个其他 JVM 选项。
对于许多 Java 工作负载,JVM 堆是最大的单个内存使用者。目前,OpenJDK 默认允许使用计算节点内存的最多 1/4 (1/-XX:MaxRAMFraction
) 用于堆,无论 OpenJDK 是否在容器中运行。因此,**必须**覆盖此行为,尤其是在也设置了容器内存限制的情况下。
至少有两种方法可以实现上述目标
如果设置了容器内存限制并且 JVM 支持实验性选项,请设置 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
。
JDK 11 中已删除 |
这会将 -XX:MaxRAM
设置为容器内存限制,并将最大堆大小 (-XX:MaxHeapSize
/ -Xmx
) 设置为 1/-XX:MaxRAMFraction
(默认为 1/4)。
直接覆盖 -XX:MaxRAM
、-XX:MaxHeapSize
或 -Xmx
之一。
此选项涉及硬编码值,但具有允许计算安全裕度的优点。
默认情况下,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 的内存占用 (第 1 部分)、OpenShift 中调整 Java 的内存占用 (第 2 部分) 和 OpenJDK 和容器 找到详细的附加信息。
如果多个 JVM 在同一容器中运行,则必须确保它们都已正确配置。对于许多工作负载,需要为每个 JVM 分配一定比例的内存预算,并可能留出相当大的安全裕度。
许多 Java 工具使用不同的环境变量 (JAVA_OPTS
、GRADLE_OPTS
等) 来配置它们的 JVM,确保将正确的设置传递给正确的 JVM 可能具有挑战性。
OpenJDK 始终尊重JAVA_TOOL_OPTIONS
环境变量,并且在 JVM 命令行上指定的其他选项将覆盖JAVA_TOOL_OPTIONS
中指定的值。默认情况下,为了确保这些选项默认情况下用于基于 Java 的代理镜像中运行的所有 JVM 工作负载,OpenShift Dedicated Jenkins Maven 代理镜像设置
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap -Dsun.zip.disableMemoryMapping=true"
JDK 11 中已删除 |
这并不能保证不需要其他选项,但旨在作为一个有用的起点。
希望从 Pod 内动态发现其内存请求和限制的应用程序应使用下行 API。
配置 Pod 以添加MEMORY_REQUEST
和MEMORY_LIMIT
节。
创建一个类似于以下内容的 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 | 添加此节以发现应用程序内存限制值。 |
运行以下命令创建 Pod。
$ oc create -f <file_name>.yaml
使用远程 shell 访问 Pod。
$ oc rsh test
检查是否应用了请求的值。
$ env | grep MEMORY | sort
MEMORY_LIMIT=536870912
MEMORY_REQUEST=402653184
还可以通过 |
如果容器中所有进程的总内存使用量超过内存限制,或者在节点内存严重耗尽的情况下,OpenShift Dedicated 可以终止容器中的进程。
当进程由于内存不足 (OOM) 而被终止时,这可能会导致容器立即退出。如果容器 PID 1 进程收到**SIGKILL**信号,则容器将立即退出。否则,容器行为取决于其他进程的行为。
例如,容器进程以代码 137 退出,表明它收到了 SIGKILL 信号。
如果容器没有立即退出,则可以按如下方式检测 OOM 终止:
使用远程 shell 访问 Pod。
# oc rsh test
运行以下命令查看/sys/fs/cgroup/memory/memory.oom_control
中当前的 OOM 终止计数:
$ grep '^oom_kill ' /sys/fs/cgroup/memory/memory.oom_control
oom_kill 0
运行以下命令来触发 OOM 终止:
$ sed -e '' </dev/zero
Killed
运行以下命令查看sed
命令的退出状态:
$ echo $?
137
代码137
表示容器进程以代码 137 退出,表明它收到了 SIGKILL 信号。
运行以下命令查看/sys/fs/cgroup/memory/memory.oom_control
中的 OOM 终止计数器是否递增:
$ grep '^oom_kill ' /sys/fs/cgroup/memory/memory.oom_control
oom_kill 1
如果 Pod 中的一个或多个进程被 OOM 终止,则当 Pod 随后退出(无论是否立即退出)时,其阶段将为失败,原因将为OOMKilled。根据restartPolicy
的值,OOM 终止的 Pod 可能会被重启。如果未重启,则复制控制器等控制器会注意到 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
当节点内存耗尽时,OpenShift Dedicated 可能会将其节点上的 Pod 驱逐。根据内存耗尽的程度,驱逐可能是优雅的或非优雅的。优雅驱逐意味着每个容器的主进程 (PID 1) 收到 SIGTERM 信号,然后一段时间后,如果进程尚未退出,则会收到 SIGKILL 信号。非优雅驱逐意味着每个容器的主进程会立即收到 SIGKILL 信号。
被驱逐的 Pod 的阶段为失败,原因为Evicted。无论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