Эксплуатация¶
Резервное копирование¶
Что бэкапить¶
| Что | Где | Критичность |
|---|---|---|
| PostgreSQL (Hub) | volume postgres_data |
CRITICAL — основная БД |
| PostgreSQL (DomainScope) | свой volume | HIGH — теряем discovery |
./storage/ (артефакты, отчёты) |
bind mount | MEDIUM — можно перезалить |
| Helm secrets / Vault | внешнее хранилище | CRITICAL — без них Hub не стартует |
Конфиги (.env, values.yaml) |
git | LOW — версионируются |
Ежедневный бэкап БД¶
Docker Compose¶
# /etc/cron.daily/hub-backup.sh
#!/bin/bash
set -euo pipefail
BACKUP_DIR=/backup/hub
DATE=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR"
# Hub
docker exec sshub-postgres pg_dump -U securityhub securityhub | \
gzip > "$BACKUP_DIR/hub-$DATE.sql.gz"
# DomainScope (если рядом)
docker exec ds-postgres pg_dump -U domainscope domainscope | \
gzip > "$BACKUP_DIR/ds-$DATE.sql.gz"
# Storage (отчёты)
tar -czf "$BACKUP_DIR/storage-$DATE.tar.gz" /opt/hub/storage/
# Очистка старых (>30 дней)
find "$BACKUP_DIR" -name "*.gz" -mtime +30 -delete
Kubernetes¶
Используйте CronJob с pgbackrest / Velero / k8s-snapshot:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hub-postgres-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: pg-dump
image: postgres:15
envFrom:
- secretRef: { name: hub-postgres-credentials }
command:
- /bin/sh
- -c
- |
pg_dump -h hub-postgres -U $POSTGRES_USER $POSTGRES_DB | gzip > /backup/hub-$(date +%F).sql.gz
volumeMounts:
- name: backup, mountPath: /backup
volumes:
- name: backup
persistentVolumeClaim: { claimName: backup-pvc }
restartPolicy: OnFailure
Off-site¶
Бэкапы только локально на той же VM — не бэкапы. Регулярно перекладывайте:
- S3 (s3cmd, rclone)
- Yandex Object Storage
- Защищённый bastion-host
Восстановление¶
# Создать пустую БД (если ещё нет)
docker exec sshub-postgres psql -U securityhub -c 'CREATE DATABASE securityhub;'
# Залить
gunzip -c hub-backup-20260601.sql.gz | docker exec -i sshub-postgres psql -U securityhub securityhub
# Перезапустить backend (миграции должны быть прокатанные)
docker compose restart backend worker
Тестируйте восстановление раз в квартал. Бэкап без отрепетированного restore — не бэкап.
Мониторинг¶
Адреса проверки состояния¶
| Сервис | Адрес | Что показывает |
|---|---|---|
| Backend Hub | GET /health |
Живость процесса |
| Backend Hub | GET /version |
Версия компонента |
| Backend Hub | GET /api/v1/version |
То же, внутри пространства API |
| DomainScope | GET /health |
Живость |
| DomainScope | GET /ready |
Готовность: база доступна, циклы не устарели |
| DomainScope | GET /healthz |
Живость приёмника команд перепроверки |
Эти адреса подходят для внешнего наблюдения — Zabbix, Uptime Kuma, blackbox-exporter и любых аналогов.
У worker нет собственного адреса проверки
Worker не обслуживает HTTP. Его состояние оценивается косвенно — по тому, разбирается ли очередь задач (см. ниже), и по журналу процесса. Живой процесс с неразбираемой очередью выглядит здоровым для оркестратора, но таковым не является.
Очередь задач — главный показатель¶
Самый содержательный признак того, что обработка идёт, — состояние очереди:
| Запрос | Что даёт |
|---|---|
GET /api/v1/jobs/stats |
Сводка по состояниям задач |
GET /api/v1/jobs |
Перечень задач с фильтрами |
POST /api/v1/jobs/<id>/retry |
Повторить сбойную задачу |
Растущее число задач в ожидании при неизменном числе выполненных означает, что worker не работает или не справляется. Задачи, исчерпавшие попытки, остаются в состоянии ошибки — их видно и в интерфейсе, и через этот API.
Журналы¶
Уровень подробности задаётся LOG_LEVEL (debug, info, warn, error) и
меняется независимо от APP_ENV — подробный вывод можно включить в
промышленной среде для диагностики, не меняя формат записей.
Что наблюдать¶
| Что | Как проверять |
|---|---|
| Backend жив | Периодический запрос /health |
| Очередь разбирается | GET /api/v1/jobs/stats, сравнение с предыдущим значением |
| База доступна | Журнал backend без ошибок соединения |
| Место на томе отчётов | Переполнение прекращает приём новых отчётов |
| Место на томе базы | Переполнение останавливает запись |
| Сроки устранения | GET /api/v1/sla/violations и сводки на дашборде |
| Свободное место на диске | стандартный node_exporter / системный мониторинг |
| Размер БД | SELECT pg_size_pretty(pg_database_size('securityhub')); |
Логирование¶
Где логи¶
- Compose:
docker compose logs <service>, ротация через docker-daemon (/etc/docker/daemon.json→log-opts) - K8s: stdout pods → cluster logging (Loki, Elastic, etc.)
Формат¶
В APP_ENV=production — JSON (zap), легко парсится:
{
"level": "info",
"ts": "2026-06-02T12:34:56.789Z",
"msg": "report uploaded",
"report_id": "rpt-...",
"engine": "gosec",
"findings_count": 47,
"user_id": "user-...",
"trace_id": "abc..."
}
Полезные grep'ы¶
# Все upload-операции
docker compose logs backend | jq -r 'select(.msg=="report uploaded")'
# Ошибки за последний час
docker compose logs --since 1h backend | jq 'select(.level=="error")'
# Кто логинился сегодня
docker compose logs backend | jq -r 'select(.msg=="user login") | "\(.ts) \(.email)"'
Логи DomainScope¶
# Compose
docker compose -f docker/docker-compose.yml logs -f domain-scope
# systemd
sudo journalctl -u domain-scope -f
# По циклам
docker compose logs domain-scope | grep "cycle"
Retention¶
- Docker default: бесконечно (заполнит диск). Настройте:
- Логи в
./logs/— ротируйте черезlogrotate - K8s —
kubelet log-rotation(включён по умолчанию, проверьте/etc/kubernetes/)
Очистка старых данных¶
Reports (storage)¶
Очистка запускается ежечасно. Расписание задано в коде и переменными
окружения не управляется: CLEANUP_SCHEDULE — устаревшая переменная, она
только вызывает предупреждение в журнале, если задана.
Что удаляется:
| Что | Переменная | По умолчанию |
|---|---|---|
| Обработанные отчёты | CLEANUP_RETENTION_COMPLETED_DAYS |
старше 7 суток |
| Отчёты с ошибкой разбора | CLEANUP_RETENTION_FAILED_DAYS |
старше 30 суток |
| Необработанные отчёты | CLEANUP_RETENTION_PENDING_DAYS |
0 — не удаляются |
| Файлы без записи в базе | CLEANUP_RETENTION_ORPHAN_HOURS |
старше 24 часов |
Удаляются и файл, и запись в базе. Проверить, что именно будет удалено, не
удаляя, можно через CLEANUP_DRY_RUN=true.