Меня зовут Михаил Новиков, я архитектор в команде, которая развивает один из пользовательских сервисов. В статье расскажу, зачем мы за 5,5 недель внедрили х��ос-тесты, что учли при их настройке и почему ломаем прод Mindbox. 

Мы решили искоренить дефекты на проде 

В нашем сервисе используется кластер Redis. Однажды во время планового обслуживания сработал алерт: одно из приложений не могло подключиться к Redis, хотя он отвечал на запросы. Оказалось, что обращение направлено к реплике, которая находится на обслуживании. Пришлось вручную перезапускать поды в Kubernetes, чтобы приложение увидело рабочую реплику. Мы разбирались почти час, а 90% пользователей не могли подключиться к нашему сервису.

Вскоре случился новый дефект. Снова отказал весь функционал нашего сервиса, теперь из-за остановки одного из трех брокеров Kafka. В этот раз к сервису потеряли доступ 5% пользователей.

Каждый раз мы тратили время на решение инцидентов и нарушали обязательства перед клиентами. Нужно было найти способ выявлять подобные дефекты до того, как они проявятся. Такой способ — хаос-тестирование.

Хаос-тестирование: что это и зачем нужно

Хаос-тестирование — это способ проверить, насколько  устойчива система. Во время тестов в неё преднамеренно вносят сбои или нештатные ситуации (например, отключают сервера, увеличивают нагрузку, ломают сети). 

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

Метод не новый, и в этой статье я не буду вдаваться в теорию. В конце статьи добавлю материалы, на которые мы опирались при внедрении хаос-тестов. 

Как мы разворачивали хаос-тесты

Весь процесс внедрения от идеи до запуска первого теста на проде занял у нас чуть меньше полутора месяцев. Первые 1,5 недели собирали гипотезы и описывали стабильное состояние системы. Следующие 4 недели три разработчика писали тесты, вспомогательные уведомления и отображения прохождений для них. Написали тесты отказа брокера Kafka и ноды Cassandra, смены мастера Redis, перезапуска подов приложения, деградации Cassandra и общей проверки инфраструктуры.  

Этап 1. Контрольная точка

В любом эксперименте сначала определяют стабильное состояние системы, чтобы было с чем сравнивать результат эксперимента. Перед тем, как запускать хаос-тесты, нужно зафиксировать правильное поведение системы: показатели метрик, приложений и вспомогательных ресурсов. Система должна вернуться к нему после того, как искусственный сбой закончится. 

Наша система выполняет маркетинговые сценарии, составленные пользователем. Поэтому мы опередили стабильное состояние через алерты и Success Rate метрику.

Состояние

Как измеряем

Все сценарии выполняются в обычном режиме

Нет алертов о работе сервиса и сбоев Heartbeat. 

Heartbeat — это сервис, который раз в 15 секунд запускает заранее подготовленные искусственные сце��арии. Если происходит сбой, присылает оповещение в течение минуты.

Договор с клиентом (SLA) не нарушается

Не нарушена Success Rate метрика.

Метрика рассчитывается как сумма успешно завершенных событий ко всем событиям, ушедшим в ошибку. Должен быть больше 0,9: это значит, что 90 из 100 событий обработаны успешно. 

Все метрики наблюдаем на отдельном борде Grafana:

На графике Heartbeat больше нуля, а значит, сценарий корректно отрабатывает вызовы. Когда сценарий выполняется 15–20 секунд, то происходит кратковременный скачок в ноль. Если ноль на графике постоянно, сервис не работает
На графике Heartbeat больше нуля, а значит, сценарий корректно отрабатывает вызовы. Когда сценарий выполняется 15–20 секунд, то происходит кратковременный скачок в ноль. Если ноль на графике постоянно, сервис не работает

Сервис Heartbeat подходит для проверок большинства сбоев, например сбоя Kafka. Если выключение брокера ломает систему, Heartbeat это поймает и покажет. Но скрипт не универсальный и на некоторых тестах не срабатывает. Например, мы тестировали разрыв связи между сервисами: один сервис переставал видеть другой, но продолжал нормально работать за счет долгоживущего кеша. Однажды связь не восстановилась после окончания теста, а наши графики стабильного состояния этого не показали. Время жизни данных в кеше завершилось, и система перестала корректно работать.

Чтобы оценить стабильное состояние системы, смотрите на основные процессы, с каким SLA они выполняются, какая у них статистическая норма. Важно настроить дашборд с этими состояниями и уведомления о нештатной работе. Во время тестирования надо убедиться, что в случае сбоя, вызванного тестом, стабильное состояние реально нарушается. И точно знать, что есть мониторинг и алерты на сбой.

Этап 2. Гипотезы для проверки хаос-тестами

Мы проводим для каждой гипотезы отдельный эксперимент. Считаем, что важно их не усложнять — например, проверять только один элемент системы: Redis или Cassandra, но не оба вместе. 

Для нас первым источником гипотез стали инциденты с Redis и Kafka, описанные в начале статьи. Для ситуации, когда приложение обращалось к нерабочей реплике Redis, придумали такую формулировку: смена мастера кластера Redis не приводит к деградации сервиса.

Дефект с остановкой одного из трех брокеров Kafka дал нам такую тему для эксперимента: отказ одного брокера Kafka не влияет на выполнение сценариев.

Позднее к гипотезам о падении части инфраструктуры мы добавили другие, о деградации части системы, например: сетевые задержки до одной из нод Cassandra в 90 перцентилей не влияют на время выполнения запросов от приложения.

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

Пример гипотезы из двух элементов: деплой приложения (перезапуск подов приложения) при отказе одного брокера Kafka не приводит к сбоям сервиса.

Упростить ее можно так:

  • отказ одного брокера Kafka не приводит к сбоям сервиса;

  • деплой приложения (перезапуск подов приложения) не приводит к сбоям сервиса.

Двойные гипотезы мы формулировали по следам ситуаций, когда сам по себе сбой Kafka не приводил к деградации, но при перезапуске мы не могли стартовать сервисы из-за неверных настроек приложения.

Гипотезы для тестов не обязательно строить на уже случившихся инцидентах. Можно предполагать любые ситуации:

1. Можно строить гипотезы об отказе других элементов кластера, которые ещё не приводили к дефектам. Например, реплика SQL базы данных, Cassandra.

2. От гипотез отказа можно перейти к гипотезам о деградации. В реальной жизни мы чаще сталкиваемся с ситуациями, когда какой-то элемент системы не полностью отказал, а стал отвечать с задержками или терять часть запросов. Пример — наша гипотеза, что сетевые задержки до одной из нод Cassandra не влияют на время выполнения запросов от приложения.

3. Можно подумать о гипотезах из нескольких частей, как в нашем двойном предположении, ��то деплой приложения (перезапуск подов приложения) при отказе одного брокера Kafka не приводит к сбоям сервиса. Или продумать ситуации более глобальных отказов, например, целой зоны в облаке или одного из датацентров.

Этап 3. Эксперимент

Чтобы провести эксперимент, мы выбрали среду и инструменты, подобрали и написали тесты, продумали, как их остановить, и убедились, что не сломаем ничего у соседей — определили радиус поражения. Дальше обо всем по порядку.

Среда для тестов

На нашем проде 60 миллионов конечных пользователей, тысяча компаний и до 2 миллионов RPM. Воспроизвести все это в тестовой среде мы не можем. Если проводить хаос-тесты только на модели без пользователей, обязательно что-то упустим. Поэтому мы приняли волевое решение и вносим хаос в настоящий прод раз в день с понедельника по четверг. 

У нас в течение дня может быть десяток релизов, как это устроено, уже рассказывали в статье про наш релизный цикл. Большинство тестов запускается раз в день, их результаты помогают понять, что выкаченные изменения не привели к дефектам и деградации.

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

Сначала все тесты проходят на стейдже, затем в бета-среде — на 10% реальных клиентов. По результатам тестов мы отлаживаем систему, чтобы она переживала отказы, которые имитируют сценарии тестов. Когда тесты стабильно проходят на бета-среде, мы запускаем их на прод
Сначала все тесты проходят на стейдже, затем в бета-среде — на 10% реальных клиентов. По результатам тестов мы отлаживаем систему, чтобы она переживала отказы, которые имитируют сценарии тестов. Когда тесты стабильно проходят на бета-среде, мы запускаем их на прод

Инструмент для тестов

Выбор инструмента зависит от параметров, которые нужно проверять, и от стека, который применяется в вашем продукте. Наши сервисы живут в Kubernetes. Вспомогательные штуки, вроде Kafka и Cassandra, — на облачных виртуалках. Redis используем готовый, его предоставляет провайдер. Нам интересны хаос-тесты, которые умеют дружить с Kubernetes и делать что-то на виртуалках, поэтому мы выбрали Chaos Mesh.

Chaos Mesh разворачивается в Kubernetes и из коробки умеет ломать сеть и убивать поды. В его состав входит утилита Chaosd, которую можно поставить на виртуальные машины, чтобы наводить хаос там — рестартовать процессы, нарушать работу сети и диска. Полный список есть в документации Chaos Mesh. 

Из минусов разве что UI: он очень сырой и создавать из ��его эксперименты бывает сложно. Но это нам не мешает: в нашей инфраструктуре релизы управляются через Helm, а хаос-тесты создаются через yml-описания. Вот пример декларации простейшего эксперимента, который убивает (action: pod-kill) 50% подов с выбранной меткой (labelSelectors):

kind: PodChaos
apiVersion: chaos-mesh.org/v1alpha1
metadata:
  namespace: my-namespace
  name: kill-pods-0
spec:
  selector:
    namespaces:
      - my-namespace
    labelSelectors:
      app: my-app-label
  mode: fixed-percent
  value: '50'
  action: pod-kill
  gracePeriod: 5

Чтобы настроить регулярный запуск тестов, используем элемент Chaos Mesh — расписание (schedule). Chaos Mesh не умеет ограничивать параллельный запуск разных расписаний. Параметр concurrencyPolicy может только запретить новый запуск, если предыдущий еще не завершился. Чтобы ограничить одновременное выполнение разных расписаний, подб��раем их вручную.  

Для описания шагов эксперимента используем элемент Workflow. Внутри Workflow идет последовательность действий по созданию хаоса. Chaos Mesh поддерживает последовательные шаги templateType: Serial и параллельные templateType: Parallel.

В примере kind: Schedule определяет, что это расписание, а внутри type: Workflow говорит, что по расписанию будем запускать последовательность шагов.

apiVersion: chaos-mesh.org/v1alpha1
kind: Schedule
metadata:
  name: cassandra-node-fault-workflow
  namespace: my-namespace
spec:
  schedule: 30 18 * * 1-4
  startingDeadlineSeconds: 1800
  concurrencyPolicy: Forbid
  historyLimit: 1
  type: Workflow
  workflow:
    entry: cassandra-node-fault-entry
    templates:
    - name: cassandra-node-fault-entry
      templateType: Serial
      deadline: 10m
      children:
      - notify-test-started
      - stop-cassandra-node
    - name: stop-cassandra-node
      templateType: PhysicalMachineChaos
      deadline: 3m
      physicalmachineChaos:
        action: process
        address:
        - http://cassandra-a-1-v2:31767
        - http://cassandra-b-1-v2:31767
        - http://cassandra-d-1-v2:31767
        mode: one
        process:
          process: java
          recoverCmd: systemctl restart cassandra
          signal: 9
    - name: notify-test-started
      templateType: Task
      task:
        container:
          args:
          - -c
          - "notify command here"
          command:
          - /bin/sh
          image: curlimages/curl:7.78.0
          name: notify-chaos-test-started

Радиус поражения

При подготовке к эксперименту важно понять, просчитать и минимизировать потенциальный радиус поражения от экспериментального сбоя. Что пострадает, кроме основной цели теста, какие еще сервисы могут работать нештатно или ломаться от привнесенного хаоса — это необходимо учесть до запуска тестов. Если не понимаете радиус поражения, то ломать прод нельзя — последствия становятся непредсказуемыми. 

В своем сервисе мы знаем все функ��ии, которые могут сломаться, и все способы повлиять на другие части системы. Они ограничены и контролируемы. И мы не стали делать хаос-тесты Redis там, где на одном кластере работают десятки микросервисов разных команд — радиус поражения был бы слишком велик.

Рекомендую четко высчитывать радиус поражения и не запускать хаос-тестирование, если точно не знаете, что может сломаться. Подсчеты должны опираться на важность сервиса, на его уровень критичности. Если у вас еще нет градации уровней критичности — ее нужно ввести. Прежде чем попытаться все сломать, вы должны знать, как быстро сможете восстановить нормальную работу, если тест пойдёт не по плану. 

Кнопка аварийной остановки

Чтобы хаос не вырвался из-под контроля, обязательно должен быть способ вручную остановить тест и вернуть систему в стабильное состояние.

В Chaos Mesh можно руками поставить эксперимент на паузу или настроить, чтобы он останавливался автоматически в определённых состояниях системы.

Кнопка ручной остановки эксперимента в интерфейсе Chaos Mesh
Кнопка ручной остановки эксперимента в интерфейсе Chaos Mesh

Мы еще не сделали вариант с автоматической остановкой, но при старте хаос-теста в командный канал приходит сообщение со ссылками на эксперимент и инструкцию по ручной остановке.

Каких-то встроенных интеграций с мессенджерами у Chaos Mesh нет, но работает механизм Webhook, примерно так:

 type: 'Workflow'
  workflow:
    entry: notify-test-started
    templates:
      - name: notify-test-started
        templateType: Task
        task:
          container:
            name: notify-chaos-test-started
            image: cr.yandex/crpetiko0nqk7f4hqfl3/docker.io/curlimages/curl:7.78.0
            command:
              - sh
              - -c
              - >-
                curl -s -X POST -d '{
                  "channel": "#maintenance-notifications-chaos",
                  "text": "{{ .Values.somevalue }} chaos testing is carried out.",
                  ...
                }' -H 'Content-Type: application/json' https://mindbox.messanger.ru
Сообщение от бота в командном канале: хаос-тест остановил одну из нод Cassandra
Сообщение от бота в командном канале: хаос-тест остановил одну из нод Cassandra

Тесты собственной разработки 

В Chaos Mash есть готовые тесты и сценарии экспериментов для часто встречающихся конфигураций, они подробно описаны в документации. Нам они не подошли, потому что мы используем управляемый Redis, который находится в облаке. Стандартных экспериментов для такой ситуации нет, но в Chaos Mesh есть эксперимент с типом Task, который позволяет выполнять произвольный код. Вот пример, где мы инициируем смену мастера Redis, через такой вызов:

apiVersion: chaos-mesh.org/v1alpha1
kind: Workflow
metadata:
  namespace: my-namespace
  name: redis-failover-chaostest-staging-workflow-7zdgp
  labels:
    managed-by: redis-failover-chaostest-staging-workflow
spec:
  entry: redis-failover-chaostest
  templates:
    - name: redis-failover-chaostest-scenarios-common-staging
      templateType: Task
      task:
        container:
          name: redis-chaostest
          image: cr.yandex/crpo9tj76o3c7pi8i72n/redis-maintenance:1.0.7
          args:
            - failover
            - '-r'
            - '-c'
            - c9q89rfji5uhvfl95hlv
            - '-k'
            - $(CHAOS_TEST_REDIS_API_SECRET_KEY)
          envFrom:
            - secretRef:
                name: chaos-test-credentials-staging
          resources: {}
   # other steps here...

Сообщения о сбоях во время тестов

У нас есть два типа сообщений о сбоях — алертов: 

  1. Система не работает → клиенты страдают, мы теряем деньги.

  2. Система работает, но что-то деградировало → клиент не страдает, система может восстановиться сама. Если не восстановилась за заданное время, то мы получим алерт, потому что в долгосрочной перспективе ситуация опасна. 

Мы не отключаем алерты, когда проводим хаос-тесты, по двум причинам:

  • тестируем на проде и все еще должны следить за его состоянием;

  • укладываемся с тестами в рамки времени срабатывания штатных алертов. 

Продолжительность тестов настраиваем так, чтобы тестовые события заканчивались раньше, чем сработают алерты второго типа — о деградации. Если система выйдет из стабильного состояния от тестового события, мы получим алерт, если не выйдет — тест пройдет незаметно, а гипотеза подтвердится. 

Например, если один из брокеров Kafka действительно отключится и не вернется в работу, мы получим алерт второго типа примерно через полчаса. Поэтому для теста отключения брокера подбираем промежуток времени короче получаса. Если система не сломается, то срабатываний не будет.

Но возможен и другой подход — остановить некоторые алерты на время тестов. Как поступать, зависит от особенностей конкретной системы, универсального рецепта нет.

Этап 4. Анализ результатов и улучшение сервиса 

После того, как тесты выполнены, нужно проанализировать результаты и сравнить их с исходной гипотезой эксперимента. Например, мы выловили и исправили до возникновения инцидентов такие ситуаци:

  1. Гипотеза: отказ одного брокера Kafka не влияет на выполнение сценариев.

    Реальность: из-за отказа брокера Kafka не обрабатывался перезапуск подов. Min In Sync Replicas был настроен некорректно, из-за этого не создавались топики для пода на старте.
    Улучшение: поменяли настройки Min In Sync Replicas.

  2. Гипотеза: сетевые задержки до одной из нод Cassandra не влияют на время запросов от приложения.

    Реальность: влияют, запрос всегда шел к одной ноде.
    Улучшение: настроили механизм Speculative Query Execution.



Внедрение хаос-тестов заняло полтора месяца. В результате мы избавились от нарушений обязательств перед клиентами (SLA) в тех приложениях, которые покрыли тестами. Несколько раз по результатам тестов исправляли дефекты до того, как они повлияли на пользователей.

Кратко повторю,  что мы сделали, чтобы внедрить хаос-тесты:

  1. Провели подготовку к внедрению, которая заняла 1,5 недели. За это время определили стабильное состояния системы и подобрали метрики, по которым его отслеживать. Сформулировали гипотезы для экспериментов.

  2. За следующие 4 недели написали тесты, проанализировали результаты и внесли в систему улучшения. Профит!

Полезности по теме

  1. Доклад Дмитрия Баскакова о внедрении хаос-тестов в Mindbox на Yandex Scale 2024.

  2. Подборка видео, репозиториев, статей и материалов по хаос-тестированию из доклада.

  3. Обзор инструментов для хаос-тестирования в двух частях:
    Часть 1: kube-monkey, chaoskube, Chaos Mesh.
    Часть 2: Litmus Chaos, Chaos Toolkit, KubeInvaders и другие.