Троттлинг в Kubernetes. Или как настроить лимиты, чтобы приложения не “тормозили”

Главная рекомендация - отказаться от лимитов!

А теперь подробнее.

Когда у вас много пользователей используют один кластер Kubernetes, возникает вопрос - как задать квоты, чтобы и приложениям хватало ресурса, и не случилось ситуации, когда из-за одного прожорливого соседа страдают все поды на ноде? 

Начну с того, что самым распространенным способом является задание request и limit по CPU и RAM. С оперативной памятью все достаточно просто - при превышении потребления, OMM-Killer остановит процесс. А вот с CPU есть целый ряд нюансов и возможностей наступить на грабли.

Это происходит из-за того, что ресурс процессора делится не долями, а по времени. 

Это можно представить так

Kubernetes использует ядро Linux, и если у вас лимит равен 0,4 CPU (400 m) и в ноде 1 ядро, то каждые 100 мл.сек (0,1 сек) на решение ваших задач система выделит 40 мл.сек. Если вашему приложению нужно больше тактов вычислений, то когда закончится выделенная квота, будет троттлинг (пропуск тактов процессора). Важно отметить, что пару лет назад ситуация была хуже, но ряд фиксов ядра Linux существенно улучшили ситуацию. При этом проблема все равно осталась.

Проблема в том, что если ресурса не хватает, то приложению придется ждать начала следующей эпохи (100 мл. сек) чтобы завершить вычисление. 

Но редко когда в ноде по 1 vCPU. Что будет, если ядер - 4? 

Тогда при лимите в 400m(0,4 vCPU) будет представлена квота всего по 10 мл.сек на ядро. Но в совокупности - 40 мл.сек.

Примечательно, что чем больше ядер в ноде, тем хуже будет становиться ситуация. Детальный эксперимент по деградации отклика приложения при увеличении мощности подробно описан в данной статье.

Приведем лишь одну таблицу, согласно которой увеличение количества ядер в 48 раз дало ухудшение работы в 95 процентиле в 10 раз.

И подобная деградация приводит к увеличению времени отклика приложения, потере сетевой связности и другим неприятным явлениям.

Самое неприятное то, что проблема “скрыта”. У вас нода по мониторингу стоит почти “пустая”, при этом приложения тормозят.

Возможные решения

  1. Отказаться от лимитов.

  1. Самым напрашивающимся решением является ручной подбор оптимального значения лимитов. Но давайте будем честны, предсказать будущее даже для стабильных нагрузок проблематично. А если даже мы можем это сделать руками, то встает вопрос - а нужен ли нам Kubernetes?

  1. Более продвинутой альтернативой является использование динамического выделения ресурсов при фиксировании троттлинга. Но, во-первых, это сложно, во-вторых, это требует перезагрузки подов, что не всегда приемлемо.

  1. При этом вы можете использовать только Request, отказавшись от Limit. Request отвечает за планирование Kubernetes подов на ноды. А именно, вне зависимости от фактического потребления (даже если его почти нет), если реквестами занято 100% CPU, новые поды на данную ноду планироваться не будут.

    Но нас интересует другая особенность. При отсутствии лимитов, в случае конкуренции за ресурс, Kubernetes будет учитывать доли реквестов конкретных подов относительно всех подов, для выделения им вычислительного ресурса. Конечно, это не гарантирует, что не случится ситуации с процессорным голоданием, но в большинстве случаев может существенно улучшить производительность.

    Подробнее это описано в документации kubernetes по ссылке.

Но прежде чем бить тревогу, лучше понять - есть ли у вас данная проблема.

Измерить троттлинг можно используя следующую команду

cat /sys/fs/cgroup/cpu/cpu.stat

В работе Amvera Cloud, облака для развертывания IT-приложений через push в Git, мы столкнулись с данной проблемой. Она приводила к эпизодическим затруднениям, которые проявлялись либо как проблемы сети для некоторых проектов, либо как замедленная работа приложений. Для ее решения мы отказались от лимитов там, где это можно (но не везде), а в остальных случаях решаем задачу комбинацией автоскейлинга, мониторинга и ручного администрирования. 

Об Amvera

Если у вас несколько микросервисов и вы не хотите тратить время на настройку CI/CD, мониторинга и эксплуатации Kubernetes, попробуйте наше облако - Amvera Cloud. Amvera — альтернатива managed Kubernetes. У нас вы сможете обновлять ваши сервисы через простые коммиты в Git и получите почти все преимущества Kubernetes, не задумываясь об администрировании, настройки CI/CD, мониторинга, алертинга и сопутствующих сервисов. Это выйдет намного дешевле, чем классический managed k8s. Kubernetes у нас внутри, но вы полностью абстрагированы от его администрирования, достаточно просто привязать к сервису ваш Git-репозиторий и обновлять приложения командой “git push amvera master”.

А если у вас сложный проект и вам нужна помощь в построении инфраструктуры на основе Kubernetes, или просто консультация, пишите мне в телеграм (Кирилл Косолапов), либо оставьте контакт для связи на странице нашей DevOps-команды. За спрос денег не берем, если это небольшая консультация или что-то простое, поможем бесплатно. А если сложное - договоримся.

Но если вы можете полностью отказаться от лимитов в Kubernetes, рекомендуем это сделать! Это убережет от множества, на первый взгляд, абсолютно неочевидных проблем.

И если вы хотите подробнее ознакомиться с темой троттлинга в Kubernetes, рекомендуем прочитать следующие статьи 1, 2, 3, 4.

@kirillkosolapov
20.02.2024 18:43 UTC
Первоисточник

Комментарии

@VadimMichaylov
20.02.2024 13:45 UTC
0

А как сильно может из-за троттлинга тормозить приложение? Просто если на 5% это одно, и может и не надо ничего делать, и это не проблема, а если на порядок - другое

@kirillkosolapov
20.02.2024 13:47 UTC
0

Тут по разному, но может и на "порядок", особенно в 95-99-ом процентиле запросов. А для некоторрых отраслей и 5% критично

@trublast
21.02.2024 06:24 UTC
+1

Немного глаза режет мл.сек. Миллилитров секунд? Почему не мсек? По-русски должно быть или милли или м.

https://ru.m.wikipedia.org/wiki/Приставки_СИ

@trublast
21.02.2024 06:38 UTC
+1

Не рассмотрен ещё один компромиссный (и, конечно же, спорный) вариант:

Допустим, ваше приложение в отсутствии лимитов потребляет 200m cpu, и работает на нодах с 4 или 8 ядрами. В некоторых случаях имеет смысл поставить лимит 2000m cpu (2 ядра). Скорее всего, при нормальном режиме работы приложения, троллинга никогда не случится. Но если что-то пойдет не так, например из-за ошибки в коде приложения, или из-за dos атаки на него, то приложение "не съест" всю ноду и всё-таки будет тротлиться.

@psmolkin
22.02.2024 06:05 UTC
-1

Всю ноду не съест, потому что request гарантирует приложению его минимально доступное время CPU.

Ещё по теме рекомендую For the Love of God, Stop Using CPU Limits on Kubernetes и Why You Should Keep Using CPU Limits on Kubernetes

Upd: вот что действительно может съесть весь CPU, так это какой-нибудь iowait, но тут никакие лимиты не спасут.

@Prikalel
21.02.2024 15:56 UTC
0

прочитал как троллинг

@PetyaUmniy
22.02.2024 06:04 UTC
+3

С памятью все просто... Ну нет, с памятью все еще хуже. :)
Во-первых предложеное решение не сработает. Лимиты по памяти должны быть безальтернативно установлены. Иначе это грозит выселением всех подов с ноды или даже зависанием последней.
Во-вторых потребности в CPU гораздо проще определяются - смотришь график потребления CPU и выставляешь, и если нагрузка не меняется в 100 раз - у тебя все хорошо. С памятью так вообще не работает и сильно зависит от типа приложения. Одним ставишь лимит на 30% больше максимального RSS - и они работают как ни в чем ни бывало, например nginx. Другим ставить на 2000% больше максимального RSS в районе WSS - они стабильно и воспроизводимо падают, например postgres. Как формализовать установку лимитов по памяти, да так чтобы не тренироваться узнаванием "когда оно падает", до сих пор не знаю.
И еще кстати метрики CPU собираются как prometheus counter, а memory как gauge. А это значит что пропустить пик потребления CPU, который бы уперся в лимиты - невозможно, а по памяти - запросто (просто не попали и интервал "скребления", приходите "поскребсти" в следующих раз)