OpenShift широко используется в мире в качестве платформы для критически важных корпоративных приложений. От таких приложений ждут, что они будут работать в режиме высокой доступности, выдавая типичные для отрасли «пять девяток» и обеспечивая непрерывное обслуживание конечных пользователей и клиентов. OpenShift предлагает целый ряд технологий развертывания в режиме высокой доступности, помогающих не допускать простоя приложения, даже когда его экземпляры или нижележащая ИТ-инфраструктура деградируют или перезапускаются. Сегодня мы рассмотрим девять лучших практик обеспечения высокой доступности приложений на платформе OpenShift.
spec:
replicas: 3
По умолчанию применяется стратегия Rolling. По ней pod’ы старой версии приложения меняются на pod’ы новой версии последовательно, одним за другим. Причем система следит, чтобы всегда оставался хотя бы один работающий pod.
Что касается стратегии Recreate, то здесь сначала убираются все старые pod’ы, и лишь затем развертываются новые. Понятно, что при обновлении будет иметь место определенный простой. Он может не представлять проблему, если регламент предусматривает окна обслуживания или перебои в работе приложения. Однако для критически важных приложений с высокими уровнями SLA и повышенными требованиями к доступности следует использовать стратегию развертывания Rolling.
strategy
type: RollingUpdate
По умолчанию период корректной обработки сигнала SIGTERM составляет 30 секунд. Если вы считает, что на это надо больше времени, то можно подкрутить параметр развертывания terminationGracePeriod.
spec:
...
spec:
terminationGracePeriodSeconds: 30
containers:
...
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
Приложение принимает трафик лишь тогда, когда проверка готовности заканчивается положительно и приложение получает статус Ready. Если проверка еще не закончилась или результат отрицательный, то приложение не принимает трафик и остается вне пула.
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
Зонд readinessProbes мы уже показали выше, а пример с initContainer, который ожидает готовности БД, выглядит так:
initContainers:
- name: wait-postgres
image: postgres:12.1-alpine
command:
- sh
- -c
- |
until (pg_isready -h example.org -p 5432 -U postgres); do
sleep 1
Done
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: my-pdb
spec:
selector:
matchLabels:
foo: bar
minAvailable: 2
HPA масштабирует число pod’ов по следующей формуле:
X = N * (c/t)
Здесь X – это желаемое число реплик, N – текущее число реплик, c – текущее значение метрики, t – целевое значение метрики. Подробнее алгоритм описывается в документации.
Для примера рассмотрим HPA по загрузке ЦП, который варьирует число pod’ов в диапазоне от 3 до 10 для поддержания средней загрузки ЦП на уровне 50%:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
Еще пример: здесь HPA по использованию памяти варьирует число pod’ов от 3 до 10, чтобы средний расход памяти составлял 500Mi:
<same as previous example>
metrics:
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 500Mi
Примечание. В этих примерах используется API версии autoscaling/v2beta2, последней на момент написания статьи. Для метрик использования ЦП можно использовать либо API версии autoscaling/v1, либо autoscaling/v2beta2. Для метрик использования памяти должна применяться только версия autoscaling/v2beta2.
Как и HPA, VPA автоматически рассчитывает целевые значения путем мониторинга использования ресурсов. Однако, в отличие от HPA, он вначале выселяет pod с узла, чтобы обновить его согласно новым лимитам на ресурсы. VPA учитывает любые регулирующие политики PDB-квот, чтобы гарантировать отсутствие нарушений в работе приложения при выселении pod’ов. И наконец, затем, когда выполняется повторное развертывание рабочей нагрузки, VPA’шный веб-хук mutating admission перезаписывает ресурсы pod’а с оптимизированными лимитами на ресурсы и запросы, прежде чем pod’ы пойдут на узлы.
Рассмотрим пример VPA для автоматического режима, который назначает запросы на ресурсы при создании pod’а и обновляет существующие pod’ы. Причем, он вначале завершает их, если запрошенные ресурсы сильно отличаются от новых рекомендаций:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: vpa-recommender
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: frontend
updatePolicy:
updateMode: "Auto"
Помимо автоматического есть другие режимы VPA, подробнее см. документацию.
Функция Pod Topology Spread Constraints позволяет равномерно распределять реплики pod’ов по кластеру, вот как она настраивается:
kind: Pod
apiVersion: v1
metadata:
name: my-pod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- image: "docker.io/ocpqe/hello-pod"
name: hello-pod
В этом примере заданы два ограничения pod topology spread. Первое используется для того, чтобы pod’ы распределялись между узлами путем ссылки на топологическую метку "node" (задается через параметр topologyKey). Чтобы это работало эффективно, каждый узел в кластере должен иметь метку "node" с уникальным значением. Параметр maxSkew задает максимально допустимую разницу в количестве pod’ов между любыми двумя узлами.
Второе ограничение pod topology spread в этом примере используется для равномерного распределения pod’ов между зонами доступности. Чтобы оно работало эффективно, каждый узел в кластере должен иметь метку "zone", в значении которой прописывается зона доступности, к которой отнесен этот узел. Используя два этих ограничения, можно обеспечить равномерное распределение pod’ов как между зонами доступности, так и между узлами в этих зонах.
Такое переключение трафика можно организовать средствами маршрутов OpenShift Route. Вот как, например, выглядит маршрут на «синюю» версию сервиса:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-app
spec:
port:
targetPort: 8080
to:
kind: Service
name: my-app-blue
Чтобы переключить трафик на «зеленую» версию после ее развертывания, в маршруте достаточно изменить секцию "to":
<same as previous>
kind: Service
name: my-app-green
Недостаток сине-зеленой схемы состоит в том, что она полностью переключает весь трафик: запросы идут либо туда, либо туда. А иногда надо, чтобы часть трафика уходила на старую версию, а часть – на новую, и желательно иметь возможность варьировать эти части. Именно эту задачу и решает канареечное развертывание.
Вот как выглядит маршрут OpenShift, переводящий 10% трафика на новую («зеленую») версию приложения:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-app
spec:
port:
targetPort: 8080
to:
kind: Service
name: my-app-blue
weight: 90
alternateBackends:
- kind: Service
name: my-app-green
weight: 10
Процент трафика, который уходит на заданный сервис, рассчитывается по формуле «weight / сумма всех значений weight». В нашем примере сервис my-app-blue («синяя» версия) будет обрабатывать 90% трафика, так как 90/(90+100) = 0,9. А на долю сервиса my-app-green («зеленая» версия) останется 10%. Таким образом, канареечное развертывание позволяет постепенно наращивать долю новой версии в обработке клиентских или пользовательских запросов, мониторя исправность приложения.
1. Множественные реплики
Когда приложение развернуто и работает в виде нескольких экземпляров (pod’ов), то при исчезновении одного из них оно будет работать и дальше. Если же pod только один, то после его исчезновения приложение просто станет недоступно (хотя обычно и ненадолго). В целом, когда у вас есть только один экземпляр приложения, то любое его нештатное завершение – это простой. Поэтому у приложения должно быть минимум две одновременно работающие реплики. А встроенный балансировщик нагрузки будет распределять сетевой трафик между pod’ам развертывания.Рекомендации
Мы рекомендуем сразу задавать несколько реплик при развертывании:spec:
replicas: 3
2. Стратегия обновления приложения
Есть две стратегии развертывания: «накатом» (Rolling) и «с нуля» (Recreate).По умолчанию применяется стратегия Rolling. По ней pod’ы старой версии приложения меняются на pod’ы новой версии последовательно, одним за другим. Причем система следит, чтобы всегда оставался хотя бы один работающий pod.
Что касается стратегии Recreate, то здесь сначала убираются все старые pod’ы, и лишь затем развертываются новые. Понятно, что при обновлении будет иметь место определенный простой. Он может не представлять проблему, если регламент предусматривает окна обслуживания или перебои в работе приложения. Однако для критически важных приложений с высокими уровнями SLA и повышенными требованиями к доступности следует использовать стратегию развертывания Rolling.
Рекомендации
Мы рекомендуем использовать RollingUpdate везде, где это возможно:strategy
type: RollingUpdate
3. Корректная обработка сигнала SIGTERM
Когда перезапускает развертывание OpenShift Container Platform или удаляется pod, Kubernetes посылает сигнал SIGTERM всем контейнерам этого pod’а, чтобы они могли корректно завершить свою работу. Обычно корректная отработка SIGTERM сводится к завершению обработки клиентских запросов или к сохранению состояния.По умолчанию период корректной обработки сигнала SIGTERM составляет 30 секунд. Если вы считает, что на это надо больше времени, то можно подкрутить параметр развертывания terminationGracePeriod.
Рекомендации
Приложения должны обрабатывать сигнал SIGTERM, чтобы корректно завершать свою работу. При необходимости настройте должным образом параметр terminationGracePeriod:spec:
...
spec:
terminationGracePeriodSeconds: 30
containers:
...
4: Зонды (probes)
Зонды (проверки health check) играют важную роль в мониторинге исправности приложений. Зонды готовности (readiness probes) определяют, готово ли приложение к приему трафика. Зонды активности (liveness probes) проверяют, отвечает ли приложение, или его пора перезапустить.Зонды активности (liveness probes)
Зонды активности используются для того, чтобы перезапустить приложение, если оно сломалось. Это функция самовосстановления в Kubernetes/OpenShift, и она важна для того, чтобы приложения имели возможность попробовать восстановиться автоматически.livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
Зонды готовности (readiness probe)
Зонды готовности используются для того, чтобы вывести приложение из пула балансировки нагрузки, если оно перешло в нежелательное состояние и стало неспособно обслуживать трафик. Эти зонды предотвращают подключение клиентов к приложению, когда его надо временно вывести из пула балансировки для восстановления (но не для перезапуска).Приложение принимает трафик лишь тогда, когда проверка готовности заканчивается положительно и приложение получает статус Ready. Если проверка еще не закончилась или результат отрицательный, то приложение не принимает трафик и остается вне пула.
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Зонды запуска (startup probe)
Некоторые приложения могут иметь большое время запуска. Например, если в ходе инициализации они должны выполнить много задач, или зависят от внешнего сервиса, который может быть не готов на момент запуска приложения. Это затрудняет настройку зондов liveness и readiness, поскольку сложно определить, сколько времени приложению потребуется для запуска. И здесь помогут зонды запуска (startup probe), которые собственно и детектируют сам момент запуска приложения. После того, как такой зонд рапортует об успешном запуске приложения, в дело вступают зонды активности и готовности:startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
Рекомендации
Мы рекомендуем использовать зонды Liveness и Readiness, чтобы постоянно контролировать доступность приложения и его способность обрабатывать входящий трафик. Также можно использовать startup-зонды для определения момента запуска приложения.5. Контроль готовности внешних зависимостей
Когда приложение запускается, оно не должно аварийно завершаться, если вдруг отсутствует какая-то из его зависимостей, например, не отвечает база данных. Кроме того, хотелось бы регулярно контролировать исправность этих зависимостей не только при запуске, но и в ходе работы приложения. Для этого можно использовать initContainers/startupProbe, чтобы проверять внешние зависимости перед запуском основного контейнера. А когда приложение уже запущено и работает, можно использовать readinessProbe для главного контейнера, чтобы он трактовался как Ready, только если успешно подключен к исправным внешним зависимостям.Зонд readinessProbes мы уже показали выше, а пример с initContainer, который ожидает готовности БД, выглядит так:
initContainers:
- name: wait-postgres
image: postgres:12.1-alpine
command:
- sh
- -c
- |
until (pg_isready -h example.org -p 5432 -U postgres); do
sleep 1
Done
Рекомендации
Мы рекомендуем использовать initContainer или startupProbe, чтобы отложить запуск приложения до тех пор, пока не будут исправны его зависимости. Во время работы приложения используйте readinessProbe, чтобы следить за состоянием зависимостей.6: Квоты PodDisruptionBudget (PDB)
Квоты Pod Disruption Budge (PDB) ограничивают кластер в возможности отключать реплики pod’ов при проведении технического обслуживания. Иначе говоря, это минимальное число реплик, которые всегда должны оставаться работающими. При расселении узла командой drain все pod’ы на нем удаляются и затем повторно диспетчеризуются. При высокой нагрузке расселение может негативно влиять на доступность. Для того, чтобы поддерживать доступность во время обслуживания кластера, и служат квоты PDB.Рекомендации
PDB рекомендуется применять для критически важных приложений в режиме продакшен. С помощью этих квот можно вводить максимальное ограничение на количество pod’ов приложения, которые разрешено отключать на период его обслуживания.apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: my-pdb
spec:
selector:
matchLabels:
foo: bar
minAvailable: 2
7. Автомасштабирование
В OpenShift есть два инструмента автомасштабирования pod’ов приложений, горизонтальный и вертикальный:- Horizontal pod autoscaler (HPA)
- Vertical pod autoscaler (VPA)
Horizontal pod autoscaler (HPA)
Функция HPA автоматически увеличивает или уменьшает количество pod’ов на основании собранных данных метрик. С ее помощью можно поддерживать доступность приложения на требуемом уровне и улучшать отклик при внезапном всплеске трафика.HPA масштабирует число pod’ов по следующей формуле:
X = N * (c/t)
Здесь X – это желаемое число реплик, N – текущее число реплик, c – текущее значение метрики, t – целевое значение метрики. Подробнее алгоритм описывается в документации.
Для примера рассмотрим HPA по загрузке ЦП, который варьирует число pod’ов в диапазоне от 3 до 10 для поддержания средней загрузки ЦП на уровне 50%:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
Еще пример: здесь HPA по использованию памяти варьирует число pod’ов от 3 до 10, чтобы средний расход памяти составлял 500Mi:
<same as previous example>
metrics:
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 500Mi
Примечание. В этих примерах используется API версии autoscaling/v2beta2, последней на момент написания статьи. Для метрик использования ЦП можно использовать либо API версии autoscaling/v1, либо autoscaling/v2beta2. Для метрик использования памяти должна применяться только версия autoscaling/v2beta2.
Vertical pod autoscaler (VPA)
Если горизонтальный автоскейлер HPA увеличивает-уменьшает количество pod’ов, то его вертикальный собрат VPA варьирует объем ресурсов, выделяемых pod’у. VPA оптимизирует значения запросов на ресурсы процессора и памяти, позволяя максимально повысить эффективность использования вычислительных мощностей кластера.Как и HPA, VPA автоматически рассчитывает целевые значения путем мониторинга использования ресурсов. Однако, в отличие от HPA, он вначале выселяет pod с узла, чтобы обновить его согласно новым лимитам на ресурсы. VPA учитывает любые регулирующие политики PDB-квот, чтобы гарантировать отсутствие нарушений в работе приложения при выселении pod’ов. И наконец, затем, когда выполняется повторное развертывание рабочей нагрузки, VPA’шный веб-хук mutating admission перезаписывает ресурсы pod’а с оптимизированными лимитами на ресурсы и запросы, прежде чем pod’ы пойдут на узлы.
Рассмотрим пример VPA для автоматического режима, который назначает запросы на ресурсы при создании pod’а и обновляет существующие pod’ы. Причем, он вначале завершает их, если запрошенные ресурсы сильно отличаются от новых рекомендаций:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: vpa-recommender
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: frontend
updatePolicy:
updateMode: "Auto"
Помимо автоматического есть другие режимы VPA, подробнее см. документацию.
Что важно знать, прежде чем применять VPA:
- Чтобы VPA мог автоматически удалять pod’ы, pod’ов должно быть минимум 2.
- Чтобы VPA мог рекомендовать ресурсы и применять рекомендации к новым pod’ам, pod’ы уже должны быть запущены в проекте.
- VPA реагирует на большинство событий out-of-memory, но не на все.
Рекомендации
- Избегайте использовать HPA и VPA в тандеме, если только вы не настроите HPA на использование пользовательских или внешних метрик.
- В продакшене используйте VPA в режиме рекомендаций (Recommendation mode). Это поможет выяснить оптимальные значения запросов на ресурсы и как они меняются со временем.
- Для парирования внезапных всплесков нагрузки используйте HPA, а не VPA, поскольку VPA выдает рекомендации на основании более продолжительной выборки данных.
8. Ограничения Pod Topology Spread Constraints
Одна из основных задач OpenShift заключается в том, чтобы автоматически распределять pod’ы по узлам кластера. Однако если все реплики уходят в один и тот же домен отказа (это может быть узел, серверная стойка или зона доступности) и в какой-то момент этом домен становится неисправен, то случится простой приложения. И этот простой устранится только тогда, когда реплики будут повторно развернуты в другом домене. Предотвратить такой вариант событий позволяют ограничения Pod Topology Spread Constraints.Функция Pod Topology Spread Constraints позволяет равномерно распределять реплики pod’ов по кластеру, вот как она настраивается:
kind: Pod
apiVersion: v1
metadata:
name: my-pod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- image: "docker.io/ocpqe/hello-pod"
name: hello-pod
В этом примере заданы два ограничения pod topology spread. Первое используется для того, чтобы pod’ы распределялись между узлами путем ссылки на топологическую метку "node" (задается через параметр topologyKey). Чтобы это работало эффективно, каждый узел в кластере должен иметь метку "node" с уникальным значением. Параметр maxSkew задает максимально допустимую разницу в количестве pod’ов между любыми двумя узлами.
Второе ограничение pod topology spread в этом примере используется для равномерного распределения pod’ов между зонами доступности. Чтобы оно работало эффективно, каждый узел в кластере должен иметь метку "zone", в значении которой прописывается зона доступности, к которой отнесен этот узел. Используя два этих ограничения, можно обеспечить равномерное распределение pod’ов как между зонами доступности, так и между узлами в этих зонах.
Рекомендации
Используйте Pod Topology Spread Constraints, чтобы разносить pod’ы по разным доменам отказа. Для надежного распределения рабочих нагрузок узлы кластера должны иметь правильные метки.9. Развертывание приложений по сине-зеленому методу и канареечным способом
На большинстве предприятий развертывание приложений и функций должно выполняться так, чтобы не прерывать работу пользователей или процессов, которые с ними взаимодействуют. Поэтому в корпоративной ИТ-практике широко применяются сине-зеленая и канареечная схемы развертывания. Рассмотрим, как организовать их в рамках OpenShift.Сине-зеленое развертывание (Blue/Green)
Согласно этой стратегии, новая версия приложения («зеленая») развертывается рядом со старой («синяя»), а затем трафик разом переключается с «синей» версии на «зеленую». Если с новой версией возникают проблемы, то трафик легко переключается обратно на старуюТакое переключение трафика можно организовать средствами маршрутов OpenShift Route. Вот как, например, выглядит маршрут на «синюю» версию сервиса:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-app
spec:
port:
targetPort: 8080
to:
kind: Service
name: my-app-blue
Чтобы переключить трафик на «зеленую» версию после ее развертывания, в маршруте достаточно изменить секцию "to":
<same as previous>
kind: Service
name: my-app-green
Недостаток сине-зеленой схемы состоит в том, что она полностью переключает весь трафик: запросы идут либо туда, либо туда. А иногда надо, чтобы часть трафика уходила на старую версию, а часть – на новую, и желательно иметь возможность варьировать эти части. Именно эту задачу и решает канареечное развертывание.
Канареечное развертывание (Canary Deployment)
Канареечное развертывание позволяет перенаправить на новую версию приложения лишь часть трафика, к примеру, 10%. А оставшиеся 90% будут по-прежнему обрабатываться старой версией. Если новая версия работает нормально и в ней не обнаруживаются ошибки, то ее долю в обработке трафика можно постепенно увеличить до 100%.Вот как выглядит маршрут OpenShift, переводящий 10% трафика на новую («зеленую») версию приложения:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-app
spec:
port:
targetPort: 8080
to:
kind: Service
name: my-app-blue
weight: 90
alternateBackends:
- kind: Service
name: my-app-green
weight: 10
Процент трафика, который уходит на заданный сервис, рассчитывается по формуле «weight / сумма всех значений weight». В нашем примере сервис my-app-blue («синяя» версия) будет обрабатывать 90% трафика, так как 90/(90+100) = 0,9. А на долю сервиса my-app-green («зеленая» версия) останется 10%. Таким образом, канареечное развертывание позволяет постепенно наращивать долю новой версии в обработке клиентских или пользовательских запросов, мониторя исправность приложения.
Рекомендации
Используйте сине-зеленую или канареечную стратегию, чтобы предотвратить нарушения в штатной работе людей и сервисов при развертывании новых версий приложения. Используйте канареечный метод, если хотите гибко и постепенно переводить пользователей на новую версию, не отрубая старую одним махом.9 лучших практик развертывания приложений высокой доступности на платформе OpenShift
OpenShift широко используется в мире в качестве платформы для критически важных корпоративных приложений. От таких приложений ждут, что они будут работать в режиме высокой доступности, выдавая...
habr.com