Тротлинг Go приложений в k8s
В этом посте разберемся с gomaxprocs и тротлингом go приложений в k8s
Основная метрика при мониторинге контейнеров в k8s - trottling, она показывает сколько раз в момент времени тротлится ваш контейнер, в некоторых случаях мы можем видеть тротлинг, когда до лимитов cpu еще далеко, тогда откуда он берется?
Как работает тротлинг в k8s
K8s для реализации лимитов использует CFS Quota, настройки лимитов можно увидеть в файлах cfs_period_us и cfs_quota_us
Квота работает на периодах веремени:
cfs_period_us - задает продолжительность периода, это всегда 100мс
cfs_quota_us - задает доступное время внутри каждого периода
Таким образом получаем следующее:
---
resources:
limits:
cpu: 1000m = 100ms
cpu: 500m = 50ms
cpu: 100m = 10ms
Представим, что у нас есть приложение, которому для выполнения своей задачи необходимо 200мс процессорного времени, без лимитов график его работы выглядел бы следующим образом и тротлинга мы бы не увидели.

Теперь представим, что мы задали limits.cpu: 400m, приложению для его работы стало доступно только 40мс процессорного времени внутри каждого треда в 100мс, процесс ранее выполняющийся за 200мс теперь занимает 440мс
Приложение работает в течении 5 периодов по 100мс, в каждом из которых работает только 40мс, остальные 60мс тротлится.
Правильный лимит в таком случае составлял бы 2000m или его отсутствие :)

Если с фундаментальным механизмом тротлинга понятно, то для приложений на go необходимо разобраться с горутинами
Goroutines
Для реализации параллелизма и возможности выполнения нескольких задач одновременно в go используются горутины
Главная особенность горутин состоит в том, что они могут выполняться параллельно, то есть на многоядерных архитектурах есть возможность выполнять отдельные горутины на отдельных ядрах процессора, тем самым горутины будут выполняться паралелльно, и программа завершится быстрее
Горутины на нескольких ядрах
В go есть возможность указать доступное колличество ядер для запуска горутин, для этого существует переменная GOMAXPROCS
Значением по умолчанию для перменной GOMAXPROCS является общее колличество ядер доступных в системе ( Go 1.5 - Go 1.24)
В Go 1.25+ переменная имеет значение CPU limit контейнера (с округлением вверх до целого, минимум 2)
Тротлинг Go приложений в k8s
Таким образом Go приложение запущенное на сервере скажем с 96 ядрами и имеющее cpu.limit = 1000m, будет “видеть” все 96 ядер и попытается распределить на них нагрузку, в таком случае мы непременно получим большие значения тротлинга
Примеры тюнинга gomaxprocs
Здесь у меня cert-manager имеющий limit = 100m, без явного GOMAXPROCS, имеется тротлинг порядка 60%

Поставим GOMAXPROCS = 1, тротлинг упал в 2 раза

Еще один пример, grafana, тут будет еще круче и наглядней :)
Графана имеет cpu.limit = 1500m и тротлится на 15%

Выставим GOMAXPROCS=1 и ужмем cpu.limit оставив только 1000m

Тротлинг упал до допустимых значений, в этом случае мы оптимизировали приложение да еще и съэкономили ресурсы кластера
Итог
Что можно сделать прямо сейчас?
- Следите за GOMAXPROCS в ваших кластерах, старайтесь выставлять значения равные лимитам или около того
- Если у вас есть возможность обновить версию Go до 1.25 или новее, это будет самым простым способом получить оптимальные настройки для контейнеров
fly save! =)