Архитектура высокой доступности
Оригинал: High-Availability Architecture

Привет @a-kuprin (https://github.com/a-kuprin)!
Пара вопросов:
Целевая архитектура (обзор)
- Какие сервисы будут общедоступными? Судя по графику, я ожидаю, что все 4:
Edge-API
дапи: край-SRV
дапи: узел-мгр
версия
Действительно ли нам нужно, чтобы все они не были частными?
Столп Б — децентрализованное API становится одноцелевым
- Как вы думаете масштабировать сервис, который обрабатывает обратный вызов PoC? Он использует высокопроизводительное локальное хранилище для артефактов PoC. Это хранилище похоже на дерево Меркла и требует высокой производительности для чтения и записи.
Мы хотим, чтобы один экземпляр отвечал за весь один цикл PoC?
Постоянные обновления
С первой частью однозначно согласен, давайте сделаем думаю вторую часть можно отложить
Kubernetes и Helm (цель развертывания)
Я поддерживаю идею разрешить развертывание Kubernetes. Но я бы оставил простейший подход «один экземпляр для каждой службы» в качестве базового подхода к развертыванию в Deploy/join . Чтобы не усложнять регистрацию для мелких майнеров
В целом, это твердая долгосрочная цель. Я бы попытался разделить его на более мелкие шаги по реализации и этапам развертывания. И сохраняйте их совместимость с развернутой версией:
постоянное обновление (я думаю, самое высокое)
- отдельное событие, указанное в node-manager/etc.
- ....

Какие сервисы, как ожидается, будут общедоступными?
Я думаю, что достаточно иметь Edge-API (который на самом деле берет общедоступные конечные точки из децентрализованного API), а версия dapi должна быть частной, поскольку это инструмент администратора и node-mgr, который используется внутри devshardd, порожденного versiond.
2. Как вы думаете масштабировать сервис, который обрабатывает обратный вызов PoC?
Я думаю, это следует проанализировать глубже, я не учел это дерево Меркла, например, использование хранилища.
Kubernetes и Helm (цель развертывания)
Я предполагаю, что это дополнительная функция, а не замена развертывания Docker Compose. Когда рефакторинг высокой доступности готов, довольно легко использовать команду перезаписи агента для управления. И я думаю, что даже небольшие майнеры могут извлечь выгоду из kubernetes, поскольку полная установка и обновление (если инженер DevOps знаком с kube и такими инструментами, как ArgoCD) может быть еще проще и плавнее.

Привет @gmorgachev (https://github.com/gmorgachev)!
Я взял часть 1 (поточные обновления), она реализована здесь: #1437 (https://github.com/gonka-ai/gonka/pull/1437)
Вкратце, что он делает: когда управление публикует новый двоичный файл с тем же именем версии, versiond запускает новый двоичный файл на новом порту, отправляет на него трафик только после того, как он действительно готов, и позволяет старому завершить все текущие запросы перед его остановкой. Если новый двоичный файл не запускается, старый просто продолжает работать.
О совместимости с уже развернутыми версиями: старые уже выпущенные двоичные файлы продолжают работать под новой версией, а одноэкземплярные установки SQLite сохраняют текущее поведение. Для мелких майнеров ничего не меняется, никаких изменений конфигурации не требуется.
Охвачено тестами: модульные тесты, тест docker e2e и новый тест testermint, в котором длинный запрос выдерживает бинарное обновление.
Дайте мне знать, если у вас есть какие-либо вопросы.

2. Как вы думаете масштабировать сервис, который обрабатывает обратный вызов PoC? Он использует высокопроизводительное локальное хранилище для артефактов PoC. Это хранилище похоже на дерево Меркла и требует высокой производительности для чтения и записи.
Думаю, достаточно использовать NATS в режиме репликации Raft и хранилище в памяти.
Поток будет таким: Тонкий (dapi: Edge-srv) прокси-сервер, получающий обратные вызовы PoC и отправляющий их в NATS. NATS реплицирует их для обеспечения отказоустойчивости, но не сохраняется, чтобы поддерживать скорость. И затем у нас есть один потребитель для подписи и отправки артефакта в цепочку (Active Worker) и несколько потребителей только для чтения (Passive Worker) (также компонент dapi).
Пассивные работники просто извлекают все новые сообщения, и сообщение имеет TTL, например, 24 часа. Активный работник управляется очередью, поэтому каждое сообщение имеет ровно одного потребителя, и сообщение подтверждается, когда оно подписано и отправлено в цепочку.
Активные и пассивные исполнители создают smst-дерево и сохраняют артефакты на собственных томах.

Сделайте каждую службу на стороне узла горизонтально масштабируемой и безопасной с последовательным обновлением, без единой точки отказа, а также предоставьте готовое к использованию Kubernetes развертывание, упакованное в виде диаграммы Helm.
Это предложение основано на текущей архитектуре ( ../high-availability-architecture.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/high-availability-architecture.md)) и дизайне бинарного развертывания ( ../rolling-update.md). (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md)). Это не меняет цепочку вывода.
1. Цели
- Нет единой точки отказа. Каждая служба на стороне узла запускает ≥2 экземпляров, в идеале на разных машинах.
- Последовательные обновления без прерывания текущей работы (см. ../rolling-update.md).
- Масштабируйте путь чтения/обслуживания (цепочные запросы, разветвление вывода, обратные вызовы PoC) по горизонтали.
- Сохраняйте эффекты цепочки ровно один раз. Даже при наличии множества экземпляров каждая транзакция цепочки и каждое действие, управляемое блоком, происходят один раз.
- Готовое к использованию Kubernetes развертывание через Helm. Вышеупомянутая декомпозиция HA является необходимым условием для упаковки стека узлов в виде диаграммы Helm: одна диаграмма (или поддиаграммы) для каждого сервиса, внешние общие зависимости (Postgres, NATS, Redis) и собственные примитивы K8s для масштабирования, работоспособности, дренажа и чередующихся обновлений (см. §9).
Что уже соответствует планке
Что сегодня блокирует HA
decentralized-api (dapi) — это единый монолитный процесс: его прослушиватель/фазовый механизм цепочки событий не имеет выбора лидера, он встраивает NATS для каждого процесса, хранит локальный набор ключей для подписи и напрямую запрашивает цепочку. Два экземпляра dapi будут дублировать транзакции и команды ML (см. ../high-availability-architecture.md §5). Это предложение реструктурирует dapi, чтобы сделать его более доступным.
2. Целевая архитектура (обзор)
┌─────────────────────────────┐ клиенты ────▶ │ прокси (N, за L4 LB) │ └────────────┬─────────────┘ ┌â€────────────────────┬────────┴──────────┬──────────────────────┐ ▼ ▼ ▼ ▼ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ Edge-api │ │ dapi: Edge-srv │ │ dapi: node-mgr │ │ versiond[-router]│ │ (N, HA) │ │ (N, PoC/admin) │ │ (брокер/PoC) │ │ → devshardd │ │ Цепочка HA │ │ Обратные вызовы REST│ │ + лидер │ │ (общий PG) │ │ прокси+кеш │ └───────┬───────┘ └───────┬───────┘ └─────────────┘ │ + концентратор событий │ │ │ └──────┬───────┘ │ публиковать сообщения цепочки │ публиковать сообщения цепочки │ события (pub/sub) ▼ ▼ │ ┌─────────────────────────────── ┐ ├─────────────▶│ NATS (автономный, HA) │ ◀── одна очередь для │ │ тем: Chain.tx, event.*, ... │ всех связанных по цепочке сообщений │ └─────────────────┬───────────────┘ │ ▼ (одиночный потребитель/сообщение) │ ┌────────────────────┐ │ │ служба подписи │ знаки с теплым ключом, │ │ (N, группа очередей) │ отправляет tx в цепочку │ └──────────┬───────────┘ │ ▼ ▼ цепочка вывода Redis (состояние общего Edge-API: (gRPC + RPC) блокировка лидера, курсор событий, кеш)
Два столпа:
- Edge-api становится высокодоступным цепным доступом + концентратором событий. Отдельный уровень Edge-API собирает события блоков из цепочки, публикует их для NATS (и других подписчиков) и кэширует запросы цепочки, чтобы каждая служба узла не открывала свои собственные подписки gRPC/RPC. Эту же поверхность позже можно повторно использовать в информационных панелях и мониторинге (чтение API + потока событий) без привязки наблюдаемости к dapi или devshardd. Redis поддерживает общее состояние и выборы лидера Edge-API.
- B. dapi разбивается на независимо масштабируемые сервисы вокруг автономной очереди HA NATS, службы подписывания без сохранения состояния, Postgres в качестве единственного бэкэнда с сохранением состояния и обработчиков REST/echo без сохранения состояния.
3. Компонент A — Edge-API в качестве концентратора событий высокой доступности и цепного кэша
Назначение отдельного Edge-API
Edge-api выделен как отдельный сервис, поэтому узел имеет один уровень, ориентированный на цепочку, с двумя заданиями:
- Блокировать события — один раз подписаться на цепочку вывода (события CometBFT NewBlock + per-tx), нормализовать их и передать поток NATS и другим подписчикам (dapi node-manager, devshardd, PoC-воркерам).
- Кэш запросов — обслуживайте и кэшируйте API-интерфейсы чтения цепочки (участников, эпох, параметров, условного депонирования и т. д.), чтобы каждый потребитель не набирал gRPC/RPC независимо.
Такое разделение позволяет dapi и devshardd сосредоточиться на логике своего домена, в то время как Edge-API отвечает за то, как узел взаимодействует с цепочкой. Одна и та же поверхность чтения HTTP/gRPC и разветвление событий могут быть повторно использованы позднее системами мониторинга и мониторинга (страницы состояния, инструменты управления операциями, внешние наблюдатели) без встраивания клиентов цепочки в каждый двоичный файл продукта.
Сегодня Edge-API — это прокси-сервер без сохранения состояния, доступный только для чтения, для 22 маршрутов уровня A. Мы расширяем его, чтобы сделать его уровнем цепочки доступа для всего узла.
3.1 Переместите прослушиватель событий в Edge-API
- Переместите прослушиватель событий цепочки, который находится в dapi ( decentrized-api/internal/event_listener/ ), в Edge-api.
- Edge-api подписывается на цепочку (события CometBFT WebSocket NewBlock + RPC BlockResults для каждой передачи) и повторно публикует нормализованные события для NATS и других потребителей (службы dapi, devshardd) через pub/sub.
- Потребители (dapi node-manager, PoC-сервисы, devshardd) подписываются на события Edge-API вместо того, чтобы открывать свои собственные цепочки подписок. Это удаляет N независимых цепочек подписок и централизует обработку блоков.
3.2 Выборы лидера (только один экземпляр запускает события)
Edge-api масштабируется до N экземпляров, но побочные эффекты, управляемые блоками, должны сработать один раз. Итак:
- Каждый экземпляр остается синхронизированным (каждый может обслуживать запросы и удерживать теплый курсор событий), но только избранный лидер перемещает курсор канонического блока и излучает авторитетный поток событий.
- Redis удерживает блокировку лидера (например. SET NX PX аренду с продлением) и общий курсор событий (last_processed_height), чтобы новый лидер возобновлял работу точно с того места, где остановился старый — без пробелов и повторов.
- Испускаемые события распространяются на все экземпляры (и нижестоящие службы) через pub/sub, поэтому последователи и потребители видят один и тот же поток, созданный лидером. При потере лидера другой экземпляр берет блокировку в пределах TTL аренды и продолжает работу с курсора Redis.
3.3 Redis как общее состояние Edge-API
Таким образом, Edge-api становится высокодоступным прокси + кешем для цепочки вывода и единственным источником событий цепочки для узла.
3.4 Потребители перестают напрямую запрашивать цепочку
- dapi больше не запрашивает цепочку вывода напрямую. Он использует HA Edge-API для цепного чтения и подписывается на Edge-API для событий. Это сужает dapi до его уникальных обязанностей (ниже).
- devshardd также подписывается на события Edge-API (создание/установление условного депонирования, новый блок/фаза), а не поддерживает собственную цепочку WebSocket, сокращая количество подключений для каждой дочерней цепочки. (devshardd сохраняет собственный путь передачи gRPC для споров или направляет их через очередь подписывающих сторон — см. §5.)
4. Столп Б — децентрализованное API становится одноцелевым
После компонента A dapi освобождается от обязанностей по цепным запросам и подписке на события. Его остающиеся уникальные обязанности заключаются в следующем:
- Менеджер узлов (брокер: жизненный цикл узла ML на фазу эпохи).
- Админ-панель (админка REST: узел CRUD, регистрация модели, отчет о настройке и т.д.).
- Обработчик PoC/cPoC + парсер (прием артефактов, обработка фиксации, проверка вне сети, предоставление доказательств).
Они становятся независимо развертываемыми сервисами, в основном без сохранения состояния. Единственные изменяемые серверные части — это Postgres (общий) и очередь NATS.
4.1 Декомпозиция сервиса
4.2 Автономная очередь HA NATS
Замените встроенный NATS для каждого процесса ( decentrized-api/internal/nats/server/server.go ) на автономный кластерный NATS (JetStream), общий для всех экземпляров.
- Каждое связанное с цепочкой сообщение публикуется в NATS (субъект, например, Chain.tx), содержащее само сообщение и метаданные. Продюсерами являются любые службы, которым необходимо осуществлять запись в цепочку (менеджер узлов, фиксация PoC, Edge-srv, споры devshardd).
- Очередь — это единственный, надежный и достаточно упорядоченный путь к цепочке. Он выдерживает перезапуск экземпляра и отделяет производителей от подписывающей стороны.
4.3 Служба подписывающей стороны (подпись горячим ключом, однократное использование)
- Подписывающее лицо является потребителем группы очередей NATS для Chain.tx: с помощью группы очередей каждое сообщение доставляется ровно одному экземпляру подписывающего лица, поэтому N подписывающих лиц разделяют нагрузку, но никогда не подписывают одно и то же сообщение дважды.
- Подписывающая сторона держит «теплый» ключ (связку ключей Cosmos), заключает в authz.MsgExec плату с холодной учетной записи, где это применимо (текущая модель в cosmosclient/tx_manager), подписывает и осуществляет широковещательную передачу в цепочку.
- Состояние широковещательной рассылки/наблюдения/повторной попытки перемещается в устойчивые потоки NATS (эквиваленты txs_to_send/txs_to_observe), поэтому любой экземпляр подписывающей стороны может принимать повторные попытки. Ключи идемпотентности (например, идентификатор вывода + тип сообщения) защищают от дублирования отправки при повторных попытках или аварийном переключении.
- Поскольку подписание изолировано за очередью, «теплый» ключ находится только у подписывающего лица — другим службам связка ключей никогда не понадобится.
4.4 Postgres как единственный сервер обработки данных высокой доступности
- Все изменяемые состояния, которые должны пережить потерю экземпляра, хранятся в Postgres (полезные данные, статистика, артефакты PoC/метаданные фиксации, конфигурация/курсоры по мере необходимости). SQLite KV для каждого процесса (например, dapi apiconfig Last_processed_height) перемещается в Postgres/Redis, поэтому любой экземпляр является взаимозаменяемым.
- Это отражает правило devshardd: несколько экземпляров ⇒ Postgres ( ../high-availability-architecture.md §4).
4.5 Эхо-работники без сохранения состояния (обратные вызовы PoC + администратор)
- Уровень Echo HTTP для обратных вызовов PoC и REST администратора является неизменяемым: он только читает/записывает Postgres или публикует в NATS. Поэтому он может запускать N экземпляров за прокси без координации.
- Единственные операции, требующие семантики однократного выполнения (фазовые команды этапа), принадлежат избранному лидером менеджеру узла, а не эхо-работникам.
5. Постоянные обновления
Последовательные обновления применяются для каждой службы и повторно используют дизайн в ../rolling-update.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md). Стек высокой доступности зависит от одной и той же семантики стока на двух уровнях: двоичная замена внутри работающего супервизора и эвакуация всего хоста за липким маршрутизатором.
Концепции непрерывного обновления (сводка)
План чередующихся обновлений (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md) определяет, как мы развертываем новые двоичные файлы, не прекращая работу в реальном времени. Три гарантии оператора:
- Запросы, уже принятые старым экземпляром, могут завершиться — мы не завершаем работу, пока работа еще продолжается.
- Новый экземпляр должен быть готов до того, как он получит трафик.
- После того как новый экземпляр станет доступен, к нему пойдут новые запросы; старый экземпляр разряжается до момента простоя, а затем завершает работу.
Синий/зеленый + слив внутри версии (Часть 1 §1.1). Когда управление публикует то же имя версии, новый двоичный файл sha256, versiond загружает новый devshardd, запускает его на новом порту, в то время как старый дочерний элемент продолжает обслуживать, ждет GET /ready (а не только прием TCP), атомарно меняет местами таблицу маршрутов в процессе, чтобы новые запросы попадали в новый дочерний элемент, отмечает слив старого дочернего элемента (вне таблицы маршрутов, но все еще жив), опрашивает текущий счетчик до нуля (или истечения времени ожидания), затем SIGTERM с длительным завершением работы благодать. Старое и новое могут перекрываться только тогда, когда устойчивое состояние находится в общем Postgres — SQLite является однозаписывающим и не может поддерживать одновременных дочерних элементов (часть 1, §1.2).
Два дренажных слоя — не сливаться (Часть 1 §1.7–§1.8).
Во время двоичного обмена devshardd липкая маршрутизация не изменяется: маршрутизатор по-прежнему указывает на versiond-N:8080 ; меняется только дочерний порт внутри версии. Слив маршрутизатора предназначен для случаев, когда сам процесс версии должен покинуть пул (уменьшение масштаба, обслуживание хоста, обновление двоичного файла с версией).
Сигналы, которые план добавляет в devshardd: /healthz (живучесть), /ready (ворота готовности к смене маршрутов), /drain/status (работа в реальном времени) и настраиваемый DEVSHARD_SHUTDOWN_GRACE, чтобы потоки SSE не обрезались через 5 с.
Картирование Kubernetes (часть 2). Те же гарантии сопоставляются с RollingUpdate ( maxUnavailable: 0 , maxSurge: 1 ), readinessProbe → /ready , preStop (удаление из конечных точек до SIGTERM ) и TerminationGracePeriodSeconds, согласованными с отсрочкой завершения работы. Эвакуация пода/хоста соответствует части 1 §1.8 (очистка маршрутизатора), а не двоичному обмену с инверсией.
Как чередующиеся обновления применяются в этом предложении высокой доступности
- Службы без сохранения состояния (edge-api, эхо-работники Edge-srv, подписывающее лицо): стандартное скользящее обновление — запуск нового экземпляра, проверка работоспособности, маршрутизация к нему, слив старого. За их маршрутизаторами/группами очередей это прозрачно.
- Службы, выбираемые лидером (эмиттер Edge-API, менеджер узлов): скользящее обновление может вызвать передачу управления лидером; аренда Redis + курсор делает это безопасным (новый лидер возобновляется с курсора).
- versiond / devshardd (та же версия, новый двоичный файл): синий/зеленый + сток внутри versiond, при этом общий Postgres обеспечивает правильное перекрытие старого и нового — см. ../rolling-update.md §1.
- Замена хоста versiond, уменьшение масштаба или обслуживание: слив на versiond-router (отметьте восходящий поток вниз, подождите, пока закрепленные депонированные устройства простаивают, затем остановите хост) — см. ../rolling-update.md §1.8.
- NATS/Redis/Postgres: запуск в собственных режимах HA/кластера; обновляются с использованием собственных процедур обновления, независимо от развертывания приложений.
6. Kubernetes и Helm (цель развертывания)
Оверлеи Docker Compose ( local-test-net/ , Deploy/join/ ) сегодня доказывают топологию с несколькими экземплярами; производственная высокая доступность должна быть реализована в Kubernetes с диаграммой Helm, которая кодирует те же границы сервисов, что и в этом предложении.
Диаграмма Хелма сама по себе не создает монолитной HA.
Предполагаемая форма диаграммы (высокий уровень)
Шлем с доставкой
- Единая зонтичная диаграмма (или приложение-из-приложений) для узла Gonka: включение/отключение наложений высокой доступности (мультиграничное API, многоверсионность, внешний NATS/Redis) через значения.
- Документированные значения для PGHOST , URL-адреса NATS, URL-адреса Redis, цепочки URL-адресов gRPC/RPC, количества реплик, ограничений ресурсов и времени ожидания постепенного завершения работы, согласованного с продолжительностью вывода/SSE.
- CI: шаблон рулевого управления / контрольная информация об изменениях диаграммы; дополнительный вид дым.
Compose остается путем разработки/интеграционного тестирования; Helm станет целью для разработки Kubernetes, как только будут реализованы основные этапы A–B и поэтапные этапы 1–5.
7. Примечания
- Redis против NATS JetStream KV для курсора/блокировки — не добавляйте Redis, если JetStream KV достаточно. (Предложение предполагает использование Redis в указанном направлении.)
- Аннулирование кеша для кеша цепочки Edge-API вокруг границ эпохи/фазы. Это следует использовать повторно (https://github.com/gonka-ai/gonka/pull/1272).

Привет @a-kuprin (https://github.com/a-kuprin)!
Пара вопросов:
Целевая архитектура (обзор)
- Какие сервисы будут общедоступными? Судя по графику, я ожидаю, что все 4:
Edge-API
дапи: край-SRV
дапи: узел-мгр
версия
Действительно ли нам нужно, чтобы все они не были частными?
Столп Б — децентрализованное API становится одноцелевым
- Как вы думаете масштабировать сервис, который обрабатывает обратный вызов PoC? Он использует высокопроизводительное локальное хранилище для артефактов PoC. Это хранилище похоже на дерево Меркла и требует высокой производительности для чтения и записи.
Мы хотим, чтобы один экземпляр отвечал за весь один цикл PoC?
Постоянные обновления
С первой частью однозначно согласен, давайте сделаем думаю вторую часть можно отложить
Kubernetes и Helm (цель развертывания)
Я поддерживаю идею разрешить развертывание Kubernetes. Но я бы оставил простейший подход «один экземпляр для каждой службы» в качестве базового подхода к развертыванию в Deploy/join . Чтобы не усложнять регистрацию для мелких майнеров
В целом, это твердая долгосрочная цель. Я бы попытался разделить его на более мелкие шаги по реализации и этапам развертывания. И сохраняйте их совместимость с развернутой версией:
постоянное обновление (я думаю, самое высокое)
- отдельное событие, указанное в node-manager/etc.
- ....

Какие сервисы, как ожидается, будут общедоступными?
Я думаю, что достаточно иметь Edge-API (который на самом деле берет общедоступные конечные точки из децентрализованного API), а версия dapi должна быть частной, поскольку это инструмент администратора и node-mgr, который используется внутри devshardd, порожденного versiond.
2. Как вы думаете масштабировать сервис, который обрабатывает обратный вызов PoC?
Я думаю, это следует проанализировать глубже, я не учел это дерево Меркла, например, использование хранилища.
Kubernetes и Helm (цель развертывания)
Я предполагаю, что это дополнительная функция, а не замена развертывания Docker Compose. Когда рефакторинг высокой доступности готов, довольно легко использовать команду перезаписи агента для управления. И я думаю, что даже небольшие майнеры могут извлечь выгоду из kubernetes, поскольку полная установка и обновление (если инженер DevOps знаком с kube и такими инструментами, как ArgoCD) может быть еще проще и плавнее.

Привет @gmorgachev (https://github.com/gmorgachev)!
Я взял часть 1 (поточные обновления), она реализована здесь: #1437 (https://github.com/gonka-ai/gonka/pull/1437)
Вкратце, что он делает: когда управление публикует новый двоичный файл с тем же именем версии, versiond запускает новый двоичный файл на новом порту, отправляет на него трафик только после того, как он действительно готов, и позволяет старому завершить все текущие запросы перед его остановкой. Если новый двоичный файл не запускается, старый просто продолжает работать.
О совместимости с уже развернутыми версиями: старые уже выпущенные двоичные файлы продолжают работать под новой версией, а одноэкземплярные установки SQLite сохраняют текущее поведение. Для мелких майнеров ничего не меняется, никаких изменений конфигурации не требуется.
Охвачено тестами: модульные тесты, тест docker e2e и новый тест testermint, в котором длинный запрос выдерживает бинарное обновление.
Дайте мне знать, если у вас есть какие-либо вопросы.

2. Как вы думаете масштабировать сервис, который обрабатывает обратный вызов PoC? Он использует высокопроизводительное локальное хранилище для артефактов PoC. Это хранилище похоже на дерево Меркла и требует высокой производительности для чтения и записи.
Думаю, достаточно использовать NATS в режиме репликации Raft и хранилище в памяти.
Поток будет таким: Тонкий (dapi: Edge-srv) прокси-сервер, получающий обратные вызовы PoC и отправляющий их в NATS. NATS реплицирует их для обеспечения отказоустойчивости, но не сохраняется, чтобы поддерживать скорость. И затем у нас есть один потребитель для подписи и отправки артефакта в цепочку (Active Worker) и несколько потребителей только для чтения (Passive Worker) (также компонент dapi).
Пассивные работники просто извлекают все новые сообщения, и сообщение имеет TTL, например, 24 часа. Активный работник управляется очередью, поэтому каждое сообщение имеет ровно одного потребителя, и сообщение подтверждается, когда оно подписано и отправлено в цепочку.
Активные и пассивные исполнители создают smst-дерево и сохраняют артефакты на собственных томах.

Make every node-side service horizontally scalable and rolling-update safe, with no single point of failure, and deliver a Kubernetes-ready deployment packaged as a Helm chart .
This proposal builds on the current architecture ( ../high-availability-architecture.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/high-availability-architecture.md) ) and the binary-rollout design ( ../rolling-update.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md) ). It does not change the inference-chain.
1. Goals
- No single point of failure. Every node-side service runs ≥2 instances, ideally across machines.
- Rolling updates with zero dropped in-flight work (see ../rolling-update.md ).
- Scale the read/serve path (chain queries, inference fan-out, PoC callbacks) horizontally.
- Keep exactly-once chain effects. Even with many instances, each chain transaction and each block-driven action happens once .
- Kubernetes-ready deployment via Helm. The HA decomposition above is the prerequisite for packaging the node stack as a Helm chart : one chart (or subcharts) per service, externalized shared dependencies (Postgres, NATS, Redis), and native K8s primitives for scaling, health, drain, and rolling updates (see §9).
What already meets the bar
What blocks HA today
decentralized-api (dapi) is a single monolithic process: its chain event listener / phase engine has no leader election , it embeds a per-process NATS , holds a local keyring for signing, and queries the chain directly . Two dapi instances would duplicate transactions and ML commands (see ../high-availability-architecture.md §5). This proposal restructures dapi so it can be made highly available.
2. Target architecture (overview)
┌──────────────────────────────┐ clients ────▶ │ proxy (N, behind L4 LB) │ └───────────────┬───────────────┘ ┌──────────────────────┬──────────┴───────────┬───────────────────────┐ ▼ ▼ ▼ ▼ ┌─────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ edge-api │ │ dapi: edge-srv │ │ dapi: node-mgr │ │ versiond[-router]│ │ (N, HA) │ │ (N, PoC/admin) │ │ (broker/PoC) │ │ → devshardd │ │ HA chain │ │ REST callbacks│ │ + leader │ │ (shared PG) │ │ proxy+cache │ └───────┬────────┘ └───────┬────────┘ └────────────────┘ │ + event hub │ │ │ └──────┬───────┘ │ publish chain msgs │ publish chain msgs │ events (pub/sub) ▼ ▼ │ ┌──────────────────────────────────────┐ ├─────────────▶│ NATS (standalone, HA) │ ◀── one queue for │ │ subjects: chain.tx, events.*, ... │ all chain-bound msgs │ └───────────────────┬──────────────────┘ │ ▼ (single consumer / msg) │ ┌──────────────────────┐ │ │ signer service │ signs with warm key, │ │ (N, queue group) │ sends tx to chain │ └──────────┬───────────┘ │ ▼ ▼ inference-chain Redis (shared edge-api state: (gRPC + RPC) leader lock, event cursor, cache)
Two pillars:
- A. edge-api becomes the highly-available chain access + event hub. A separate edge-api tier gathers block events from the chain, publishes them to NATS (and other subscribers), and caches chain queries so node services do not each open their own gRPC/RPC subscriptions. The same surface can later be reused by dashboards and monitoring (read APIs + event stream) without coupling observability to dapi or devshardd. Redis backs edge-api's shared state and leader election.
- B. dapi is decomposed into independently-scalable services around a standalone HA NATS queue, a stateless signer service , Postgres as the only stateful backend, and stateless REST/echo workers.
3. Pillar A — edge-api as the HA event hub & chain cache
Purpose of a separate edge-api
edge-api is split out as its own service so the node has one chain-facing tier with two jobs:
- Block events — subscribe to the inference-chain once (CometBFT NewBlock + per-tx events), normalize them, and transmit the stream to NATS and other subscribers (dapi node-manager, devshardd, PoC workers).
- Query cache — serve and cache chain read APIs (participants, epochs, params, escrows, etc.) so every consumer does not dial gRPC/RPC independently.
That separation keeps dapi and devshardd focused on their domain logic while edge-api owns how the node talks to the chain. The same HTTP/gRPC read surface and event fan-out can be reused later by dashboard and monitoring systems (status pages, ops tooling, external observers) without embedding chain clients in each product binary.
Today edge-api is a stateless read-only proxy for 22 Tier A routes. We extend it to be the chain-access layer for the whole node.
3.1 Move the event listener into edge-api
- Relocate the chain event listener that lives in dapi ( decentralized-api/internal/event_listener/ ) into edge-api.
- edge-api subscribes to the chain (CometBFT WebSocket NewBlock + RPC BlockResults per-tx events) and re-publishes normalized events to NATS and other consumers (dapi services, devshardd) via pub/sub.
- Consumers (dapi node-manager, PoC services, devshardd) subscribe to edge-api events instead of opening their own chain subscriptions. This removes N independent chain subscriptions and centralizes block processing.
3.2 Leader election (only one instance triggers events)
edge-api scales to N instances, but block-driven side effects must fire once . So:
- Every instance stays in sync (each can serve queries and hold a warm event cursor), but only the elected leader advances the canonical block cursor and emits the authoritative event stream.
- Redis holds the leader lock (e.g. SET NX PX lease with renewal) and the shared event cursor ( last_processed_height ) so a new leader resumes exactly where the old one stopped — no gaps, no replays.
- Emitted events are propagated to all instances (and downstream services) via pub/sub, so followers and consumers see the same stream the leader produced. On leader loss, another instance takes the lock within the lease TTL and continues from the Redis cursor.
3.3 Redis as edge-api shared state
edge-api thus becomes a highly-available proxy + cache for the inference-chain , and the single source of chain events for the node.
3.4 Consumers stop querying the chain directly
- dapi no longer queries the inference-chain directly. It uses HA edge-api for chain reads and subscribes to edge-api for events. This shrinks dapi to its unique responsibilities (below).
- devshardd likewise subscribes to edge-api events (escrow created/settled, new block/phase) rather than maintaining its own chain WebSocket, reducing per-child chain connections. (devshardd keeps its own gRPC tx path for disputes, or routes them through the signer queue — see §5.)
4. Pillar B — decentralized-api becomes single-purpose
After Pillar A, dapi sheds chain-query and event-subscription duties. Its remaining unique responsibilities are:
- Node manager (broker: ML node lifecycle per epoch phase).
- Admin panel (admin REST: node CRUD, model registration, setup report, etc.).
- PoC / cPoC handler + scraper (artifact ingest, commit worker, off-chain validation, proof serving).
These become independently deployable, mostly-stateless services. The only mutable backends are Postgres (shared) and the NATS queue.
4.1 Service decomposition
4.2 Standalone HA NATS queue
Replace the embedded per-process NATS ( decentralized-api/internal/nats/server/server.go ) with a standalone, clustered NATS (JetStream) shared by all instances.
- Every chain-bound message is published to NATS (subject e.g. chain.tx ), carrying the message and metadata. Producers are any service that needs to write to chain (node-manager, PoC commit, edge-srv, devshardd disputes).
- The queue is the single, durable, ordered-enough path to the chain. It survives instance restarts and decouples producers from the signer.
4.3 Signer service (warm-key signing, exactly-once consume)
- The signer is a NATS queue-group consumer of chain.tx : with a queue group, each message is delivered to exactly one signer instance , so N signers share the load but never double-sign the same message.
- The signer holds the warm key (Cosmos keyring), wraps in authz.MsgExec with feegrant from the cold account where applicable (current model in cosmosclient/tx_manager ), signs, and broadcasts to the chain.
- Broadcast/observe/retry state moves to the durable NATS streams ( txs_to_send / txs_to_observe equivalents) so any signer instance can pick up retries. Idempotency keys (e.g. inference id + msg type) guard against duplicate submission across retries/failover.
- Because signing is isolated behind the queue, the warm key lives only in the signer — other services never need the keyring.
4.4 Postgres as the only HA data backend
- All mutable state that must survive instance loss lives in Postgres (payloads, stats, PoC artifacts/commits metadata, config/cursors as needed). Per-process SQLite KV (e.g. dapi apiconfig last_processed_height ) moves to Postgres/Redis so any instance is interchangeable.
- This mirrors the devshardd rule: multi-instance ⇒ Postgres ( ../high-availability-architecture.md §4).
4.5 Stateless echo workers (PoC callbacks + admin)
- The Echo HTTP layer for PoC callbacks and admin REST is immutable : it only reads/writes Postgres or publishes to NATS. Therefore it can run N instances behind the proxy with no coordination.
- The only operations needing single-execution semantics (phase-driven stage commands) belong to the leader-elected node-manager , not the echo workers.
5. Rolling updates
Rolling updates apply per service and reuse the design in ../rolling-update.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md) . The HA stack depends on the same drain semantics at two layers: binary swap inside a live supervisor, and whole-host evacuation behind the sticky router.
Rolling-update concepts (summary)
The rolling-update plan (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md) defines how we roll out new binaries without dropping in-flight work . Three operator guarantees:
- Requests already accepted by an old instance may finish — we do not kill while work is still running.
- A new instance must be ready before it receives traffic.
- After the new instance is reachable, new requests go to it; the old instance drains until idle, then exits.
Blue/green + drain inside versiond (Part 1 §1.1). When governance publishes a same version name, new sha256 binary, versiond downloads the new devshardd , starts it on a new port while the old child keeps serving, waits for GET /ready (not just TCP accept), atomically swaps the in-process route table so new requests hit the new child, marks the old child draining (out of the route table but still alive), polls in-flight count until zero (or a drain timeout), then SIGTERM with a long shutdown grace . Old and new can overlap only when durable state lives in shared Postgres — SQLite is single-writer and cannot support concurrent children (Part 1 §1.2).
Two drain layers — do not conflate (Part 1 §1.7–§1.8).
During a devshardd binary swap, sticky routing is unchanged: the router still points at versiond-N:8080 ; only the child port inside versiond swaps. Router drain is for when the versiond process itself must leave the pool (scale-down, host maintenance, versiond binary upgrade).
Signals the plan adds to devshardd : /healthz (liveness), /ready (readiness gate for route swap), /drain/status (in-flight work), and configurable DEVSHARD_SHUTDOWN_GRACE so long SSE streams are not cut at 5s.
Kubernetes mapping (Part 2). The same guarantees map to RollingUpdate ( maxUnavailable: 0 , maxSurge: 1 ), readinessProbe → /ready , preStop (drop from endpoints before SIGTERM ), and terminationGracePeriodSeconds aligned with shutdown grace. Pod/host evacuation maps to Part 1 §1.8 (router drain), not the in-versiond binary swap.
How rolling updates apply in this HA proposal
- Stateless services (edge-api, edge-srv echo workers, signer): standard rolling update — bring a new instance up, health-check, route to it, drain the old. Behind their routers / queue groups this is transparent.
- Leader-elected services (edge-api emitter, node-manager): a rolling update may trigger a leader handoff; the Redis lease + cursor make this safe (new leader resumes from the cursor).
- versiond / devshardd (same version, new binary): blue/green + drain inside versiond, with the shared Postgres making old+new overlap correct — see ../rolling-update.md §1.
- versiond host replace, scale-down, or maintenance: drain at versiond-router (mark upstream down, wait for pinned escrows idle, then stop the host) — see ../rolling-update.md §1.8.
- NATS / Redis / Postgres : run in their own HA/cluster modes; updated with their native rolling procedures, independent of app rollouts.
6. Kubernetes & Helm (deployment target)
Docker Compose overlays ( local-test-net/ , deploy/join/ ) prove multi-instance topology today; production HA should land on Kubernetes with a Helm chart that encodes the same service boundaries as this proposal.
A Helm chart alone does not make a monolith HA.
Intended chart shape (high level)
Helm deliverable
- Single umbrella chart (or app-of-apps) for a Gonka node: enable/disable HA overlays (multi edge-api, multi versiond, external NATS/Redis) via values.
- Documented values for PGHOST , NATS URL, Redis URL, chain gRPC/RPC URLs, replica counts, resource limits, and graceful shutdown timeouts aligned with inference/SSE duration.
- CI : helm template / helm lint on chart changes; optional kind smoke.
Compose remains the developer / integration-test path; Helm is the target for production Kubernetes once Pillars A–B and phasing steps 1–5 are in place.
7. Notes
- Redis vs NATS JetStream KV for the cursor/lock — avoid adding Redis if JetStream KV suffices. (Proposal assumes Redis per the stated direction.)
- Cache invalidation for edge-api chain cache around epoch/phase boundaries. This should be reused (https://github.com/gonka-ai/gonka/pull/1272)

Hi @a-kuprin (https://github.com/a-kuprin) !
Couple questions:
Target architecture (overview)
- Which serveses are expected to be publicly available?Based on graph i expect all 4:
edge-api
dapi: edge-srv
dapi: node-mgr
versiond
Do we really need all of them to be not private?
Pillar B — decentralized-api becomes single-purpose
- How do you think to scale service which handles PoC callback? It uses high performant local storage for PoC artifacts. This storage is merkle-tree like and requires high performance for r/w
Of we want single instance to be responsible for the whole single PoC cycle?
Rolling updates
Definitely agree with part 1, let's do it I think we can postpone part 2
Kubernetes & Helm (deployment target)
I support the idea to allow kubernetes deployment. But i'd keep simplest single-instance-per-each-service as base deploy approach in deploy/join . To not overcomplicate onboarding for small miners
Overall, that's a solid long term goal. I'd try to split it in smaller steps for implementation and phases of deploy. And keep them compartible with deployed version:
rolling updated (i think highest)
separate event listered from node-manager / etc
- ....

Which serveses are expected to be publicly available?
I think it's enough to have edge-api (that actually takes public endpoints from decentralized-api) and versiond dapi should be private as it is admin tool and node-mgr that is used internally by devshardd spawned by versiond
2. How do you think to scale service which handles PoC callback?
I think this should be analyzed deeper, I didn't took into account this merkle-tree like storage use
Kubernetes & Helm (deployment target)
I assume it to be additional feature, not the replacement of docker compose deployment. When high-availability refactoring is ready it is quite easy using agent rewrite compose to helm. And I think even small miners can benefit from kubernetes, as full installation and update (if DevOps engeneer is familiar with kube and tooling like ArgoCD) could be even easier and smoother

Hi @gmorgachev (https://github.com/gmorgachev) !
I picked up part 1 (rolling updates), it is implemented here: #1437 (https://github.com/gonka-ai/gonka/pull/1437)
In short, what it does: when governance publishes a new binary with the same version name, versiond starts the new binary on a new port, sends traffic to it only after it is really ready, and lets the old one finish all its in-flight requests before stopping it. If the new binary fails to start, the old one just keeps serving.
About compatibility with already deployed versions: old already-released binaries keep working under the new versiond, and SQLite single-instance setups keep the current behavior. Nothing changes for small miners, no config changes needed.
Covered by tests: unit tests, a docker e2e test, and a new testermint test where a long request survives the binary update.
Let me know if you have any questions.

2. How do you think to scale service which handles PoC callback? It uses high performant local storage for PoC artifacts. This storage is merkle-tree like and requires high performance for r/w
I think it's enough to use NATS in Raft replication mode, and in-memory store.
The flow would be: Thin (dapi: edge-srv) proxy getting PoC callbacks and sending them to NATS NATS replicating them for fault-tolerance but doesn't persist to keep it fast And then we have single-consumer for signing and send artifact to chain (Active Worker) , and multiple read-only consumers (Passive Workers) (also dapi component)
Passive workers just pull all new messages, and message has TTL for example 24h, Active worker is managed by queue so each message has exactly one consumer, and message is acknowledged when it is signed and sent to chain.
Сделайте каждую службу на стороне узла горизонтально масштабируемой и безопасной с последовательным обновлением, без единой точки отказа, а также предоставьте готовое к использованию Kubernetes развертывание, упакованное в виде диаграммы Helm.
Это предложение основано на текущей архитектуре ( ../high-availability-architecture.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/high-availability-architecture.md)) и дизайне бинарного развертывания ( ../rolling-update.md). (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md)). Это не меняет цепочку вывода.
1. Цели
Что уже соответствует планке
Что сегодня блокирует HA
decentralized-api (dapi) — это единый монолитный процесс: его прослушиватель/фазовый механизм цепочки событий не имеет выбора лидера, он встраивает NATS для каждого процесса, хранит локальный набор ключей для подписи и напрямую запрашивает цепочку. Два экземпляра dapi будут дублировать транзакции и команды ML (см. ../high-availability-architecture.md §5). Это предложение реструктурирует dapi, чтобы сделать его более доступным.
2. Целевая архитектура (обзор)
┌─────────────────────────────┐ клиенты ────▶ │ прокси (N, за L4 LB) │ └────────────┬─────────────┘ ┌â€────────────────────┬────────┴──────────┬──────────────────────┐ ▼ ▼ ▼ ▼ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ Edge-api │ │ dapi: Edge-srv │ │ dapi: node-mgr │ │ versiond[-router]│ │ (N, HA) │ │ (N, PoC/admin) │ │ (брокер/PoC) │ │ → devshardd │ │ Цепочка HA │ │ Обратные вызовы REST│ │ + лидер │ │ (общий PG) │ │ прокси+кеш │ └───────┬───────┘ └───────┬───────┘ └─────────────┘ │ + концентратор событий │ │ │ └──────┬───────┘ │ публиковать сообщения цепочки │ публиковать сообщения цепочки │ события (pub/sub) ▼ ▼ │ ┌─────────────────────────────── ┐ ├─────────────▶│ NATS (автономный, HA) │ ◀── одна очередь для │ │ тем: Chain.tx, event.*, ... │ всех связанных по цепочке сообщений │ └─────────────────┬───────────────┘ │ ▼ (одиночный потребитель/сообщение) │ ┌────────────────────┐ │ │ служба подписи │ знаки с теплым ключом, │ │ (N, группа очередей) │ отправляет tx в цепочку │ └──────────┬───────────┘ │ ▼ ▼ цепочка вывода Redis (состояние общего Edge-API: (gRPC + RPC) блокировка лидера, курсор событий, кеш)Два столпа:
3. Компонент A — Edge-API в качестве концентратора событий высокой доступности и цепного кэша
Назначение отдельного Edge-API
Edge-api выделен как отдельный сервис, поэтому узел имеет один уровень, ориентированный на цепочку, с двумя заданиями:
Такое разделение позволяет dapi и devshardd сосредоточиться на логике своего домена, в то время как Edge-API отвечает за то, как узел взаимодействует с цепочкой. Одна и та же поверхность чтения HTTP/gRPC и разветвление событий могут быть повторно использованы позднее системами мониторинга и мониторинга (страницы состояния, инструменты управления операциями, внешние наблюдатели) без встраивания клиентов цепочки в каждый двоичный файл продукта.
Сегодня Edge-API — это прокси-сервер без сохранения состояния, доступный только для чтения, для 22 маршрутов уровня A. Мы расширяем его, чтобы сделать его уровнем цепочки доступа для всего узла.
3.1 Переместите прослушиватель событий в Edge-API
3.2 Выборы лидера (только один экземпляр запускает события)
Edge-api масштабируется до N экземпляров, но побочные эффекты, управляемые блоками, должны сработать один раз. Итак:
3.3 Redis как общее состояние Edge-API
3.4 Потребители перестают напрямую запрашивать цепочку
4. Столп Б — децентрализованное API становится одноцелевым
После компонента A dapi освобождается от обязанностей по цепным запросам и подписке на события. Его остающиеся уникальные обязанности заключаются в следующем:
Они становятся независимо развертываемыми сервисами, в основном без сохранения состояния. Единственные изменяемые серверные части — это Postgres (общий) и очередь NATS.
4.1 Декомпозиция сервиса
4.2 Автономная очередь HA NATS
Замените встроенный NATS для каждого процесса ( decentrized-api/internal/nats/server/server.go ) на автономный кластерный NATS (JetStream), общий для всех экземпляров.
4.3 Служба подписывающей стороны (подпись горячим ключом, однократное использование)
4.4 Postgres как единственный сервер обработки данных высокой доступности
4.5 Эхо-работники без сохранения состояния (обратные вызовы PoC + администратор)
5. Постоянные обновления
Последовательные обновления применяются для каждой службы и повторно используют дизайн в ../rolling-update.md (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md). Стек высокой доступности зависит от одной и той же семантики стока на двух уровнях: двоичная замена внутри работающего супервизора и эвакуация всего хоста за липким маршрутизатором.
Концепции непрерывного обновления (сводка)
План чередующихся обновлений (https://github.com/gonka-ai/gonka/blob/ak/pixelplex-refactoring-into-r2/devshard/docs/rolling-update.md) определяет, как мы развертываем новые двоичные файлы, не прекращая работу в реальном времени. Три гарантии оператора:
Синий/зеленый + слив внутри версии (Часть 1 §1.1). Когда управление публикует то же имя версии, новый двоичный файл sha256, versiond загружает новый devshardd, запускает его на новом порту, в то время как старый дочерний элемент продолжает обслуживать, ждет GET /ready (а не только прием TCP), атомарно меняет местами таблицу маршрутов в процессе, чтобы новые запросы попадали в новый дочерний элемент, отмечает слив старого дочернего элемента (вне таблицы маршрутов, но все еще жив), опрашивает текущий счетчик до нуля (или истечения времени ожидания), затем SIGTERM с длительным завершением работы благодать. Старое и новое могут перекрываться только тогда, когда устойчивое состояние находится в общем Postgres — SQLite является однозаписывающим и не может поддерживать одновременных дочерних элементов (часть 1, §1.2).
Два дренажных слоя — не сливаться (Часть 1 §1.7–§1.8).
Во время двоичного обмена devshardd липкая маршрутизация не изменяется: маршрутизатор по-прежнему указывает на versiond-N:8080 ; меняется только дочерний порт внутри версии. Слив маршрутизатора предназначен для случаев, когда сам процесс версии должен покинуть пул (уменьшение масштаба, обслуживание хоста, обновление двоичного файла с версией).
Сигналы, которые план добавляет в devshardd: /healthz (живучесть), /ready (ворота готовности к смене маршрутов), /drain/status (работа в реальном времени) и настраиваемый DEVSHARD_SHUTDOWN_GRACE, чтобы потоки SSE не обрезались через 5 с.
Картирование Kubernetes (часть 2). Те же гарантии сопоставляются с RollingUpdate ( maxUnavailable: 0 , maxSurge: 1 ), readinessProbe → /ready , preStop (удаление из конечных точек до SIGTERM ) и TerminationGracePeriodSeconds, согласованными с отсрочкой завершения работы. Эвакуация пода/хоста соответствует части 1 §1.8 (очистка маршрутизатора), а не двоичному обмену с инверсией.
Как чередующиеся обновления применяются в этом предложении высокой доступности
6. Kubernetes и Helm (цель развертывания)
Оверлеи Docker Compose ( local-test-net/ , Deploy/join/ ) сегодня доказывают топологию с несколькими экземплярами; производственная высокая доступность должна быть реализована в Kubernetes с диаграммой Helm, которая кодирует те же границы сервисов, что и в этом предложении.
Диаграмма Хелма сама по себе не создает монолитной HA.
Предполагаемая форма диаграммы (высокий уровень)
Шлем с доставкой
Compose остается путем разработки/интеграционного тестирования; Helm станет целью для разработки Kubernetes, как только будут реализованы основные этапы A–B и поэтапные этапы 1–5.
7. Примечания