Реклама
Перетяжка // Коробка 3.0

Kubernetes 1.37 читает большие коллекции из etcd потоком и экономит память

Нужны Kubernetes 1.37 и etcd 3.7; проверка по метрике listStream.

Обложка: Kubernetes 1.37 читает большие коллекции из etcd потоком и экономит память

1 сентября в блоге Kubernetes объявили, что функция etcd RangeStream перешла в бету в Kubernetes v1.37 и включена по умолчанию. В паре с etcd 3.7 она меняет способ, которым API server вычитывает из хранилища целые коллекции объектов: вместо страниц фиксированного размера по числу ключей etcd отдаёт поток чанков, ограниченных по байтам. Автор анонса Джеффри Ин из Google обещает меньше памяти на обеих сторонах и более предсказуемые пики.

Если вы держите кластер с десятками тысяч подов или крупными объектами и видели пики памяти на kube-apiserver или etcd именно во время чтения коллекций после рестарта, это обновление адресовано вам. Тем, у кого кластер маленький, а etcd остался на 3.6, ничего не изменится: API server сам поймёт, что стриминг недоступен, и продолжит работать по-старому.

Ключевые выводы
  • EtcdRangeStream в v1.37 имеет статус beta и включён по умолчанию; для работы нужен etcd 3.7 или новее.
  • Раньше страница ограничивалась числом ключей, поэтому страница крупных объектов могла весить сколько угодно; теперь чанки ограничены по байтам.
  • Стриминг используется при инициализации watch cache и при list-запросах, которые уходят в etcd напрямую.
  • Проверка: метрика etcd_request_duration_seconds_count с operation="listStream" больше нуля.
  • Отключение: флаг --feature-gates=EtcdRangeStream=false на kube-apiserver.

Откуда брались пики памяти

Большую часть list- и watch-запросов API server обслуживает из своего кеша в памяти, watch cache. Чтобы этот кеш наполнить, при старте и при каждой реинициализации нужно вычитать полное состояние ресурса из etcd. Для ресурсов с большим числом объектов или с крупными объектами, вроде подов, такое чтение дорогое.

Пагинация там уже была: API server запрашивал у etcd фиксированное число ключей за раз. Проблема в том, что страница, ограниченная числом ключей, ничего не знает о размере объектов. Страница из крупных объектов остаётся очень большой, расход памяти трудно предсказать, а неудачное сочетание размера объектов и параллельных чтений заканчивается OOM. Хуже того, обычный вызов Range в etcd собирает страницу целиком до отправки, а API server держит её в памяти, пока декодирует, так что один и тот же payload одновременно лежит с обеих сторон. Основная нагрузка ложится на etcd, и именно там стриминг помогает сильнее всего.

Что делает RangeStream

В etcd 3.7 появился потоковый вариант того же чтения, RPC RangeStream. Он принимает тот же RangeRequest, что и Range, и возвращает тот же набор результатов, но не собирает ответ целиком, а режет его на чанки и стримит. Размер чанка подстраивается под возвращаемые значения, поэтому коллекция крупных объектов ограничена байтами, а не числом ключей, и память освобождается по ходу потока.

Когда функция включена, API server использует RangeStream везде, где читает из etcd целую коллекцию: при инициализации watch cache и в fallback-путях, когда list-запрос нельзя обслужить из кеша и приходится идти в etcd напрямую. Каждый чанк декодируется по прибытии и освобождается до запроса следующего, так что ни одна из сторон не держит коллекцию целиком.

Требования и совместимость

  • Kubernetes v1.37 или новее.
  • etcd v3.7 или новее.

RangeStream используется, когда на kube-apiserver включён feature gate EtcdRangeStream (в v1.37 он beta и включён по умолчанию) и etcd не старше 3.7. Поддержку на стороне etcd API server определяет при старте и дополнительно откатывается в рантайме, если вызов вернул Unimplemented. Поэтому API server 1.37 в паре со старым etcd сам продолжит ходить по пагинированному Range, ничего настраивать не нужно. Kubernetes 1.37 вышел 26 августа; etcd 3.7 анонсирован в июле, и RangeStream добавлен туда заранее под эту интеграцию.

Выключить функцию можно флагом:

			--feature-gates=EtcdRangeStream=false
		

Как убедиться, что стриминг работает

API server записывает потоковые чтения под отдельной меткой операции в своих etcd-метриках. Ненулевое значение означает, что RangeStream используется:

			etcd_request_duration_seconds_count{operation="listStream"}
		

Если счётчик остаётся нулевым, API server по-прежнему ходит по пагинированному Range, и самая вероятная причина в том, что etcd старше 3.7. На практике это значит, что обновление control plane до 1.37 без обновления etcd эффекта не даст; проверять стоит обе версии.

Кому это нужно, а кому нет

По нашей оценке, выигрыш заметят кластеры, где list-запросы большие: много подов, крупные CRD, частые реинициализации watch cache после рестартов API server или сбоев. В небольших кластерах путь чтения при etcd 3.7 тоже сменится, но эффект, по нашей оценке, будет мал: там пиковый расход и так укладывался в лимиты. Цифр экономии памяти в анонсе нет, так что оценивать эффект придётся по собственным графикам потребления etcd и kube-apiserver до и после включения. Управляемые сервисы Kubernetes у российских облачных провайдеров обновляют версии control plane и etcd по своему графику, поэтому сначала стоит свериться с их release notes.

Подробности дизайна вынесены в KEP-5966, вопросы принимают в канале #sig-etcd в Slack Kubernetes.

Источники: Блог Kubernetes: etcd RangeStream Cuts Memory Use on Large List Reads, KEP-5966: etcd RangeStream, Анонс etcd v3.7

Изображение на обложке: Kubernetes