Сто раз сломай, один раз поправь или как мы улучшали тестирование отказоустойчивости и восстановления API

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Привет, хабровчане!

Меня зовут Нурыев Асхат, я ведущий инженер по автоматизации в DINS. За время работы в компании я участвовал в решении множества сложных задач. В этой статье я поделюсь историей улучшения процесса и автоматизации тестирования высокой доступности и восстановления после отказа подсистемы API, состоящей из множества компонент. 

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

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

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

Большие планы

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

Чтобы лучше понять всю сложность задачи, которая перед нами стояла, приведу некоторые цифры:

  • API имеет более 1000 точек входа.

  • Более 30-ти видов сервисов участвуют в обработке запросов и каждый из них устанавливается как минимум в 2-х, а как максимум в 15-ти экземплярах.

  • Наши сервисы устанавливаются как на реальном железе, так и на виртуалках и, конечно, в Docker контейнерах.

  • На момент начала нашей работы тестирование занимало 3-4 месяца.

Вот так выглядит архитектурная диаграмма этой системы (в реальности такая «красота» разворачивается еще и в куче дата-центров).

В самом начале

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

Очень хотелось разобраться:

  • Как все организовано? 

  • Почему тестирование проходит так долго?

  • Какие сценарии отказов проверяют коллеги?

Чтобы ответить на все эти вопросы, я пошел общаться с ребятами из разных команд, задействованных в тестировании. 

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

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

  • падение сервиса,

  • переполнение диска,

  • отключение питания машины или просто выход из строя сетевой карты.

И отказы, которые затрагивают сразу большие куски системы:

  • отключение питания дата-центра, 

  • разрыв связи между разными дата-центрами,

  • отключение сети внутри дата-центра и т.п.

Ребята знали, как воспроизвести эти отказы в лабах, и время от времени проверяли, как все работает.

Для тех, кому интересно, вот часть этих отказов:

N

Type

Name

Description

1

Location

service-survival-mode

Put all servers in survival mod

2

Location

db-block-network

Block network on database host

3

Location

block-internal-network

Emulate core switch deny

4

Location

stop

Shutdown all VHS hosts

5

Location

block-network

Disable main rtr in location

6

Location

block-network-between-locations

Block network between locations

7

Location

stop-ccm-master

Stop ccm master in location

8

Location

stop-all-ccm

Stop all ccm nodes

9

Location

stop-all-ccs

Stop all ccs nodes

10

For Docker service

block-network

Block network to docker service

11

For Docker service

stop-service

Scale to 0 by CCM

12

For Docker service

kill

13

For Docker service

restart-container

Restart service by CCM

14

For Virtual Machine

kill

Kill main process of service

15

For Virtual Machine

stop

Stop service

16

For Virtual Machine

stop-server

Shutdown VMs

17

For Virtual Machine

block-network

Disable network for VMs

18

For Virtual Machine

disk-congestion

Disk-congestion on VMs

Процесс тестирования

В общем и целом с отказами разобрался. Настало время проанализировать процессы. И вот тут я увидел несколько проблем:

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

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

  • В-третьих, разбор этих тестов занимал много времени.

В общем, процесс происходил по следующей схеме, и каждая итерация могла растянутся на несколько дней: 

Первоначальная схема тестирования
Первоначальная схема тестирования

Gatling спешит на помощь

Как улучшить синхронизацию между командами или вовсе избежать ее? Слету решение я не нашел. Но для начала решил поработать с тестами.

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

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

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

Дело недолгое, если есть опыт.

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

Так мы и стали делать. Я запускал джобу с перфоманс тестами и давал сигналы команде Operations для запуска отказов.

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

И тут я вспомнил, что в Gatling можно включить запись результатов в реальном времени, это должно помочь. 

  • Включил запись результатов в реальном времени в Influx.

  • Подготовил дашборд в Grafana.

А чтобы было легче найти отказы, попросил Operations отмечать время запуска сценария отказа или восстановления там же в Grafana при помощи аннотаций. 

В результате получились такие графики:

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

Сразу видно, что после отказа появились ошибки, а после восстановления — пропали. Значит, тут есть баги, — подумали мы и бросились к разработчикам, но не тут-то было. 

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

К счастью, и тут помог Gatling. Мы подготовили дополнительные графики и сразу увидели падающие вызовы.

Мониторинг нагрузки в реальном времени с разбиением по API позволяет быстро найти источник ошибок
Мониторинг нагрузки в реальном времени с разбиением по API позволяет быстро найти источник ошибок

Намного лучше, сразу все видно! Но все равно получается долго…

По-прежнему необходимо договариваться, нужно ждать пока у команды Operations появится время, чтобы запускать все эти сценарии и выставлять аннотации.

Тогда мы решили двигаться дальше и запилить сервис, который сам выполнял бы все отказы. Для новых сервисов в компании есть прекрасные готовые шаблоны — бери и добавляй свою логику.

Сервис генерации отказов

Логика сервиса отказов — поломка и восстановления системы. То есть нужно интегрировать DGS с инфраструктурными сервисами, такими как VSphere, Marathon, сетевыми устройствами и даже с ИБП. Однако инфраструктурой у нас занимаются Operations, и чтобы все заработало, сперва нужно получить соответствующие доступы.

В итоге мы договорились работать над проектом вместе, чтобы оперативно решать все возникающие проблемы. 

Сервис решили назвать DGS — Disaster Generator Service.Получилась следующая схема:

На момент создания сервиса у нас использовалась связка mesos/marathon. Хотя сейчас мы используем Spinnaker и Kubernetes с helmchart-ами, изначально сервис выглядел именно так.
На момент создания сервиса у нас использовалась связка mesos/marathon. Хотя сейчас мы используем Spinnaker и Kubernetes с helmchart-ами, изначально сервис выглядел именно так.

Но часть отказов нельзя было воспроизвести при помощи сервисов. Нужно было как-то ломать хосты изнутри (забивать диск данными или убивать процесс), как это делали раньше вручную. Мы решили закинуть эти скрипты на Ansible-сервер, который мог бы выполнять их по запросу.

Первую версию сервиса я написал в кооперации с коллегой из команды Operations. 

Он добавлял нужные скрипты на Ansible и выдавал права на доступ к различным сервисам.

И тут вылезла еще одна проблема. Оказалось, что запустить скрипты недостаточно, нужно убедится, что они отработали правильно. Зачастую запуски отказов не приводили к реальными отказам системы. Нужно было тщательно следить за корректностью работы скриптов и правильно использовать коды возврата в них.

Какое-то время ушло на отладку скриптов и сценариев, зато потом тестирование пошло намного проще. Запустил перфоманс-тесты через Jenkins пайплайн и сиди дергай curl-ом API для поломки/восстановления сервисов, классно!

Магия пайплайнов

Уже на этом этапе время тестирования подсистемы API уменьшилось до 2-3 недель.

Тесты молотили без остановки, отказы запускались и откатывались по запросу к DGS, но анализировать результаты по прежнему было сложно.

Графики в Grafana отлично подходят для мониторинга текущего состояния, однако их явно недостаточно для полноценного анализа прогона.

Да и запускать отказы руками тоже со временем надоедает. Кроме того, приходится делать перерывы на ночь, что существенно замедляет весь процесс.

Следующим шагом стало создание Jenkins-пайплайна, запускающего все необходимые сценарии по списку, мы назвали его тест-планом. Он состоял из списка сервисов и списка отказов — к каждому сервису применяются все подходящие ему сценарии. То есть к сервису в докере применяются сценарии докера, а к сервису на виртуалке — сценарии отказа виртуалки. 

Реализовать пайплайн было несложно, так что в скором времени все тесты стали выполняться за 3-4 дня. 

Путь к API формируется на основе uri-сервиса и имени сценария, просто и понятно.

Пример файла с тест-планом для тестирования высокой доступности и восстановления после отказа

Пример файла с тест-планом
{
  "testPlan": {
    "minutesBetweenDisasters": 5,
    "dgsUri": "http://service.name:8080/restapi/",
    "dgsAuthToken": "test",
    "services": [
      "sap",
      "asp",
      . . .
    ],
    "dockerServices": [
      "abc",
      "Bcd",
      . . .
    ],
    "locations": [
      "loc71",
      "Loc72",
          . . .
    ],
    "serviceTestCases": [
      {
        "failScenario": "service/block-network",
        "recoveryScenario": "service/recover-network"
      },
      {
        "failScenario": "service/stop-server",
        "recoveryScenario": "service/start-server"
      },
      {
        "failScenario": "service/stop",
        "recoveryScenario": "service/start"
      },
      {
        "failScenario": "service/kill",
        "recoveryScenario": "service/recover"
      },
      {
        "failScenario": "service/disk-congestion",
        "recoveryScenario": "service/disk-recover-congestion"
      }
    ],
    "dockerServiceTestCases": [
      {
        "failScenario": "docker/stop-service",
        "output": "instances",
        "recoveryScenario": "docker/start-service",
        "input": "dockerInstanceQuantity"
      },
      {
        "failScenario": "docker/restart-container",
        "recoveryScenario": "autorecovery"
      },
      {
        "failScenario": "docker/block-network",
        "recoveryScenario": "docker/recover-network"
      }
    ],
    "locationTestCases": [
      {
        "failScenario": "location/block-network",
        "recoveryScenario": "location/recover-network"
      },
      {
        "failScenario": "location/block-network-between-locations",
        "recoveryScenario": "location/recover-network-between-locations"
      },      
      {
        "failScenario": "location/switchover",
        "recoveryScenario": "autorecovery"
      }
    ]
  }
}

Отчеты, отчеты и снова отчеты

Теперь тестирование проходило достаточно быстро, но анализ результатов вызывал головную боль.

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

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

А что если собрать данные не останавливая тесты? Gatling пишет статистику выполнения в simulation.log файл. Что если отрезать соответствующий кусок, и по нему сгенерировать отчет?

 После некоторых экспериментов удалось получить вполне приемлемый отчет за сессию поломки компонента. Для этого сделали скрипт.

Скрипт для нарезки логов Gatling-симуляции для генерации промежуточного отчета.
#!/bin/bash
SIZE=0
if [[ -d results/report ]]
then
rm -rf results/report
fi

DIR=( `ls results | grep "ha"` )

if [[ -f simulation-backup.log ]]
then
  SIZE=( `cat simulation-backup.log | wc -l`) && echo "Backup lines: "$SIZE
  cp results/$DIR/simulation.log simulation-backup.log
  echo "Simulation file lines: " && wc -l  results/$DIR/simulation.log
  tail -n +$SIZE simulation-backup.log >simulation-parts.log
  echo "Parts file lines: " && wc -l simulation-parts.log
else
  cp results/$DIR/simulation.log simulation-backup.log
fi

if [[ -f simulation-result.log ]]
then
  rm -f simulation-result.log
fi

if [[ $SIZE -gt 0 ]]
then
   head -n1 results/$DIR/simulation.log > simulation-result.log 
   ls | grep parts | grep -v 00 | xargs -I{} cat {}>>simulation-result.log
else
   cp simulation-backup.log simulation-result.log
fi
echo "Result has lines: " && wc -l simulation-result.log
ls | grep parts | xargs -I{} rm -f {}
mkdir -p results/report
cp simulation-result.log results/report/simulation.log

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

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

Небольшие отчеты гораздо проще анализировать и передавать командам разработки
Небольшие отчеты гораздо проще анализировать и передавать командам разработки

Из отчета видно, когда именно происходили изменения в системе и к каким последствиям они приводили. Слева видно, как запустился отказ и ненадолго появились ошибки, при восстановлении ошибок уже не было, но увеличилось время ответа

Отлично, теперь можно начинать разбор после первого же отказа! Прогон тестов занимает те же 3-4 дня, но анализировать результаты можно прямо в процессе выполнения. 

В результате пришли к следующей схеме работы:

Схема тестирования с учетом автоматизации
Схема тестирования с учетом автоматизации

Казалось бы, на этом все, можно остановиться. Однако на практике все несколько сложнее. Иногда происходит что-то непредвиденное и восстановление сервисов не отрабатывает. Или же находятся ошибки в некоторых сервисах, и нужно привлекать разработчиков, обновлять систему, а потом перезапускать тесты. В общем, нужно больше гибкости при запуске. 

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

Мониторинг

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

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

Отсюда возникла идея собрать все триггеры, срабатывающие во время отказов, в дополнительный отчет.

На момент реализации тестирования большая часть сервисов использовала для мониторинга Zabbix. Довольно громоздкая и немного устаревшая система, но с довольно развитым API. Оказалось, что собрать сработавшие алерты из мониторинга — тривиальная задача.  

История срабатывания и восстановления алертов вместе с отчетом об ошибках в API из перфоманс сессии позволяет понять соответствует ли система ожиданиям.  Для отслеживания регресса, можно считать число ошибок на тех или иных API и отмечать (не)срабатывание и (не)восстановление алертов.

Коротко о главном

Статья получилась обзорной и не затрагивает глубоких аспектов тестирования и деталей разработки отказов. Но надеюсь, все же будет полезной для тех, кто захочет сделать что-то подобное у себя. 

Если кому то захочется узнать больше деталей, пишите в комментариях. 

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

  1. Найти самые вероятные сценарии отказов на продакшене.

  2. Проанализировать текущий процесс тестирования.

  3. Использовать непрерывный поток запросов перфоманс-тестов вместо разовых запусков функциональных.

  4. Настроить дашборды для анализа результатов в реальном времени.

  5. Автоматизировать все, что имеет смысл автоматизировать.
    5.1 Генерацию отказов и восстановления системы
    5.2 Jenkins пайплайны для управления процессом

  6. Улучшить генерацию отчетов, чтобы можно было начать анализ результатов как можно раньше.

  7. Собрать данные мониторинга о сработавших алертах.

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

Источник: https://habr.com/ru/company/dins/blog/647137/


Интересные статьи

Интересные статьи

Цель статьи, – показать примеры управления реализацией стратегии с помощью корпоративной единой информационной площадки на доступном инструменте, - Битрикс24. В статье на простом языке обсуждаются воз...
Недавно мы с коллегами работали над задачей автоматического распознавания русского рукописного текста. В предыдущей статье была описана работа над созданием нашего датасета для обучения моде...
Сегодня юнит-тесты невероятно полезны. Думаю, они есть в большинстве из недавно созданных проектов. Юнит-тесты являются важнейшими в enterprise-приложениях с обилием бизнес-логики, пото...
В этом году у меня в локтях обеих рук развился синдром кубитального канала — травма, вызванная повторяющимися нагрузками. В результате этого я практически не могу пользоваться мышью и...
Продолжаю свой цикл статей, посвящённый проблематике телемедицины — темы, значительно повысившей свою актуальность в наши дни. В предыдущей статье я препарировал начинку просте...