Fairway: балансировщик корпоративных прокси, который сам выясняет, какой выход лучше для какого сайта

В компании редко бывает один прокси. Обычно это пара Squid в основном ЦОДе, свои прокси в филиалах и отдельные внешние выходы для облачных сервисов. Клиенты при этом настроены просто: PAC‑файл с двумя адресами по порядку, и всё.
Беда в том, что «какой прокси лучше» зависит от сайта. Партнёрский банк пускает в API только с согласованных адресов, и выход питерского филиала в этот список не попал. Зеркало пакетов через европейский выход отдаёт втрое быстрее, чем через московский. Канал в филиале узкий, и выгрузка отчёта через него идёт минутами. Health check всё это не видит: прокси жив и отвечает, просто конкретный сайт через него не работает.
Fairway решает именно эту задачу. Он встаёт перед вашими прокси, копит статистику по каждой паре «домен, прокси» и на каждом запросе выбирает лучший выход для этого сайта. Это один бинарник на Go без зависимостей, с веб‑панелью внутри, под лицензией Apache 2.0.
Как это устроено

Клиенты знают один адрес, http://fairway:7770. Панель живёт на порту 7771. Апстримами могут быть HTTP, HTTPS и SOCKS5-прокси, а также прямой выход.
Настройка строится из трёх понятий:
прокси — ваши апстримы с адресом, учёткой, страной и комментарием;
лист — набор прокси, внутри которого fairway выбирает сам: например, все российские прокси компании или только внешние выходы;
правило домена — какой лист обслуживает сайт и, если нужно, свои таймауты, лимиты и длительность бана.


Всё это правится в панели или в одном JSON‑файле. Конфиг перечитывается на лету, а битая правка не применяется, так что файл можно спокойно держать в git и раскатывать Ansible.
Установка на Linux‑сервер одной командой:
curl -fsSLO https://raw.githubusercontent.com/Crasher69/fairway/main/install.sh
sudo bash install.sh --allow-from 10.0.0.0/8
Скрипт ставит systemd‑сервис и открывает порты только для внутренней сети. Есть также бинарники под Windows и macOS и Docker‑образ.
Как выбирается прокси
Для каждой пары «домен, прокси» fairway считает одно число, цену. Это оценка того, сколько секунд занял бы через этот прокси типичный ответ в 64 КБ. В неё входят время подключения, время до первого байта и скорость, а ошибки увеличивают цену: прокси, у которого падает каждый десятый запрос, выглядит заметно дороже быстрого, но надёжного.
Дальше работает взвешенная лотерея: чем дешевле прокси, тем больше трафика он получает, но не весь. Ещё 10% запросов уходит на разведку, иначе лидер забрал бы всё, и узнать, что медленный прокси починился, было бы неоткуда.
Главное правило: медленный прокси остаётся в ротации с маленькой долей, а забаненный выключается полностью, но только для того сайта, где его забанили. На скриншоте выше spb-01 не пускают в банковское API, и он не получает туда запросов, но продолжает обслуживать все остальные сайты.
Бан наступает после трёх ответов 403 или 429 подряд, трёх обрывов соединения подряд или страницы проверки вместо содержимого. Если сайт так и не заработал через этот прокси, каждый следующий бан вдвое длиннее: 5, 10, 20, 40 минут, потом час. После бана прокси получает ровно один пробный запрос, и только успех возвращает его в ротацию.
Запрос, который не дождался ответа через один прокси, fairway повторяет через другой, и клиент этого не замечает. Но POST, который дошёл до сайта и не получил ответа, не повторяется: лучше вернуть клиенту ошибку, чем отправить в банк второй платёж.
Зачем балансировщику расшифровка TLS
HTTPS обычно идёт туннелем, и балансировщик видит только байты. Ответ 403 внутри туннеля не отличить от нормального, поэтому бан ловится только по обрывам.
Для важных доменов можно включить расшифровку. Fairway выпускает свой корневой сертификат, вы раскатываете его групповой политикой, и для этих сайтов становятся видны настоящие HTTP‑статусы и страницы антибот‑проверок. Это даёт точное обнаружение банов и повтор запроса через другой прокси прямо посреди соединения. Для софта с certificate pinning, например банк‑клиентов, расшифровку лучше не включать.
Плагины на WebAssembly
Корпоративная специфика у всех своя: список прокси живёт в CMDB, о банах надо писать дежурному, безопасники хотят метку на каждом запросе. Для этого в fairway есть плагины.
Плагин — это модуль WebAssembly, который работает в песочнице. Он не видит ни диска, ни сети, ни памяти fairway, а получает только те права, которые администратор подтвердил при включении: читать или менять конфиг, получать события о банах, ходить по HTTP на разрешённые хосты, обрабатывать запросы. Один и тот же файл плагина работает на Linux, Windows и macOS.

Плагины бывают двух видов.
base работает с конфигом, событиями и таймером: например, раз в 10 минут сверяет лист с CMDB.
hook вдобавок видит запросы через прокси и может менять заголовки и тело или ответить сам. Вот целиком плагин, который добавляет заголовок к каждому запросу:
package main
import fairway "github.com/Crasher69/fairway/plugin-sdk"
func init() { fairway.Register(fairway.Plugin{ OnRequest: func(req *fairway.HTTPRequest) (*fairway.HTTPResponse, error) { req.Headers.Set("X-Corp-Gateway", "fairway-msk") return nil, nil }, })
}
func main() {}
обирается он обычным Go (GOOS=wasip1 GOARCH=wasm), а настройки, права и лог плагина видны на его странице в панели. Упавший или зависший плагин не кладёт прокси: запрос просто идёт без его правки.

Чем это отличается от Squid и HAProxy
Они тоже умеют несколько апстримов, но балансируют статически: по кругу, по числу соединений или по весам, а живость проверяют одним health check. Fairway отвечает на другой вопрос: работает ли этот прокси с этим сайтом и насколько хорошо. Squid он не заменяет: кеш, ACL и интеграция с AD остаются на нём, а fairway решает, в какой из них отправить запрос.
По скорости запаса хватает: на локальном стенде это около 60 тысяч запросов в секунду через туннели и около 7 тысяч запросов в секунду с расшифровкой.
Итого
Если вы хоть раз выясняли, почему у бухгалтерии банк‑клиент не открывается, а у разработчиков открывается, fairway закрывает этот класс проблем. Он сам замечает, какой прокси не работает с каким сайтом, убирает его только для этого сайта и возвращает, когда тот починится.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.