Учитывая процесс, который создает большой кеш страниц ядра Linux через файлы mmap'd, выполнение в докер-контейнере (cgroup) с ограничением памяти вызывает ошибки выделения слэба ядра:
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252395] SLUB: Unable to allocate memory on node -1 (gfp=0x2080020)
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252402] cache: kmalloc-2048(2412:6c2c4ef2026a77599d279450517cb061545fa963ff9faab731daab2a1f672915), object size: 2048, buffer size: 2048, default order: 3, min order: 0
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252407] node 0: slabs: 135, objs: 1950, free: 64
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252409] node 1: slabs: 130, objs: 1716, free: 0
Просмотр slabtop
Я вижу, что количество объектов buffer_head, radix_tree_node и kmalloc * сильно ограничено в контейнере, запущенном с ограничением памяти. По-видимому, это имеет патологические последствия для пропускной способности ввода-вывода в приложении и наблюдается с помощью iostat
. Этого не происходит, даже если кэш страниц использует всю доступную память на хост-ОС, работающей вне контейнера или контейнера без ограничения памяти.
Это кажется проблемой в учете памяти ядра, когда кеш страниц ядра не учитывается в памяти контейнеров, а в объектах SLAB, которые его поддерживают. Поведение кажется неправильным, потому что при запуске, когда большой пул объектов slab предварительно выделен, контейнер с ограниченной памятью работает нормально, свободно используя существующее пространство slab. Только плита, выделенная в контейнере, засчитывается в контейнер. Кажется, что никакая комбинация параметров контейнера для памяти и памяти ядра не решает проблему (за исключением того, что не устанавливают ограничение памяти вообще или ограничение настолько большое, что не ограничивает slab, но это ограничивает адресуемое пространство). Я попытался полностью отключить учет kmem, пропустив cgroup.memory=nokmem
при загрузке.
Информация о системе:
- Linux ip-10-10-17-135 4.4.0-1087-aws # 98-Ubuntu SMP
- AMI Ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-20190204.3
- Docker версия 18.09.3, сборка 774a1f4
- Java 10.0.1 2018-04-17
Чтобы воспроизвести проблему, вы можете использовать мой PageCache Java-код. Это простой пример репродукции встроенной библиотеки базы данных, которая активно использует файлы, отображаемые в памяти, для развертывания в очень быстрой файловой системе. Приложение развертывается на экземплярах AWS i3.baremetal через ECS. Я сопоставляю большой том от хоста до док-контейнера, где хранятся файлы с отображенной памятью. Агент AWS ECS требует установки ненулевого предела памяти для всех контейнеров. Ограничение памяти вызывает поведение патологической плиты и результирующая пропускная способность ввода-вывода приложения совершенно неприемлема.
Это полезно для drop_caches
между запусками с использованием echo 3 > /proc/sys/vm/drop_caches
. Это очистит кэш страницы и связанный пул объектов slab.
Предложения о том, как исправить, обойти или даже где сообщить об этой проблеме, будут приветствоваться.
UPDATE Похоже, что обновление до Ubuntu 18.04 с ядром 4.15 исправляет наблюдаемую ошибку выделения kmalloc. Версия Java, кажется, не имеет значения. Похоже, это связано с тем, что каждая C1-группа v1 может выделять кеш страниц только до предела памяти (с несколькими cgroups это сложнее, поскольку только одна cgroup "взимается" за распределение по схеме учета общих страниц), Я считаю, что это теперь соответствует предполагаемому поведению. В ядре 4.4 мы обнаружили, что наблюдаемые ошибки kmalloc были пересечением использования программного raid0 в C1-группе v1 с ограничением памяти и очень большим кешем страниц. Я полагаю, что cgroups в ядре 4.4 смогли отобразить неограниченное количество страниц (ошибка, которую мы сочли полезной) до момента, когда ядру не хватило памяти для связанных объектов slab, но у меня все еще нет курение пистолета за дело.
В ядре 4.15 нашим контейнерам Docker требуется установить предел памяти (через AWS ECS), поэтому мы выполнили задачу по снятию ограничения памяти, как только контейнер будет создан в /sys/fs/cgroup/memory/docker/{contarainer_id}/memory.limit_in_bytes
. Кажется, это работает, хотя это не очень хорошая практика, чтобы быть уверенным. Это позволяет нам вести себя так, как мы хотим - неограниченное использование ресурсов кэша страниц на хосте. Поскольку мы запускаем приложение JVM с фиксированной кучей, риск снижения производительности ограничен.
Для нашего варианта использования было бы замечательно иметь возможность полностью исключить кэш страницы (дисковое пространство mmap) и связанные объекты slab для cgroup, но сохранить ограничение на кучу & стек для процесса докера. Существующая схема учета совместно используемых страниц довольно сложна для осмысления, и мы бы предпочли, чтобы кеш страниц LRU (и связанные ресурсы SLAB) использовали полный объем памяти хостов, как в случае, когда предел памяти вообще не установлен.
Я начал следить за некоторыми разговорами о LWN, но я немного в неведении. Может быть, это ужасная идея? Я не знаю... добро пожаловать совет о том, как действовать дальше или куда идти дальше.