Netpulse_SasS/deploy/README.md
byrsapty 65b37a28ff
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
Git-двигун NCM: версіювання конфігів, переливання історії, довільне порівняння
go-git, чистий Go без cgo. Голий репозиторій на тенанта, гілка на
пристрій за ідентифікатором (ім'я змінюють, історія не має від цього
розсипатись), файл за іменем. Однаковий вміст нового коміту не створює.

netpulse-gitsync переливає накопичену історію й відтворює втрачений
репозиторій із бази — тіла конфігів там і так лежать зашифрованими.

У вебі з'явився вибір версії, з якою порівнювати: сервер це вмів
(?from=), інтерфейс — ні.

Дорогою виправлено чотири тести, які CI запустив уперше з базою.
Серед них справжня помилка: машинний токен не міг читати мапи —
ACL мап отримував порожній рядок замість uuid і давав 500.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:15:14 +03:00

9.3 KiB
Raw Blame History

Розгортання NetPulse

Один хост, docker compose, автоматичний TLS. Такого розгортання вистачає до кількох тисяч хостів на моніторингу; розносити служби по машинах має сенс тоді, коли впирається БД, а не застосунок.

Що з чого складається

Служба Роль Порт
db PostgreSQL 16 + TimescaleDB — усі дані
cache DragonflyDB — черги й тимчасові стани
migrate накочування схеми; відпрацьовує і зупиняється
api HTTP API + вшитий інтерфейс 8080
collector gRPC-колектор, до якого підключаються зонди 9443
proxy Caddy: сертифікати, HTTPS, проксі до двох служб 80/443/9443

Історія конфігів живе в Git на томі git-data, спільному для api й collector: перший читає її для порівняння версій, другий пише під час бекапу.

Назовні дивиться лише 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). Наступні запуски токена вже не потребують — том із посвідченням має пережити перестворення контейнера, інакше кожен старт вимагатиме нового запрошення.

Усі зʼєднання зонда вихідні: у мережі клієнта не треба відкривати жодного порту.

Бекап

Три речі, і всі три обовʼязкові:

  1. Базаусе, крім секретів у відкритому вигляді.
  2. NETPULSE_DEK — без нього паролі SSH і SNMP-community з бекапу не розшифрувати. У БД лежить лише шифротекст.
  3. NETPULSE_JWT_SECRET — без нього після відновлення всі активні сесії відваляться. Не смертельно, але користувачі помітять.
docker compose exec -T db \
  pg_dump -U netpulse -d netpulse -Fc --no-owner \
  > netpulse-$(date +%F).dump

Формат -Fc (custom), а не простий SQL: він стискається і дозволяє відновлювати вибірково.

Ключі зберігати окремо від дампа — інакше сенс шифрування секретів зникає: той, хто дістав бекап, дістав і ключ до нього.

Том git-data бекапити не обовʼязково: тіла конфігів лежать зашифрованими в базі, і репозиторій повністю відтворюється з неї —

docker compose run --rm api netpulse-gitsync

Зворотне невірно: з репозиторію базу не відновити. Тому джерелом істини лишається дамп, а Git — похідне сховище, яке коштує один запуск команди.

Автоматично, щодня

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 оновлює його сам.