Отказоустойчивость информационных систем необходима для обеспечения непрерывности работы системы и минимизации возможности потери данных в случае сбоев или отказов в работе оборудования. Это особенно важно для критических для бизнеса систем.
Мы начали использовать геораспределенные кластеры и повысили надежность сервисов. В статье опишем, какими инструментами это делали, какие сложности возникали и какие получили результаты.
Привет, Хабр, меня зовут Артур Мечетин, и в этой статье мы со Станиславом Столбовым из Byndyusoft расскажем о том, как повысили стабильность приложений в К8s кластерах с высокой критичностью для бизнеса.
Сделали мы это на продукте «Система управления складом»: приемка, хранение, сборка и отгрузка. Данные процессы поддерживают разные команды с единым процессом CI/CD, но разными зонами ответственности. Мы стремимся улучшать производительность систем и отказоустойчивость, потому что сталкивались с проблемами доступности наших продуктовых решений из-за ошибок технических специалистов. Вследствие этого задались вопросами повышения качества инфраструктуры. Одним из решений стало использование нескольких K8s кластеров с применением Istio-Multicluster.
—
Микросервисы, которые мы создаем, работают в K8s кластере, интегрированы друг с другом, с DBaaS (PostgreSQL, Kafka, Mongo, Rеdis и др.) и приложениями других команд. Всем важно сохранять высокий SLI. 5-минутная остановка сервиса грозит многомиллионными потерями для бизнеса.
И наверняка многие уже считают, что защищены, т. к. отказоустойчивость в Kubernetes достигается за счет использования репликационных контроллеров, которые обеспечивают автоматическое масштабирование и восстановление при сбоях. Контроллеры отслеживают состояние подов и гарантируют надежность. Можно представить себе K8s кластер как ультрасовременный небоскреб, в котором есть все для автономного функционирования.
Но что если на 45 этаже произошел пожар? Паника начинается на соседних этажах, а потом может распространиться и на другие. А что если на водоканале отключили воду во всем микрорайоне? Точно так же проблемы могут происходить и с K8s кластерами. Как только происходит что-то непредвиденное, пользователи становятся недовольны и это влияет на весь бизнес. Проблемы случаются, ни одна IT-компания не гарантирует SLA 100%. Даже такой гигант, как Yandex Cloud, гарантирует SLA (Service Level Agreement, уровень обслуживания) на уровне 99,9% для облачных сервисов (до 8h 41m 38s в год).
Несколько раз непредвиденные обстоятельства происходили и в нашей инфраструктуре. Это побудило нас исследовать технологию объединения K8s кластеров Istio Multicluster.
Ссылки:
Задачу поделили на несколько этапов.
Подготовка proof of concept и проверка гипотез
Доработка CI/CD
Подготовка среды
Дистрибуция сервисов
Базовая модель работы сетевой архитектуры на проекте с несколькими микросервисами выглядела следующим образом.
Балансировщик принимает от пользователей запросы и направляет в кластер. Далее сайдкар Envoy (часть Istio, запущенный как Sidecar-контейнер в каждом Pod’е) для Ingress-Controller перехватывает запрос, обогащая полезными штуками (Mutual TLS, observability, Circuit Breaker), направляет в сторону Pod’а приложения. Решение, куда направить запрос, принимается на основе информации, которую получает Envoy от Istio-Сontrol при любом изменении в контроллере. (Подробнее о принципах работы Istio можно почитать в статье https://habr.com/ru/companies/oleg-bunin/articles/726958/)
Proof-of-Concept
Два имеющихся кластера в разных ДЦ объединяли в Istio Multicluster.
На этом этапе выяснилось, что только несколько человек обладали экспертизой по Istio Multicluster, но готового рабочего решения для Production в компании не было, поэтому мы стали первопроходцами. Последовательность настройки Istio Multicluster для двух K8s кластеров оказалась следующей.
Включили модуль istio для Deckhouse.
apiVersion: deckhouse.io/v1alpha1 kind: ModuleConfig metadata: name: istio spec: version: 2 enabled: true
На Namespace приложения в двух кластерах установили Label
istio-injection: enabled
, чтобы Istio подключал свои сайдкары для каждого Pod в рамках Namespace.Включили Istio Multicluster в модуле Istio для Deckhouse с помощью параметра istio.spec.settings.multicluster.enabled = true.
Для Ingress-Controller’а включили Istio Sidecar с помощью параметра
ingressnginxcontroller.spec.enableIstioSidecar = true.Для двух кластеров в модуль kube-dns добавили общий clusterDomainAliases.
Было:apiVersion: deckhouse.io/v1alpha1 kind: ModuleConfig metadata: name: kube-dns spec: version: 1 enabled: true settings: clusterDomainAliases: - cluster.local
Стало:
apiVersion: deckhouse.io/v1alpha1 kind: ModuleConfig metadata: name: kube-dns spec: version: 1 enabled: true settings: clusterDomainAliases: - cluster.local - alpha.p.mesh
Для двух кластеров в модуль control-plane-manager добавили в общий certSAN.
Было:apiVersion: deckhouse.io/v1alpha1 kind: ModuleConfig metadata: name: control-plane-manager spec: version: 1 enabled: true settings: apiserver: certSANs: - kubernetes.default.svc.cluster.local
Стало:
apiVersion: deckhouse.io/v1alpha1 kind: ModuleConfig metadata: name: control-plane-manager spec: version: 1 enabled: true settings: apiserver: certSANs: - kubernetes.default.svc.cluster.local - kubernetes.default.svc.alpha.p.mesh
Важно: после операции API-сервер начинает перезапускаться до тех пор, пока не применится новая конфигурация.
Установили общий кластерный домен alpha.p.mesh для обоих кластеров с помощью dhctl.
Пример запуска:kubectl -n d8-system exec -ti deploy/terraform-auto-converger -- dhctl config edit cluster-configuration
Важно: после операции инвалидируются auth-токены, поэтому кластерные компоненты начнут перезапускаться, пока не получат новый. Некоторые из них могут «застрять», убедитесь, что все системные компоненты были успешно перезапущены.
Связали кластеры с помощью CR IstioMulticluster.
Далее мы написали Helm templates для этого в репозитории с приложениями Argo CD, и это позволило нам переиспользовать конфиг для других кластеров.
Микросервис, работающий на одном из кластеров, становится доступным на другом кластере в рамках одинаковых неймспейсов. Нужна настройка внешнего балансировщика на работу с двумя разными кластерами, и PoC готов к демо.
Схема работы
В случае если микросервис в зоне A недоступен, система Istio перенаправляет запросы на работающий кластер в зоне B. Ни один запрос не пропадает.
До использования Multicluster рабочий pipeline CI/CD умел деплоить только в один кластер, нужно было гарантировать доставку во второй. Сейчас сервисы последовательно заливаются в два кластера в разных дата-центрах, предусмотрели роллбэк в случае fail. Для ускорения и более качественного CD-процесса деплой должен быть параллельным + Canary deploy + Blue/Green, но это пока не реализовано.
Подготовка инфраструктуры с имеющимися Argo CD, permissions, RBAC, Nginx, Prometheus rules: требуют много согласований, т.к. много внешних интеграций/зависимостей. Все межсервисные интеграции у нас решаются через api gateway (Kong), команда сама управляет api своих приложений. Сложно было объяснять коллегам, что у нас что-то меняется (т.к. пользователей много), поэтому большую часть работы проводили бесшовно. Юзеры=разработчики заливали свои микросервисы, не обращая внимания, что сервисы параллельно теперь живут в новом кластере до какого-то времени.
Готовность проекта к переключению в мультикластерность
Итак, команда разработчиков обучена, все микросервисы синхронизированы между кластерами, подготовлены новые точки доступа в приложения, настроены алерты в новом кластере. Пользователи системы должны начать использовать новые урлы для работы с системой. Тут тоже предусмотрен CI/CD, который обновляет приложения на терминалах (Android-устройства), это сильно помогло.
Предпоследний этап: пользователи начали работать через новый урл. Успех. Всем спасибо.
Стресс-тест: останавливаем приложения в одном из кластеров, наблюдаем за работой приложений.
Ценно: выход из строя одного кластера не влияет на систему, пользователи спокойны, бизнес работает. Теперь не зависим от мощностей одного ДЦ, т. к. можем легко заказать сколько угодно в другом. Технические работы на кластере можно проводить в любое время.
Планы: логи сервисов живут в кластере, как и tracing, поэтому сложнее стало понять проблему. Но это мы еще исправим. Алерты в двух кластерах поддерживать стало сложнее, но это поправимо, будем выносить все логи и трейсы в единое место. После обкатки технологии и устранения недостатков попробуем распространить на другие проекты.
Что хочется, но не получается: Istio — ценный инструмент, по умолчанию предоставляет много полезного. Но observability недостаточно для настроек алертинга. Хотелось использовать Istio для подсчета SLO, т. к., считаю, это самый правильный вариант. Но в текущем варианте метрик от Istio не хватает: нет деления по URI, по методам.
Оверхед: инфраструктурных ресурсов требуется больше, за это приходится платить. У всего есть своя цена. ИБ-долг рассчитывается на каждую VM с приложением, поэтому количество долга выросло, хотя это мотивирует на работу с уязвимостями в приложениях.
Есть несколько важных моментов, которые рекомендую предусмотреть при использовании Istio-Multicluster.
Настраивайте Locality Failover, это поможет избежать большого трафика между кластерами.
При большом числе микросервисов и кластере контролируйте, какие из них должны использовать Istio. Мы делаем это лейблом на namespace, но также можно внутри неймспейса точечно отключить/включить с помощью лейбла sidecar.istio.io/inject на pod.
Ограничьте рассылку информации от Istio к envoy только на нужные namespace через CRD Sidecar, сводка по умолчанию отправляется на все pod’ы envoy и может перегрузить сеть и controlplane.
Запуск Istio контейнера должен происходить до запуска проверок основного пода.
Deckhouse featureannotations: proxy.istio.io/config: |- holdApplicationUntilProxyStarts: true
Остановка Istio контейнера должна происходить после остановки основного пода
Deckhouse featureannotations: inject.istio.io/templates: sidecar,d8-hold-istio-proxy-termination-until-application-stops
Какие трудности возникали
При попытке настроить PoC два дня пытались понять, в чем проблема, когда сервисы упорно не хотели видеть друг друга. Дебаг показал, что в одном из кластеров версия Istio 1.13, в другом — 1.16. После обновления до 1.16 проблема ушла.
Некоторые сервисы долго обрабатывают sigterm, Istio останавливается сразу: нужно предусмотреть, чтобы Istio sidecar ждал остановки основного pod’a.
На некоторых сервисах не настроили Locality Failover, и при сетевом сбое получили недоступность 50% запросов на несколько минут.
При включенном Locality Failover трафик распределялся неравномерно между подами в одном кластере ввиду особенностей приоритетов балансировки Istio и Envoy. В нашем случае ноды в одном кластере находились в разных зонах. Поэтому Istio считал, что поды одного приложения не локальны друг к другу и отправлял больше запросов к 2 подам в одной зоне, пересылая немного запросов на 3-й pod.
Мы решили это сменой приоритета на topology.istio.io/network, единого для всего кластера. Чуть подробнее про настройку приоритетов можно прочитать тут.
Пример конфигурации:apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: example spec: host: example.host trafficPolicy: loadBalancer: localityLbSetting: enabled: true failoverPriority: - "topology.istio.io/network"
Ресурсы вне K8s
Postgresql также разнесли по разным ЦОД, использовали кластер Patroni + etcd.
Kafka, mongo пока оставлены без внимания, т. к. менее приоритетны, но, скорее всего, дойдет очередь и до них.
Итог
Мы поняли, что Istio — это интересный развивающийся инструмент, который при правильном и умелом использовании дает много полезного. Можно смело использовать для опытов с мультикластерами K8s, если есть время на проведение тестирования, и только после этого включать на продуктивной среде. Мы не рассматривали другие инструменты, поэтому, если кто-то считает, что есть более оптимальный выбор, будем рады увидеть в комментариях ваш опыт.
Не бойтесь экспериментов, пробуйте, творите, рискуйте и делитесь любыми результатами. Только так можно добиться успеха.