Управление надежностью сторонних зависимостей¶
или Когда чужая ошибка становится вашей проблемой¶
Наш сервис может быть идеально спроектирован, безупречно написан и тщательно протестирован. Но если платежный или SMS шлюз падают - наш бизнес тоже падает. Если купленный CDN перестает отвечать - наши пользователи видят ошибки. Если облачный провайдер объявляет об инциденте в регионе - наша доступность становится нулевой.
Сторонние зависимости - это дыры в тщательно спроектированной и дорогущей броне нашей системы, которые мы не контролируем.И чем больше у нас таких зависимостей (а в современной архитектуре их десятки и сотни), тем больше наша надёжность зависит от чужой компетентности.
Парадокс зависимостей: почему вы отвечаете за то, что не можете контролировать?¶
Представьте, что мы управляем рестораном. У нас лучшие повара, идеальная кухня, прекрасный сервис. Но: - Наш поставщик овощей привез тухлые помидоры - Электрическая компания по ошибке отключила электричество нам, а не соседу-должнику - Служба доставки потеряла наш заказ
Для наших гостей совершенно неважно, в чем причина и чья это вина. Для них плохо именно у нас. Так же и с IT-системами: пользователю всё равно, упал наш код или упал канал от нас до провайдера. Он видит ошибку в нашем продукте.
Категории зависимостей и их риски¶
1. Инфраструктурные зависимости (самые опасные)¶
- Облачные провайдеры
- CDN
- DNS-провайдеры
- Сетевые провайдеры
Риск: Полный или частичный отказ целого региона/сервиса. Примеры приводить не буду, их тьма.
2. Сервисные зависимости (самые распространенные)¶
- Платежные шлюзы
- Сервисы коммуникаций
- Базы данных как сервис
- Сервисы аутентификации
- Внешние API
Риск: Отказ критичной бизнес-функции. Пример: Если платежный шлюз падает, мы не можем принимать платежи. Даже если всё остальное работает.
3. Библиотеки и фреймворки (самые коварные)¶
- NPM, PyPI, Maven пакеты
- Опенсорс-проекты
- Коммерческие SDK
Риск: Баги, уязвимости, несовместимости версий, прекращение предоставление для пользователей из определенных стран. Пример: Log4Shell уязвимость в 2021 году затронула миллионы приложений через популярную библиотеку логирования.
Стратегия SRE: не избегать зависимостей, а управлять рисками¶
Мы не можем отказаться от зависимостей (это экономически и технически нецелесообразно). Но мы можем системно управлять рисками.
Принцип 1: Измеряй всё, даже то, что не контролируешь¶
Метрики для каждой зависимости:
// Мониторинг доступности ХХХ API
func monitorStripe() {
response, err := http.Get("https://api.ххх.com/health")
latency := time.Since(start)
// Отправляем метрики в Prometheus
stripe_availability.Set(1 if ok else 0)
stripe_latency_seconds.Observe(latency.Seconds())
}
Что измерять: - Доступность (Health checks) - Задержка (Latency) — p50, p95, p99 - Частота ошибок (Error rate) - Лимиты и квоты (Rate limits usage)
Дашборд зависимостей должен быть, даже если у провайдера есть свой status page. Их status page может показывать "всё ок", но конкретно наш аккаунт или регион может иметь проблемы.
Принцип 2: Планируй отказ (Design for Failure)¶
Техника 1: Circuit Breaker для всех внешних вызовов¶
// Защита от падения ХХХ
stripeBreaker := gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "ХХХ-payments",
Timeout: 30 * time.Second,
})
func processPayment(amount int) error {
return stripeBreaker.Execute(func() (interface{}, error) {
// Вызов ХХХ API
return ххх.Charge.Create(...)
})
}
Любой внешний вызов должен быть обёрнут в circuit breaker - это правило.
Техника 2: Graceful Degradation¶
Вместо "Платежи не работают" → бизнес теряет деньги делаем: "Оплата картой временно недоступна. Вы можете оформить заказ, мы свяжемся с вами для оплаты".
func checkout(cart Cart) (Order, error) {
// Пытаемся провести оплату
paymentResult, err := processPayment(cart.Total)
if err != nil {
// Если платежный шлюз недоступен
if isPaymentGatewayDown(err) {
// Создаем заказ со статусом "ожидает оплаты"
order := createPendingOrder(cart)
// Отправляем уведомление менеджеру
notifyManager("Требуется ручная обработка заказа", order.ID)
return order, nil // Не возвращаем ошибку пользователю!
}
return Order{}, err
}
return createConfirmedOrder(cart, paymentResult), nil
}
Техника 3: Кэширование и дублирование данных¶
- Кэшируйте ответы внешних API, которые редко меняются (курсы валют, список стран, тарифы доставки, стоимость услуги и т.п.)
- Храните локальную копию критичных данных (например, последние успешные платежные токены)
- Используйте stale-while-revalidate подход: отдавайте устаревшие данные, пока обновляете кэш
Принцип 3: Диверсифицируй риски¶
Мульти-провайдер стратегия (для критичных зависимостей)¶
Пример для CDN:
func getAsset(userLocation string, assetPath string) ([]byte, error) {
// Выбираем CDN на основе географии пользователя и здоровья провайдеров
cdnProvider := selectCDNProvider(userLocation)
data, err := cdnProvider.Get(assetPath)
if err != nil && isRetriableError(err) {
// Пробуем запасного провайдера
backupProvider := getBackupCDNProvider()
return backupProvider.Get(assetPath)
}
return data, err
}
Где применять: CDN, платежи, SMS и т.п.
Экономика решения: Сравниваем стоимость мульти-провайдера с стоимостью простоя. Если 1 час простоя платежей стоит 500000 руб, а второй провайдер стоит 5000 руб/мес - ROI очевиден.
Мульти-регион (Multi-region)¶
Даже с одним облачным провайдером можно снизить риски: - Размещать сервисы в нескольких географических регионах - Настраивать автоматическое переключение трафика (DNS failover) - Реплицировать критичные данные между регионами
Принцип 4: Имей план Б (и В, и Г)¶
Runbook для инцидентов с зависимостями¶
Для каждой критичной зависимости должен быть документ:
Зависимость: ХХХ Payments
SLO: 99,95% доступности
Мониторинг: https://internal-monitor/xxx-payments
Статус-пейдж: https://status.xxx.com
Действия при нарушении SLO:
1. Проверить статус на status.xxx.com
2. Проверить наши метрики (error rate, latency)
3. Если подтвержден инцидент у XXX:
- Активировать режим graceful degradation
- Уведомить поддержку XXX (тикет #12345)
- Оповестить бизнес о временном отключении онлайн-оплат
- Включить альтернативный платежный шлюз (YYYPay)
4. Мониторить восстановление
Регулярные учения (Dependency Failure Drills)¶
Раз в квартал проводите "учения": 1. Имитируйте отказ зависимостей (отключите тестовый доступ к API) 2. Проверьте, как срабатывают circuit breakers 3. Убедитесь, что graceful degradation работает 4. Протестируйте ручные процедуры переключения 5. Обновите документацию по результатам
Принцип 5: Автоматизируй восстановление¶
Уровень 1: Автоматическое переключение на бекап
# Конфигурация Terraform с альтернативными провайдерами
resource "aws_route53_record" "primary" {
# Основной CDN
}
resource "aws_route53_record" "failover" {
# Резервный CDN
failover_routing_policy {
type = "SECONDARY"
}
}
Уровень 2: Автоматическое снижение функциональности
// Автоматическое включение режима read-only при падении базы данных
func handleDBFailure() {
if isDatabaseDown() {
enableReadOnlyMode()
notifyTeam("База данных недоступна, включен режим read-only")
// Продолжаем обслуживать 80% запросов (чтение)
}
}
Оценка и выбор зависимостей: due diligence¶
Прежде чем внедрять зависимость, проводите оценку:
Чек-лист оценки провайдера:¶
- SLA и компенсации: Какие гарантии? Как рассчитываются? Пример: XXX обещает 99,99% для S3, при нарушении 10% кредита.
- Статус-пейдж и история: Как часто были инциденты? Насколько прозрачны отчеты?
- Архитектура высокодоступности: Multi-AZ? Multi-region? Geo-redundancy?
- Лимиты и квоты: Есть ли лимиты запросов? Как быстро можно увеличить?
- Поддержка: SLA на ответ поддержки? Есть ли выделенный менеджер?
- Контрактные обязательства: Можно ли вывезти данные? Какие penalties при прекращении?
Технический аудит:¶
- Тестируйте в проде (аккуратно): Канареечные тесты с реальным трафиком
- Нагрузочное тестирование: Проверяйте лимиты до достижения
- Chaos-тесты: Имитируйте проблемы сети, задержки, частичные отказы
Юридические и бизнес-аспекты¶
Контрактное покрытие (Contractual Coverage):¶
- Включайте в договоры с провайдерами пункты о взаимной ответственности
- Требуйте финансовых компенсаций за нарушение SLA
- Оговаривайте процедуры эскалации и временные рамки
Страхование бизнес-рисков:¶
Некоторые компании страхуют бизнес от простоя, вызванного сбоями провайдеров.
Метрики успеха¶
Как понять, что вы хорошо управляете зависимостями?
- MTTR для инцидентов с зависимостями должен снижаться
- Количество инцидентов, где зависимость являлась root cause должно снижаться
- Coverage мониторинга (% зависимостей с настроенным мониторингом)
- Coverage graceful degradation (% зависимостей с реализованными fallback-ами)
Время простоя из-за зависимостей / Общее время простоядолжно быть небольшим и снижаться
Культурный аспект: от обвинений к партнерству¶
Плохой подход: "Опять этот *** XXX упал! Надо их закидать тикетами и пожаловаться их CPO и СЕО!" Хороший подход: "XXX - наш партнер. У них случился инцидент. Как мы можем помочь им быстрее восстановиться? Как мы можем улучшить нашу устойчивость к таким инцидентам?"
Практики: - Совместные постмортемы с провайдерами - Регулярные архитектурные обзоры - Программы бета-тестирования новых функций устойчивости
Матрица ответственности¶
Наша задача как SRE - построить матрицу управления зависимостями:
| Зависимость | Критичность | SLO провайдера | Наш SLO | Мониторинг | Circuit Breaker | Graceful Degradation | Fallback | Runbook |
|---|---|---|---|---|---|---|---|---|
| XXX | Высокая | 99.95% | 99.9% | ✅ | ✅ | ✅ | YYY | ✅ |
| SendGrid | Средняя | 99.9% | 99% | ✅ | ✅ | ❌ | SMTP сервер | ⚠️ |
| Yandex Maps API | Низкая | 99.9% | 95% | ✅ | ✅ | ✅ | Кэш | ✅ |
Мы не можем контролировать, упадет ли зависимость. Но мы полностью контролируем как наша система отреагирует на этот сбой. Идеально, когда наши пользователи даже не заметят, что что-то сломалось у нашего провайдера.
Управление зависимостями это не про паранойю. Это про здоровый инженерный прагматизм: признать, что мир неидеален, и построить систему, которая продолжает работать даже когда этот мир подводит.