Подобрать правильные запросы по процессору и памяти вручную почти невозможно. Поставишь мало, и поды начнёт душить ограничитель или убивать по нехватке памяти. Поставишь много, и ресурсы простаивают, а счёт за облако растёт. Вертикальный автоскейлер обещает решить это, подбирая запросы сам по фактическому потреблению. Звучит как мечта, но в продакшене он легко превращается в источник нестабильности, особенно когда рядом работает горизонтальный автоскейлер. Два автоматических механизма начинают тянуть под в разные стороны, и система идёт вразнос. Разберём, как устроен вертикальный автоскейлер, в чём суть его конфликта с горизонтальным и как заставить их ужиться.
Чем вертикальное масштабирование отличается от горизонтального
Сначала разведём два подхода, потому что путаница между ними и порождает беду. Горизонтальный автоскейлер добавляет поды: когда нагрузка растёт, он плодит новые экземпляры приложения. Вертикальный автоскейлер делает существующие поды больше или меньше, подстраивая запросы по процессору и памяти на каждый экземпляр. Один масштабирует вширь, другой вглубь.
Это разные ответы на разные потребности. Если приложение хорошо распараллеливается и выигрывает от большего числа экземпляров, нужен горизонтальный. Если же приложению нужно больше ресурсов на сам экземпляр, а не больше экземпляров, помогает вертикальный. Вертикальный особенно ценен там, где трудно угадать аппетит заранее: он наблюдает за реальным потреблением и приводит запросы в соответствие, экономя деньги и предотвращая удушение и убийства по памяти.
Как устроен вертикальный автоскейлер внутри
Чтобы понимать его поведение, полезно знать его устройство. Он состоит из трёх частей. Рекомендатель наблюдает за историей потребления процессора и памяти и вычисляет, сколько ресурсов поду на самом деле нужно. Обновитель решает, какие работающие поды пора привести в соответствие с рекомендацией, и вытесняет их. Контроллер допуска вмешивается при создании нового пода и проставляет ему рекомендованные запросы.
Ключевая деталь скрыта в механике применения рекомендаций. Чтобы изменить запросы работающего пода, классический вертикальный автоскейлер обязан этот под пересоздать: вытеснить старый и дать создать новый с новыми запросами. То есть применение рекомендации означает перезапуск пода. Это и есть корень многих проблем в продакшене, потому что перезапуск это всегда кратковременное нарушение работы.
Какие режимы обновления существуют и чем они опасны
Поведение определяется режимом обновления, и от его выбора зависит всё. Режимов четыре. Режим бездействия только выдаёт рекомендации, ничего не меняя: самый безопасный, его и берут для начала. Начальный режим проставляет запросы лишь при создании пода, не трогая работающие. Автоматический и пересоздающий режимы реально применяют рекомендации к работающим подам, вытесняя их.
Опасность автоматических режимов в том самом перезапуске. Без ограничений автоматический режим способен нарушить работу сервиса вытеснением подов в неподходящий момент. Поэтому при его использовании обязательно ставят бюджет нарушений, гарантирующий, что определённое число подов всегда останется доступным. Отдельное жёсткое правило: для приложений с состоянием и для одиночных реплик автоматические режимы противопоказаны. Если реплика одна, её пересоздание означает простой сервиса на время перезапуска. Базы данных и прочие хранящие состояние нагрузки держат в режиме бездействия, а рекомендации применяют вручную в окно обслуживания.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Off" # только рекомендации, безопасный старт
В чём суть войны двух автоскейлеров
А теперь к главному конфликту. Вертикальный и горизонтальный автоскейлеры вступают в схватку, когда оба нацелены на одну и ту же метрику. Классический случай это когда горизонтальный масштабирует по загрузке процессора, а вертикальный одновременно меняет запросы по процессору.
Механика катастрофы такая. Горизонтальный считает загрузку процессора как отношение потребления к запросу. Вертикальный в это время меняет сам запрос. Стоит вертикальному поднять запрос процессора, как вычисляемая горизонтальным загрузка падает, ведь знаменатель вырос, хотя реальное потребление осталось прежним. Горизонтальный видит упавшую загрузку и сокращает число подов. Меньше подов означает больше нагрузки на оставшиеся, потребление на под растёт, вертикальный снова поднимает запрос, горизонтальный снова сокращает поды. Система входит в раскачку, дёргая и число подов, и их размер туда-сюда. Это и называется болтанкой, и в продакшене она проявляется как нестабильное, непредсказуемое масштабирование.
Простое и категоричное правило отсюда: никогда не давайте обоим автоскейлерам управлять одной и той же метрикой. Это самая частая и самая болезненная ошибка при их совместном использовании.
Как заставить их ужиться
Раз конфликт возникает на общей метрике, решение очевидно: развести их по разным метрикам. Рабочий шаблон таков: вертикальный управляет памятью, а горизонтальный масштабирует по процессору. Тогда они не пересекаются и не мешают друг другу.
# Вертикальный отвечает только за память
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-api-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-api
resourcePolicy:
containerPolicies:
- containerName: web-api
controlledResources: ["memory"]
---
# Горизонтальный отвечает только за процессор
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Здесь ключевое это указание, какими ресурсами управляет вертикальный автоскейлер. Ограничив его памятью, мы убираем пересечение с горизонтальным, который смотрит на процессор. Самый осторожный вариант ещё консервативнее: держать вертикальный в режиме рекомендаций, а всё реальное масштабирование отдать горизонтальному, применяя рекомендации по памяти вручную в окна обслуживания.
Почему границы рекомендаций обязательны
Отпускать вертикальный автоскейлер без границ опасно. Без них он способен порекомендовать крошечные запросы в период затишья или огромные после кратковременного всплеска. Поэтому всегда задают минимально и максимально допустимые значения, удерживающие рекомендации в разумном коридоре.
resourcePolicy:
containerPolicies:
- containerName: web-api
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "2000m"
memory: "4Gi"
Минимум выставляют по базовой потребности приложения, чтобы оно не задохнулось в простое. Максимум привязывают к вместимости узла и бюджету, и разумное эмпирическое правило гласит, что верхняя граница не должна превышать половины ёмкости самого крупного узла, иначе под просто некуда будет поместить.
Где вертикальный автоскейлер ошибается чаще всего
Честность требует назвать слабые места, потому что они существенны. Логика рекомендаций реактивна и слепа к контексту: автоскейлер смотрит на прошлое потребление и не понимает смысла нагрузки. Историю он хранит недолго, около восьми дней, что ограничивает точность для нагрузок с длинными или сезонными циклами. Недельный пик отчётности или ежемесячный всплеск он попросту не запомнит как закономерность.
Особняком стоят приложения на виртуальной машине Java. Для них рекомендации часто неточны, потому что управление памятью внутри машины скрывает истинное потребление от наблюдателя снаружи. Машина забирает у системы крупный кусок памяти под себя и распоряжается им внутри, а автоскейлер видит лишь общую картину и легко ошибается. Та же осторожность нужна для нагрузок со сложным профилем ресурсов, вроде вычислений на ускорителях или сервисов с выраженной зависимостью от времени суток. Ещё одна тонкость: контейнеры-спутники вроде прокси сервисной сети управляют своими ресурсами сами, и их разумно исключать из ведения вертикального автоскейлера, чтобы он их не трогал.
Что меняет изменение ресурсов без перезапуска
Свежее развитие способно смягчить главную боль вертикального масштабирования. Поскольку традиционно применение рекомендации требовало пересоздания пода, появилась возможность менять ресурсы работающего пода на месте, без перезапуска. Это убирает издержки нарушения работы при применении рекомендаций: под не вытесняется, а просто получает новые запросы на лету.
Важно понимать, что это решает проблему перезапуска, но не проблему качества рекомендаций. Изменение на месте делает применение дешевле, однако реактивная и слепая к контексту логика рекомендатора никуда не девается. Поэтому даже с изменением на месте разумнее сначала понаблюдать за рекомендациями, а уж потом доверять автоматическому применению, особенно на нагрузках со сложным профилем.
Какой стратегии придерживаться в продакшене
Вертикальный автоскейлер это полезный, но капризный инструмент, требующий дисциплины. Разумный путь складывается из нескольких шагов. Начинать всегда с режима рекомендаций, чтобы неделю понаблюдать за тем, что автоскейлер советует, прежде чем что-либо ему доверять. Обязательно задавать минимальные и максимальные границы, не давая ему уходить в крайности. Никогда не позволять вертикальному и горизонтальному автоскейлерам управлять одной метрикой: классическая безопасная связка это вертикальный по памяти и горизонтальный по процессору. Для нагрузок с состоянием и одиночных реплик не включать автоматические режимы, применяя рекомендации вручную в окна обслуживания. Ставить бюджет нарушений, если всё же используется автоматический режим. И следить за частотой вытеснений: всплеск выселений сигналит о раскачке, которую пора унять.
Главная мысль в том, что автоматическое управление ресурсами без понимания границ опаснее ручного подбора. Тот, кто включает оба автоскейлера на процессоре и отпускает их без присмотра, получает болтанку, дёргающую и размер, и число подов в самый неподходящий момент. А тот, кто разводит их по разным метрикам, ставит границы и начинает с наблюдения, получает правильно подобранные поды, которые не душатся, не гибнут от нехватки памяти и не транжирят бюджет на простаивающие ресурсы.