Перейти к основному содержимому

Куда развивать Netgap: размышления о направлениях и порядке работ

· 11 мин. чтения
Андрей Ганюшкин
Enterprise Architect | Author & Lead Maintainer of Netgap

Важно. Это размышления, а не план работ. Ни один пункт ниже не является обязательством, ни один порядок — зафиксированным роадмепом, ни одна версия в примере — обещанием даты. Я разбираю, какие направления развития вообще есть у Netgap, чем они отличаются по цене и отдаче и почему один порядок работ мне кажется разумнее другого. Через месяц измерения или запрос от пользователя могут переставить приоритеты — и это нормально.

Три предыдущих поста дали материал, из которого можно строить рассуждение о развитии: измерения MVP показали, где стоит потолок производительности, каталог оптимизаций — какие приёмы имеют смысл, а каталог вариантов logical airgap — какое вообще есть пространство решений. Здесь я пытаюсь свести это в одну картину: что делать дальше и в каком порядке.

Отправная точка: где Netgap сегодня

На момент написания этой статьи Netgap — это комбинация трёх позиций каталога вариантов:

  • 2.2 pull-модель — основа. Исполнитель netgap-executor из доверенного сегмента сам подключается к двум gRPC-мостам шлюза; доверенный сегмент не принимает ни одного входящего соединения.
  • 2.1 терминация TCPnetgap-gateway в недоверенной зоне поднимает HTTP-сервер, полностью завершает клиентское соединение и держит его в пуле, отслеживая таймауты и брошенные соединения; исходящий вызов целевого сервиса делает уже исполнитель.
  • 4.1 RPC с узкой схемой — контракт мостов описан двумя короткими .proto: RequestBridgeApi.GetNextRequest отдаёт correlation_id, headers, body, path, method и trace_context, а ResponseBridgeApi.SendResponse принимает correlation_id, headers, body, status_code, trace_context и target_processing_time. Больше через границу не проходит ничего.

Между запросом и ответом состояние живёт в асинхронной очереди в памяти шлюза — в терминах каталога это посредник уровня 3, но локальный, внутри процесса в недоверенной зоне, а не отдельная инфраструктура. Оба RPC сейчас унарные: исполнитель забирает задачи опросом с адаптивным интервалом. Сквозная корреляция, логи аудита по каждому запросу, разделение задержки на накладные расходы Netgap и время работы целевого сервиса, трассировка через OTLP, метрики Prometheus и события в SIEM — все это уже реализовано.

В текущей реализации Netgap нет ни взаимной аутентификации мостов (mTLS), ни лимита длины очереди, ни ограничений на размер тела и набор допустимых путей. Это не скрытые недостатки, а честная граница первой версии — и одновременно готовый список ближайших работ.

По каким критериям я сравниваю направления

Чтобы порядок работ не сводился к «сделаем то, что интереснее писать», каждое направление ниже оценивается по четырём осям:

ОсьВопросЗачем нужна
ТрудозатратыЭто работа в существующих компонентах или новая сущность?Отсекает красивое, но неподъёмное на текущем этапе
Прирост функциональностиЧто пользователь сможет сделать после, чего не мог до?Отличает функцию от рефакторинга
Расширение применимостиСколько новых сценариев и строк каталога открывается?Один шаг может открыть три сценария
Ценность для продвиженияСнимает ли шаг возражение, которое обесценивает весь проект?Без mTLS и фильтрации продукт не проходит первый чек-лист

К этим четырём осям я добавляю правило, выведенное из сопоставления каталога оптимизаций с измерениями MVP: работать имеет смысл только в том слое, где находится текущий потолок. В MVP потолок стоял в однопоточности брокера — и любые микрооптимизации Rust-кода не сдвинули бы сквозной RPS вообще. Сейчас в боевой архитектуре брокера нет, очередь живёт в памяти шлюза, а значит потолок нужно заново измерить, прежде чем что-то в нём оптимизировать. Пока измерений на боевом контуре нет, любые пункты «про производительность» в рассуждениях ниже стоят ниже пунктов «про применимость».

Что и в каком порядке реализовать

Базовый сценарий один и тот же во всём разделе: внешний клиент синхронно вызывает API во внутреннем контуре. Порядок ниже определяется не номерами версий, а тем, насколько шаг расширяет этот сценарий на единицу вложенных усилий.

Цвет ячейки в таблицах ниже — это оценка значения внутри своей колонки, от самого выгодного к самому невыгодному:

лучшеехорошеесреднеехудшее

Шкала у каждой колонки своя: в «трудозатратах» зелёное — это меньше, а в «приросте функциональности» и «ценности для продвижения» зелёное — это больше.

Шаг 1. Дёшево и сразу заметно

Что добавляет к сценариюТрудозатратыПрирост функциональностиЦенность для продвижения
TLS и mTLS на мостах — взаимная аутентификация сторон обязательна для любого разговора с безопасникамиНизкие: библиотека уже в стеке, работа в конфигурации и в установлении соединенияВысокий: закрывает очевидный вопрос «а кто там подключился к шлюзу»Максимальная: без mTLS продукт не проходит первый же чек-лист
Server-streaming вместо опроса — исполнитель держит открытый поток и получает задачу событием, а не через poll_interval_ms: 50Низкие: изменение схемы и цикла исполнителяСредний: уходит средняя задержка в половину интервала опроса и паразитная нагрузкаСредняя: даёт понятную цифру в замерах
Allow-list методов и путей, лимит размера тела — вариант 4.4 каталогаНизкие: проверка на входе шлюзаВысокий: шлюз начинает быть фильтром, а не только транспортомВысокая: это то, что отличает logical airgap от обратного прокси
Ограниченная очередь и backpressure — вместо неявного роста памяти явный отказ с 503НизкиеСредний: предсказуемая деградация под нагрузкойСредняя: снимает вопрос «а что будет при всплеске»

Все четыре пункта — работа в существующих компонентах, без новых сущностей. Они же закрывают ровно те замечания, которые возникают при первом чтении конфигурации.

Шаг 2. Превращает продукт в платформу вариантов

Что добавляет к сценариюТрудозатратыПрирост функциональностиЦенность для продвижения
Абстракция транспорта моста: один интерфейс, несколько реализаций — gRPC (есть), файловый спул (3.2), брокер (3.1)Средние: выделение границы в коде, тесты на каждую реализациюОчень высокий: одна и та же логика начинает работать поверх разных механизмов разрываМаксимальная: реализация разных вариантов паттерна
Подписанный конверт сообщения (4.3) поверх любого транспортаСредние: формат, подпись, ротация ключейВысокий: доверие перестаёт зависеть от канала — обязательное условие для спула и диодаВысокая: позволяет говорить про транспорт «любой», не теряя модель угроз

Это самый выгодный шаг по соотношению «усилия к результату»: он не добавляет ни одной новой функции для конечного пользователя, но открывает сразу несколько строк каталога.

Шаг 3. Открывает соседние сценарии малой кровью

Что добавляет к сценариюТрудозатратыПрирост функциональностиЦенность для продвижения
Односторонний режим приёма — fire-and-forget для логов, метрик и событий, без обратного каналаНизкие поверх шага 2: тот же транспорт, ответ не ожидаетсяВысокий: закрывает целый класс задач «вынести телеметрию из контура»Высокая: это самый частый первый запрос от промышленных заказчиков
Спул-транспорт, совместимый с диодом (3.2 плюс 0.2)Средние: формат пакета, повторы, контроль целостностиВысокий: продукт начинает работать там, где сеть между сегментами запрещена в принципеОчень высокая: делает возможным разговор с сегментом КИИ
Асинхронный контракт ticket/callback (4.5) как альтернативный режим APIСредние: хранилище заявок и его очисткаСредний: снимает ограничение на длительность операцииСредняя

Шаг 4. Дорогое и нишевое

Что добавляет к сценариюТрудозатратыПрирост функциональностиЦенность для продвижения
Инспекция и CDR содержимого (3.7)Высокие: форматы, лицензии на движки, производительностьСредний для API-сценария: полезно для файлов, почти не нужно для JSONВысокая, но только в сегменте документооборота
Human-in-the-loop и четыре глаза (4.6)Высокие: интерфейс оператора, роли, журнал решенийНизкий для синхронного сценария — он перестаёт быть синхроннымСредняя: заметно в регламентных внедрениях
Интеграция с аппаратным диодом (0.2)Высокие: железо, стенд, свой протокол без подтвержденийВысокий для односторонних сценариев, нулевой для синхронныхОчень высокая, но требует шага 3 как фундамента
Несколько целевых сервисов на одном исполнителеСредние: маршрутизация по пути и правиламСредний: удобство эксплуатацииНизкая

Логика порядка простая и та же, что в каталоге оптимизаций: сначала снимается то, что мешает применить продукт вообще (шифрование и фильтрация), потом расширяется пространство применимости (транспорты), потом добавляются соседние сценарии, и только затем — дорогая ниша.

Как выглядел бы роадмеп

Ещё раз: это пример раскладки шагов по релизам, а не план выпусков. Номера версий условны, дат нет намеренно — они появятся только когда шаг будет реально начат.

Условная версияЧто входитКритерий готовности
0.x + 1mTLS на обоих мостах, allow-list методов и путей, лимит размера тела, ограниченная очередь с 503Запуск без mTLS невозможен по конфигурации; запрос вне allow-list не доходит до исполнителя и попадает в аудит
0.x + 2Server-streaming на RequestBridge, замеры сквозной задержки до и послеИзмеренная медианная задержка ниже прежней минимум на половину интервала опроса; исполнитель без задач не создаёт трафика
0.x + 3Абстракция транспорта моста, gRPC как первая реализация поверх неёСмена транспорта — правка конфигурации, а не кода; набор тестов моста проходит для каждой реализации
0.x + 4Подписанный конверт сообщения, ротация ключейКомпрометация посредника не позволяет подменить сообщение; проверка подписи обязательна и выключается только явно
0.x + 5Односторонний режим приёма, спул-транспортРаботает контур, где между сегментами нет сетевой связности вообще
1.0Стабильные схемы мостов, зафиксированный формат конверта и конфигурации, полный набор метрик и аудитаОбратная совместимость контрактов заявлена и проверяется тестами
после 1.0Ticket/callback, маршрутизация на несколько целей, инспекция содержимого, интеграция с диодомПо запросу, а не по инерции

Отдельно про производительность. Пока боевой контур не измерен так же честно, как MVP, я не готов писать в роадмеп ни одной оптимизации. Порядок здесь тоже понятен из предыдущей статьи: сначала снять ограничения ОС и убрать блокировки, потом аллокации и копии, потом расшить посредника — и только затем affinity, io_uring и прочее. Первым же шагом в этом направлении будет не оптимизация, а воспроизводимый нагрузочный стенд на боевых компонентах: без него любая оптимизация — угадывание.

Чего делать не стоит

Отказ от варианта — такое же решение, как и его реализация, поэтому явно:

  • Reverse-tunnel (2.3) как режим работы продукта. Даёт удобство ценой сквозного потока внутри туннеля — то есть ровно того, что паттерн должен устранять. Это будет не Netgap, а ещё один connector.
  • SPA и port-knocking (2.5) внутри продукта. Полезный периметровый приём, но он не создаёт разрыва и относится к зоне ответственности межсетевого экрана, а не шлюза.
  • Собственный брокер как обязательный посредник (3.1). MVP уже показал, что брокер становится потолком производительности; очередь в памяти шлюза дешевле и быстрее. Брокер имеет смысл только как одна из реализаций транспорта после шага 2 — для тех, у кого он и так есть.
  • Разделяемая память между ВМ (3.6). Микросекундные задержки красивы, но требуют, чтобы оба сегмента жили на одном гипервизоре. Это противоречит основному сценарию, где сегменты разнесены.
  • Экзотика уровня 5. Оптические, акустические и бумажные каналы в продукте не нужны: их пропускная способность несовместима с синхронным API, а как транспорт для одностороннего режима они проигрывают спулу и диоду по всем параметрам, кроме зрелищности.
  • Kernel bypass и zero-copy на границе. Разобрано в каталоге оптимизаций: splice отменяет инспекцию содержимого, а инспекция и есть смысл шлюза. К тому же потолок сегодня не там.
  • Неограниченные очереди ради пиковых цифр. Оттуда же: без backpressure всплеск нагрузки превращается не в замедление, а в отказ по памяти.

Что я считаю главным принципом

Строгость изоляции нужно выбирать не максимальную, а достаточную для сценария — и дальше вкладываться в то, чтобы выбранный уровень был реализован без дыр. Аппаратный диод, поставленный ради галочки перед сервисом, который всё равно должен отвечать синхронно, защищает хуже, чем аккуратно сделанная pull-модель с mTLS и валидацией схемы.

Из этого принципа и вытекает вся раскладка выше: шаг 1 закрывает дыры в уже выбранном уровне, шаг 2 даёт возможность выбирать уровень под сценарий, а шаги 3 и 4 добавляют уровни, которых сейчас нет.

Важно. Всё вышеизложенное — рассуждение, а не фиксация планов. Порядок шагов отражает моё сегодняшнее понимание соотношения «усилия — результат» и может измениться от первого же серьёзного запроса или первого же честного замера. Актуальное состояние работ всегда стоит смотреть в репозитории и в документации, а не в блоге.


Это четвёртый технический пост серии. Ранее: измерения MVP и локализация узкого места, каталог оптимизаций logical airgap и анализ вариантов реализации паттерна.