Комментарий переводчика: оригинал статьи был опубликован в официальном блоге Kubernetes. Автор объясняет работу swap в Linux и на тестах показывает, как разные значения параметров ядра влияют на расход памяти и поведение swap в Kubernetes.
Функция Kubernetes Node system swap support (признана стабильной в v1.34, подробнее о фиче — в обзоре Kubernetes 1.22) разрешает использовать swap. Это значительный отход от общепринятой практики отключения swap ради прогнозируемости производительности. Эта статья посвящена тонкой настройке swap на Linux-узлах, на которых и доступна данная функция. Поддержка swap позволяет им задействовать вторичное хранилище для расширения виртуальной памяти, когда заканчивается физическая, и таким образом повысить эффективность использования ресурсов и уменьшить количество OOM-событий.
Впрочем, с включением swap не всё так просто. Стабильность и скорость работы узлов, когда память на исходе, сильно зависят от настроек ядра Linux. Ошибка в конфигурации может ударить по производительности, попутно конфликтуя с логикой вытеснения (eviction), которую использует kubelet.

В этой статье я углублюсь в критически важные параметры ядра Linux, которые управляют поведением swap. Рассмотрим, как они влияют на производительность рабочих нагрузок Kubernetes, утилизацию swap и ключевые механизмы вытеснения. Изучим результаты различных тестов, демонстрирующих влияние разных конфигураций. И, наконец, я поделюсь своими идеями об оптимальных настройках для стабильных и высокопроизводительных кластеров Kubernetes.
Введение в swap Linux
Если говорить в общих чертах, ядро Linux управляет памятью через «страницы» — блоки размером, как правило, 4 КиБ. Когда физическая память заканчивается, специальный алгоритм в ядре решает, какие страницы выгрузить в swap. Сама логика этого выбора довольно сложна, но в общем можно сказать, что на неё влияют несколько ключевых моментов:
как часто используются страницы (паттерны доступа к страницам);
были ли страницы изменены («грязные» ли они);
насколько сильно система нуждается в свободной памяти.
Анонимная и файловая память
Важно понимать, что не все страницы памяти одинаковы. Ядро различает анонимную (anonymous) и файловую (file-backed) память.
Анонимная память не связана с конкретным файлом на диске. Пример: куча (heap) и стек программы. Для приложения это его «личное пространство», и когда ядру необходимо освободить эти страницы, оно должно записать их на выделенное swap-устройство.
Файловая память связана с определённым файлом в файловой системе. Например, это может быть код программы, подключённые библиотеки, файловый кеш. Когда ядру нужно место, оно может просто «выбросить» такие страницы, если те не менялись (такие страницы называют «чистыми»). Если же страница менялась («грязная»), то ядро сначала сохранит изменения в файл на диске, а уже потом освободит память.
При нехватке памяти система без swap может освободить место, только отбрасывая «чистые» файловые страницы, но анонимную память ей выгружать некуда. Включение swap как раз и даёт такую возможность: ядро может перемещать редко используемые страницы памяти на диск, чтобы сберечь оперативную память и избежать OOM-убийств.
Ключевые параметры ядра для настройки swap
В Linux есть несколько параметров ядра для тонкой настройки swap. Управлять ими можно через sysctl.
vm.swappiness: — самый известный параметр. Это число от 0 до 200 (в старых ядрах — до 100), которое говорит ядру, что ему делать в первую очередь: выгружать анонимную память в swap или сбрасывать файловый кеш. Высокое значение (>90): ядро будет активно выгружать редко используемую анонимную память, чтобы освободить место для файлового кеша. Низкое значение (<10): ядро в первую очередь будет сбрасывать файловый кеш.
vm.min_free_kbytes: предписывает ядру держать свободным определённый минимальный объём памяти в качестве буфера. Когда объём свободной памяти падает ниже этого значения, ядро начинает агрессивно освобождать страницы (сначала через swap, а в крайнем случае — через OOM-убийства).
Зачем это нужно: это страховка, которая гарантирует, что у ядра всегда будет немного памяти для самых критичных операций.
Влияние на swap: чем выше
min_free_kbytes, тем раньше ядро начнёт использовать swap при росте потребления памяти.
vm.watermark_scale_factor: управляет интервалом между тремя порогами памяти: low, min и high (они рассчитываются на основе min_free_kbytes).
Что это за пороги:
low: когда память опускается ниже этого уровня, просыпается процесс kswapd и начинаёт потихоньку освобождать страницы. Это начало swap-цикла.
min: память упала до этого минимума — система начинает агрессивно искать память, задерживая её выделение для новых процессов. Если освободить страницы не получится, в действие вступит OOM.
high: процесс очистки останавливается, когда свободная память достигает этого уровня.
Влияние: чем выше
watermark_scale_factor, тем больше разрыв между порогами low и min. Это даёт kswapd больше времени на спокойную работу, прежде чем система «впадёт в панику».
Представьте типичный сервер: есть долго работающий процесс, часть памяти которого со временем становится «холодной» (не используется). Высокое значение swappiness поможет выгрузить эту «холодную» память в swap, освободив её для других процессов, которые выиграют от файлового кеша.
Настраивая min_free_kbytes и watermark_scale_factor так, чтобы своппинг начинался раньше, вы даёте kswapd больше времени на выгрузку памяти на диск и предотвращаете OOM-убийства при внезапных скачках потребления памяти.
Тесты различных параметров swap
Чтобы разобраться, как в реальности параметры swap’а влияют на систему, я провёл ряд стресс-тестов.
Тестовый стенд
Окружение: GKE в Google Cloud.
Версия Kubernetes: 1.33.2.
Конфигурация узла: n2-standard-2 (8 ГБ оперативной памяти, swap 50 ГБ на диске pd-balanced, без шифрования), Ubuntu 22.04.
Нагрузка: самописное приложение на Go, которое умеет потреблять память с заданной скоростью, нагружать файловый кеш и имитировать разный доступ к памяти (случайный или последовательный).
Мониторинг: сайдкар-контейнер, который каждую секунду снимает системные метрики.
Защита: важнейшим системным компонентам (kubelet, container runtime, sshd) запретили использовать swap, выставив
memory.swap.max=0в их cgroup.
Методология тестирования
Тестовый под запускался на узлах с разными значениями swappiness (0, 60 и 90), попутно менялись параметры min_free_kbytes и watermark_scale_factor. Целью было посмотреть, что произойдёт при сильной нагрузке на память и I/O.
Наглядная демонстрация работы swap
Графики ниже — результат стресс-теста на 100 МБ/с — показывают swap в действии. При уменьшении свободной памяти (график «Memory Usage Over Time») растут использование swap («Swap Used (GiB)») и активность выгрузки в него («Swap Out (MiB/s)»). Важно, что чем больше система использует swap, тем выше I/O-активность и соответствующее время ожидания («IO Wait %» на графике «CPU Usage Over Time»), что говорит о нагрузке на процессор.

Результаты
Первоначальные тесты с параметрами ядра по умолчанию (swappiness=60, min_free_kbytes=68 МБ, watermark_scale_factor=10) при высокой нагрузке на память быстро приводили к OOM-убийствам и даже к неожиданным перезагрузкам узла. Правильный подбор параметров ядра позволяет достичь хорошего баланса между стабильностью и производительностью узла.
Влияние swappiness
Параметр swappiness напрямую влияет на решение ядра: будет ли оно освобождать анонимную память (в swap) или удалять страничный кеш. Чтобы это проверить, я провел тест, в котором один под генерировал и удерживал файловый кеш, а второй потреблял анонимную память со скоростью 100 МБ/с, чтобы понаблюдать за предпочтениями ядра при освобождении памяти.
Результаты показывают, что ядро действительно меняет свои предпочтения:
swappiness=90: ядро интенсивно выгружало неактивную анонимную память в swap, чтобы сохранить побольше файлового кеша. В итоге swap использовался постоянно и активно, что создавало большую нагрузку на диск («Blocks Out») и заставляло процессор простаивать, дожидаясь завершения I/O-операций.swappiness=0: ядро предпочитало чистить файловый кеш, «экономя» swap. Тут важно понимать, что swap никуда не девался. Когда памяти становилось совсем мало, ядро всё равно начинало выгружать анонимную память.
Выбор зависит от конкретной рабочей нагрузки. Если она чувствительна к I/O-задержкам, лучше ставить swappiness поменьше. Если же приложению нужен большой и быстрый файловый кеш, может помочь высокое значение swappiness — но только если у вас быстрый диск, который выдержит нагрузку.
Настройка пороговых значений для предотвращения вытеснения и OOM-убийств
Наиболее серьёзной проблемой, с которой я столкнулся, была необходимость согласовать быстрое потребление памяти с механизмом вытеснения (eviction) kubelet'а. Когда тестовый под, намеренно сконфигурированный на превышение лимитов по памяти, занимал её с высокой скоростью (например, 300–500 МБ/с), свободная память в системе быстро заканчивалась.
При пороговых значениях по умолчанию буфер для освобождения памяти был слишком мал. Прежде чем kswapd успевал освободить достаточно памяти, переместив данные в swap, узел переходил в критическое состояние, что приводило к двум возможным исходам:
Вытеснение: если менеджер вытеснения kubelet'а обнаруживал, что
memory.availableопускалось ниже его порога, он вытеснял под.Убийство по OOM: в некоторых сценариях с высокой скоростью потребления памяти OOM-убийца активировался до того, как kubelet успевал завершить вытеснение. Иногда даже убивались более высокоприоритетные поды, которые не были источником проблемы.
Чтобы смягчить остроту этой проблемы, я поправил пороговые значения:
Увеличил
min_free_kbytesдо 512 МиБ: повышенное значение заставляет ядро начинать освобождение памяти значительно раньше, обеспечивая запас по времени.Увеличил
watermark_scale_factorдо 2000: это увеличило разрыв между порогами low и high (примерно с 337 до 591 МБ, судя по /proc/zoneinfo моего тестового узла), что дало больше времени на своппинг.
Такое сочетание оставило kswapd больше пространства для манёвра и времени на выгрузку страниц на диск при резких скачках потребления памяти. В моих тестах это успешно предотвратило и преждевременные вытеснения, и OOM-убийства.
В таблице сравниваются пороговые значения из /proc/zoneinfo (для узла без NUMA):
|
|
Узел 0, зона Normal pages free 583273 boost 0 min 10504 low 13130 high 15756 spanned 1310720 present 1310720 managed 1265603 | Узел 0, зона Normal pages free 470539 min 82109 low 337017 high 591925 spanned 1310720 present 1310720 managed 1274542 |
График ниже подтверждает, что размер буфера ядра и коэффициент масштабирования — ключевые факторы, определяющие, как система реагирует на нагрузку по памяти. С правильной комбинацией этих параметров система использует swap эффективно, избегая вытеснения подов и сохраняя стабильность.

Риски и рекомендации
Swap в Kubernetes — мощный инструмент, но с ним связаны риски, которые нужно контролировать через аккуратную настройку.
Риск падения производительности: swap в разы медленнее оперативной памяти. Если набор данных, с которым работает приложение, улетит в swap, производительность катастрофически упадёт из-за I/O-операций (т. н. thrashing). Чтобы повысить производительность, swap желательно размещать на SSD.
Риск не заметить утечку памяти: swap может замаскировать утечки памяти в приложении. Без него приложение бы быстро упало по OOM, а с ним будет медленно деградировать, пожирая ресурсы и тормозя весь узел. Причину найти будет гораздо сложнее.
Риск помешать вытеснению подов: kubelet постоянно следит за нехваткой памяти и убирает поды с узла, чтобы освободить ресурсы. Неправильные настройки могут привести к тому, что OOM killer сработает раньше, чем kubelet успеет корректно вытеснить под. Поэтому важно правильно выставить
min_free_kbytes, чтобы механизм вытеснения kubelet работал предсказуемо.
Контекст Kubernetes
Вместе swap-параметры ядра и порог вытеснения kubelet'а создают на узле несколько очагов давления на память. Eviction-threshold нужно настроить так, чтобы kubelet успевал вытеснять поды до того, как ядро начнёт убивать процессы по OOM.

Как видно из графика, в идеале нужно создать достаточно большую «swap-зону» (между отметками high и minimum), чтобы ядро могло справиться с нагрузкой на память, сбрасывая данные в swap, до того, как объём доступной памяти попадёт в зону вытеснения / прямого освобождения.
С чего начать
Основываясь на результатах тестов, рекомендую следующие базовые настройки для Linux-узлов со swap. Обязательно протестируйте их на своих приложениях.
vm.swappiness=60: дефолтное значение Linux — хорошая отправная точка для большинства рабочих нагрузок. Идеальное значение зависит от типа нагрузки, а для чувствительных к swap приложений может потребоваться более тонкая настройка.vm.min_free_kbytes=500000(500 МБ): сделайте это значение достаточно большим (например, 2–3 % от всей памяти узла), чтобы обеспечить системе хороший запас.vm.watermark_scale_factor=2000: создайте большое «окно» для работы kswapd. Это предотвратит OOM-убийства при резких скачках потребления памяти.
При первичной настройке swap в кластере обязательно погоняйте тесты производительности с реальными нагрузками в тестовом окружении. Производительность swap сильно зависит от окружения: нагрузки на процессор, типа диска (SSD или HDD) и характера операций ввода-вывода.
P. S.
Читайте также в нашем блоге:
