Как мы строили HA-инфраструктуру для банка
Отказоустойчивость — это не одна галочка в чек-листе, а набор решений на каждом слое. Делюсь тем, что реально держит нагрузку в проде.
Банковская инфраструктура не прощает простоев. Когда сервис обрабатывает платежи, каждая минута недоступности — это не только деньги, но и доверие. За время работы с production-инфраструктурой О!Банка я понял главное: высокая доступность строится снизу вверх, и слабое звено на любом уровне обнуляет всю остальную работу.
Принцип: ни одной единой точки отказа
Первое, что мы сделали — нарисовали карту зависимостей и нашли каждый single point of failure (SPOF). Один балансировщик? SPOF. Одна нода с базой? SPOF. Один аплинк? Тоже. Цель — чтобы выход из строя любого отдельного компонента не приводил к недоступности сервиса.
Правило, которое мы повесили на стену: «Если ты не можешь выдернуть этот сервер из розетки в рабочий день — у тебя нет HA, у тебя есть надежда».
Слой балансировки: Keepalived + HAProxy
На входе мы поставили пару балансировщиков в режиме active-passive с плавающим IP через
Keepalived (VRRP). Трафик распределяет HAProxy с активными health-check'ами:
нода, не прошедшая проверку, мгновенно выводится из ротации.
frontend fe_api
bind 10.0.0.10:443 ssl crt /etc/haproxy/certs/api.pem
default_backend be_api
backend be_api
balance roundrobin
option httpchk GET /actuator/health
http-check expect status 200
server app-01 10.0.1.11:8443 check ssl verify none inter 2s fall 3 rise 2
server app-02 10.0.1.12:8443 check ssl verify none inter 2s fall 3 rise 2
server app-03 10.0.1.13:8443 check ssl verify none inter 2s fall 3 rise 2
Здоровье приложения отдаёт Spring Boot Actuator (/actuator/health), причём health-эндпоинт
учитывает доступность зависимостей — БД, очередей. Это важно: сервис, который «жив», но не видит базу,
не должен принимать трафик.
Приложения: stateless и горизонтальное масштабирование
Сервисы мы держим stateless — состояние выносится в БД, кэш и очереди. Это даёт два свойства бесплатно: можно масштабироваться горизонтально и можно убивать любой под без последствий. В Kubernetes это выражается простыми, но строгими правилами:
- минимум 3 реплики ключевых сервисов, разнесённые по разным нодам через
podAntiAffinity; PodDisruptionBudget, чтобы во время апдейтов нод не выносило сразу все реплики;- корректные
readiness/livenessprobes — без них rolling update ломает доступность.
spec:
replicas: 3
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels: { app: payments-api }
topologyKey: kubernetes.io/hostname
Данные — самое сложное
Stateless-сервисы масштабировать легко. А вот база — это всегда компромисс между консистентностью и доступностью. Для PostgreSQL мы используем потоковую репликацию с автоматическим failover (Patroni), синхронной репликой для критичных данных и регулярными проверками восстановления из бэкапа.
Отдельно подчеркну: бэкап, который ни разу не восстанавливали, — это не бэкап. Мы регулярно поднимаем копию из бэкапа в отдельном окружении и сверяем целостность. Это сэкономило нам нервы не один раз.
Честный SLA: считаем, а не обещаем
«Четыре девятки» звучит красиво, но 99.99% — это всего ~52 минуты простоя в год. Чтобы реально в это попасть, нужно резервирование на каждом уровне и быстрый MTTR. Мы научились считать доступность не по ощущениям, а по метрикам из Prometheus, и привязали алерты к error budget, а не к отдельным пикам.
- 99.9% → ~8.7 часа простоя в год;
- 99.95% → ~4.4 часа;
- 99.99% → ~52 минуты.
Disaster Recovery: план, который проверяют
HA защищает от отказа компонента. DR — от потери целого дата-центра. Мы определили RPO/RTO для каждого критичного сервиса и, главное, периодически проводим учения: руками выводим из строя зону и смотрим, что отвалится. Каждое такое учение находит то, что не учли в схеме.
Итог
Высокая доступность — это не продукт и не одна технология. Это дисциплина: убирать SPOF, проверять предположения, тренировать восстановление. Самые надёжные системы, которые я видел, были не самыми сложными — а самыми проверенными.