Масштабирование Kubernetes в Pinterest: через сбои и аварии

Kate

Administrator
Команда форума
nf1pxl0ljp63lhodsy3hjusdk58.png

Kubernetes API Server вылетел с ошибкой (OOMKilled)

Прошло больше года с нашего [компании Pinterest] перехода на платформу Kubernetes. С тех пор мы разработали множество новых функций, гарантировали надёжность и масштабируемость платформы, а также накопили опыт и лучшие практики.

В целом, платформа Kubernetes всем понравилась. Согласно результатам опроса, три её главных преимущества — более простое управление вычислительными ресурсами, лучшая изоляция ресурсов и сбоев, а также более гибкое масштабирование.

К концу 2020 года мы запустили в кластерах Kubernetes более 35 тыс. подов на 2500 узлах для наших корпоративных пользователей, и это количество быстро растёт.

Итоги 2020 года вкратце​

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

Однако в начале 2020 года случился серьёзный сбой. В одном из кластеров внезапно выросло количество новых подов (примерно в три раза больше запланированного). Для удовлетворения спроса система автоматического масштабирования подняла 900 новых узлов. kube-apiserver начал тормозить и выдавать ошибки, а затем вылетел из-за нехватки ресурсов. Повторная попытка из Kubelets привела к семикратному скачку нагрузки на kube-apiserver. Всплеск записей привёл к тому, что хранилище etcd достигло предельного объёма данных и стало отклонять все запросы на запись, а платформа полностью отключилась в смысле управления рабочей нагрузкой. Для восстановления etcd мы выполнили ряд операций: сжатие старых версий, дефрагментацию и отключение аварийных оповещений. Кроме того, пришлось временно масштабировать мастера Kubernetes, где размещаются kube-apiserver и etcd, чтобы хватило ресурсов.

glth2ktniemzqgvfegtk0drjchm.png

Рис. 1. Всплески задержки сервера Kubernetes API

Позже в 2020 году проявился баг в интеграции с kube-apiserver одного из наших компонентов, что привело к всплеску дорогостоящих запросов к kube-apiserver (перечисление всех модулей и подов). В итоге на мастер-узле не хватило ресурсов — и kube-apiserver ушёл в состояние OOMKilled. К счастью, проблемный компонент обнаружили и быстро откатили. Но во время инцидента производительность платформы пострадала от деградации, включая задержку выполнения рабочей нагрузки и задержку с выдачей статусов.

nf1pxl0ljp63lhodsy3hjusdk58.png

Рис. 2. Kubernetes API Server ушёл в состояние OOMKilled

Готовимся к масштабированию​

Мы постоянно размышляли, как лучше обеспечить надёжность и контроль над платформой. Тем более когда некоторые инциденты хлёстко бьют по нашим слабым местам. Не имея огромного штата инженеров, а только скромную команду с ограниченными ресурсами, пришлось глубоко копать, чтобы понять коренные причины, выявить первоочередные меры и определить приоритеты по соотношению затрат и выгоды. Наша стратегия работы со сложной экосистемой Kubernetes — максимально использовать открытые инструменты и внести свой вклад в Open Source, но не отказываться от написания собственных внутренних компонентов.

oweubyf8dgdwdoe_1xug3uqx-sa.png

Архитектура платформы Kubernetes в компании Pinterest (синим показаны компоненты собственной разработки, зелёным — опенсорсные)

Управление​

Соблюдение квот на ресурсы​

В самом Kubernetes есть квоты ресурсов. Они гарантируют, что никто не запросит и не займёт неограниченный объём ресурсов: подов, CPU, памяти и т. д. Как упоминалось выше, всплеск генерации подов в одном из кластеров может перегрузить kube-apiserver и вызвать каскадный сбой. Вот почему для стабильности важно ограничить использование ресурсов в каждом пространстве имён (namespace) на кластере.

Здесь мы столкнулись с рядом проблем. Оказалось, что принудительное применение квоты ресурсов в каждом пространстве имён неявно требует указания запросов и ограничений для всех подов и контейнеров. Мы это реализовали. На платформе Pinterest Kubernetes рабочие нагрузки в разных пространствах имён принадлежат разным командам для разных проектов, а пользователи платформы настраивают свою рабочую нагрузку с помощью Pinterest CRD (определения пользовательских ресурсов). В слой преобразования CRD мы добавили запросы ресурсов по умолчанию, а также лимиты для всех подов и контейнеров. Кроме того, на уровне проверки CRD мы отклоняем любую спецификацию подов без запросов на ресурсы и указанных лимитов.

Ещё одной задача — оптимально управлять квотами между командами и организациями. Чтобы безопасно активировать принудительное применение квот, мы смотрим на историческое использование ресурсов, добавляем 20% запаса поверх пикового значения и устанавливаем его в качестве начальной квоты для каждого проекта. Мы запустили cron для мониторинга квот и отправки предупреждений владельцам проектов, если использование ресурсов приближается к указанному пределу (предупреждения рассылаются в рабочие часы). Это побуждает владельцев лучше планировать мощности и заранее запрашивать изменения квот. Изменение проверяется вручную, а потом автоматически развёртывается.

Обеспечение клиентского доступа​

На всех клиентов KubeAPI распространяются лучшие практики Kubernetes:

Фреймворк контроллера​

Фреймворк контроллера (оператора) предоставляет общий кэш для оптимизации операций чтения. Он использует архитектуру «информер-рефлектор-кэш». Информеры составляют список и наблюдают за интересующими объектами с сервера kube-apiserver. Рефлектор отражает изменения объекта в базовом кэше и распространяет наблюдаемые события на обработчики событий. Несколько компонентов внутри одного контроллера (оператора) могут регистрировать обработчики для событий onCreate, onUpdate и OnDelete из информеров и извлекать объекты из кэша, а не напрямую с kube-apiserver. Так уменьшается количество ненужных и избыточных вызовов.

3w3szblz0qstroew5uuhcp8qji8.png

Рис. 4. Фреймворк контроллера Kubernetes

Ограничение полосы​

Вызовы API обычно поступают из разных контроллеров и разных потоков. Kubernetes поставляет свой клиент API с ограничением количества запросов по алгоритму текущего (дырявого) ведра, с настройкой QPS (запросов в секунду) и всплесков. Вызовы API за пределами лимита подавляются, чтобы единственный контроллер не забил всю полосу kube-apiserver.

Общий кэш​

В дополнение к встроенному кэшу kube-apiserver, который поставляется вместе с фреймворком, мы добавили в API платформы ещё один слой с информером записи через кэш. Это сделано для того, чтобы предотвратить ненужные вызовы чтения, сильно бьющие по kube-apiserver. Повторное использование кэша на стороне сервера также позволило избежать использования толстых клиентов в коде приложения.

Для доступа к kube-apiserver из приложений мы принудительно проводим все запросы через API, чтобы присвоить идентификаторы безопасности: так осуществляется контроль доступа и управление потоками. Для доступа к kube-apiserver из контроллеров рабочей нагрузки следует убедиться, что все контроллеры реализованы на платформе управления с ограничением полосы (количества запросов).

Устойчивость​

Укрепление Kubelet​

Одна из ключевых причин каскадного сбоя нашей плоскости управления Kubernetes — это устаревшая реализация рефлектора с неограниченным количеством повторных попыток при обработке ошибок. Такие недоработки могут стать критичными, особенно когда сервер API вылетает из памяти, что легко ведёт к синхронизации рефлекторов по всему кластеру.

Этот вопрос мы очень тесно обсуждали с сообществом: были зарегистрированы соответствующие проблемы, затем обсуждались решения. В конце концов, пул-реквесты (1, 2) были рассмотрены и приняты. Идея в том, чтобы добавить экспоненциальный откат, используя логику повторений из рефлектора ListWatch с джиттером, чтобы kubelet и другие контроллеры не пытались долбить сервер запросами после перегрузки kube-apiserver и сбоя запросов. Это улучшение устойчивости полезно в целом, но мы нашли его особенно критичным на стороне kubelet по мере того, как растёт число узлов и подов в кластере Kubernetes.

Настройка параллельных запросов​

Чем больше узлов под нашим управлением, тем быстрее создаются и удаляются рабочие нагрузки и тем больше вызовов API обрабатывает QPS-сервер. Сначала мы увеличили максимальные параметры одновременных вызовов API как для мутирующих, так и для немутирующих операций (то есть для операций с изменением настроек и без изменения настроек) на основе расчётных рабочих нагрузок. Эти два параметра обеспечивают, что количество обработанных вызовов API не превышает указанного числа и, следовательно, ограничивает использование CPU и памяти kube-apiserver.

Внутри цепочки обработки запросов API каждый запрос в Kubernetes сначала проходит через группу фильтров. Именно здесь применяются лимиты. Если количество вызовов API превышает заданный порог, клиенту возвращается ответ «Cлишком много запросов» (429), чтобы инициировать правильные повторные попытки. В будущем мы планируем дополнительно изучить функции EventRateLimit с более тонким контролем допуска и обеспечить лучшее качество услуг.

Кэшировать больше историй​

Watch-кэш или дежурный кэш — это механизм внутри kube-apiserver, который кэширует прошлые события каждого типа ресурса в кольцевом буфере, чтобы наилучшим образом обслуживать вызовы из определённой версии. Чем больше кэш, тем больше событий можно сохранить на сервере и тем больше вероятность беспрепятственного обслуживания потоков событий клиентами в случае разрыва соединения. Учитывая этот факт, мы также улучшили целевой размер оперативной памяти kube-apiserver, который в конце концов внутренне переносится в объём кэша на основе эвристики для обслуживания более надёжных потоков событий. В документации kube-apiserver более подробно описаны параметры настройки watch-кэша.

tpm4tdzofxtbk3ubyvr6odlipuu.png

Рис. 5. Watch-кэш Kubernetes

Удобство эксплуатации​

Наблюдаемость​

Чтобы быстрее реагировать на инциденты и устранять их последствия, нужно чётко отслеживать плоскости управления Kubernetes. Задача найти баланс между полным покрытием всех возможных сбоев и приемлемым порогом чувствительности. Из существующих метрик Kubernetes мы сортируем и выбираем важные метрики для мониторинга и/или оповещений. Кроме того, используем kube-apiserver для более детального охвата областей, чтобы быстро найти первопричину любого сбоя. Наконец, настраиваем статистику оповещений и пороговые значения, чтобы уменьшить шум и ложные тревоги.

На высоком уровне мы отслеживаем загрузку kube-apiserver, просматривая QPS и параллельные запросы, частоту ошибок и задержки запросов. Можно разбить трафик по типам ресурсов, по командам в запросах и соответствующим аккаунтам. Для дорогого трафика (как листинг) мы также измеряем полезную нагрузку запроса по количеству объектов и размеру байт, так как они могут легко перегрузить kube-apiserver даже с небольшим QPS. Наконец, отслеживаем некоторые важные показатели производительности сервера — количество запросов на обработку событий watch в etcd и количество отложенных событий обработки (delayed processing count).

mk2ztc17ldrx3jeu72sf4xo4ebu.png

Рис. 6. Вызовы API Kubernetes по типу

Удобство отладки​

Чтобы лучше понять производительность и потребление ресурсов контрольной плоскости, мы разработали инструмент для визуализации etcd с помощью библиотек boltdb и flamegraph. Результаты анализа хранилища данных дают представление об оптимизации использования платформы.

dxsmivmyia2y8n1cluowlpg0fm4.png

Рис. 7. Визуализации данных Etcd на один ключ

Кроме того, мы взяли профайлер языка Go pprof и визуализировали объём памяти кучи. Мы быстро выявили наиболее ресурсоёмкие ветви кода и шаблоны запросов, например, преобразование объектов ответа при вызовах ресурсов списка. В рамках расследования критического OOM-сбоя kube-apiserver мы обнаружили ещё одно важное обстоятельство: кэш страниц kube-apiserver засчитывается в лимит памяти cgroup, при этом анонимное использование памяти крадёт кэш страниц для той же cgroup. Таким образом, даже если kube-apiserver использует всего 20 ГБ памяти кучи, вся cgroup может упереться в лимит 200 ГБ памяти. Хотя текущая настройка ядра по умолчанию не предусматривает упреждающего восстановления назначенных страниц для эффективного повторного использования, в настоящее время мы изучаем мониторинг на основе файла memory.stat, заставляя cgroup возвращать как можно больше страниц при приближении к лимиту памяти.

w3beu1vnjkfr_lqafikmosg-w_u.png

Рис. 8. Профилирование памяти сервера Kubernetes API

Вывод​

Благодаря усилиям по повышению управляемости и надёжности мы значительно сократили внезапные скачки в использовании ресурсов, контролируем пропускную способность плоскости управления, обеспечивая стабильность и производительность всей платформы. Как показано на графике ниже, после оптимизации количество запросов в секунду в kube-apiserver упало на 90%, что повысило стабильность, эффективность и надёжность. Глубокое знание внутренних компонентов Kubernetes, а также полученные новые знания позволят нашим инженерам лучше справиться с работой системы и обслуживанием кластеров.

pdqudbvupidkf-lmvsh36qfjzdo.png

Рис. 9. Снижение количества запросов к kube-apiserver после оптимизации

Вот некоторые ключевые выводы, которые, надеюсь, помогут вам повысить надёжность платформы Kubernetes и решить проблемы с масштабированием:

  1. Диагностируйте проблемы, чтобы добраться до первопричины. Сосредоточьтесь на «том, что есть», прежде чем решить, «что с этим делать». Первый шаг к решению проблем — это понять, что является узким местом и почему. Добраться до первопричины — половина решения.
  2. Почти всегда стоит сначала изучить маленькие последовательные улучшения, а не радикальные изменения архитектуры. Это очень важно, особенно когда у вас небольшая команда.
  3. Принимайте решения на основе данных. Правильная телеметрия покажет, на чём сосредоточиться и что оптимизировать в первую очередь.
  4. Критические компоненты инфраструктуры должны разрабатываться с учётом устойчивости. Распределённые системы подвержены сбоям, и лучше всегда готовиться к худшему. Правильные ограничения помогут предотвратить каскадные отказы и свести к минимуму радиус взрыва.

Планы на будущее​

Федеративная структура​

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

Проактивное планирование ресурсов​

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


Источник статьи: https://habr.com/ru/company/itsumma/blog/552818/
 
Сверху