-XX:+UseParallelGC
-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -XX:GCTimeRatio=4
-XX:AdaptiveSizePolicyWeight=90.
作为集群管理员,您可以通过管理应用程序内存来帮助您的集群高效运行,方法是:
确定容器化应用程序组件的内存和风险需求,并配置适合这些需求的容器内存参数。
配置容器化应用程序运行时(例如,OpenJDK)以最佳方式遵守已配置的容器内存参数。
诊断和解决与在容器中运行相关的内存相关错误条件。
建议在继续操作之前,完整阅读 AWS 上 Red Hat OpenShift 服务如何管理计算资源的概述。
对于每种资源(内存、CPU、存储),AWS 上的 Red Hat OpenShift 服务允许在 Pod 中的每个容器上设置可选的**请求**和**限制**值。
请注意以下关于内存请求和内存限制的内容
内存请求
如果指定了内存请求值,它会影响 AWS 上 Red Hat OpenShift 服务的调度程序。调度程序在将容器调度到节点时会考虑内存请求,然后为容器在所选节点上隔离请求的内存。
如果节点的内存耗尽,AWS 上的 Red Hat OpenShift 服务会优先驱逐其内存使用量最超过其内存请求的容器。在严重的内存耗尽情况下,节点 OOM 杀手可能会根据类似的指标选择并终止容器中的进程。
集群管理员可以分配配额或为内存请求值分配默认值。
集群管理员可以覆盖开发人员指定的内存请求值,以管理集群超额分配。
内存限制
如果指定了内存限制值,它会对可以在容器中的所有进程中分配的内存设置硬性限制。
如果容器中所有进程分配的内存超过内存限制,节点内存不足 (OOM) 杀手将立即选择并终止容器中的进程。
如果同时指定了内存请求和限制,则内存限制值必须大于或等于内存请求。
集群管理员可以分配配额或为内存限制值分配默认值。
最小内存限制为 12 MB。如果由于 `Cannot allocate memory` pod 事件导致容器无法启动,则内存限制太低。增加或删除内存限制。删除限制允许 Pod 占用无限的节点资源。
在 AWS 上的 Red Hat OpenShift 服务中调整应用程序内存大小的步骤如下:
确定预期的容器内存使用情况
确定预期的平均和峰值容器内存使用情况,如有必要,可通过经验方法(例如,通过单独的负载测试)确定。请记住考虑所有可能在容器中并行运行的进程:例如,主应用程序是否会生成任何辅助脚本?
确定风险承受能力
确定驱逐的风险承受能力。如果风险承受能力低,则应根据预期的峰值使用情况加上一定的安全裕度来请求内存。如果风险承受能力较高,则根据预期的平均使用情况请求内存可能更合适。
设置容器内存请求
根据上述内容设置容器内存请求。请求越准确地代表应用程序的内存使用情况,效果越好。如果请求过高,集群和配额使用效率会降低。如果请求过低,则应用程序驱逐的几率会增加。
如有必要,设置容器内存限制
如有必要,设置容器内存限制。设置限制会产生这样的效果:如果容器中所有进程的组合内存使用量超过限制,则会立即终止容器进程,因此这是一个双刃剑。一方面,它可以使意外的内存使用过量尽早显现(“快速失败”);另一方面,它也会突然终止进程。
请注意,某些 AWS 上的 Red Hat OpenShift 服务集群可能需要设置限制值;某些集群可能会根据限制来覆盖请求;某些应用程序镜像依赖于设置限制值,因为这比请求值更容易检测到。
如果设置了内存限制,则不应将其设置为小于预期的峰值容器内存使用情况加上一定的安全裕度。
确保应用程序已调优
如有必要,确保应用程序已针对已配置的请求和限制值进行调优。此步骤与池化内存的应用程序(例如 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
。
|
这会将 -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 的内存占用 (第一部分)、调整 OpenShift 中 Java 的内存占用 (第二部分) 和 OpenJDK 和容器。
如果多个 JVM 在同一个容器中运行,则必须确保它们都已正确配置。对于许多工作负载,有必要为每个 JVM 分配一定百分比的内存预算,并可能留出相当大的安全裕量。
许多 Java 工具使用不同的环境变量 (JAVA_OPTS
、GRADLE_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"
|
这并不能保证不需要其他选项,但旨在作为有用的起点。
希望在 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
也可以通过 |
如果容器中所有进程的总内存使用量超过内存限制,或者在严重的节点内存耗尽情况下,Red Hat OpenShift Service on AWS 可以终止容器中的进程。
当进程因内存不足 (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 随后退出(无论是否立即退出)时,其阶段将为 **Failed**,原因将为 **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
当节点内存耗尽时,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