Отказоустойчивость информационных систем необходима для обеспечения непрерывности работы системы и минимизации возможности потери данных в случае сбоев или отказов в работе оборудования. Это особенно важно для критических для бизнеса систем. 

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

Привет, Хабр, меня зовут Артур Мечетин, и в этой статье мы со Станиславом Столбовым из 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.

Ссылки:

https://istio.io/

Задачи, которые решает Istio

Задачу поделили на несколько этапов.

  • Подготовка 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 кластеров оказалась следующей.

  1. Включили модуль istio для Deckhouse.

    apiVersion: deckhouse.io/v1alpha1
    kind: ModuleConfig
    metadata:
      name: istio
    spec:
      version: 2
      enabled: true
  2. На Namespace приложения в двух кластерах установили Label istio-injection: enabled, чтобы Istio подключал свои сайдкары для каждого Pod в рамках Namespace.

  3. Включили Istio Multicluster в модуле Istio для Deckhouse с помощью параметра istio.spec.settings.multicluster.enabled = true.

  4. Для Ingress-Controller’а включили Istio Sidecar с помощью параметра
    ingressnginxcontroller.spec.enableIstioSidecar = true.

  5. Для двух кластеров в модуль 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
  1. Для двух кластеров в модуль 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-сервер начинает перезапускаться до тех пор, пока не применится новая конфигурация.

  1. Установили общий кластерный домен alpha.p.mesh для обоих кластеров с помощью dhctl

    Пример запуска:

    kubectl -n d8-system exec -ti deploy/terraform-auto-converger -- dhctl config edit cluster-configuration

Важно: после операции инвалидируются auth-токены, поэтому кластерные компоненты начнут перезапускаться, пока не получат новый. Некоторые из них могут «застрять», убедитесь, что все системные компоненты были успешно перезапущены.

  1. Связали кластеры с помощью 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 feature 

    annotations:
      proxy.istio.io/config: |-
        holdApplicationUntilProxyStarts: true

  • Остановка Istio контейнера должна происходить после остановки основного пода
    Deckhouse feature

    annotations:
      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, если есть время на проведение тестирования, и только после этого включать на продуктивной среде. Мы не рассматривали другие инструменты, поэтому, если кто-то считает, что есть более оптимальный выбор, будем рады увидеть в комментариях ваш опыт.  

Не бойтесь экспериментов, пробуйте, творите, рискуйте и делитесь любыми результатами. Только так можно добиться успеха.

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


  1. virrus
    12.12.2023 07:24

    С одной стороны: Технические работы на кластере можно проводить в любое время.

    С другой стороны: Дебаг показал, что в одном из кластеров версия Istio 1.13, в другом — 1.16. После обновления до 1.16 проблема ушла.

    То есть сейчас ваша инфраструктура зависит от одного сервиса, который не умеет в обратную совместимость в рамках minor версии? Как обновлять такой сервис?


    1. archi144 Автор
      12.12.2023 07:24

      Хороший вопрос.
      Благодаря тому, что Deckhouse предоставляет возможность использовать несколько версий Istio одновременно, нет проблем зафиксировать версию Istio для сайдкаров с помощью лейбла на namespace istio.io/rev=vXxYZ, выкатить на тест новую версию и довести её до продакшена.
      Подробнее про это можно почитать в документации к Deckhouse.


      1. virrus
        12.12.2023 07:24

        В этой документации более важно то, что заявлена поддержка совместимости двух минорных версий. Тогда несовместимость 1.13 и 1.16 это не баг, а фича, и обновлять ДЦ независимо все же можно.


    1. sastolbov
      12.12.2023 07:24

      Критичных сервисов в кластере много, но процесс обновлений выстроен и отработан: есть тестовые стенды, препрод стенды, и IaC. Сначала обновление обкатывается на не прод средах, только потом катится на прод в установленный регламентный период.

      Обратную 100% совместимость, к сожалению, никто не гарантирует.


      1. virrus
        12.12.2023 07:24

        Софт, который используется для географического разнесения сервисов по разным ДЦ обязан быть обратносовместимым, потому как нельзя одновременно обновлять два ДЦ (пытаться можно, но часто будет очень грустно). Но выше уже разобрались, конкретно эта пара версий имеет право не работать вместе.