Ця стаття була опублікована більше року тому. Старіші статті можуть містити застарілу інформацію. Перевірте, чи інформація на цій сторінці не втратила актуальності з моменту її публікації.

Налаштування Linux Swap для Kubernetes: детальний огляд

Можливість NodeSwap у Kubernetes, яка, ймовірно, отримає статус stable у майбутньому випуску Kubernetes v1.34, дозволяє використання swap: це значна зміна порівняно з традиційною практикою вимкнення swap для передбачуваної продуктивності. Ця стаття зосереджена виключно на налаштуванні swap на вузлах Linux, де ця можливість доступна. Дозволяючи вузлам Linux використовувати вторинне сховище для додаткової віртуальної памʼяті, коли фізична RAM вичерпана, підтримка swap на вузлах спрямована на покращення використання ресурсів та зменшення кількості припинень процесів через нестачу памʼяті (OOM kills).

Однак, увімкнення swap не є рішенням "під ключ". Продуктивність та стабільність ваших вузлів під тиском на памʼять критично залежать від набору параметрів ядра Linux. Неправильна конфігурація може призвести до погіршення продуктивності та втручання в логіку виселення Kubelet.

У цьому дописі ми розглянемо критичні параметри ядра Linux, які керують поведінкою swap. Ми дослідимо, як ці параметри впливають на продуктивність робочих навантажень Kubernetes, використання swap та критичні механізми виселення. Також представимо різні результати тестів, що демонструють вплив різних конфігурацій, та поділимось своїми висновками щодо досягнення оптимальних налаштувань для стабільних та високопродуктивних кластерів Kubernetes.

Вступ до Linux swap

Загалом ядро Linux керує памʼяттю через сторінки, зазвичай розміром 4КіБ. Коли фізична памʼять стає обмеженою, алгоритм заміни сторінок ядра вирішує, які сторінки перемістити у swap-простір. Хоча точна логіка є складною оптимізацією, на цей процес прийняття рішень впливають певні ключові фактори:

  1. Шаблони доступу до сторінок (як нещодавно до сторінок зверталися)
  2. "Забрудненість" сторінок (чи були сторінки модифіковані)
  3. Тиск на памʼять (наскільки терміново системі потрібна вільна памʼять)

Анонімна памʼять проти памʼяті, що підтримується файлами

Важливо розуміти, що не всі сторінки памʼяті однакові. Ядро розрізняє анонімну памʼять та памʼять з файловою підтримкою.

Анонімна памʼять: Це памʼять, яка не підтримується конкретним файлом на диску, така як купа та стек програми. З точки зору застосунку це приватна памʼять, і коли ядру потрібно звільнити ці сторінки, воно повинно записати їх на спеціально відведений пристрій swap.

Памʼять з файловою підтримкою: Ця памʼять підтримується файлом у файловій системі. Сюди входять виконуваний код програми, спільні бібліотеки та кеші файлової системи. Коли ядру потрібно звільнити ці сторінки, воно може просто відкинути їх, якщо вони не були модифіковані ("чисті"). Якщо сторінка була модифікована ("брудна"), ядро спочатку повинно записати зміни назад у файл, перш ніж її можна буде відкинути.

Хоча система без swap все ще може під тиском звільняти чисті сторінки памʼяті з файловою підтримкою, скидаючи їх, у неї немає способу скинути анонімну памʼять. Увімкнення swap надає цю можливість, дозволяючи ядру переміщати менш часто використовувані сторінки памʼяті на диск, щоб зберегти памʼять і уникнути OOM kills у системі.

Ключові параметри ядра для налаштування swap

Щоб ефективно налаштувати поведінку swap, Linux надає кілька параметрів ядра, якими можна керувати за допомогою sysctl.

  • vm.swappiness: Це найвідоміший параметр. Це значення від 0 до 200 (100 у старіших ядрах), яке визначає, чому ядро віддає перевагу: свопінгу анонімних сторінок памʼяті чи звільненню сторінок памʼяті з файловою підтримкою (кешу сторінок).
    • Високе значення (наприклад, 90+): Ядро буде агресивно переміщувати в своп менш використовувану анонімну памʼять, щоб звільнити місце для кешу файлів.
    • Низьке значення (наприклад, < 10): Ядро буде намагатися в першу чергу скинути сторінки кешу файлів, а не робити своп анонімній памʼяті.
  • vm.min_free_kbytes: Цей параметр вказує ядру зберігати мінімальну кількість памʼяті вільною як буфер. Коли кількість вільної памʼяті падає нижче цього буфера безпеки, ядро починає більш агресивно звільняти сторінки (свопінг, а зрештою — OOM kills).
    • Призначення: Це діє як запобіжник, щоб забезпечити ядру достатньо памʼяті для критичних запитів на виділення, які не можуть бути відкладені.
    • Вплив на swap: Встановлення більшого min_free_kbytes ефективно підвищує мінімальне значення для вільної памʼяті, змушуючи ядро раніше ініціювати своп під час тиску на памʼять.
  • vm.watermark_scale_factor: Це налаштування контролює проміжок між різними водяними знаками: min, low та high, які обчислюються на основі min_free_kbytes.
    • Пояснення водяних знаків:
      • low: Коли вільна памʼять нижча за цю позначку, процес ядра kswapd прокидається, щоб звільнити сторінки у фоновому режимі. Саме тоді починається цикл свопу.
      • min: Коли вільна памʼять досягає цього мінімального рівня, агресивне звільнення сторінок блокуватиме виділення памʼяті процесам. Нездатність звільнити сторінки призведе до OOM kills.
      • high: Звільнення памʼяті зупиняється, як тільки вільна памʼять досягає цього рівня.
    • Вплив: Більший watermark_scale_factor створює більший буфер між водяними знаками low та min. Це дає kswapd більше часу для поступового звільнення памʼяті, перш ніж система потрапить у критичний стан.

У типовому серверному робочому навантаженні у вас може бути довготривалий процес з деякою памʼяттю, яка стає "холодною". Вищий показник swappiness може звільнити RAM, вивантажуючи холодну памʼять у swap, для інших активних процесів, які можуть виграти від збереження свого кешу файлів.

Налаштування параметрів min_free_kbytes та watermark_scale_factor для раннього переміщення вікна свопінгу надасть більше місця для kswapd, щоб скинути памʼять на диск і запобігти OOM kills під час раптових сплесків потреби в памʼяті.

Тести та результати свопу

Щоб зрозуміти реальний вплив цих параметрів, ми провели серію стрес-тестів.

Налаштування тесту

  • Середовище: GKE на Google Cloud
  • Версія Kubernetes: 1.33.2
  • Конфігурація вузла: n2-standard-2 (8GiB RAM, 50GB swap на диску pd-balanced, без шифрування), Ubuntu 22.04
  • Робоче навантаження: Спеціально розроблена програма на Go, призначена для виділення памʼяті з налаштовуваною швидкістю, створення тиску на кеш файлів та імітації різних шаблонів доступу до памʼяті (випадковий проти послідовного).
  • Моніторинг: sidecar-контейнер, що збирає системні метрики щосекунди.
  • Захист: Критичним системним компонентам (kubelet, середовище виконання контейнерів, sshd) було заборонено використовувати swap, встановивши memory.swap.max=0 у їхніх відповідних cgroups.

Методологія тестування

Був запущений Pod зі стрес-тестом на вузлах з різними налаштуваннями swappiness (0, 60 та 90) з варіюванням параметрів min_free_kbytes та watermark_scale_factor, щоб спостерігати за результатами під час сильного виділення памʼяті та тиску введення/виведення.

Візуалізація роботи свопу

Графік нижче, з тесту на стрес з швидкістю 100MBps, показує роботу swap. Коли вільна памʼять (на графіку "Використання памʼяті") зменшується, використання swap (Swap Used (GiB)) та активність swap-виводу (Swap Out (MiB/s)) зростають. Що важливо, коли система більше покладається на swap, також зростає I/O активність та відповідний час очікування (IO Wait % на графіку "Використання CPU"), що вказує на навантаження на CPU.

Графік, що показує використання CPU, памʼяті, swap та I/O активності на вузлі Kubernetes

Висновки

Мої початкові тести з типовими параметрами ядра (swappiness=60, min_free_kbytes=68MB, watermark_scale_factor=10) швидко призвели до OOM kills і навіть несподіваних перезавантажень вузлів під час високого навантаження на памʼять. З вибором відповідних параметрів ядра можна досягти хорошого балансу між стабільністю вузла та продуктивністю.

Вплив swappiness

Параметр swappiness безпосередньо впливає на вибір ядра між звільненням анонімної памʼяті (свопом) та скиданням кешу сторінок. Щоб це спостерігати, я провів тест, у якому один Pod створював та утримував тиск на кеш файлів, після чого другий Pod виділяв анонімну памʼять зі швидкістю 100MB/s, щоб побачити, чому ядро віддає перевагу під час звільнення памʼяті:

Мої висновки виявили чіткий компроміс:

  • swappiness=90: Ядро проактивно робило своп неактивної анонімної памʼяті, щоб зберегти кеш файлів. Це призвело до високого та стабільного використання swap та значної I/O активності ("Blocks Out"), що, в свою чергу, викликало сплески очікування I/O на CPU.
  • swappiness=0: Ядро надавало перевагу скиданню сторінок кешу файлів, затримуючи споживання swap. Однак важливо розуміти, що це не вимикає своп. Коли тиск на памʼять був високим, ядро все ще робило своп анонімної памʼяті на диск.

Вибір залежить від робочого навантаження. Для робочих навантажень, чутливих до затримок введення/виведення, бажано нижче значення swappiness. Для робочих навантажень, які покладаються на великий та часто використовуваний кеш файлів, може бути корисним вищий показник swappiness, за умови, що диск достатньо швидкий, щоб впоратися з навантаженням.

Налаштування водяних знаків для запобігання виселенню та OOM kills

Найбільшою проблемою, з якою я зіткнувся, була взаємодія між швидким виділенням памʼяті та механізмом виселення Kubelet. Коли мій тестовий Pod, який навмисно був налаштований на надмірне виділення памʼяті (overcommit), виділяв її високими темпами (наприклад, 300-500 MBps), система швидко залишалася без вільної памʼяті.

З типовими водяними знаками буфер для звільнення був занадто малим. Перш ніж kswapd встигав звільнити достатньо памʼяті шляхом свопу, вузол потрапляв у критичний стан, що призводило до двох можливих наслідків:

  1. Виселення Kubelet Якщо менеджер виселення kubelet виявляв, що memory.available нижче його порогу, він виселяв Pod.
  2. OOM killer У деяких сценаріях з високою швидкістю OOM killer активувався раніше, ніж виселення встигало завершитися, іноді вбиваючи Podʼи вищого пріоритету, які не були джерелом тиску.

Щоб помʼякшити це, я налаштував водяні знаки:

  1. Збільшено min_free_kbytes до 512MiB: Це змушує ядро почати звільнення памʼяті набагато раніше, забезпечуючи більший буфер безпеки.
  2. Збільшено watermark_scale_factor до 2000: Це розширило проміжок між водяними знаками low та high (з ≈337MB до ≈591MB у моєму тестовому вузлі в /proc/zoneinfo), ефективно збільшуючи вікно свопінгу.

Ця комбінація надала kswapd більшу операційну зону та більше часу для свопінгу сторінок на диск під час сплесків памʼяті, успішно запобігаючи як передчасним виселенням, так і OOM kills у моїх тестових запусках.

Таблиця порівнює рівні водяних знаків з /proc/zoneinfo (вузол без NUMA):

min_free_kbytes=67584KiB та watermark_scale_factor=10min_free_kbytes=524288KiB та watermark_scale_factor=2000
Node 0, zone Normal
  pages free 583273
  boost 0
  min 10504
  low 13130
  high 15756
  spanned 1310720
  present 1310720
  managed 1265603
Node 0, zone Normal
  pages free 470539
  min 82109
  low 337017
  high 591925
  spanned 1310720
  present 1310720
  managed 1274542

Графік нижче показує, що розмір буфера ядра та коефіцієнт масштабування відіграють вирішальну роль у визначенні того, як система реагує на навантаження на памʼять. З правильним поєднанням цих параметрів система може ефективно використовувати простір swap, щоб уникнути виселення та підтримувати стабільність.

Порівняння різних налаштувань min_free_kbytes, що показує різницю у впливі на Swap, Використання памʼяті та Виселення

Ризики та рекомендації

Увімкнення swap у Kubernetes є потужним інструментом, але воно повʼязане з ризиками, якими потрібно управляти за допомогою ретельного налаштування.

  • Ризик погіршення продуктивності Своп значно повільніший, ніж доступ до RAM. Якщо активний робочий набір застосунку буде переміщено до свопу, її продуктивність різко постраждає через високі часи очікування введення/виведення (thrashing). Своп бажано забезпечити сховищем на базі SSD для покращення продуктивності.

  • Ризик маскування витоків памʼяті Своп може приховати витоки памʼяті в застосунках, які інакше призвели б до швидкого OOM kills. Зі swap застосунок із витоком памʼяті може повільно погіршувати продуктивність вузла з часом, ускладнюючи діагностику першопричини.

  • Ризик відключення виселень Kubelet активно моніторить вузол на предмет тиску на памʼять та завершує Podʼи, щоб звільнити ресурси. Неправильне налаштування може призвести до OOM kills до того, як kubelet матиме можливість виселити Podʼи належним чином. Правильно налаштований min_free_kbytes є критично важливим для забезпечення ефективності механізму виселення kubelet.

Контекст Kubernetes

Разом водяні знаки ядра та поріг виселення kubelet створюють серію зон тиску памʼяті на вузлі. Параметри порогу виселення потрібно відрегулювати, щоб керовані Kubernetes виселення відбувалися до OOM kills.

Бажані пороги для ефективного використання swap

Як показано на діаграмі, ідеальна конфігурація полягає в тому, щоб створити достатньо велику "зону свопінгу" (між водяними знаками high та min), щоб ядро могло обробляти тиск памʼяті шляхом свопу, перш ніж доступна памʼять впаде в зону виселення/прямого звільнення (Direct Reclaim).

На основі цих висновків, я рекомендую таке як відправну точку для вузлів Linux з увімкненим swap. Вам слід провести тестування продуктивності з вашими власними робочими навантаженнями.

  • vm.swappiness=60: Типове значення Linux є хорошою відправною точкою для робочих навантажень загального призначення. Однак, ідеальне значення залежить від робочого навантаження, і застосунки, чутливі до swap, можуть потребувати більш ретельного налаштування.
  • vm.min_free_kbytes=500000 (500MB): Встановіть це на досить високе значення (наприклад, 2-3% від загальної памʼяті вузла), щоб забезпечити розумний буфер безпеки.
  • vm.watermark_scale_factor=2000: Створіть більше вікно для роботи kswapd, запобігаючи OOM kills під час раптових сплесків виділення памʼяті.

Я рекомендую проводити тести продуктивності з вашими власними робочими навантаженнями в тестових середовищах при першому налаштуванні swap у вашому кластері Kubernetes. Продуктивність swap може бути чутливою до різних особливостей середовища, таких як навантаження на CPU, тип диска (SSD проти HDD) та шаблони введення/виведення.