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

Функція Kubernetes NodeSwap, яка, ймовірно, отримає статус 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-очищення системи.

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

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

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

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

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

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

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

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

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

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

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

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

Графік нижче, з тесту на стрес з швидкістю 100MBps, показує роботу swap. Коли вільна памʼять (на графіку "Використання памʼяті") зменшується, використання swap (Використано swap (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-очищень і навіть несподіваних перезавантажень вузлів під час високого навантаження на памʼять. З вибором відповідних параметрів ядра можна досягти хорошого балансу між стабільністю вузла та продуктивністю.

Вплив swappiness

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

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

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

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

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

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

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

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

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

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

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

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

min_free_kbytes=67584KiB та watermark_scale_factor=10min_free_kbytes=524288KiB та watermark_scale_factor=2000
Вузол 0, зона Нормальна
  вільні сторінки 583273
  підвищення 0
  мін 10504
  низький 13130
  високий 15756
  охоплено 1310720
  присутній 1310720
  керується 1265603
Вузол 0, зона Нормальна
  вільні сторінки 470539
  мін 82109
  низький 337017
  високий 591925
  охоплено 1310720
  присутній 1310720
  керується 1274542

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

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

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

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

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

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

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

Kubernetes context

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

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

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

На основі цих висновків, я рекомендую наступне як відправну точку для вузлів 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) та шаблони введення/виведення.