Два образи замість пʼяти: серверні команди їдуть з одного модуля й мусять ходити в схему БД однією версією, а зонд лишається окремо — у чужій мережі йому не місце з DSN і ключами шифрування. Стек піднімає БД, кеш, міграції, API, колектор і Caddy з автоматичним TLS. Міграції окремою службою, бо API піднімається в кількох примірниках і гонка за схему нікому не потрібна. deploy/README.md описує бекап як три речі: дамп, DEK і JWT-ключ. Без DEK дамп не відновлюється — у БД лише шифротекст. Дорогою виправлено .gitignore: голі "netpulse-agent" і "netpulse-server" ігнорували ще й каталоги cmd/ з кодом команд. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| .env.example | ||
| Caddyfile | ||
| Dockerfile.agent | ||
| Dockerfile.server | ||
| README.md | ||
Розгортання NetPulse
Один хост, docker compose, автоматичний TLS. Такого розгортання
вистачає до кількох тисяч хостів на моніторингу; розносити служби по
машинах має сенс тоді, коли впирається БД, а не застосунок.
Що з чого складається
| Служба | Роль | Порт |
|---|---|---|
db |
PostgreSQL 16 + TimescaleDB — усі дані | — |
cache |
DragonflyDB — черги й тимчасові стани | — |
migrate |
накочування схеми; відпрацьовує і зупиняється | — |
api |
HTTP API + вшитий інтерфейс | 8080 |
collector |
gRPC-колектор, до якого підключаються зонди | 9443 |
proxy |
Caddy: сертифікати, HTTPS, проксі до двох служб | 80/443/9443 |
Назовні дивиться лише proxy. api і collector портів не публікують:
до них ходять через нього.
Перший запуск
git clone <репозиторій> netpulse && cd netpulse
cp deploy/.env.example .env
Заповнити .env. Три значення обовʼязкові й генеруються так:
openssl rand -base64 24 # POSTGRES_PASSWORD
echo "np1=$(openssl rand -hex 32)" # NETPULSE_DEK
openssl rand -base64 48 # NETPULSE_JWT_SECRET
Далі:
docker compose up -d --build
docker compose run --rm api netpulse-user \
-tenant default -login admin -role owner -name "Адміністратор"
Пароль команда спитає інтерактивно — щоб він не осів в історії оболонки й у списку процесів.
Інтерфейс — на https://<NETPULSE_DOMAIN>.
Підключення зонда
В інтерфейсі: Зонди → Додати зонд. Видане запрошення (np_enr_…)
одноразове — після реєстрації воно згоряє.
На машині, де стоятиме зонд:
docker run -d --name netpulse-agent --restart unless-stopped \
--cap-add NET_RAW \
-v netpulse-agent:/var/lib/netpulse \
netpulse/agent:dev \
-server netpulse.example.com:9443 \
-enroll np_enr_… \
-name "Зонд у Львові" \
-modules icmp,snmp,topology,ncm
Зонд обміняє запрошення на постійний токен і збереже його в
/var/lib/netpulse/agent.json (права 0600). Наступні запуски токена вже
не потребують — том із посвідченням має пережити перестворення
контейнера, інакше кожен старт вимагатиме нового запрошення.
Усі зʼєднання зонда вихідні: у мережі клієнта не треба відкривати жодного порту.
Бекап
Три речі, і всі три обовʼязкові:
- База — усе, крім секретів у відкритому вигляді.
NETPULSE_DEK— без нього паролі SSH і SNMP-community з бекапу не розшифрувати. У БД лежить лише шифротекст.NETPULSE_JWT_SECRET— без нього після відновлення всі активні сесії відваляться. Не смертельно, але користувачі помітять.
docker compose exec -T db \
pg_dump -U netpulse -d netpulse -Fc --no-owner \
> netpulse-$(date +%F).dump
Формат -Fc (custom), а не простий SQL: він стискається і дозволяє
відновлювати вибірково.
Ключі зберігати окремо від дампа — інакше сенс шифрування секретів зникає: той, хто дістав бекап, дістав і ключ до нього.
Автоматично, щодня
15 3 * * * cd /opt/netpulse && docker compose exec -T db pg_dump -U netpulse -d netpulse -Fc --no-owner > /var/backups/netpulse-$(date +\%F).dump && find /var/backups -name 'netpulse-*.dump' -mtime +30 -delete
Відновлення
TimescaleDB вимагає рамки навколо відновлення: без неї фонові процеси агрегації втручаються в наливання даних і дамп лягає пошкодженим.
docker compose stop api collector
docker compose exec -T db psql -U netpulse -d postgres -c \
'DROP DATABASE IF EXISTS netpulse; CREATE DATABASE netpulse;'
docker compose exec -T db psql -U netpulse -d netpulse -c \
'CREATE EXTENSION IF NOT EXISTS timescaledb;'
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT timescaledb_pre_restore();'
docker compose exec -T db pg_restore -U netpulse -d netpulse --no-owner \
< netpulse-2026-08-25.dump
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT timescaledb_post_restore();'
docker compose up -d api collector
У .env має лежати той самий NETPULSE_DEK, що й на момент дампа.
Інакше застосунок підніметься, але кожна спроба скористатись збереженим
паролем поверне помилку розшифрування — і виглядатиме це як зламані
креденшели, а не як втрачений ключ.
Перевірка після відновлення:
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT count(*) FROM core.devices;'
curl -sf https://<NETPULSE_DOMAIN>/healthz && echo OK
Оновлення
git pull
docker compose up -d --build
migrate відпрацює першим і не дасть піднятись API, якщо схема не
накотилась. Міграції йдуть по одній у транзакції; уже застосований файл
зі зміненою контрольною сумою зупиняє весь запуск — це захист від
мовчазного розходження схеми з кодом.
Відкат схеми не передбачений: зворотні міграції на даних телеметрії коштують дорожче, ніж відновлення з бекапу.
Зміна ключа шифрування
Ключі перелічуються через кому, новий — першим:
NETPULSE_DEK=np2=<новий hex>,np1=<старий hex>
Нові секрети шифруються першим ключем, старі читаються своїм. Прибирати старий ключ можна лише після того, як усі секрети перезаписані.
Чому саме так
Два образи, а не пʼять. api, collector, migrate, netpulse-user
і netpulse-secret — з одного модуля, з половиною спільного коду. Один
образ гарантує, що API і колектор ходять у схему БД однією версією; окремі
образи дають їм можливість розʼїхатись саме там, де це найдорожче.
Зонд — окремо: він їде в чужу мережу, і DSN, ключі шифрування та команди заведення користувачів не повинні бути в тому образі навіть як невикористані файли.
Міграції окремою службою. API піднімається в кількох примірниках; накочування схеми зі старту означало б гонку між ними.
TLS на проксі, а не в застосунку. Прострочений сертифікат на системі, яка сама має повідомляти про проблеми, — найгірший спосіб дізнатись про проблему. Caddy оновлює його сам.