排查 OOM 事件


本页面可帮助您排查和解决 Google Kubernetes Engine (GKE) 中的内存不足 (OOM) 事件。了解如何识别 OOM 事件的常见原因,区分容器级和节点级发生情况,并应用解决方案。

本页面适用于希望验证应用是否已成功部署的应用开发者,以及希望了解 OOM 事件的根本原因并验证平台配置的平台管理员和运维人员。如需详细了解我们在 Google Cloud 内容中提及的常见角色和示例任务,请参阅常见的 GKE 用户角色和任务

OOM 事件的常见原因

OOM 事件通常发生在负载或流量高峰期间,此时应用内存用量激增并达到为容器配置的内存限制。

以下情况可能会导致 OOM 事件:

  • 内存限额不足:Pod 清单中的 resources.limits.memory 设置对于应用的典型或峰值内存需求来说过低。
  • 未定义内存请求或限制:如果未定义 resources.limits.memoryresources.requests.memory,则容器的内存用量不受限制。
  • 高负载或激增负载:即使限额通常足够,突然激增的极端负载也可能会使系统资源(包括内存)不堪重负。
  • 内存泄漏:应用可能存在代码缺陷,导致无法正确释放内存。

OOM 事件可能会引发级联故障,因为剩余的容器数量减少,无法处理流量,从而增加了剩余容器的负载。这些容器随后也可能会终止。

Kubernetes 如何处理 OOM 事件

Linux OOM Killer 会处理每个 OOM 事件。OOM Killer 是一种内核进程,当系统内存严重不足时会激活。其目的是通过有策略地终止进程来释放资源,从而防止系统完全崩溃。内核使用评分系统来选择要停止的进程,目的是保持系统稳定性并最大限度地减少数据丢失。

在 Kubernetes 环境中,OOM Killer 在两个不同的范围内运行:控制措施组 (cgroup),影响一个容器;系统,影响整个节点。

容器级 OOM 终止

当容器尝试超出其预定义的内存限制时,会发生容器级 OOM 终止。Kubernetes 会将每个容器分配给具有硬内存限制的特定 cgroup。当容器的内存用量达到此限制时,内核会先尝试回收相应 cgroup 中的内存。如果内核无法使用此进程回收足够的内存,则会调用 cgroup OOM Killer。它会终止相应 cgroup 中的进程,以强制执行资源边界。

当容器中的主进程以这种方式终止时,Kubernetes 会观察到该事件并将容器的状态标记为 OOMKilled。Pod 配置的 restartPolicy 随后会决定结果:

  • AlwaysOnFailure:容器会重启。
  • Never:容器不会重启,并保持终止状态。

通过将故障隔离到有问题的容器,OOM Killer 可防止单个有故障的 Pod 使整个节点崩溃。

cgroup 版本如何影响 OOM Killer 行为

OOM 终止行为在不同 cgroup 版本之间可能存在显著差异。如果您不确定自己使用的是哪个 cgroup 版本,请检查集群节点的 cgroup 模式

  • cgroup v1 中,容器内存 cgroup 中的 OOM 事件可能会导致不可预测的行为。OOM Killer 可能会终止相应 cgroup 中的任何进程,包括不是容器主进程 (PID 1) 的子进程。

    此行为给 Kubernetes 带来了巨大挑战。由于 Kubernetes 主要监控主容器进程的运行状况,因此它不会感知到这些“部分”OOM 终止。即使关键子进程已终止,主容器进程也可能会继续运行。此行为可能会导致 Kubernetes 或运维人员无法立即发现的细微应用故障,但仍可在节点的系统日志 (journalctl) 中看到。

  • cgroup v2 可提供更可预测的 OOM Killer 行为。

    为了帮助保证 cgroup v2 环境中的工作负载完整性,OOM Killer 会防止部分终止,并确保出现以下两种结果之一:终止属于相应 cgroup 及其后代的所有任务(使 Kubernetes 可以看到故障),或者当工作负载没有使用过多内存的任务时,工作负载保持不变并继续运行,而不会发生意外的内部进程终止。

    对于您希望 cgroup v1 行为终止单个进程的场景,kubelet 为 cgroup v2 提供了 singleProcessOOMKill 标志。此标志可让您进行更精细的控制,在发生 OOM 事件时终止单个进程,而不是整个 cgroup。

系统级 OOM 终止

系统级 OOM 终止是一种更严重的事件,当整个节点(而不仅仅是单个容器)耗尽可用内存时,就会发生这种事件。如果所有进程(包括所有 Pod 和系统守护程序)的总内存用量超过节点的容量,则可能会发生此事件。

当此节点内存不足时,全局 OOM Killer 会评估节点上的所有进程,并终止某个进程以为整个系统回收内存。 所选进程通常是短期且使用大量内存的进程。

为防止出现严重的 OOM 情况,Kubernetes 使用节点压力逐出来管理节点资源。此进程涉及在资源(例如内存或磁盘空间)变得极低时从节点中逐出不太重要的 Pod。系统级 OOM 终止表示此逐出进程无法足够快地释放内存来防止问题发生。

如果 OOM Killer 终止容器的进程,其效果通常与 cgroup 触发的终止相同:容器会标记为 OOMKilled,并根据其政策重新启动。不过,如果关键系统进程已终止(这种情况很少见),节点本身可能会变得不稳定。

调查 OOM 事件

以下各部分将帮助您检测和确认 OOM 事件,从最简单的 Kubernetes 工具开始,逐步深入到更详细的日志分析。

检查 Pod 状态是否存在可见的 OOM 事件

确认 OOM 事件的第一步是检查 Kubernetes 是否观察到 OOM 事件。当容器的主进程终止时,Kubernetes 会观察到该事件,这是 cgroup v2 环境中的标准行为。

  • 检查 Pod 的状态:

    kubectl describe pod POD_NAME
    

    POD_NAME 替换为您要调查的 Pod 的名称。

    如果发生了可见的 OOM 事件,则输出类似于以下内容:

    ...
      Last State:     Terminated
        Reason:       OOMKilled
        Exit Code:    137
        Started:      Tue, 13 May 2025 19:05:28 +0000
        Finished:     Tue, 13 May 2025 19:05:30 +0000
    ...
    

如果您在 Reason 字段中看到 OOMKilled,则说明您已确认相应活动。Exit Code137 也表示发生了 OOM 终止。如果 Reason 字段具有不同的值,或者尽管应用出现错误但 Pod 仍在运行,请继续下一部分进行进一步调查。

搜索日志中的不可见 OOM 事件

如果子进程终止,但主容器进程继续运行(cgroup v1 环境中的常见情况),则 OOM 终止对 Kubernetes 来说是“不可见”的。您必须搜索节点的日志,才能找到这些事件的证据。

如需查找不可见的 OOM 终止,请使用 Logs Explorer:

  1. 在 Google Cloud 控制台中,前往 Logs Explorer。

    前往 Logs Explorer

  2. 在查询窗格中,输入以下其中一个查询:

    • 如果您已有认为发生过 OOM 事件的 Pod,请查询该特定 Pod:

      resource.type="k8s_node"
      jsonPayload.MESSAGE:(("POD_NAME" AND "ContainerDied") OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      

      替换以下内容:

      • POD_NAME:您要查询的 Pod 的名称。
      • CLUSTER_NAME:Pod 所属集群的名称。
    • 如需了解哪些 Pod 或节点发生了 OOM 事件,请查询所有 GKE 工作负载:

      resource.type="k8s_node"
      jsonPayload.MESSAGE:("ContainerDied" OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      
  3. 点击运行查询

  4. 在输出中,搜索包含字符串 TaskOOM 的日志条目,以找到 OOM 事件。

  5. 可选:如果您搜索了所有 GKE 工作负载的 OOM 事件,并想确定发生 OOM 事件的具体 Pod,请完成以下步骤:

    1. 对于每个事件,请记下与其关联的容器 ID。
    2. 通过查找包含字符串 ContainerDied 且发生在 OOM 事件后不久的日志条目,来确定容器停止情况。将 OOM 事件中的容器 ID 与相应的 ContainerDied 行进行匹配。

    3. 在您匹配 container IDs 后,ContainerDied 行通常会包含与失败容器关联的 Pod 名称。此 Pod 受到了 OOM 事件的影响。

使用 journalctl 获取实时信息

如果您需要对系统进行实时分析,请使用 journalctl 命令。

  1. 使用 SSH 连接到节点:

    gcloud compute ssh NODE_NAME --location ZONE
    

    替换以下内容:

    • NODE_NAME:要检查的节点的名称。
    • ZONE:节点所属的 Compute Engine 可用区。
  2. 在 shell 中,浏览节点系统日志中的内核消息:

    journalctl -k
    
  3. 分析输出以区分事件类型:

    • 容器级终止:日志条目包含 memory cgroupmem_cgroupmemcg 等字词,表明系统强制执行了 cgroup 限制。
    • 系统级终止:日志条目是一条常规消息(例如 Out of memory: Killed process...),但未提及 cgroup。

解决 OOM 事件

如需解决 OOM 事件,请尝试以下解决方案:

  • 提高内存限制:这是最直接的解决方案。修改 Pod 清单,以提供更高的 resources.limits.memory 值,从而满足应用的用量峰值。如需详细了解如何设置限制,请参阅 Kubernetes 文档中的 Pod 和容器的资源管理
  • 添加或调整内存请求:在 Pod 的清单中,验证 resources.requests.memory 字段是否设置为典型使用情况下的实际值。 此设置有助于 Kubernetes 将 Pod 调度到具有足够内存的节点上。
  • 横向扩缩工作负载:为了分散流量负载并减轻任何单个 Pod 的内存压力,请增加副本数量。如需让 Kubernetes 主动扩缩工作负载,请考虑启用 Pod 横向自动扩缩
  • 纵向扩缩节点:如果节点上的许多 Pod 都接近限制,则节点本身可能太小。如需增加节点的大小,请将工作负载迁移到具有更多内存的节点池。 如需让 Kubernetes 主动扩缩节点,请考虑启用 Pod 纵向自动扩缩
  • 优化应用:检查应用,以找出并解决内存泄漏问题,并优化在流量高峰期间占用大量内存的代码。
  • 删除有问题的工作负载:对于非关键工作负载,作为最后的办法,您可以删除 Pod 以立即缓解集群压力。

后续步骤