От переводчика: иногда даже ключевой компонент инфраструктуры — Vertical Pod Autoscaler (VPA) — может выйти из строя из-за скрытых настроек. В этой статье Тибо Жаме рассказывает, как некорректная конфигурация VPA обрушила платформу и как команда разобралась в причинах и восстановила стабильность.

Поучительная история о том, как мы расследовали странное поведение кластера и нашли скрытую настройку, которая сделала нашу платформу стабильнее.

Всё началось с незначительного алерта о пропаже метрик, но быстро переросло в лавину вытеснений подов. Мы полностью лишились мониторинга, рискуя стабильностью всего кластера.

В этой статье описываются события ночи 10 февраля, когда автор и его коллеги, Фабиан Сельес Роса (Fabián Sellés Rosa) и Танат Локеджароенларб (Tanat Lokejaroenlarb), всю ночь занимались поиском скрытой ошибки в конфигурации Vertical Pod Autoscaler, которая оставалась незамеченной на протяжении нескольких месяцев. Это потребовало глубокого анализа внутреннего устройства VPA, многочасовой отладки и в конечном счёте привело к обнаружению единственного скрытого параметра, который и решил проблему.

Предыстория: пара слов про VPA и зачем он нужен

В Adevinta мы эксплуатируем SCHIP — внутреннюю платформу на базе Kubernetes. Она представляет собой мультитенантную среду исполнения, включающую более 30 кластеров в 4 регионах и 90 тысяч подов. В пиковые моменты SCHIP обрабатывает более 300 тысяч запросов в секунду. Одним из ключевых компонентов, обеспечивающих стабильность и автономность нашего парка кластеров, является вертикальный автоскейлер подов (Vertical Pod Autoscaler, VPA), который динамически корректирует запросы на ресурсы у рабочих нагрузок.

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

Инцидент: массовое вытеснение подов компонентом VPA

10:30. Пропадают метрики, срабатывает система оповещения

Мы поняли, что что-то пошло не так, когда Prometheus начал терять метрики, из-за чего тут же сработал алерт:

Проверили поды Prometheus и обнаружили, что их вытеснил VPA. Вмешательство VPA при резком увеличении потребления ресурсов вполне ожидаемо, однако тут явно что-то было не так.

Вытеснения не прекращались.

Сначала грешили на сам Prometheus, так как VPA нередко перезапускает его, пока тот подгружает метрики со своего тома. В таких случаях старый инстанс Prometheus может быть «убит» из-за нехватки памяти (OOMKill) ещё до того, как новый успеет приступить к работе. Мы даже попробовали восстановить его, удалив Persistent Volume Claims (PVC), но это не помогло.

11:00. Вытеснений становится больше

Изучив события в кластере, заметили, что вытеснялись не только поды Prometheus. Также пострадало значительное количество системных подов:

37s Normal EvictedByVPA pod/xxx-b45cd8b9d-bbcrn Pod was evicted by VPA Updater to apply resource recommendation.
35s Normal EvictedByVPA pod/xxxxx Pod was evicted by VPA Updater to apply resource recommendation.
36s Normal EvictedByVPA pod/xxxxxxx Pod was evicted by VPA Updater to apply resource recommendation.
36s Normal EvictedByVPA pod/xxxxx Pod was evicted by VPA Updater to apply resource recommendation.
34s Normal EvictedByVPA pod/xxxxx Pod was evicted by VPA Updater to apply resource recommendation.
35s Normal EvictedByVPA pod/xxxxx Pod was evicted by VPA Updater to apply resource recommendation.
34s Normal EvictedByVPA pod/xxxxx Pod was evicted by VPA Updater to apply resource recommendation.
33s Normal EvictedByVPA pod/prometheus-cluster-metrics-prometheus-0 Pod was evicted by VPA Updater to apply resource recommendation.
34s Normal EvictedByVPA pod/xxxxxxx Pod was evicted by VPA Updater to apply resource recommendation.
32s Normal EvictedByVPA pod/prometheus-xxxx-dev-0 Pod was evicted by VPA Updater to apply resource recommendation.
33s Normal EvictedByVPA pod/prometheus-xxxxx-pro-0 Pod was evicted by VPA Updater to apply resource recommendation.
32s Normal EvictedByVPA pod/prometheusxxxxx-pro-0 Pod was evicted by VPA Updater to apply resource recommendation.
32s Normal EvictedByVPA pod/prometheus-xxxx-pro-0 Pod was evicted by VPA Updater to apply resource recommendation.
Число вытеснений резко возросло
Число вытеснений резко возросло

Проблема казалась глобальной, поэтому начали смотреть недавние изменения. Это было похоже на погоню за призраками: мы откатывали некоторые PR, которые могли бы повлиять на стабильность VPA, но ничего не помогало…

Когда поды начинают вытесняться слишком часто, возникает «текучка». В Prometheus это оборачивается лавинообразным ростом числа уникальных временных рядов. Отсюда и OOMKill'ы — постоянная смена подов создавала слишком много новых временных рядов, и память просто заканчивалась…

Получалось, что потеря метрик — не причина проблемы, а лишь её следствие, вызванное бесконечным циклом вытеснений.

Мы снова вернулись к VPA.

Чтобы было понятно, VPA состоит из трёх компонентов:

  • Recommender — формирует запросы на ресурсы CPU/памяти на основе наблюдаемого использования.

  • Admission-контроллер — вставляет рекомендуемые запросы на ресурсы в поды при их запуске.

  • Updater — вытесняет поды, которые не соответствуют рекомендациям, чтобы их можно было пересоздать с новыми значениями ресурсов.

По большей части всё это отлично работает. Пока однажды вдруг не перестаёт.

11:30. Ищем проблему в VPA

К этому моменту мы решили углубиться в данные VPA:

  • состояние Recommender’а VPA и его логи;

  • конфигурацию VPA и обновления ресурсов;

  • VerticalPodAutoscalerCheckpoint — объект, который хранит данные о потреблении ресурсов.

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscalerCheckpoint
metadata:
 name: nginx
spec:
 containerName: nginx-ingress-controller
 vpaObjectName: ingress
status:
 cpuHistogram:
   bucketWeights:
     "0": 39
     "2": 1
     "3": 104
     "4": 339
     "5": 2342
     "6": 3338
     "7": 10000
     "8": 8512
     "9": 2405
     "10": 470
     "11": 260
     "12": 168
     "13": 32
     "14": 59
     "15": 14
     "16": 20
     "17": 13
     "18": 2
   referenceTimestamp: "2025-02-12T00:00:00Z"
   totalWeight: 1007.6418972627253
 memoryHistogram:
   bucketWeights:
     "22": 6317
     "23": 8
     "24": 1307
     "29": 1616
     "30": 26
     "31": 10000
     "34": 2291
     "35": 1
     "36": 4660
     "37": 1
   totalWeight: 7.305844833039835
 totalSamplesCount: 1256603

Сначала предположили, что Recommender пострадал из-за повреждения данных в VerticalPodAutoscalerCheckpoint. Поэтому удалили несколько объектов VerticalPodAutoscalerCheckpoint и несколько раз перезапустили Recommender, но вытеснения так и не прекратились.

15:00. Оцениваем ущерб и пытаемся что-то сделать

Проблема явно не собиралась рассасываться сама собой. Вытеснения шли полным ходом. В основном страдали поды со связанными объектами VPA, то есть самая критическая системная часть: Prometheus, логгеры, ингрессы и менеджеры сертификатов. Из-за этого началось снижение показателей SLO для многих сервисов, что негативно сказалось на стабильности платформы:

Ingress SLO
Ingress SLO
DNS SLO
DNS SLO
Logging SLO
Logging SLO

Исчерпав очевидные решения, мы предприняли два шага по смягчению последствий:

  1. Отключили VPA Recommender: надеялись, что без новых рекомендаций VPA прекратит инициировать вытеснения.

  2. Увеличили запросы на ресурсы для Prometheus: думали, что это позволит избежать завершения процесса Prometheus из-за нехватки памяти (OOMKill) и не потерять важные метрики наблюдаемости.

20:00. Оповещение о сбоях в работе вебхуков

Мы надеялись, что отключение VPA Recommender поможет, но быстро поняли, что это не сработало. И как только мы взяли паузу, чтобы переосмыслить ситуацию, прилетел новый алерт — на этот раз о сбоях вебхуков:

У нас в кластере работает несколько admission- и mutating-вебхуков, поэтому был настроен алерт, который срабатывает, когда процент сбоев вебхуков превышает критический порог. Новый алерт ясно дал понять, что:

  • отключение Recommender’а ничего не изменило — вытеснения продолжились;

  • трудности были и у admission-контроллера VPA, что намекало на более глубокую проблему.

Стало очевидно, что нужно менять направление поисков. Вместо того чтобы пытаться остановить рекомендации, нужно было понять, почему сбоит admission-контроллер VPA и не он ли является первопричиной бесконечных вытеснений.

20:30. Отладка admission-вебхука

Оказалось, что проблема носит более глубокий характер. Вытеснение подов продолжалось, и нам нужно было разобраться в происходящем на внутреннем уровне. Поскольку за вытеснение подов отвечает VPA Updater, мы решились на следующий шаг. 

Взяли исходный код именно той версии VPA Updater’а, которая была развёрнута в нашем кластере, запустили локальный отладчик с правами доступа к кластеру «только для чтения» (на всякий случай) и расставили контрольные точки (breakpoints) для отслеживания логики вытеснения.

Во время отладки всплыла ключевая деталь: у подов отсутствовали две критически важные аннотации: vpaObservedContainers и vpaUpdates. Эти аннотации информируют VPA Updater о том, что под был обработан admission-контроллером. Поскольку их не было, VPA Updater полагал, что поды неактуальны, и вытеснял их.

Это открытие подвело нас к следующему очевидному вопросу — какой компонент отвечает за добавление этих аннотаций?

Ответ: admission-контроллер VPA

Теперь всё встало на свои места: за внедрение этих аннотаций в новые или обновляемые поды отвечает admission-контроллер VPA.

Это также идеально согласовывалось с оповещениями о сбоях в работе вебхука, которые мы получали.

Ищем проблему в вебхуке

Чтобы подтвердить свою теорию, проверили метрики задержки (latency) вебхука, и результаты оказались ошеломляющими: 99-й перцентиль (p99) задержки вебхука превышал 20 секунд. То есть большинство запросов были опасно близки к предельному тайм-ауту, установленному в Kubernetes:

Попробовали смягчить проблему, увеличив тайм-аут до минуты, но обнаружили, что:

В Kubernetes существует жёсткое ограничение в 30 секунд для конфигурации admission-вебхуков.

Дальнейший анализ логов admission-контроллера показал множество записей с троттлингом на стороне клиента, который длился более 20 секунд, что объясняло наблюдаемую задержку:

Жёсткий троттлинг в Kubernetes
Жёсткий троттлинг в Kubernetes

Зашли в тупик: FlowSchemas не помогли

Первой мыслью было увеличить пропускную способность API-сервера, подправив FlowSchemas. Мы думали, что admission-контроллер заваливает API-сервер запросами. Сказано — сделано: применили новый объект FlowSchema, но изменений не заметили:

apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
 annotations:
   apf.kubernetes.io/autoupdate-spec: "false"
 name: temp-vpa-fix
spec:
 distinguisherMethod:
   type: ByUser
 matchingPrecedence: 100
 priorityLevelConfiguration:
   name: exempt
 rules:
 - nonResourceRules:
   - nonResourceURLs:
     - '*'
     verbs:
     - '*'
   resourceRules:
   - apiGroups:
     - '*'
     clusterScope: true
     namespaces:
     - '*'
     resources:
     - '*'
     verbs:
     - '*'
   subjects:
   - kind: User
     user:
       name: system:serviceaccount:kube-system:vpa-recommender
   - kind: User
     user:
       name: system:serviceaccount:kube-system:vpa-updater
   - kind: User
     user:
       name: system:serviceaccount:kube-system:vpa-admission-controller

Сюрприз, запрятанный в коде

Изучая код во время отладки, заметили одну маленькую, но решающую деталь — параметр, зарытый в коде admission-контроллера:

kube-api-qps: "5" # default
kube-api-burst: "10" # default

В документации Helm-чарта и VPA о нём ничего не говорилось, поэтому мы о нём и не знали.

Поняв, что это и есть корень зла, немедленно выкатили фикс, повысив лимиты запросов к API:

PR с фиксом, который увеличивает клиентские лимиты
PR с фиксом, который увеличивает клиентские лимиты

Прорыв

Как только фикс применился, задержки вебхука мгновенно упали.

  • Перцентиль p99 радикально снизилась.

  • Недостающие аннотации наконец-то стали проставляться.

  • Вытеснения подов полностью прекратились.

Вот оно! Наконец мы всё починили.

Вытеснения прекратились
Вытеснения прекратились
Задержка упала
Задержка упала

Поиск первопричины

Анализируя события, мы обнаружили, что одно из наших изменений значительно увеличило количество объектов VPA в кластере.

Наша версия VPA делала слишком много запросов к API, усугубляя проблему с троттлингом. Мы и не подозревали, что admission-контроллер работал с лимитом запросов, слишком низким для новой нагрузки. Это привело к сбоям вебхуков и в итоге к той самой бесконечной петле вытеснений.

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

Ключевые выводы и уроки 

1. Знай свои инструменты

Сначала мы сфокусировались на Recommender'е VPA и Checkpoint'ах, но настоящая проблема была в Updater'е VPA. Этот случай ещё раз показал, насколько важно понимать весь жизненный цикл того, как VPA управляет рабочими нагрузками.

Каждый компонент играет определённую роль. Если неправильно определить источник сбоя, можно потратить кучу времени впустую. Будь то VPA, автоскейлеры или другая базовая инфраструктура, для быстрой отладки крайне важно знать, какая часть за что отвечает.

2. Не зацикливайся на недавних правках

Когда что-то ломается, проще всего свалить вину на последние изменения. Такое узкое видение заставило нас сначала винить во всём обновление чарта Prometheus.

Но внимательная отладка показала, что проблема существовала месяцами. Троттлинг начался ещё в июле, но стал критичным, только когда число объектов VPA в кластере резко возросло.

Задержка вебхуков начала расти с июля
Задержка вебхуков начала расти с июля

Это научило нас тому, что свежие изменения — это лишь катализатор, а настоящая причина может таиться глубоко в истории системы. Теперь вместо того, чтобы сразу винить последнюю правку, мы в первую очередь:

  • смотрим на динамику метрик в прошлом;

  • проверяем корреляции, а не строим догадки о причинах;

  • системно отсекаем варианты, прежде чем делать выводы.

3. Сбои вебхуков — тихие убийцы

В отличие от validating-вебхуков, mutating-вебхук VPA (в нашей настройке) не блокирует создание подов. А значит, его сбои сразу не заметны. Они накапливаются со временем и вызывают целый каскад проблем: поды постоянно вытесняются, пропадают аннотации, растёт нестабильность.

Упавший вебхук не только «бьёт» по подам, с которыми работал, он может вызвать масштабную нестабильность во многих сервисах. Этот случай показал нам, как важно:

  • следить за задержкой вебхуков и частотой их сбоев;

  • относиться к сбоям вебхуков как к серьёзным инцидентам, даже если на первый взгляд они не кажутся критичными;

  • понимать, как отказ вебхука отражается на нижележащих компонентах системы.

4. Open Source-софт — крутая штука

Доступ к исходному коду VPA и запущенный параллельно отладчик стали ключом к разгадке проблемы. Без них мы бы ещё долго шли по ложному следу.

Считается, что Open Source-инструменты просто работают «из коробки», однако, когда что-то идёт не так, жизненно необходимо понимать, как это ПО устроено внутри.

Главные уроки:

  1. Чтение исходного кода помогает найти скрытые настройки (например, kube-api-qps и kube-api-burst).

  2. Отладка в реальном времени позволяет проследить логику выполнения программы и проверить догадки.

  3. Умение работать с Open Source-проектами превращает гадание на кофейной гуще в структурированное расследование.

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

Заключительные мысли

Инцидент был изматывающим, но в то же время очень поучительным. Он напомнил, как важно хорошо понимать инструменты, на которые мы полагаемся, особенно если от них зависит ключевая инфраструктура.

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

Статью подготовили: Фабиан Сельес Роса (Fabián Sellés Rosa), Танат Локеджароенларб (Tanat Lokejaroenlarb) и Тибо Жаме (Thibault Jamet).

P. S.

Читайте также в нашем блоге:

Комментарии (0)