Как задеплоить Ethereum Full Node в Kubernetes: практический гайд для DevOps
Если вы DevOps-инженер и впервые сталкиваетесь с блокчейном, разворачивание Ethereum ноды может показаться непростой и непривычной задачей. В отличие от стандартных веб-приложений, здесь два независимых клиента, аутентификация через JWT, огромные объёмы данных и долгая синхронизация. Но если разобраться в особенностях, то деплой через Kubernetes не сложнее, чем настройка обычного stateful-приложения.
В этой статье мы разберем из каких слоёв состоит современный Ethereum (после Merge), как взаимодействуют execution и consensus клиенты, где в этой цепочке используется JWT. Я покажу практический процесс разворачивания и его отладки Ethereum full node в k8s кластере с использованием написанного для этого Helm chart. В качестве основной среды для инфраструктуры я буду использовать свой MacBook Pro c macOS 26.3, на котором развернем kubernetes кластер через k3d. Далее, мы задеплоим ноду, проверим её работоспособность, разберём типичные проблемы и их решения. В качестве клиентов будем использовать Geth (execution layer) и Nimbus (consensus layer).
Содержание:
- Архитектура Ethereum: “два мозга” одной ноды
- Как они взаимодействуют?
- Системные требования
- Шаг 1: Создание локального k8s кластера
- Шаг 2: Установка Ingress Controller
- Шаг 3: Подготовка конфигурации helm chart
- Шаг 4: Проверка helm chart перед установкой
- Шаг 5: Установка helm chart в k8s кластер
- Шаг 6: Smoke-тесты (RPC/Beacon)
- Обновление чарта
- Бонус: Включаем Checkpoint Sync для Nimbus
- Выводы
Архитектура Ethereum: “два мозга” одной ноды
С сентября 2022 года (после события The Merge) Ethereum работает на Proof-of-Stake консенсусе и разделён на два слоя. Представьте, что Ethereum нода — это организм с двумя полушариями мозга: одно отвечает за «что делать» (выполнение транзакций), другое — за «правильно ли это» (консенсус). Оба работают независимо, но без связи между ними нода мертва.
Execution Layer (EL) - «левое полушарие». Это рациональная часть ноды, «исполнитель» (runtime). Слой, который:
Это рациональная часть ноды, «исполнитель». Слой, который:
- Обрабатывает транзакции и выполняет смарт-контракты в EVM
- Поддерживает mempool
- Хранит состояние блокчейна (accounts, contracts, balances)
- Предоставляет JSON-RPC API для dApps и кошельков (по умолчанию порт 8545)
Популярные клиенты: Geth, Nethermind, Erigon, Besu, Reth
Consensus Layer (CL) — «правое полушарие». А это «контролёр», который следит за порядком в сети. Слой, который:
- Управляет консенсусом и валидацией блоков
- Контролирует стейкинг и работу валидаторов
- Финализирует блоки через механизм Casper FFG
- Предоставляет Beacon API (по умолчанию порт 5052)
Популярные клиенты: Lighthouse, Prysm, Teku, Nimbus, Lodestar
Как они взаимодействуют?
Как и полушария мозга связанные между собой, EL и CL обмениваются данными через Engine API — защищённый JSON-RPC интерфейс поверх HTTP. Для взаимной аутентификации используется JWT token — своего рода общий секрет, подтверждающий что оба клиента «свои». Без него клиенты не смогут обмениваться данными и нода не будет работать.
Системные требования
Если верить сайту, то для EL (geth) понадобятся следующие ресурсы:
- Fast CPU with 4+ cores
- 16 GB+ RAM
- Fast SSD with 2+TB
- 25+ MBit/s bandwidth
Но я бы рекомендовал для production закладывать больше CPU и RAM, так как нагрузка на сеть ethereum постоянно меняется. Для архивной ноды, которая хранит полные исторический данные понадобится больше места (~12TB).
В процессе деплоя ноды и тестирования работоспособности helm-чарта такие ресурcы не понадобятся.
Для CL (Nimbus Beacon Chain), согласно руководству на сайте, потребуется:
- 4GB RAM
- 200GB Storage space
Предварительная настройка окружения
Итак, для деплоя ноды на macos нам понадобятся - helm, kubernetes (k3d) и клиент для взаимодействия kubectl. Если у вас уже поднят свой k8s кластер, то шаг с установкой и настройкой k3d можно пропустить.
Установка инструментов
Проверка версий
Шаг 1: Создание локального k8s кластера
Создаём k3s кластер через k3d с нужными параметрами:
Параметры:
- --servers 1 — один control plane узел (для локального теста достаточно)
- --agents 1 — один worker узел (опционально, можно без него)
- --disable=traefik — по умолчанию k3d использует traefik в качестве ingress-контроллера, для большей универсальности я буду использовать nginx. (опционально, можно оставить traefik)
- -p "80:80@loadbalancer" -p "443:443@loadbalancer" — пробрасываем порты с хоста в k3d loadbalancer (без этого ingress не будет доступен на localhost)
Проверяем, что кластер создан
Выведем список k8s нод:
Если видите статус `Ready` у всех воркеров значит кластер готов к работе.
Шаг 2: Установка Ingress Controller
Для тестирования доступа к сервису изнутри кластера по внешнему url в helm chart созданы ingress. Чтобы эта связка работала, установим ingress-nginx-controller:
При установке, мы задаем имя ingressClass=nginx-dev, чтобы не путаться в будущем. Контроллер создаст Service типа LoadBalancer, k3d свяжет его с проброшенными портами 80/443, и сервис станет доступен на локальном хосте.
В колонке EXTERNAL-IP будут внутренние Docker IP нод k3d (например, как у нас 172.19.0.3,172.19.0.4) —это нормально. Трафик с localhost:80/443 всё равно попадает в ingress благодаря -p флагам при создании кластера (macOS → k3d loadbalancer → ingress-nginx). В целом, для локального теста ingress не обязателен (можно обойтись port-forward), но мы хотим проверить как отрабатывают ingress-сущности нашего чарта — поэтому он нужен.
Шаг 3: Подготовка конфигурации helm chart
Сгенерировать JWT secret
JWT — это по сути 32-байтовый hex-секрет, который должен быть одинаковым у Geth и Nimbus:
Создание values.local.yaml
Текущий values.yaml ориентирован на production (крупные ресурсы и диски), для обычного MacBook это будет тяжело. Сделаем локальный override-файл в папке с чартом helm/values.local.yaml и вставим туда токен, который мы генерировали выше:
В целом, если его не указывать, то chart сгенерирует случайный секрет автоматически. Но при следующем обновлении чарта (helm upgrade) он будет обновляться каждый раз, что приведёт к рестарту подов и перезапуску ноды, это создаст время простоя (downtime). Для стабильного локального тестирования лучше его зафиксировать.
Для ingress свяжем тестовый домен с нашим локальным хостом, добавив в /etc/hosts следующие строки:
Далее сделаю несколько замечаний по поводу конфигурации helm chart.
Почему StatefulSet, а не Deployment?
Ethereum нода — это типичный stateful workload, как база данных. Geth хранит сотни гигабайт состояния блокчейна, Nimbus — десятки гигабайт данных beacon chain. Потеря этих данных означает пересинхронизацию с нуля, которая может занять дни. Поэтому в Helm chart используется StatefulSet, а не Deployment.
StatefulSet даёт нам то, чего Deployment не может: стабильные имена подов (eth-node-geth-0 вместо случайного хеша), что упрощает мониторинг и отладку; гарантированную привязку к хранилищу — при рестарте или перепланировании под получит обратно тот же PersistentVolume с данными блокчейна; упорядоченное обновление — поды обновляются последовательно, и мы можем убедиться, что один клиент успешно поднялся, прежде чем обновлять следующий.
По сути, правило простое: если приложение хранит данные, которые нельзя потерять — используйте StatefulSet.
Про хранилище: hostPath vs PVC
В нашем values.local.yaml мы используем persistence.type=hostPath — данные блокчейна пишутся напрямую в директорию на ноде кластера. Для локального тестирования через k3d это удобно: не нужно настраивать StorageClass и выделять PVC.
Однако у hostPath есть серьёзные ограничения. Данные привязаны к конкретной ноде — если под переедет на другую ноду кластера, он потеряет всю БД блокчейна. Отсутствует контроль доступа — любой под на той же ноде может читать и писать в эту директорию. Нет встроенных механизмов бэкапа и расширения диска.
Для production-окружений рекомендуется использовать PersistentVolumeClaim (PVC) с подходящим StorageClass. В облаке это, как правило, SSD-диски провайдера (gp3 в AWS, pd-ssd в GCP, managed-csi в Azure). Но есть сценарий, когда hostPath в production оправдан — bare-metal кластеры с локальными NVMe-дисками. Если ваш Kubernetes развёрнут на физических серверах, каждый из которых оснащён быстрым NVMe-накопителем, то hostPath (или его более безопасная альтернатива — local PersistentVolume) даёт максимальную производительность без сетевых задержек. Именно так работают многие production-стейкинг операторы: выделенный сервер с NVMe, нода привязана к конкретной машине через nodeAffinity, а данные лежат на локальном диске. В этом случае привязка к ноде — не баг, а фича, потому что Ethereum нода и так stateful и не предполагает миграцию между серверами «на лету».
Шаг 4: Проверка helm chart перед установкой
Склонируем чарт из репо и запустим проверку:
Так же можно запустить тестовый деплой чарта с параметром --dry-run
Шаг 5: Установка helm chart в k8s кластер
Проверим список подов:
Статусы Pending/ContainerCreating/PodInitializing — это нормально на старте, k8s стягивает docker образы клиентов, создаёт volumes и подготавливает поды. Переход в Running может занять 30-60 секунд
На скриншоте видно, что поды залетели на разные ноды кубернетиса: geth на k3d-eth-local-agent-0, а nimbus на k3d-eth-local-server-0.
Проверяем логи Geth:
Geth поднялся и ждет когда синхронизируется beacon chain - Nimbus. После синхронизации напишет "Block synchronisation started" и пойдет дальше синхронизироваться сам.
Проверяем логи Nimbus:
В последней строчке он показывает какой слот (slot=...) и эпоха (epoch=...) сейчас актуальные и сколько по времени осталось синкаться (sync="..d..h..m (0.00%). Всё хорошо, процесс пошел.
Шаг 6: Smoke-тесты (RPC/Beacon)
Пробросим порт Geth RPC на локальный хост и в другой вкладке проверим что он отвечает:
Проверяем версию и процесс синка:
Отлично, geth отвечает.
Теперь сделаем тоже самое с nimbus:
Nimbus корректно возвращает версию
Ожидаемые ответы: HTTP 200 (нода синхронизирована) или HTTP 206 (нода синкается). Оба варианта означают, что Nimbus работает корректно.
Services в helm chart настроены корректно и проксируют запрос к нужному контейнеру. Давайте проверим ingress, кинем RPC-запросы через внешний url:
Отлично. По внешнему url тоже всё работает корректно.
Обновление чарта
Давайте попробуем обновить версию Nimbus. Проверяем актуальную версию на официальном GitHub репо(на момент написания статьи v26.2.1), находим актуальные тег для образа docker, и добавляем изменения в values.local.yaml:
После чего применяем изменения
Проверяем
Проверяем версию:
Отлично, версия обновилась.
Бонус: Включаем Checkpoint Sync для Nimbus
Если вы обратили внимание на логи Nimbus, синхронизация с нуля занимает значительное время — от нескольких дней до недели и более. Nimbus скачивает и верифицирует каждый слот начиная с генезис-блока, что надёжно, но очень медленно.
Существует альтернативный подход — checkpoint sync (он же trusted node sync). Суть в том, что вместо синхронизации с нуля, Nimbus скачивает недавнее финализированное состояние цепочки от доверенного узла и начинает работу уже от этой точки. Это сокращает время синхронизации CL с дней до нескольких минут.
Как это работает:
Nimbus запрашивает последний финализированный checkpoint (состояние + блок) через Beacon API доверенного узла, записывает их в свою базу данных и стартует с этой точки. Исторические блоки до checkpoint'а дозагружаются параллельно (backfill) — это не блокирует работу ноды.
Как использовать:
Для Nimbus checkpoint sync добавим в конфиг дополнительные аргументы запуска:
Флаг --external-beacon-api-url указывает публичный endpoint для загрузки checkpoint state, а --trusted-block-root — корень блока для верификации. Чтобы получить актуальный trusted-block-root можно выполнить команду:
Либо скопировать на сайте beaconstate.info
При успешном выполнении в логах Nimbus вы увидите:
А в логах клиента Geth что-то вроде этого:
Публичные endpoint'ы для checkpoint sync:
Сообщество поддерживает список проверенных публичных endpoint'ов на eth-clients.github.io/checkpoint-sync-endpoints. Популярные для mainnet:
Альтернативный вариант — initContainer с trustedNodeSync:
Можно обойтись без поиска trusted-block-root, создав следующий init-container с trustedNodeSync:
⚠ оба варианта работают только при первом запуске на пустой базе данных. Если Nimbus уже начал синхронизацию с генезиса, эти флаги будут проигнорированы. Чтобы переключиться на checkpoint sync, нужно сначала удалить директорию /data/db в контейнере Nimbus.
Заключение
Мы прошли полный путь от создания локального k8s кластера до работающей Ethereum full node с проверенными RPC и Beacon API эндпоинтами. Давайте зафиксируем ключевые моменты.
Два клиента обязательны — после The Merge Ethereum не работает без связки execution (Geth) и consensus (Nimbus) клиентов. Они общаются через Engine API и без общего JWT секрета нода не заведётся.
StatefulSet, а не Deployment — блокчейн нода хранит сотни гигабайт данных, потеря которых означает дни на пересинхронизацию. StatefulSet гарантирует стабильные имена подов и привязку к хранилищу.
Checkpoint sync экономит дни — вместо синхронизации consensus layer с генезиса (неделя+), checkpoint sync позволяет начать работу за минуты. Главное помнить, что он работает только на пустой БД.
Терпение при синхронизации — даже с checkpoint sync для Nimbus, Geth всё равно потребуется значительное время на snap sync execution layer. Для mainnet это часы или дни в зависимости от железа и канала. Ускорить процесс можно через snapshots.
Мониторинг обязателен — без Prometheus/Grafana вы не поймёте, на каком этапе синхронизация, сколько пиров подключено и есть ли проблемы с ресурсами . Настройка мониторинга для Ethereum ноды — тема для отдельной статьи.
Helm chart делает процесс воспроизводимым: развернуть ноду в другом кластере — вопрос нового values файла. Весь код чарта доступен в репозитории (ссылка).
Если вам интересны практические DevOps гайды по блокчейну и AI, разборы инфраструктуры, Docker/Kubernetes конфигурации и troubleshooting реальных проблем — подписывайтесь на телеграм-канал Art of Chain. Никаких спекуляций и трейдинга — только техническая сторона блокчейн-инфраструктуры.