Архитектура 2.0
Атака криптомайнера на прошлой неделе стала поводом — но не причиной. К моменту инцидента уже накопился достаточный список архитектурных ограничений, которые становились все очевиднее по мере роста сети. Я давно понимал, что точечными правками здесь не обойтись.
Решение — пересмотреть все. Не отдельные компоненты — а принципы, на которых построена архитектура.
Основные ограничения:
Дефицит выходных узлов. Покупать серверы за рубежом становится сложнее. Я начал привлекать участников, которые поднимают Core-ноды самостоятельно — но механизм подключения оставался ручным: SSH-доступ, настройка вручную, ручной контроль.
Дефицит доменных имен. Старая архитектура требовала отдельного домена для каждого узла — входного и выходного. При масштабировании это стало операционным ограничением: не хватало доменов, чтобы принимать новых участников даже при наличии серверных ресурсов.
Доверие между узлами ядра. Узлы ядра были объединены в доверенную SSH-сеть. Удобно для управления — опасно в эксплуатации. Компрометация одного узла открывала доступ ко всем остальным: именно это и было использовано в инциденте.
Жесткая древовидная топология. Каждый узел ядра держал вокруг себя свою группу входных узлов. При масштабировании каждый такой узел становился потенциальным бутылочным горлышком. Слои не масштабировались независимо.
Что изменилось.
От дерева — к двум независимым кластерам.
Входной слой — однородный Kubernetes-кластер. Любой Entry-под может обслужить любого клиента. Новый участник добавляет worker node в кластер — поды распределяются автоматически. Выходной слой — федеративный: независимые Core-ноды участников. Слои масштабируются независимо друг от друга, количество входных и выходных узлов не связано.
Решает: жесткую топологию, проблемы при масштабировании.
Нулевое доверие между узлами.
Узлы не доверяют друг другу по умолчанию. Все взаимодействие — только через взаимную аутентификацию по сертификатам (mTLS). Управляющий кластер — единственный удостоверяющий центр. Никакого SSH-доверия, никаких доверенных сетей между нодами ядра.
Решает: горизонтальное распространение при компрометации одного узла.
Один домен на кластер.
Входной слой живет под единым доменом ячейки. Выходной — тоже. Entry-под при подключении к Core-ноде указывает SNI ячейки — независимо от реального IP конкретного сервера. Для наблюдателя весь трафик ячейки выглядит как взаимодействие с одним сервисом.
Решает: дефицит доменных имен; один домен теперь обслуживает произвольное количество узлов.
Subscription вместо статической конфигурации.
Клиент держит не конкретный адрес, а URL подписки. При обновлении — приложение само забирает актуальную конфигурацию. Смена узла, ротация gRPC-пути, переключение ячейки — для пользователя все это происходит прозрачно.
Решает: операционную нагрузку при ротации узлов и конфигураций.
Онбординг через Docker.
Участник устанавливает Docker, получает одноразовый join-токен, запускает контейнер. Контейнер генерирует ключевую пару локально, отправляет CSR на управляющий кластер, получает сертификат и регистрируется в сети. Приватный ключ не покидает сервер участника. SSH-доступа к серверу не передается.
Решает: операционную сложность онбординга; подключение новых участников — через вступительный токен и запуск контейнера.
Выделенный управляющий кластер.
Отдельный независимый кластер управляющих серверов с Kubernetes control plane, HA etcd и Root CA — отдельно от плоскости данных. Здесь же сервисная инфраструктура: Telegram-бот, резервное копирование, subscription-сервис. Управляющий кластер не участвует в передаче трафика — только управление и PKI.
Решает: единую точку отказа; отказоустойчивость за распределенного кластера серверов.
В итоге сеть масштабируется горизонтально в обоих слоях — быстро и без ручных операций. Компрометация узла или целой ячейки — изолированный инцидент, не затрагивающий остальных. Участники восстанавливают инфраструктуру с нуля через тот же механизм онбординга.
Подробная техническая документация Архитектуры 2.0 — в разделе архитектуры.