Kubernetes можна налаштувати на використання своп-памʼяті на вузлі, що дозволяє ядру звільняти фізичну памʼять, витісняючи сторінки до резервного сховища. Це корисно в багатьох сценаріях. Наприклад, на вузлах, де працюють робочі навантаження, яким може бути корисний своп, зокрема ті, що займають багато памʼяті, але в кожен момент часу звертаються лише до її частини. Своп також допомагає запобігти завершенню Podʼів під час стрибків тиску на памʼять, захищає вузли від стрибків використання памʼяті на рівні системи, які можуть порушити їх стабільність, дозволяє гнучкіше керувати памʼяттю на вузлі й багато іншого.
Щоб дізнатися про налаштування свопу у вашому кластері, прочитайте Налаштування своп-памʼяті на вузлах Kubernetes.
Використання свопу на вузлі можна уявити кількома способами. Якщо kubelet уже працює на вузлі, після налаштування свопу його потрібно перезапустити, щоб він виявив своп.
Коли kubelet запускається на вузлі, на якому своп налаштовано й доступно (з конфігурацією failSwapOn: false), kubelet:
Конфігурація свопу на вузлі доступна адміністратору кластера через memorySwap у KubeletConfiguration. Як адміністратор кластера ви можете вказати поведінку вузла за наявності своп-памʼяті, встановивши memorySwap.swapBehavior.
Вам потрібно вибрати поведінку свопу. Різні вузли у вашому кластері можуть використовувати різну поведінку свопу.
Для вузлів Linux можна вибрати таку поведінку свопу:
NoSwap (стандартно)LimitedSwapЯкщо ви виберете поведінку NoSwap і налаштуєте kubelet на допуск простору свопу (failSwapOn: false), ваші робочі навантаження не використовуватимуть своп.
Однак процеси поза контейнерами під керуванням Kubernetes, такі як сервіси systemd (і навіть сам kubelet!), можуть використовувати своп.
Про ввімкнення свопу у вашому кластері читайте в розділі Налаштування своп-памʼяті на вузлах Kubernetes.
kubelet використовує API середовища виконання контейнерів і вказує йому застосувати певну конфігурацію (наприклад, для cgroup v2 — memory.swap.max) так, щоб увімкнути бажану конфігурацію свопу для контейнера. Для середовищ виконання, які використовують контрольні групи (cgroups), середовище виконання контейнерів відповідає за запис цих налаштувань до cgroup на рівні контейнера.
kubelet тепер збирає статистику метрик на рівні вузла та контейнера, доступну через HTTP-точки доступу kubelet /metrics/resource (її використовують переважно інструменти моніторингу, такі як Prometheus) та /stats/summary (її використовують переважно автомасштабувальники). Це дозволяє клієнтам, які можуть безпосередньо надсилати запити до kubelet, відстежувати використання свопу та залишок своп-памʼяті під час використання LimitedSwap. Крім того, до cadvisor додано метрику machine_swap_bytes, яка показує загальну фізичну місткість свопу машини. Докладніше дивіться на цій сторінці.
Наприклад, підтримуються такі /metrics/resource:
node_swap_usage_bytes: поточне використання свопу вузлом у байтах.container_swap_usage_bytes: поточне використання свопу контейнером у байтах.container_swap_limit_bytes: поточне обмеження свопу контейнера в байтах.kubectl top --show-swapЗапити до метрик корисні, але дещо незручні, оскільки ці метрики призначені для програмного забезпечення, а не для людей. Щоб отримувати ці дані зручнішим способом, команду kubectl top розширено підтримкою метрик свопу через прапорець --show-swap.
Щоб отримати інформацію про використання свопу на вузлах, можна використовувати kubectl top nodes --show-swap:
kubectl top nodes --show-swap
Результат буде подібним до такого:
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%) SWAP(bytes) SWAP(%)
node1 1m 10% 2Mi 10% 1Mi 0%
node2 5m 10% 6Mi 10% 2Mi 0%
node3 3m 10% 4Mi 10% <unknown> <unknown>
Щоб отримати інформацію про використання свопу Podʼами, можна використовувати kubectl top pods --show-swap:
kubectl top pod -n kube-system --show-swap
Результат буде подібним до такого:
NAME CPU(cores) MEMORY(bytes) SWAP(bytes)
coredns-58d5bc5cdb-5nbk4 2m 19Mi 0Mi
coredns-58d5bc5cdb-jsh26 3m 37Mi 0Mi
etcd-node01 51m 143Mi 5Mi
kube-apiserver-node01 98m 824Mi 16Mi
kube-controller-manager-node01 20m 135Mi 9Mi
kube-proxy-ffgs2 1m 24Mi 0Mi
kube-proxy-fhvwx 1m 39Mi 0Mi
kube-scheduler-node01 13m 69Mi 0Mi
metrics-server-8598789fdb-d2kcj 5m 26Mi 0Mi
Додано нове поле статусу вузла node.status.nodeInfo.swap.capacity, щоб повідомляти про місткість свопу вузла.
Наприклад, за допомогою такої команди можна отримати місткість свопу вузлів у кластері:
kubectl get nodes -o go-template='{{range .items}}{{.metadata.name}}: {{if .status.nodeInfo.swap.capacity}}{{.status.nodeInfo.swap.capacity}}{{else}}<unknown>{{end}}{{"\n"}}{{end}}'
Результат буде подібним до такого:
node1: 21474836480
node2: 42949664768
node3: <unknown>
<unknown> вказує на те, що поле .status.nodeInfo.swap.capacity не встановлено для цього вузла. Імовірно, це означає, що на вузлі не налаштовано своп або, менш імовірно, що kubelet не може визначити місткість свопу вузла.Node Feature Discovery — це надбудова Kubernetes для виявлення апаратних можливостей і конфігурації. Її можна використовувати, щоб виявити, на яких вузлах налаштовано своп.
Наприклад, щоб зʼясувати, на яких вузлах налаштовано своп, використайте таку команду:
kubectl get nodes -o jsonpath='{range .items[?(@.metadata.labels.feature\.node\.kubernetes\.io/memory-swap)]}{.metadata.name}{"\t"}{.metadata.labels.feature\.node\.kubernetes\.io/memory-swap}{"\n"}{end}'
Результат буде подібним до такого:
k8s-worker1: true
k8s-worker2: true
k8s-worker3: false
У цьому прикладі своп налаштовано на вузлах k8s-worker1 та k8s-worker2, але не на k8s-worker3.
Наявність свопу в системі знижує передбачуваність. Хоча своп може підвищити продуктивність, роблячи доступною більше оперативної памʼяті, повернення даних у памʼять є важкою операцією, іноді повільнішою на багато порядків, що може призвести до неочікуваного зниження продуктивності. Крім того, своп змінює поведінку системи під тиском на памʼять. Увімкнення свопу збільшує ризик галасливих сусідів, коли Podʼи, що часто використовують свою оперативну памʼять, можуть спричиняти витіснення інших Podʼів до свопу. Крім того, оскільки своп дозволяє робочим навантаженням у Kubernetes використовувати більше памʼяті, ніж можна передбачувано врахувати, а також через неочікувані конфігурації пакування, планувальник наразі не враховує використання своп-памʼяті. Це підвищує ризик галасливих сусідів.
Продуктивність вузла з увімкненою своп-памʼяттю залежить від фізичного сховища. Під час використання своп-памʼяті продуктивність буде значно гіршою в середовищі з обмеженою кількістю операцій вводу-виводу за секунду (IOPS), наприклад у хмарній віртуальній машині з тротлінгом вводу-виводу, порівняно зі швидшими носіями, такими як твердотільні накопичувачі або NVMe. Оскільки своп може спричиняти тиск на ввід-вивід, рекомендується надавати критично важливим системним демонам вищий пріоритет затримки вводу-виводу. Дивіться відповідний розділ у рекомендаціях нижче.
На вузлах Linux томи в памʼяті (такі як монтування томів secret або emptyDir з medium: Memory) реалізовано за допомогою файлової системи tmpfs. Вміст таких томів завжди має залишатися в памʼяті, тому його не слід витісняти на диск. Щоб вміст таких томів залишався в памʼяті, використовується параметр noswap для tmpfs.
Ядро Linux офіційно підтримує параметр noswap з версії 6.3 (докладніше дивіться у Вимогах до версії ядра Linux). Однак різні дистрибутиви часто переносять цей параметр монтування й у старіші версії Linux.
Щоб перевірити, чи підтримує вузол параметр noswap, kubelet виконує такі дії:
noswap підтримується.noswap. Якщо kubelet отримає помилку, що вказує на невідомий параметр, вважатиметься, що noswap не підтримується, і його не буде використано. kubelet запише в лог запис, щоб попередити користувача, що томи в памʼяті можуть витіснятися на диск. Якщо kubelet виконає це успішно, фіктивний tmpfs буде видалено, а параметр noswap буде використано.noswap не підтримується, kubelet запише в лог попередження, а потім продовжить роботу.Приклад налаштування свопу дивіться в розділі Налаштування своп-памʼяті на вузлах Kubernetes. Однак обробка зашифрованого свопу не входить до сфери відповідальності kubelet; це загальне питання конфігурації ОС, і його слід вирішувати на цьому рівні. Адміністратор відповідає за налаштування зашифрованого свопу, щоб зменшити цей ризик.
Налаштування порогів виселення за памʼяттю для вузлів з увімкненим свопом може бути складним завданням.
Якщо своп вимкнено, доцільно налаштувати пороги виселення kubelet трохи нижче за місткість памʼяті вузла. Причина в тому, що Kubernetes має почати виселяти Podʼи до того, як на вузлі закінчиться памʼять і буде викликано OOM killer (Out Of Memory), оскільки OOM killer нічого не знає про Kubernetes, тому не враховує такі речі, як QoS, пріоритет Podʼів чи інші специфічні для Kubernetes чинники.
З увімкненим свопом ситуація складніша. У Linux параметр vm.min_free_kbytes визначає поріг памʼяті, за якого ядро починає агресивно вивільняти памʼять, зокрема витісняти сторінки до свопу. Якщо пороги виселення kubelet налаштовано так, що виселення відбувається раніше, ніж ядро почне вивільняти памʼять, робочі навантаження можуть ніколи не встигнути витіснити дані до свопу під час тиску на памʼять вузла. Однак занадто високі пороги виселення можуть призвести до вичерпання памʼяті на вузлі та виклику OOM killer, що теж не ідеально.
Щоб розвʼязати цю проблему, рекомендується встановлювати пороги виселення kubelet трохи нижчими за значення vm.min_free_kbytes. Так вузол може почати використовувати своп до того, як kubelet почне виселяти Podʼи, що дозволяє робочим навантаженням витісняти невикористовувані дані до свопу й запобігає виселенню. З іншого боку, оскільки значення лише трохи нижче, kubelet, імовірно, почне виселяти Podʼи до того, як на вузлі закінчиться памʼять, тим самим уникаючи OOM killer.
Значення vm.min_free_kbytes можна визначити, виконавши на вузлі таку команду:
cat /proc/sys/vm/min_free_kbytes
У режимі LimitedSwap обсяг свопу, доступний для Podʼа, визначається автоматично на основі частки запитаної памʼяті відносно загальної памʼяті вузла (докладніше дивіться розділ нижче).
Така архітектура означає, що зазвичай певна частина свопу залишатиметься недоступною для робочих навантажень Kubernetes. Наприклад, оскільки Kubernetes 1.37 не дозволяє використовувати своп Podʼам із класом QoS Guaranteed, обсяг свопу, пропорційний запиту памʼяті Podʼів Guaranteed, залишатиметься невикористаним робочими навантаженнями Kubernetes.
Така поведінка несе певний ризик у ситуації, коли багато Podʼів не мають права на своп. З іншого боку, вона фактично зберігає певний зарезервований системою обсяг своп-памʼяті, який можуть використовувати процеси поза межами Kubernetes, такі як системні демони й навіть сам kubelet.
Під час тестування та на основі відгуків користувачів було помічено, що продуктивність критично важливих системних демонів і сервісів може знижуватися. Це означає, що системні демони, зокрема kubelet, можуть працювати повільніше, ніж зазвичай. Якщо ви зіткнулися з цією проблемою, рекомендується налаштувати cgroup для system slice так, щоб запобігти свопінгу (тобто встановити memory.swap.max=0).
Своп може збільшити навантаження вводу-виводу на вузлі. Коли тиск на памʼять змушує ядро швидко витісняти сторінки до свопу й повертати їх, критично важливі системні демони й сервіси, що покладаються на операції вводу-виводу, можуть зазнати зниження продуктивності.
Щоб помʼякшити це, користувачам systemd рекомендується надавати пріоритет system slice щодо затримки вводу-виводу. Користувачам без systemd рекомендується налаштувати окрему cgroup для системних демонів і процесів та так само надати їй пріоритет щодо затримки вводу-виводу. Цього можна досягти, встановивши io.latency для system slice, що надає йому вищий пріоритет вводу-виводу. Докладніше дивіться в документації cgroup.
Проєкт Kubernetes рекомендує запускати вузли панелі управління без налаштованого простору свопу. Панель управління переважно розміщує Podʼи з класом QoS Guaranteed, тому своп зазвичай можна вимкнути. Основна проблема полягає в тому, що свопінг критично важливих сервісів на панелі управління може негативно вплинути на продуктивність.
Проєкт Kubernetes рекомендує використовувати зашифрований своп щоразу, коли ви запускаєте вузли з увімкненим свопом. Якщо своп розташований на розділі або в кореневій файловій системі, робочі навантаження можуть заважати системним процесам, яким потрібно записувати на диск. Коли вони використовують той самий диск, процеси можуть перевантажити своп, порушуючи ввід-вивід kubelet, середовища виконання контейнерів і systemd, що вплине на інші робочі навантаження. Оскільки простір свопу розташований на диску, важливо переконатися, що диск достатньо швидкий для запланованих сценаріїв. Альтернативно можна налаштувати пріоритети вводу-виводу між різними відображеними областями одного пристрою зберігання.
Kubernetes 1.37 не підтримує розподіл Podʼів по вузлах з урахуванням використання своп-памʼяті. Планувальник зазвичай використовує запити ресурсів інфраструктури для вибору розміщення Podʼів, а Podʼи не запитують простір свопу; вони запитують лише memory. Це означає, що планувальник не враховує своп-памʼять під час прийняття рішень щодо планування. Хоча ми активно працюємо над цим, це ще не реалізовано.
Щоб Podʼи не планувалися на вузли зі своп-памʼяттю, якщо вони спеціально не призначені для її використання, адміністратори можуть додати taints на вузли з доступним свопом і так захиститися від цієї проблеми. Taints гарантують, що робочі навантаження, які допускають своп, не перетікатимуть під навантаженням на вузли без свопу.
Пристрій зберігання, призначений для простору свопу, критично важливий для збереження чутливості системи під час високого використання памʼяті. Обертові жорсткі диски (HDD) погано підходять для цього, оскільки їх механічна природа вносить значну затримку, що призводить до серйозного зниження продуктивності та пробуксовування системи (thrashing). Для сучасних вимог до продуктивності, ймовірно, відповідним вибором для свопу є такий пристрій, як твердотільний накопичувач (SSD), оскільки його електронний доступ із низькою затримкою мінімізує сповільнення.
Конфігурація своп-памʼяті, зокрема її обмежень, є значним викликом. Її не лише легко налаштувати неправильно, але й, оскільки це властивість рівня системи, будь-яке неправильне налаштування може поставити під загрозу весь вузол, а не лише конкретне робоче навантаження. Щоб зменшити цей ризик і забезпечити справність вузла, ми реалізували своп з автоматичною конфігурацією обмежень.
З LimitedSwap Podʼам, які не належать до класу QoS Burstable (тобто Podʼам із класом QoS BestEffort/Guaranteed), заборонено використовувати своп-памʼять. Podʼи з класом QoS BestEffort мають непередбачувані шаблони споживання памʼяті, а інформації про використання ними памʼяті немає, тому важко визначити безпечний обсяг своп-памʼяті. Натомість Podʼи з класом QoS Guaranteed зазвичай використовують для застосунків, які покладаються на точне виділення ресурсів, заданих робочим навантаженням, і на негайну доступність памʼяті. Щоб зберегти згадані вище гарантії безпеки та справності вузла, цим Podʼам не дозволено використовувати своп-памʼять, коли діє LimitedSwap. Крім того, високопріоритетним Podʼам не дозволено використовувати своп, щоб памʼять, яку вони споживають, завжди перебувала в RAM і була готова до використання.
Перш ніж докладно описати обчислення обмеження свопу, потрібно визначити такі терміни:
nodeTotalMemory: загальний обсяг фізичної памʼяті, доступної на вузлі.totalPodsSwapAvailable: загальний обсяг своп-памʼяті на вузлі, доступний для використання Podʼами (частину своп-памʼяті може бути зарезервовано для потреб системи).containerMemoryRequest: запит памʼяті контейнера.Обмеження свопу налаштовується як: ( containerMemoryRequest / nodeTotalMemory ) × totalPodsSwapAvailable
Іншими словами, обсяг свопу, який може використовувати контейнер, пропорційний його запиту памʼяті, загальній фізичній памʼяті вузла та загальному обсягу своп-памʼяті на вузлі, доступному для використання Podʼами.
Важливо зазначити, що для контейнерів у Podʼах із класом QoS Burstable можна відмовитися від використання свопу, вказавши запити памʼяті, що дорівнюють обмеженням памʼяті. Контейнери, налаштовані таким чином, не матимуть доступу до своп-памʼяті.