Netpulse_SasS/deploy/README.md
byrsapty 6843bd3280
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
Розгортання на живому сервері: три виправлення, знайдені першим запуском
Образи зібрались із першого разу, але далі знайшлось три речі.

memlock без обмеження для Dragonfly дозволений не всюди: у контейнерній
віртуалізації ядро відмовляє, і контейнер не стартує взагалі. Швидкість
того не варта — обмеження знято.

Caddy нескінченно просив Let's Encrypt видати сертифікат на IP, чого той
не робить, і сайт лишався без TLS. Тепер порожня ACME_EMAIL означає
самопідписаний сертифікат. Плюс default_sni: SNI не містить IP-адрес,
тож без нього рукостискання обривалось «internal error».

У README команда заведення власника не працювала: у образу ENTRYPOINT, і
без --entrypoint ім'я команди йшло аргументом до netpulse-api. Заразом
netpulse-user навчився створювати перший кабінет — свіжа база не має
жодного, а користувач без кабінету нікуди не належить.

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

230 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Розгортання 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` портів не публікують:
до них ходять через нього.
## Перший запуск
```sh
git clone <репозиторій> netpulse && cd netpulse
cp deploy/.env.example .env
```
Заповнити `.env`. Три значення обовʼязкові й генеруються так:
```sh
openssl rand -base64 24 # POSTGRES_PASSWORD
echo "np1=$(openssl rand -hex 32)" # NETPULSE_DEK
openssl rand -base64 48 # NETPULSE_JWT_SECRET
```
Далі:
```sh
docker compose up -d --build
docker compose run --rm api netpulse-user \
-tenant default -login admin -role owner -name "Адміністратор"
```
Пароль команда спитає інтерактивно — щоб він не осів в історії оболонки
й у списку процесів.
Інтерфейс — на `https://<NETPULSE_DOMAIN>`.
## Підключення зонда
В інтерфейсі: **Зонди → Додати зонд**. Видане запрошення (`np_enr_…`)
одноразове — після реєстрації воно згоряє.
На машині, де стоятиме зонд:
```sh
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`** — без нього після відновлення всі активні
сесії відваляться. Не смертельно, але користувачі помітять.
```sh
docker compose exec -T db \
pg_dump -U netpulse -d netpulse -Fc --no-owner \
> netpulse-$(date +%F).dump
```
Формат `-Fc` (custom), а не простий SQL: він стискається і дозволяє
відновлювати вибірково.
Ключі зберігати **окремо від дампа** — інакше сенс шифрування секретів
зникає: той, хто дістав бекап, дістав і ключ до нього.
Том `git-data` бекапити не обовʼязково: тіла конфігів лежать
зашифрованими в базі, і репозиторій повністю відтворюється з неї —
```sh
docker compose run --rm --entrypoint netpulse-gitsync api
```
Зворотне невірно: з репозиторію базу не відновити. Тому джерелом істини
лишається дамп, а Git — похідне сховище, яке коштує один запуск команди.
### Автоматично, щодня
```cron
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 вимагає рамки навколо відновлення: без неї фонові процеси
агрегації втручаються в наливання даних і дамп лягає пошкодженим.
```sh
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`, що й на момент дампа.
Інакше застосунок підніметься, але кожна спроба скористатись збереженим
паролем поверне помилку розшифрування — і виглядатиме це як зламані
креденшели, а не як втрачений ключ.
Перевірка після відновлення:
```sh
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT count(*) FROM core.devices;'
curl -sf https://<NETPULSE_DOMAIN>/healthz && echo OK
```
## Оновлення
```sh
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 оновлює його сам.
## Без домену
Домен потрібен для справжнього сертифіката: Let's Encrypt не видає їх на
IP-адреси. Поки домену немає, лишіть `ACME_EMAIL` порожнім — Caddy
випише самопідписаний, і система працюватиме одразу, з попередженням у
браузері.
Коли домен зʼявиться: замінити `NETPULSE_DOMAIN`, вписати `ACME_EMAIL` і
`docker compose restart proxy`. Caddyfile змонтований, тож перезбирати
образи не треба — але й `up -d` без `restart` його не перечитає.
## Дашборд на телевізор
В інтерфейсі: **Дашборд → На телевізор → Видати посилання**. Отриману
адресу відкривають на екрані в диспетчерській — вона не потребує входу.
Телевізор нікуди не залогиниш: сесія протермінується, браузер оновиться,
і зранку на стіні висітиме форма входу замість карти мережі — рівно
тоді, коли на неї дивляться.
Що дає посилання і чого не дає:
- **тільки читання** цього дашборда: його плитки, активні алерти й
метрики тих хостів, які на ньому показані;
- метрики чужого хоста за ним не дістати навіть підбором ідентифікатора;
- решта кабінету — інвентар, конфіги, налаштування, секрети —
недоступна;
- відкликається одним рухом, старе посилання одразу мертве.
Токен показується один раз. Видати нове можна будь-коли — попереднє при
цьому перестає працювати.