docker oomkiller 与容器内的 malloc 失败

Kla*_*ger 6 memory out-of-memory docker

我写了一个简短的java程序来分配内存:

package com.company;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static final int SIZE_NATIVE_LONG_IN_BYTE = 8;

    public static void main(String[] args) {
        Integer memoryConsumptionInMiB = Integer.parseInt(args[0]);
        List<long[][]> foo = new ArrayList<long[][]>();
        int i = 0;
        while (true) {
            System.out.println(i++);
            foo.add(new long[(1024 / SIZE_NATIVE_LONG_IN_BYTE * 1024)][memoryConsumptionInMiB]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我尝试在具有几个不同参数的 docker 容器中运行它:

  1. Xmx 1G 和 docker 容器无内存限制
  2. Xmx 1G 和 docker run -m 512m
  3. Xmx 1G 和 docker run -m 512m --disable-oom-killer

这就是我运行程序的方式(如果是 3):

(编辑:上传了上述类准备好的图像)

docker run -it --oom-kill-disable -m 512m kazesberger/alpine-java-memory-tester java -classpath . com.company.Main 10 (~Megabytes allocated per iteration)
Run Code Online (Sandbox Code Playgroud)

我的期望是:

  1. 1G 时内存不足(堆)
  2. 在实际的 OutOfMemory 错误发生之前,主机操作系统会杀死 docker 容器(可通过 docker 检查或内核日志进行验证)。
  3. docker 容器内的 malloc 失败并导致 jvm 因某种 OutOfMemoryError 而终止。

实际结果:

  1. 期望得到满足
  2. 期望得到满足
  3. oomkiller 仍在杀死容器。docker 检查仍然显示 OOMKilled true

所以你看,我的实际目标并不是真正压制 oomKiller 实现其目的。我的目标是让容器内的进程无法分配它们不允许的内存。

先决条件/版本: swapoff -a

docker --version Docker 版本 17.11.0-ce-rc4,内部版本 587f1f0

docker run -it kazesberger/alpine-java-memory-tester java -version openjdk 版本“1.8.0_111-internal”

hor*_*str 0

你的期望是不正确的。--oom-kill-disable不禁用虚拟内存过量使用,如果无法分配请求的页面,则会导致malloc失败。mmap相反,该选项映射到通过 sysfs ( ) 公开的cgroup v1 功能/sys/fs/cgroup/memory/docker/<id>/memory.oom_control,并导致请求超出限制的内存的任务被阻塞,直到内存被释放或限制发生变化。

--oom-kill-disable需要 cgroup v1。因此,第一个要求是您的 docker 安装使用 cgroup v1:

$ docker system info | grep -i Cg 
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
Run Code Online (Sandbox Code Playgroud)

如果是这样的话,--oom-kill-disable应该可以使用。您可以确认 cgroup 选项是由 docker 使用 sysfs 设置的:

$ cat /sys/fs/cgroup/memory/docker/<id>/memory.oom_control
oom_kill_disable 1
under_oom 1
oom_kill 0
Run Code Online (Sandbox Code Playgroud)

如果oom_kill_disable未设置,则说明您的 docker 版本未能设置该选项。 under_oom表示已达到限制并且任务正在等待可用内存。

proc 文件系统可用于确认任务确实在等待可用内存:

$ cat /proc/<pid>/task/<tid>/stack
[<0>] __switch_to+0xbc/0xd4
[<0>] mem_cgroup_oom_synchronize+0xf0/0x158
[<0>] pagefault_out_of_memory+0x44/0x1b8
[<0>] do_page_fault+0x254/0x388
[<0>] do_translation_fault+0x54/0x74
[<0>] do_mem_abort+0x58/0xb4
[<0>] el0_ia+0x54/0x68
[<0>] el0_sync_handler+0x15c/0x1c4
[<0>] el0_sync+0x180/0x1c0
Run Code Online (Sandbox Code Playgroud)

在我的测试安装(Docker 版本 20.10.12,构建 e91ed57,Linux 5.10.76)中,我通过更新强制 docker 使用 cgroup v1,settings.json如下所示:

{
  "deprecatedCgroupv1": true
}
Run Code Online (Sandbox Code Playgroud)

使用 cgroup v2,启动容器会触发以下警告:

WARNING: Your kernel does not support OomKillDisable. OomKillDisable discarded.
Run Code Online (Sandbox Code Playgroud)

cgroup v2 不提供oom_kill_disable等效项。

您想要的是 cgroup 级别的行为vm.overcommit_memory = 2(或0作为妥协)。在主机级别进行设置以禁用过度使用会导致您预期的行为:

$ docker run -it  -m 512m test java -Xms32g -Xmx32g Main
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000fff785400000, 11453202432, 0) failed; error='Not enough space' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 11453202432 bytes for committing reserved memory.
# An error report file with more information is saved as:
# //hs_err_pid1.log
Run Code Online (Sandbox Code Playgroud)

不幸的是,据我所知,截至目前该设施还不存在。