在具有内存限制的 Docker 容器中运行 .NET Core 应用程序

Nik*_*hin 4 c# memory docker

我在 C# 的帮助下测试了 Docker 容器的不同限制。出于测试目的,我编写了一个简单的程序来生成不同类型的负载(CPU 密集型工作、内存分配、网络请求等)。

对于具有许多内存分配的场景,我编写以下代码:

try {
    var chunks = new byte[10][];
    for (var chunkId = 0; chunkId < 10; chunkId++) {
        Console.WriteLine($"Chunk: {chunkId}");
        var chunk = new byte[100 * 1024 * 1024];
        for (var i = 0; i < chunk.Length; i++)
            chunk[i] = (byte) (i % 256);
        chunks[chunkId] = chunk;
    }
}
catch (Exception) { // To capture OutOfMemoryException and pause the process
    Thread.CurrentThread.Join();
}
Run Code Online (Sandbox Code Playgroud)

此代码分配了 10 个 100MB 连续内存段(总共 1GB),但当我在具有内存限制的容器内运行它时,神奇的事情发生了:

$> docker run --memory=1g --memory-swap=1g <workload-image>
Run Code Online (Sandbox Code Playgroud)

在 RAM 限制为 1GB 的容器中,我的应用程序只能分配 6-7 个 100MB 的块,并且对新字节数组的请求失败,并显示OutOfMemoryException.

起初,我认为 CLR 会消耗某些内部结构的内存,但在深入研究内存转储数小时后,我在 .NET 堆中没有发现任何可疑的大对象。

最后,我设置了以下简单的实验。我在具有 1GB RAM 限制的 Docker 容器中运行我的应用程序,OutOfMemoryException在我的语句中处理catch并暂停进程后,我运行程序的另一个副本,它突然可以分配 3 个额外的内存块 (300MB)。

我无法解释为什么我的应用程序无法分配具有 1GB RAM 限制的所有块(或至少 9/10 块)。有人能解释一下 .NET 的这种神秘行为吗?

在 Ubuntu 18.10 中使用 Docker Desktop(Windows 10,预分配 5GB RAM 内存)和 docker 19.03.11 重现了该问题。我的项目目标netcoreapp3.1和最终图像基于mcr.microsoft.com/dotnet/core/sdk:3.1.300. 我将我的镜像推送到 Docker hub 注册表中,如果这对问题调查有帮助的话。

UPD:我用 C++ 编写了一个简单的 C# 应用程序模拟,它在 1GB 限制下成功运行。所以,我认为这与 .NET CLR 紧密相关。

此外,根据顶部(RES列),容器应用程序消耗了 724 MB:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
  1 root      20   0 5749076 741512  19932 S   0.0   9.3   0:01.05 dotnet
Run Code Online (Sandbox Code Playgroud)

rob*_*ith 7

HeapHardLimit 默认为容器内存限制的 75%,请参阅https://learn.microsoft.com/en-us/dotnet/core/run-time-config/garbage-collector#heap-limit

因此,在 1GB 容器限制的情况下,您应该能够在单个 dotnet 进程中分配 7x100MB 块。