Образи зібрались із першого разу, але далі знайшлось три речі. 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>
193 lines
7.8 KiB
YAML
193 lines
7.8 KiB
YAML
# Повний стек NetPulse: БД, кеш, міграції, API з інтерфейсом, колектор
|
||
# зондів і зворотний проксі з автоматичним TLS.
|
||
#
|
||
# Швидкий старт:
|
||
# cp deploy/.env.example .env # і заповнити секрети
|
||
# docker compose up -d
|
||
# docker compose run --rm api netpulse-user -tenant default -login admin -role owner
|
||
#
|
||
# Тільки стенд для розробки (БД і кеш, решта — з go run):
|
||
# docker compose up -d db cache
|
||
|
||
name: netpulse
|
||
|
||
x-server-env: &server-env
|
||
NETPULSE_DSN: postgres://netpulse:${POSTGRES_PASSWORD:?потрібен POSTGRES_PASSWORD}@db:5432/netpulse?sslmode=disable
|
||
NETPULSE_DEK: ${NETPULSE_DEK:?потрібен NETPULSE_DEK — див. deploy/README.md}
|
||
NETPULSE_LOG_LEVEL: ${NETPULSE_LOG_LEVEL:-info}
|
||
TZ: ${TZ:-Europe/Kyiv}
|
||
|
||
services:
|
||
db:
|
||
image: timescale/timescaledb:2.17.2-pg16
|
||
environment:
|
||
POSTGRES_USER: netpulse
|
||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?потрібен POSTGRES_PASSWORD}
|
||
POSTGRES_DB: netpulse
|
||
TIMESCALEDB_TELEMETRY: "off"
|
||
# Без цього initdb бере кодування з локалі середовища й цілком
|
||
# може створити базу як SQL_ASCII. Тексти при цьому виглядають
|
||
# цілими, а ламається інше: jsonb із екранованим не-ASCII не
|
||
# вставляється, а lower() і сортування працюють як над латиницею.
|
||
# Виправити можна лише перестворенням бази, тож задаємо одразу.
|
||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8"
|
||
command:
|
||
- postgres
|
||
- -c
|
||
- shared_preload_libraries=timescaledb
|
||
- -c
|
||
- max_connections=200
|
||
- -c
|
||
- shared_buffers=${PG_SHARED_BUFFERS:-512MB}
|
||
- -c
|
||
- timescaledb.max_background_workers=8
|
||
volumes:
|
||
- db-data:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U netpulse -d netpulse"]
|
||
interval: 5s
|
||
timeout: 5s
|
||
retries: 20
|
||
restart: unless-stopped
|
||
|
||
cache:
|
||
image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.25.5
|
||
# memlock без обмеження прискорює Dragonfly, але дозволений не всюди:
|
||
# у контейнерній віртуалізації (LXC, частина VPS) ядро відмовляє, і
|
||
# контейнер не стартує взагалі з «error setting rlimit type 8».
|
||
# Швидкість того не варта — обмеження знято.
|
||
volumes:
|
||
- cache-data:/data
|
||
restart: unless-stopped
|
||
|
||
# Міграції окремою службою, а не на старті API.
|
||
#
|
||
# API запускається в кількох примірниках, і накочування схеми зі старту
|
||
# означало б гонку між ними. Тут же — один запуск, який мусить
|
||
# завершитись успіхом, перш ніж піднімуться API й колектор.
|
||
migrate:
|
||
build: &server-build
|
||
context: .
|
||
dockerfile: deploy/Dockerfile.server
|
||
args:
|
||
VERSION: ${NETPULSE_VERSION:-dev}
|
||
COMMIT: ${NETPULSE_COMMIT:-none}
|
||
image: netpulse/server:${NETPULSE_VERSION:-dev}
|
||
entrypoint: ["netpulse-migrate"]
|
||
environment: *server-env
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
restart: "no"
|
||
|
||
api:
|
||
build: *server-build
|
||
image: netpulse/server:${NETPULSE_VERSION:-dev}
|
||
entrypoint: ["netpulse-api"]
|
||
command:
|
||
- -listen=:8080
|
||
- -alert-interval=${NETPULSE_ALERT_INTERVAL:-30s}
|
||
environment:
|
||
<<: *server-env
|
||
NETPULSE_JWT_SECRET: ${NETPULSE_JWT_SECRET:?потрібен NETPULSE_JWT_SECRET — не менше 32 байтів}
|
||
# Історія конфігів у Git. Спільний том для API й колектора:
|
||
# колектор туди пише під час бекапу, API звідти читає для порівняння
|
||
# версій. Різні томи означали б, що половина історії видна одному
|
||
# процесу, а половина другому.
|
||
volumes:
|
||
- git-data:/var/lib/netpulse
|
||
depends_on:
|
||
migrate:
|
||
condition: service_completed_successfully
|
||
cache:
|
||
condition: service_started
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"]
|
||
interval: 10s
|
||
timeout: 3s
|
||
retries: 6
|
||
start_period: 15s
|
||
restart: unless-stopped
|
||
|
||
# Колектор зондів. TLS знімає Caddy, всередині мережі — h2c, тому
|
||
# -insecure. Порт назовні сам не публікує: до нього ходять через
|
||
# проксі, який має справжній сертифікат.
|
||
collector:
|
||
build: *server-build
|
||
image: netpulse/server:${NETPULSE_VERSION:-dev}
|
||
entrypoint: ["netpulse-server"]
|
||
command:
|
||
- -listen=:9443
|
||
- -insecure
|
||
environment: *server-env
|
||
# Історія конфігів у Git. Спільний том для API й колектора:
|
||
# колектор туди пише під час бекапу, API звідти читає для порівняння
|
||
# версій. Різні томи означали б, що половина історії видна одному
|
||
# процесу, а половина другому.
|
||
volumes:
|
||
- git-data:/var/lib/netpulse
|
||
depends_on:
|
||
migrate:
|
||
condition: service_completed_successfully
|
||
restart: unless-stopped
|
||
|
||
proxy:
|
||
image: caddy:2.8-alpine
|
||
environment:
|
||
NETPULSE_DOMAIN: ${NETPULSE_DOMAIN:?потрібен NETPULSE_DOMAIN}
|
||
# Порожня пошта означає «домену ще немає» — тоді самопідписаний
|
||
# сертифікат. Інакше Caddy нескінченно проситиме Let's Encrypt
|
||
# видати сертифікат на IP, чого той не робить, і сайт лишиться
|
||
# без TLS зовсім.
|
||
NETPULSE_TLS: ${ACME_EMAIL:-internal}
|
||
volumes:
|
||
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||
- caddy-data:/data
|
||
- caddy-config:/config
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
# Окремий порт для зондів: вони говорять gRPC, а не HTTP, і
|
||
# ділити з ним 443 означало б розрізняти протоколи за шляхом —
|
||
# зайва крихкість там, де порт коштує нічого.
|
||
- "9443:9443"
|
||
depends_on:
|
||
- api
|
||
- collector
|
||
restart: unless-stopped
|
||
|
||
# Зонд на тому ж хості, що й сервер: базовий моніторинг самої
|
||
# інсталяції. Профіль, а не звичайна служба: у типовому розгортанні
|
||
# зонди стоять у мережах клієнтів, а не поруч із сервером.
|
||
agent:
|
||
build:
|
||
context: .
|
||
dockerfile: deploy/Dockerfile.agent
|
||
args:
|
||
VERSION: ${NETPULSE_VERSION:-dev}
|
||
COMMIT: ${NETPULSE_COMMIT:-none}
|
||
image: netpulse/agent:${NETPULSE_VERSION:-dev}
|
||
profiles: ["agent"]
|
||
command:
|
||
- -server=collector:9443
|
||
- -insecure
|
||
- -modules=icmp,snmp,topology,ncm
|
||
environment:
|
||
NETPULSE_ENROLL: ${NETPULSE_ENROLL:-}
|
||
NETPULSE_NAME: ${NETPULSE_AGENT_NAME:-локальний зонд}
|
||
TZ: ${TZ:-Europe/Kyiv}
|
||
cap_add:
|
||
- NET_RAW
|
||
volumes:
|
||
- agent-identity:/var/lib/netpulse
|
||
depends_on:
|
||
- collector
|
||
restart: unless-stopped
|
||
|
||
volumes:
|
||
db-data:
|
||
git-data:
|
||
cache-data:
|
||
caddy-data:
|
||
caddy-config:
|
||
agent-identity:
|