Хаос-инжиниринг: строим системы, готовые к сбоям

Проектирование с расчётом на отказ: лучшие практики хаос-инжиниринга

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

Десятилетиями целью было построить крепость. У нас был монолит и массивная база данных, которые мы охраняли с помощью советов по управлению изменениями (change management boards), чтобы поддерживать высокую доступность. Отказ был аномалией.

Та эпоха закончилась.

Теперь мы живём в мире «среднего времени восстановления» (Mean Time To Recovery, MTTR). В распределённых облачных системах отказ — не баг, а постоянное состояние. Пакеты теряются, «шумные соседи» (noisy neighbors) крадут процессорное время, а целые зоны доступности уходят в темноту.

Если вы сегодня руководите инженерной командой, ваша задача не в том, чтобы предотвращать отказы. Ваша задача — строить системы, которые в условиях отказов продолжают работать. Это и есть дисциплина хаос-инжиниринга (Chaos Engineering). Речь идёт не просто о том, чтобы что-то сломать в продакшне; это научный метод проверки устойчивости.

Разберём, как это реализовать — от слоя Kubernetes до культурных изменений, — не сжигая при этом свою платформу.

Экономическое обоснование (или как продать это вашему финансовому директору)

Прежде чем смотреть на код, посмотрим на затраты.

Бизнес-кейс для хаоса строится на колоссальной стоимости простоев. В 2017 году British Airways пережила сбой, который задержал тысячи пассажиров и обошёлся авиакомпании примерно в $102 миллиона. Совсем недавно Robinhood столкнулся с масштабным отключением в разгар исторического рыночного ралли, что привело к урегулированию на $10 миллионов.

Это были не злонамеренные атаки. Это была внутренняя хрупкость.

Если вы можете предотвратить хотя бы один инцидент уровня Sev-1 в год, программа хаос-инжиниринга, как правило, окупается. Она переводит команду от реактивного «тушения пожаров» — которое разрушает моральный дух и приводит к выгоранию — к проактивному обеспечению устойчивости.

Наука о «контролируемых взрывах»

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

Определите установившееся состояние (Steady State): Вам нужна базовая линия. Это не просто «сервер работает?». Это бизнес-метрики. Происходят ли оформления заказов? Задержка ниже 500 мс?

Сформулируйте гипотезу: «Если мы внедрим задержку в 50 мс в платёжный шлюз, сервис оформления заказов дважды повторит попытку, а затем корректно завершит работу, не упав».

Минимизируйте радиус взрыва (Blast Radius): Никогда не начинайте в продакшне на всём кластере. Начните на локальной машине разработчика. Затем — на стейджинге. Затем — в канареечном развёртывании (canary deployment) на 1% пользователей.

Техническая реализация: Kubernetes

Kubernetes спроектирован для самовосстановления (self-healing), но только если вы правильно его настроили. Мы используем такие инструменты, как Chaos Mesh, чтобы убедиться, что наши LivenessProbes и PodDisruptionBudgets действительно работают.

Вот классический эксперимент: Убийство пода (Pod Kill).

Мы хотим проверить: если фронтенд-под умирает, ReplicaSet получает уведомление, а абстракция Service немедленно удаляет мёртвый IP-адрес.

Конфигурация:

apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: frontend-pod-kill
  namespace: chaos-mesh
spec:
  action: pod-kill
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      'app': 'frontend'
  scheduler:
    cron: '@every 10m'

Что мы обнаруживаем:

Зачастую оказывается, что хотя K8s перезапускает под, приложению требуется слишком много времени на прогрев. Трафик направляется на «работающий» под, который на самом деле ещё не готов обслуживать запросы, — и в результате возникают ошибки 500. Этот эксперимент заставляет нас настраивать initialDelaySeconds в нашем ReadinessProbe.

Техническая реализация: бессерверные функции (эффект бабочки)

С бессерверными (serverless) функциями сложнее, потому что убивать нечего — нет никакого сервера. Но «эффект бабочки в serverless» вполне реален. Небольшой таймаут в Функции A запускает шторм повторных попыток в Функции B, которая перегружает базу данных и кладёт весь конвейер.

Поскольку мы не можем убить инфраструктуру, мы внедряем хаос на уровне приложения.

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

import os
import random
import time
from functools import wraps
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Конфигурация загружается из переменных окружения
CHAOS_CONFIG = {
    "is_enabled": os.environ.get('CHAOS_ENABLED', 'false') == 'true',
    "failure_rate": float(os.environ.get('CHAOS_RATE', '0.1')),
    "latency_ms": int(os.environ.get('CHAOS_LATENCY_MS', '2000')),
    "exception_msg": "Chaos Injection: Simulated Failure triggered by SRE"
}

def inject_chaos(func):
    """
    Декоратор для инъекции хаоса (задержки или исключения) в обработчик.
    """
    @wraps(func)
    def wrapper(event, context):
        # Проверяем, включён ли хаос, и бросаем кубик
        if CHAOS_CONFIG["is_enabled"] and random.random() < CHAOS_CONFIG["failure_rate"]:
            fault_type = random.choice(["latency", "exception"])
            logger.warning(f"CHAOS INJECTION TRIGGERED: Type={fault_type}")

            if fault_type == "latency":
                logger.info(f"Sleeping for {CHAOS_CONFIG['latency_ms']}ms...")
                time.sleep(CHAOS_CONFIG["latency_ms"] / 1000)
            elif fault_type == "exception":
                logger.error("Raising simulated exception...")
                raise Exception(CHAOS_CONFIG["exception_msg"])

        # Выполняем основную бизнес-логику
        return func(event, context)
    return wrapper

@inject_chaos
def lambda_handler(event, context):
    logger.info("Executing business logic...")
    return {
        'statusCode': 200,
        'body': 'Process completed successfully'
    }

Страшные истории: когда предположения не оправдываются

Теория — это хорошо, но настоящие уроки приходят из реальных катастроф.

Twilio и коллапс HTTP/2

Twilio попытался обновить свой service mesh до HTTP/2, чтобы повысить эффективность. В тестировании всё работало отлично. Затем они провели эксперимент с хаосом, внедрив 3% потери пакетов.

Результат? Производительность рухнула.

В HTTP/1.1 потеря пакетов затрагивает только одно соединение. В HTTP/2 несколько потоков (streams) разделяют одно TCP-соединение. Один потерянный пакет останавливает все потоки — это называется блокировкой начала очереди (Head-of-Line Blocking). Без того хаос-теста они бы развернули «улучшенный» протокол, который при малейшем сбое маршрутизатора вызвал бы масштабный инцидент.

Robinhood и «стадо гром» (Thundering Herd)

В 2020 году Robinhood лёг из-за эффекта «стада грома» (Thundering Herd). Сервисы поднялись после сбоя и одновременно попытались переподключиться к DNS. DNS-система не выдержала нагрузки, упала, и сервисы рухнули снова. Это была нисходящая спираль.

Простой эксперимент с «чёрной дырой» (Black Hole) показал бы, что им необходимы стратегии экспоненциальной выдержки (exponential backoff).

Человеческий фактор: учебные тревоги (Game Days)

Инструменты не создают устойчивость — её создаёт культура.

Мы проводим «учебные дни» (Game Days) — это запланированные учения. Мы выбираем цель, например сервис оформления заказов (Checkout Service), и собираем команду: Командир (Commander) для проведения теста, Писарь (Scribe) для ведения записей и Исполнитель (Responder) — обычно дежурный инженер.

Самая ценная часть учебного дня — это «разбор по горячим следам» (Hot Wash): немедленное обсуждение сразу после окончания теста. Зафиксировать в моменте комментарий вроде «меня сбило с толку это сообщение в логе» куда ценнее, чем написать формальный отчёт через неделю.

Заключение: непрерывная верификация

Мы движемся к будущему непрерывной верификации, где такие инструменты, как «ChaosEater», используют большие языковые модели (LLM) для автоматической генерации гипотез и валидационных скриптов на Python. Но чтобы начать, никакой ИИ не нужен.

Начните с малого. Убейте под на стейджинге. Проверьте свои таймауты. Постройте культуру, в которой инженеры не боятся говорить: «Мне кажется, это может сломаться».

Потому что рано или поздно — сломается. Единственный вопрос: будете ли вы при этом смотреть?

© 2026 meganuke