16 KiB
Read-only аудит failover/failback
Короткий вывод
Штатное переключение между primary и reserve меняет маршрут только для новых соединений. Уже установленные TCP/UDP-соединения не переносятся и не закрываются самим Harbor.
Поэтому:
- здоровая загрузка, игра или поток продолжаются через старый канал;
- если старый канал действительно умер, существующая сессия может оборваться независимо от переключения;
- после переключения новые соединения идут через новый канал;
- бесшовной миграции уже открытого TCP/UDP-сеанса на другой внешний адрес нет.
1. Точный механизм
Конфигурация
Gateway собирает один dual-channel sing-box config:
channel-primary;channel-reserve;- selector
channel-selector; - стабильные
tproxy-inиmixed-in; - отдельные diagnostic inbounds для проверки каждого канала.
Пользовательский трафик направляется в selector, а selector настроен с:
interrupt_exist_connections: false
См. src/server/singbox.ts:204-247.
Роль меняется через localhost Clash API:
PUT /proxies/channel-selector;- затем Harbor читает selector обратно и подтверждает выбранную роль.
См. src/server/services/singboxSelectorService.ts:29-81.
Failover API доступен только в Gateway:
PUT /api/failover;POST /api/failover/pause;POST /api/failover/switch;POST /api/failover/check.
См. src/server/http/routes/failoverRoute.ts:18-39.
Переключение selector не вызывает sing-box restart/apply/stop.
Автоматический failover
FailoverService:
- Проверяет оба канала через отдельные diagnostic inbound и выбранные HTTPS-сервисы.
- Считает канал healthy только если все проверки успешны; неизвестный результат даёт
unknown. - При сбое primary ждёт
failureWindowMs. - Переключается на reserve только если reserve healthy.
- При включённом traffic guard ждёт свежий quiet-window.
- Перед самой сменой повторно проверяет здоровье и активность.
- Выполняет selector PUT, read-back и только после этого обновляет canonical applied state.
См. src/server/features/failover/failoverService.ts:225-245, :320-514; state machine — src/shared/failover.ts:278-354.
Дефолты:
- проверка каждые 60 секунд;
- сбой primary — 120 секунд;
- восстановление primary — 15 минут;
- quiet-window — 30 секунд;
- активный трафик — выше 32 КБ/с;
- минимум на reserve — 10 минут;
- после 3 failover за 24 часа — карантин primary на 6 часов.
См. src/shared/failover.ts:137-148.
Автоматический failback
Отдельной реализации нет: это обратная ветка той же state machine.
Из reserve Harbor возвращается на primary только после:
- полного
recoveryWindowMs; minimumReserveMs;- окончания quarantine, если он действует;
- quiet-window при включённом traffic guard;
- подтверждения, что primary healthy.
Причина переключения публикуется как primary-recovered. См. src/shared/failover.ts:311-354.
2. Судьба существующих соединений
| Событие | Уже открытый TCP/UDP flow | Новые соединения |
|---|---|---|
| Автоматический failover primary → reserve | Остаётся на прежнем outbound; Harbor его не закрывает и не мигрирует | Идут через reserve |
| Автоматический failback reserve → primary | Остаётся на reserve | Идут через primary |
| Ручной selector switch | Не закрывается, если старый outbound ещё работает | Сразу идёт через выбранную роль |
| Pause | Ничего не меняет | Идут через текущую роль |
| Disable failover | Selector и текущий маршрут не меняются; dual config временно остаётся загруженным | Идут через текущую роль |
| Обычный stop/restart/config replacement | Процесс sing-box останавливается, поэтому TCP/UDP-сессии прерываются |
После запуска — по новой конфигурации |
| Реальная авария primary | Уже существующий flow может оборваться сам; Harbor не может перенести его на другой внешний IP | После selector switch новые flow идут через reserve |
Проверка interrupt_exist_connections: false непосредственно подтверждена TCP- и UDP-fixture-тестом: существующие TCP socket и UDP association продолжают обмен после switch, а новые идут через reserve. См. test/server/singbox-selector-capability.test.js:141-142, :237-356.
3. Отличия режимов
Ручное переключение
POST /api/failover/switch вызывает selector напрямую:
- traffic guard не проверяется;
- состояние здоровья целевого канала backend не проверяет;
- после успешного ручного переключения
failoverPolicy.pausedстановитсяtrue; - автоматический failback не произойдёт, пока пользователь не возобновит автоматическое переключение.
См. src/server/features/failover/failoverService.ts:248-317, :614-631.
Это означает, что ручной switch может быть выполнен даже на канал, который сейчас не подтверждён healthy. Это важная оговорка.
Автоматический failover
Автоматическое переключение:
- ждёт failure window;
- требует healthy reserve;
- при включённом guard блокируется активным или неизвестным трафиком;
- повторно валидирует условия непосредственно перед selector mutation;
- не перезапускает
sing-box.
Восстановление primary
Восстановившийся primary не получает новые соединения сразу. Сначала выдерживаются recovery/hold/quarantine условия и quiet-window. Пока они не выполнены, новые подключения остаются на reserve.
Pause и Disable
pause приостанавливает решения, но dual config и наблюдение остаются активными.
disable останавливает scheduler, probes и failover activity collector, но не переключает selector и не перезапускает процесс. После обычного stop/следующего запуска собирается single-channel config.
См. README.md:78-84, docs/product/application-state.md:102-110.
4. Практические сценарии
- Загрузка файла: при обычном автоматическом failover активная передача по умолчанию задерживает switch. Если primary всё же упал, текущая TCP-загрузка не переносится на reserve; она может завершиться ошибкой. Возобновление или новый HTTP-запрос после switch пойдёт через reserve.
- Игровая сессия: существующий TCP-сеанс остаётся на старом канале. TCP-сессия оборвётся, если primary реально недоступен. UDP-flow также не мигрирует и может начать терять пакеты или истечь по timeout; новая сессия после switch пойдёт через reserve.
- Стрим: активный поток обычно блокирует автоматический switch при включённом guard. Ручной switch может быть выполнен сразу, но существующий TCP/QUIC-поток остаётся на старом outbound. При аварии старого канала плеер должен переподключиться.
- WebSocket/долгий polling: действующий flow не переносится; новые подключения после switch используют новую роль.
- Молчащее соединение: наличие открытого socket само по себе не считается активностью. Guard смотрит на дельты переданных байтов.
5. Условия и оговорки
- Failover реализован только для Gateway, не для локального Connect или
gateway-direct. - Активность собирается существующим
/connectionsobserver каждые 2 секунды, с bounded окном до 10 секунд. См.src/server/index.ts:311-333,src/server/services/domainTrafficService.ts:323-416,:437-495. - Короткое соединение, полностью завершившееся между двумя снимками, может не попасть в activity guard.
- Неизвестная или устаревшая activity-информация блокирует автоматический switch; ручной switch остаётся доступен.
- Отключение traffic guard разрешает автоматический switch без ожидания тишины, но
interrupt_exist_connections: falseвсё равно защищает уже открытые connections от закрытия самим selector. - При изменении policy во время работающего single-channel VPN dual config становится
pending; скрытого restart нет. См.src/server/features/connection/connectionService.ts:140-201,:288-361. - Явный stop/restart или обычная смена сервера вне активного failover уже является disruptive operation:
sing-boxполучает SIGTERM и текущие сессии прекращаются. См.src/server/singboxRuntime.ts:39-62,:65-122. - Оба канала находятся в одном процессе
sing-box; process-wide crash не защищён selector-механизмом.
6. Основные файлы
src/server/singbox.ts:204-247— dual config, selector, inbound routing.src/server/services/singboxSelectorService.ts:29-81— selector PUT/read-back.src/server/features/failover/failoverService.ts:225-245— проверки каналов.src/server/features/failover/failoverService.ts:248-317— selector switch, commit и rollback.src/server/features/failover/failoverService.ts:320-514— автоматический раунд и traffic guard.src/server/features/failover/failoverService.ts:614-631— ручное переключение.src/shared/failover.ts:137-148,:278-354— дефолты и state machine.src/server/services/domainTrafficService.ts:129-160,:323-416,:437-495— классификация и activity.src/server/features/connection/connectionService.ts:140-201,:243-361— обычный apply/stop/restart.src/server/singboxRuntime.ts:39-122— фактическая остановка и перезапуск процесса.README.md:78-84— пользовательская документация.docs/product/application-state.md:102-110— контракт состояния.workpack/tasks/TASK-021-auto-server-selection-failover.md:93-106,:187-204— относящийся план и ограничения; задача не выбиралась и не изменялась.
7. Тесты, подтверждающие выводы
test/server/singbox-selector-capability.test.js:141-356
Интеграционная TCP/UDP-проверка: активный трафик задерживает failover, существующие TCP/UDP продолжают работать после switch, новые соединения идут через reserve, PID процесса не меняется. Тест opt-in и пропускается безHARBOR_SINGBOX_IMAGE.test/server/singbox-gateway-mode.test.js:69-110
Проверяет selector,interrupt_exist_connections: false, отдельные diagnostic routes и primary/reserve outbounds.test/server/failover-service.test.js:83-200
Failure window, traffic guard, both-unhealthy и failback recovery/hold/quarantine.test/server/failover-service.test.js:255-420
Disabled zero-work, отмена устаревших наблюдений и непосредственная revalidation активности.test/server/failover-service.test.js:423-520
Selector rollback, commit ordering и ручной switch с pause.test/server/domain-traffic.test.js:209-239
Activity считается по пользовательскому VPN traffic, diagnostic connection не блокирует switch.test/server/connection-service.test.js:265-310,:340-390
Restart dual config и rollback; pending edits для работающего single-channel.test/server/singbox-runtime.test.js:16-50
При изменении config runtime запускает новый процесс.test/server/failover-route.test.js:10-53
Gateway-only API и маршруты ручного switch/pause/check.test/web/failover-feature-contract.test.js:19-76
UI явно сообщает: новые подключения переключаются, открытые остаются на прежнем канале.
Review findings и residual risks
- medium —
src/server/features/failover/failoverService.ts:614-620: ручной switch не проверяет health целевого канала и не применяет traffic guard; он сразу меняет selector и ставит automation на pause. - medium —
test/server/singbox-selector-capability.test.js:141-356: capability test opt-in и использует deterministicdirectoutbounds, а не реальный VLESS/Trojan outage. - medium —
src/server/services/domainTrafficService.ts:414-416,:451-479: activity основана на polling и byte deltas; короткие или очень малые потоки могут не блокировать автоматическое решение. - info —
src/server/singboxRuntime.ts:39-122: явный stop/restart отличается от selector switch и прерывает существующие соединения. - info — архитектура одного процесса: падение всего
sing-boxне компенсируется selector failover.
Файлы не изменялись. Тесты и live Gateway в рамках read-only аудита не запускались.