Docker OOM Killer与容器内部的malloc失败比较

6

我写了一个简短的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]);
        }
    }
}

我尝试在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)

我的期望是:

  1. 堆内存超过 1G 时出现 OutOfMemory 错误。
  2. 在真正发生 OutOfMemory 错误之前,主机操作系统会杀死 Docker 容器(可以通过 docker inspect 或内核日志来证明)。
  3. Docker 容器中的 malloc 失败,导致 jvm 终止并出现某种 OutOfMemoryError。

实际结果为:

  1. 期望达成。
  2. 期望达成。
  3. oomkiller 仍然杀死容器。docker inspect 仍然显示 OOMKilled 为 true。

因此,你可以看到我的真正目标并不是抑制 oomKiller 实现其功能。相反,我的目标是使容器内的进程在分配未授权内存时失败。

前提条件/版本信息:

swapoff -a

docker --version Docker version 17.11.0-ce-rc4, build 587f1f0

docker run -it kazesberger/alpine-java-memory-tester java -version openjdk version "1.8.0_111-internal"


你是否有一个公共镜像与这个测试应用程序一起运行,以及完整的Docker命令,以便我们可以确认其行为? - Andy Shinn
谢谢你的回复,@AndyShinn。我建立了一个小的公共图像,并相应地更新了问题。 - Klaus Azesberger
1个回答

0
您的期望是不正确的。 --oom-kill-disable 并不会禁用虚拟内存过度承诺,如果 mmap 无法分配请求的页面,这将导致 malloc 失败。相反,该选项映射到通过 sysfs ( /sys/fs/cgroup/memory/docker/<id>/memory.oom_control) 公开的 cgroup v1 feature,并且会导致请求超出限制的任务阻塞,直到内存释放或限制更改。

--oom-kill-disable 需要 cgroup v1。因此,首先要求您的 docker 安装使用 cgroup v1:

$ docker system info | grep -i Cg 
 Cgroup Driver: cgroupfs
 Cgroup Version: 1

如果是这样的话,--oom-kill-disable 应该是可用的。您可以通过 sysfs 确认 docker 是否设置了 cgroup 选项:
$ cat /sys/fs/cgroup/memory/docker/<id>/memory.oom_control
oom_kill_disable 1
under_oom 1
oom_kill 0

如果未设置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

在我的测试安装中(Docker版本20.10.12,构建e91ed57,Linux 5.10.76),我通过更新settings.json来强制docker使用cgroup v1,具体操作如下:
{
  "deprecatedCgroupv1": true
}

使用cgroup v2启动容器会触发以下警告:
WARNING: Your kernel does not support OomKillDisable. OomKillDisable discarded.

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

很遗憾,据我所知,该设施至今尚不存在。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接