K8s Q A Vertical Pod Autoscaler
https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler#known-limitations https://habr.com/ru/companies/flant/articles/541642/
VPA
VerticalPodAutoscaler VPA позволяет считать потребление ресурсов pod’ами и (в случае соответствующего режима работы) вносить изменения в ресурсы контроллеров — править их requests и limits. Представим ситуацию, когда была выкачена новая версия приложения с новыми функциональными возможностями. Так случилось, что импортированная библиотека оказалось тяжелой или код местами недостаточно оптимальным, из-за чего приложение начало потреблять больше ресурсов. На тестовом контуре это изменение не проявилось, потому что обеспечить такой же уровень тестирования, как в production, сложно. Конечно, актуальные для приложения (до этого обновления) requests и limits уже были выставлены. И вот теперь приложение достигает лимита по памяти, приходит мистер ООМ и совершает над pod’ом насильственные действия. VPA может это предотвратить! Если посмотреть на ситуацию в таком срезе, то, казалось бы, это замечательный инструмент, который надо использовать всегда и везде. Но в реальности, конечно, все не так просто и важно понимать сопутствующие нюансы. Основная проблема, которая на текущий момент не решена, это изменение ресурсов через перезапуск pod’а. В каком-то ближайшем будущем VPA научится их патчить и без рестарта приложения, но сейчас не умеет. Допустим, это не страшно для хорошо написанного приложения, всегда готового к перекату: приложение разворачивается Deployment’ом с кучей реплик, настроенными PodAntiAffinity, PodDistruptionBudget, HorizontalPodAutoscaler… В таком случае, если VPA изменит ресурсы и аккуратно (по одному) перезапустит pod’ы, мы это переживем. Но бывает всё не так гладко: приложение не очень хорошо переживает перезапуск, или мы ограничены в репликах по причине малого количества узов, или у нас вообще StatefulSet…. В худшем сценарии придет нагрузка, у pod’ов вырастет потребление ресурсов, HPA начал масштабировать, а тут VPA: «О! Надо бы поднять ресурсы!» — и начнет перезапускать pod’ы. Нагрузка начнет перераспределяться по оставшимся, из-за чего pod может просто упасть, нагрузка уйдет на еще живые и в результате произойдет каскадное падение. Поэтому и важно понимать разные режимы работы VPA. Но начнем с рассмотрения самого простого — Off. Режим Off Данный режим занимается только подсчетами потребления ресурсов pod’ами и выносит рекомендации. Забегая вперед, даже скажу, что в подавляющем большинстве случаев мы у себя используем именно этот режим, именно он и является основной рекомендацией. Но вернемся к этому вопросу после рассмотрения нескольких примеров. Итак, вот простой манифест: apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata:
name: my-app-vpa
spec:
targetRef: apiVersion: "apps/v1" kind: Deployment name: my-app updatePolicy: updateMode: "Recreate" containerPolicies: - containerName: "*" minAllowed: cpu: 100m memory: 250Mi maxAllowed: cpu: 1 memory: 500Mi controlledResources: ["cpu", "memory"] controlledValues: RequestsAndLimits
Подробно разбирать каждый параметр в манифесте не будем: у нас есть перевод, в деталях рассказывающий о возможностях и особенностях VPA. Вкратце: здесь мы указываем, на какой контроллер нацелен наш VPA (targetRef) и какова политика обновления ресурсов, а также задаем нижнюю и верхнюю границы ресурсов, которыми VPA волен распоряжаться. Главное внимание — полю updateMode. В случае режима Recreate или Auto (пока не сделают упомянутую возможность патча) будет происходить пересоздание pod’а со всеми вытекающими последствиями, но мы этого не хотим и пока просто поменяем режим работы на Off: apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata:
name: my-app-vpa
spec:
targetRef: apiVersion: "apps/v1" kind: Deployment name: my-app updatePolicy: updateMode: "Off" # !!! resourcePolicy: containerPolicies: - containerName: "*" controlledResources: ["cpu", "memory"]
Теперь VPA начнет собирать метрики. Если спустя несколько минут посмотреть на него через describe, появятся рекомендации:
Recommendation: Container Recommendations: Container Name: nginx Lower Bound: Cpu: 25m Memory: 52428800 Target: Cpu: 25m Memory: 52428800 Uncapped Target: Cpu: 25m Memory: 52428800 Upper Bound: Cpu: 25m Memory: 52428800
Спустя пару дней (неделю, месяц, …) работы приложения рекомендации будут точнее. И тогда можно будет корректировать лимиты в манифесте приложения. Это позволит спастись от ООМ’ов в случае недостатка requests/limits и поможет сэкономить на инфраструктуре (если изначально был выделен слишком большой запас). А теперь — к некоторым сложностям. Другие режимы VPA Режим Initial выставляет ресурсы только при старте pod’а. Следовательно, если у вас неделю не было нагрузки, после чего случился выкат новой версии, VPA проставит низкие requests/limits. Если резко придет нагрузка, будут проблемы, поскольку запросы/лимиты установлены сильно ниже тех, что требуются при такой нагрузке. Этот режим может быть полезен, если у вас равномерная, линейно растущая нагрузка. В режиме Auto будут пересозданы pod’ы, поэтому самое сложное, чтобы приложение корректно обрабатывало процедуру рестарта. Если же оно не готово к корректному завершению работы (т.е. с правильной обработкой завершения текущих соединений и т.п.), то вы наверняка поймаете ошибки (5XX) «на ровном месте». Использование режима Auto со StatefulSet вообще редко оправдано: только представьте, что у вас VPA решит добавить ресурсы PostgreSQL в production… А вот в dev-окружении можно и поэкспериментировать, чтобы выяснить необходимые ресурсы для выставления их для production. Например,мы решили использовать VPA в режиме Initial, у нас есть Redis, а у него — параметр maxmemory. Скорее всего нам надо его изменить под свои нужды. Но загвоздка в том, что Redis’у нет дела до лимитов на уровне cgroups. Если случится так, что maxmemory будет равен 2GB, а в системе выставлен лимит в 1GB, то ничего хорошего не выйдет. Как же выставить maxmemory в то же значение, что и лимит? Выход есть! Можно пробросить значения, взяв их из VPA: apiVersion: apps/v1 kind: Deployment metadata:
name: redis labels: app: redis
spec:
replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:6.2.1 ports: - containerPort: 6379 resources: requests: memory: "100Mi" cpu: "256m" limits: memory: "100Mi" cpu: "256m" env: - name: MY_MEM_REQUEST valueFrom: resourceFieldRef: containerName: app resource: requests.memory - name: MY_MEM_LIMIT valueFrom: resourceFieldRef: containerName: app resource: limits.memory
Получив переменные окружения, можно извлечь нужный лимит по памяти и по-хорошему отнять от него условные 10%, оставив их на нужды приложения. А остальное выставить в качестве maxmemory. Вероятно, придется также придумывать что-то с init-контейнером, который sed’ает конфиг Redis, потому что базовый образ Redis не позволяет ENV-переменной пробросить maxmemory. Тем не менее, решение будет работать. Напоследок, упомяну неприятный момент, связанный с тем, что VPA «выбрасывает» pod’ы DaemonSet сразу, скопом. Со своей стороны мы начали работу над патчем, который исправляет эту ситуацию. Итоговые рекомендации по VPA Всем приложениям будет полезен режим Off. В dev можно экспериментировать с Auto и Initial. В production стоит применять только тогда, когда вы уже собрали/опробовали рекомендации и точно понимаете, что делаете и зачем. А вот когда VPA научится патчить ресурсы без перезапуска… тогда заживем! Важно также помнить, что существует ряд нюансов при совместном использовании HPA и VPA. Например, эти механизмы нельзя использовать совместно, опираясь на одни и те же базовые метрики (CPU или Memory), потому что при достижении порога по ним VPA начнет поднимать ресурсы, а HPA начнет добавлять реплики. Как результат, нагрузка резко упадет и процесс пойдет в обратную сторону — может возникнуть «flapping». Существующие ограничения более подробно описаны в документации. Благодарю @Rinck за это полезное дополнение! Заключение На этом мы завершаем обзор лучших практик в контексте деплоя HA-приложений в Kubernetes. Будем рады любой обратной связи и пожеланиям на будущие подобные материалы.
Vertical Pod Autoscaler should not be used with the Horizontal Pod Autoscaler (HPA) on CPU or memory at this moment. However, you can use VPA with HPA on custom and external metrics. Иначе у меня, как у читателя, складывается впечатление, что их можно и нужно использовать вместе :)