Привет, хабровчане!
Меня зовут Нурыев Асхат, я ведущий инженер по автоматизации в 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 при помощи аннотаций.
В результате получились такие графики:
Сразу видно, что после отказа появились ошибки, а после восстановления — пропали. Значит, тут есть баги, — подумали мы и бросились к разработчикам, но не тут-то было.
На самом деле часть API должна быть доступна при всех видах отказов, а часть — нет, и требуются некоторые дополнительные действия, чтобы они заработали. Важно понимать, какие конкретно запросы падают в тех или иных сценариях.
К счастью, и тут помог Gatling. Мы подготовили дополнительные графики и сразу увидели падающие вызовы.
Намного лучше, сразу все видно! Но все равно получается долго…
По-прежнему необходимо договариваться, нужно ждать пока у команды Operations появится время, чтобы запускать все эти сценарии и выставлять аннотации.
Тогда мы решили двигаться дальше и запилить сервис, который сам выполнял бы все отказы. Для новых сервисов в компании есть прекрасные готовые шаблоны — бери и добавляй свою логику.
Сервис генерации отказов
Логика сервиса отказов — поломка и восстановления системы. То есть нужно интегрировать DGS с инфраструктурными сервисами, такими как VSphere, Marathon, сетевыми устройствами и даже с ИБП. Однако инфраструктурой у нас занимаются Operations, и чтобы все заработало, сперва нужно получить соответствующие доступы.
В итоге мы договорились работать над проектом вместе, чтобы оперативно решать все возникающие проблемы.
Сервис решили назвать DGS — Disaster Generator Service.Получилась следующая схема:
Но часть отказов нельзя было воспроизвести при помощи сервисов. Нужно было как-то ломать хосты изнутри (забивать диск данными или убивать процесс), как это делали раньше вручную. Мы решили закинуть эти скрипты на 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 и отмечать (не)срабатывание и (не)восстановление алертов.
Коротко о главном
Статья получилась обзорной и не затрагивает глубоких аспектов тестирования и деталей разработки отказов. Но надеюсь, все же будет полезной для тех, кто захочет сделать что-то подобное у себя.
Если кому то захочется узнать больше деталей, пишите в комментариях.
Приведу краткий список действий, которые помогли нам организовать тестирование у себя и, возможно, помогут и вам:
Найти самые вероятные сценарии отказов на продакшене.
Проанализировать текущий процесс тестирования.
Использовать непрерывный поток запросов перфоманс-тестов вместо разовых запусков функциональных.
Настроить дашборды для анализа результатов в реальном времени.
Автоматизировать все, что имеет смысл автоматизировать.
5.1 Генерацию отказов и восстановления системы
5.2 Jenkins пайплайны для управления процессомУлучшить генерацию отчетов, чтобы можно было начать анализ результатов как можно раньше.
Собрать данные мониторинга о сработавших алертах.
И самое главное, запускать это все регулярно, чтобы ловить ошибки в лабах, а не на продакшене.