feat: automate production maintenance
This commit is contained in:
@@ -3,6 +3,7 @@ __pycache__/
|
|||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.cache/
|
.cache/
|
||||||
.env.production
|
.env.production
|
||||||
|
.maintenance.lock
|
||||||
.venv/
|
.venv/
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Production-контур для домена `rf4spotter.ru`, TLS, секреты
|
|||||||
Политика минимизации данных и ежедневная dry-run-first очистка описаны в [`docs/data-retention.md`](docs/data-retention.md).
|
Политика минимизации данных и ежедневная dry-run-first очистка описаны в [`docs/data-retention.md`](docs/data-retention.md).
|
||||||
|
|
||||||
Host-side мониторинг контейнеров, readiness, диска, резервных копий и TLS описан в [`docs/production-monitoring.md`](docs/production-monitoring.md).
|
Host-side мониторинг контейнеров, readiness, диска, резервных копий и TLS описан в [`docs/production-monitoring.md`](docs/production-monitoring.md).
|
||||||
|
Ежедневный systemd timer создаёт проверяемую копию до retention-очистки, а production Compose ограничивает рост JSON-логов контейнеров.
|
||||||
|
|
||||||
Фактическое состояние DNS/TLS домена и серверный чек-лист ведутся в [`docs/deployment-status.md`](docs/deployment-status.md).
|
Фактическое состояние DNS/TLS домена и серверный чек-лист ведутся в [`docs/deployment-status.md`](docs/deployment-status.md).
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
name: rf4-spotter
|
name: rf4-spotter
|
||||||
|
|
||||||
|
x-logging: &default-logging
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
proxy:
|
proxy:
|
||||||
image: caddy:2.10.2-alpine
|
image: caddy:2.10.2-alpine
|
||||||
@@ -23,6 +29,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
networks: [edge, backend]
|
networks: [edge, backend]
|
||||||
security_opt: [no-new-privileges:true]
|
security_opt: [no-new-privileges:true]
|
||||||
|
logging: *default-logging
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits: {cpus: "0.50", memory: 256M}
|
limits: {cpus: "0.50", memory: 256M}
|
||||||
@@ -43,6 +50,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
networks: [backend]
|
networks: [backend]
|
||||||
security_opt: [no-new-privileges:true]
|
security_opt: [no-new-privileges:true]
|
||||||
|
logging: *default-logging
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits: {cpus: "1.00", memory: 1G}
|
limits: {cpus: "1.00", memory: 1G}
|
||||||
@@ -63,6 +71,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
networks: [backend]
|
networks: [backend]
|
||||||
security_opt: [no-new-privileges:true]
|
security_opt: [no-new-privileges:true]
|
||||||
|
logging: *default-logging
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits: {cpus: "0.75", memory: 1G}
|
limits: {cpus: "0.75", memory: 1G}
|
||||||
@@ -106,6 +115,7 @@ services:
|
|||||||
retries: 12
|
retries: 12
|
||||||
networks: [backend, edge]
|
networks: [backend, edge]
|
||||||
security_opt: [no-new-privileges:true]
|
security_opt: [no-new-privileges:true]
|
||||||
|
logging: *default-logging
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits: {cpus: "1.00", memory: 1G}
|
limits: {cpus: "1.00", memory: 1G}
|
||||||
@@ -128,6 +138,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
networks: [backend]
|
networks: [backend]
|
||||||
security_opt: [no-new-privileges:true]
|
security_opt: [no-new-privileges:true]
|
||||||
|
logging: *default-logging
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits: {cpus: "0.75", memory: 512M}
|
limits: {cpus: "0.75", memory: 512M}
|
||||||
@@ -143,6 +154,7 @@ services:
|
|||||||
- ${BACKUP_DIRECTORY:-./backups}:/backup
|
- ${BACKUP_DIRECTORY:-./backups}:/backup
|
||||||
networks: [backend]
|
networks: [backend]
|
||||||
security_opt: [no-new-privileges:true]
|
security_opt: [no-new-privileges:true]
|
||||||
|
logging: *default-logging
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
edge:
|
edge:
|
||||||
|
|||||||
@@ -109,6 +109,18 @@ docker compose --env-file .env.production -f compose.production.yaml exec -T api
|
|||||||
|
|
||||||
Сроки и состав данных описаны в [`docs/data-retention.md`](../docs/data-retention.md). Автоматизацию включайте только после проверки dry-run на рабочем наборе.
|
Сроки и состав данных описаны в [`docs/data-retention.md`](../docs/data-retention.md). Автоматизацию включайте только после проверки dry-run на рабочем наборе.
|
||||||
|
|
||||||
|
После ручной проверки установите ежедневный timer. `maintenance.sh` использует lock от параллельного запуска и применяет retention только после успешного backup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/systemd/rf4spotter-maintenance.service /etc/systemd/system/
|
||||||
|
sudo cp deploy/systemd/rf4spotter-maintenance.timer /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now rf4spotter-maintenance.timer
|
||||||
|
systemctl list-timers rf4spotter-maintenance.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
Шаблон рассчитан на пользователя `rf4spotter`, каталог `/opt/rf4-spotter` и наличие `flock` из `util-linux`. Пользователь должен иметь доступ к Docker socket и каталогу `BACKUP_ROOT`. Результат каждого запуска хранится в systemd journal. Docker JSON-логи production-сервисов ограничены пятью файлами по 10 МБ на контейнер.
|
||||||
|
|
||||||
## 8. Мониторинг
|
## 8. Мониторинг
|
||||||
|
|
||||||
После настройки DNS, TLS и первого backup выполните:
|
После настройки DNS, TLS и первого backup выполните:
|
||||||
|
|||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
||||||
|
cd "$repo"
|
||||||
|
env_file=${COMPOSE_ENV_FILE:-.env.production}
|
||||||
|
if [ ! -r "$env_file" ]; then
|
||||||
|
echo "Production env file is not readable: $env_file" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_root=${BACKUP_ROOT:-$(sed -n 's/^BACKUP_ROOT=//p' "$env_file" | tail -n 1)}
|
||||||
|
case "$backup_root" in
|
||||||
|
/*) ;;
|
||||||
|
*) echo "BACKUP_ROOT must be an absolute path" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exec 9>"$repo/.maintenance.lock"
|
||||||
|
if ! flock -n 9; then
|
||||||
|
echo "Another RF4 Spotter maintenance run is active" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export COMPOSE_ENV_FILE="$env_file"
|
||||||
|
./deploy/backup.sh "$backup_root"
|
||||||
|
compose="docker compose --env-file $env_file -f compose.production.yaml"
|
||||||
|
$compose exec -T api python -m app.cli cleanup-retention
|
||||||
|
$compose exec -T api python -m app.cli cleanup-retention --apply
|
||||||
|
echo "Daily maintenance completed"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=RF4 Spotter backup and retention maintenance
|
||||||
|
After=docker.service network-online.target
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=rf4spotter
|
||||||
|
WorkingDirectory=/opt/rf4-spotter
|
||||||
|
ExecStart=/opt/rf4-spotter/deploy/maintenance.sh
|
||||||
|
|
||||||
|
# Backups can take longer on a growing database and object store.
|
||||||
|
TimeoutStartSec=2h
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Run RF4 Spotter backup and retention daily
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* 03:10:00
|
||||||
|
RandomizedDelaySec=10min
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
|
|
||||||
- [x] Добавить health/readiness-проверки PostgreSQL, MinIO, API и импорта; отразить их в Compose (`/health` без зависимостей, `/ready` с компонентами и режимом обязательного импорта).
|
- [x] Добавить health/readiness-проверки PostgreSQL, MinIO, API и импорта; отразить их в Compose (`/health` без зависимостей, `/ready` с компонентами и режимом обязательного импорта).
|
||||||
- [x] Добавить host-side production monitor контейнеров, `/ready`, диска, свежести/целостности backup и срока TLS; подготовлены systemd timer и runbook, реальный канал уведомлений подключается на сервере.
|
- [x] Добавить host-side production monitor контейнеров, `/ready`, диска, свежести/целостности backup и срока TLS; подготовлены systemd timer и runbook, реальный канал уведомлений подключается на сервере.
|
||||||
|
- [x] Автоматизировать ежедневную цепочку backup → dry-run → retention с блокировкой параллельного запуска; ограничить Docker JSON-логи пятью файлами по 10 МБ на сервис.
|
||||||
- [x] Добавить структурированные JSON-логи без пользовательских секретов и персональных технических данных (whitelist полей, redaction, request ID; Uvicorn access-log отключён).
|
- [x] Добавить структурированные JSON-логи без пользовательских секретов и персональных технических данных (whitelist полей, redaction, request ID; Uvicorn access-log отключён).
|
||||||
- [x] Добавить Gitea Actions CI: backend tests, Astro check/build, E2E и применение всех миграций на чистой PostgreSQL; сохранять логи Compose и Playwright-артефакты при падении (`.gitea/workflows/ci.yml`).
|
- [x] Добавить Gitea Actions CI: backend tests, Astro check/build, E2E и применение всех миграций на чистой PostgreSQL; сохранять логи Compose и Playwright-артефакты при падении (`.gitea/workflows/ci.yml`).
|
||||||
- [x] Добавить отдельный тест полного bootstrap: пустые production volumes → миграции `0010` → seed без демо-уловов → readiness → браузерная отправка и проверка moderation API (`deploy/test-production-bootstrap.sh`, 6 сентября 2026).
|
- [x] Добавить отдельный тест полного bootstrap: пустые production volumes → миграции `0010` → seed без демо-уловов → readiness → браузерная отправка и проверка moderation API (`deploy/test-production-bootstrap.sh`, 6 сентября 2026).
|
||||||
|
|||||||
@@ -28,13 +28,15 @@ echo $?
|
|||||||
```bash
|
```bash
|
||||||
sudo cp deploy/systemd/rf4spotter-monitor.service /etc/systemd/system/
|
sudo cp deploy/systemd/rf4spotter-monitor.service /etc/systemd/system/
|
||||||
sudo cp deploy/systemd/rf4spotter-monitor.timer /etc/systemd/system/
|
sudo cp deploy/systemd/rf4spotter-monitor.timer /etc/systemd/system/
|
||||||
|
sudo cp deploy/systemd/rf4spotter-maintenance.service /etc/systemd/system/
|
||||||
|
sudo cp deploy/systemd/rf4spotter-maintenance.timer /etc/systemd/system/
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now rf4spotter-monitor.timer
|
sudo systemctl enable --now rf4spotter-monitor.timer rf4spotter-maintenance.timer
|
||||||
systemctl list-timers rf4spotter-monitor.timer
|
systemctl list-timers 'rf4spotter-*'
|
||||||
journalctl -u rf4spotter-monitor.service -n 20 --no-pager
|
journalctl -u rf4spotter-monitor.service -n 20 --no-pager
|
||||||
```
|
```
|
||||||
|
|
||||||
Сам timer записывает результат в journal. Для реального оповещения подключите failed unit к существующему серверному мониторингу либо настройте внешний HTTPS-monitor на `/ready`; уведомления должны приходить минимум по состояниям readiness, disk, backup и TLS. Не передавайте `.env.production` или вывод `docker compose config` внешнему сервису.
|
Monitor timer записывает результат в journal. Maintenance timer ежедневно сначала создаёт backup, затем выполняет retention и не допускает параллельных запусков. Для реального оповещения подключите failed unit к существующему серверному мониторингу либо настройте внешний HTTPS-monitor на `/ready`; уведомления должны приходить минимум по состояниям readiness, disk, backup и TLS. Не передавайте `.env.production` или вывод `docker compose config` внешнему сервису.
|
||||||
|
|
||||||
## Реакция
|
## Реакция
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user