Рабочие процедуры (Runbooks)¶
Уровни доступа
- L2 — может выполнять диагностику (проверки, логи, health check) и перезапуск отдельных контейнеров.
- L3 — может менять конфигурацию, деплоить серверы, работать с MySQL напрямую, менять DNS.
- Процедуры с пометкой ⚠️ L3 требуют согласования с DevOps Lead.
Аварийные процедуры¶
API недоступен (P0)¶
Симптомы: клиенты не могут подключиться, api.shivavpn.io таймаутит.
# 1. Проверить прокси
ssh -p 2255 root@212.70.189.60 "docker ps; nginx -t"
# 2. Проверить бэкенд
./scripts/ssh-internal.sh 10.99.87.249 \
"docker ps --filter name=vpn-back; curl -s http://localhost:8080/actuator/health"
# 3. Если бэкенд упал — рестарт
./scripts/ssh-internal.sh 10.99.87.249 "docker restart vpn-back-blue"
# или vpn-back-green — смотри какой активный
# 4. Если не помогает — проверить ресурсы
./scripts/ssh-internal.sh 10.99.87.249 "free -h; df -h; docker stats --no-stream"
Redis упал — массовый разлогин (P0)¶
Симптомы: все пользователи одновременно разлогинились.
Redis хранит все auth сессии
Рестарт Redis = разлогин ВСЕХ пользователей. Перезапускать только при необходимости.
# 1. Проверить статус
./scripts/ssh-internal.sh 10.99.87.249 "docker ps | grep redis"
# 2. Проверить память
./scripts/ssh-internal.sh 10.99.87.249 "docker exec redis redis-cli INFO memory"
# 3. Рестарт (крайняя мера)
./scripts/ssh-internal.sh 10.99.87.249 "docker restart redis"
Redis: stop-writes-on-bgsave-error (P1)¶
Симптомы: Redis отклоняет команды записи (MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk), обычно из-за заполненного диска.
# 1. Проверить диск
./scripts/ssh-internal.sh 10.99.87.249 "df -h"
# 2. Если диск полон — освободить место
./scripts/ssh-internal.sh 10.99.87.249 "docker image prune -a -f"
./scripts/ssh-internal.sh 10.99.87.249 "journalctl --vacuum-size=100M"
# 3. Отключить блокировку записи (временно)
./scripts/ssh-internal.sh 10.99.87.249 \
"docker exec redis redis-cli -a '\$(см. Vaultwarden → Redis)' \
CONFIG SET stop-writes-on-bgsave-error no"
# 4. Принудительный BGSAVE
./scripts/ssh-internal.sh 10.99.87.249 \
"docker exec redis redis-cli -a '\$(см. Vaultwarden → Redis)' BGSAVE"
# 5. Когда диск освобождён — вернуть защиту
./scripts/ssh-internal.sh 10.99.87.249 \
"docker exec redis redis-cli -a '\$(см. Vaultwarden → Redis)' \
CONFIG SET stop-writes-on-bgsave-error yes"
MySQL: Too many connections (P1)¶
⚠️ L3 — убивание процессов MySQL только по согласованию
# 1. Текущие подключения
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysql -uvpn -p'$(см. Vaultwarden → Databases)' -e 'SHOW STATUS LIKE \"Threads_connected\"'"
# 2. Убить спящие подключения (>5 мин)
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysql -uvpn -p'$(см. Vaultwarden → Databases)' -e \"
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 300;\" | tail -n +2 | \
docker exec -i vpn-db mysql -uvpn -p'$(см. Vaultwarden → Databases)'"
# 3. Временно увеличить лимит
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysql -uvpn -p'$(см. Vaultwarden → Databases)' -e 'SET GLOBAL max_connections = 500'"
Диск заполнен (P1)¶
# 1. Проверить
./scripts/ssh-internal.sh 10.99.87.249 "df -h"
# 2. Почистить Docker
./scripts/ssh-internal.sh 10.99.87.249 "docker system prune -a -f"
# 3. Почистить логи
./scripts/ssh-internal.sh 10.99.87.249 "journalctl --vacuum-time=3d"
# 4. MySQL binlogs
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysql -uvpn -p'$(см. Vaultwarden → Databases)' \
-e 'PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY)'"
Ограничения journald и Docker
journald ограничен 500M (SystemMaxUse=500M в /etc/systemd/journald.conf).
Docker образы очищаются еженедельно через cron: /etc/cron.d/docker-cleanup (docker image prune -a -f).
Деплой¶
Backend (auto CI/CD)¶
Push в main → автоматический blue-green деплой.
# Проверить текущее состояние
./scripts/ssh-internal.sh 10.99.87.249 "cat /opt/deploy/.deploy-state"
# Откат
./scripts/ssh-internal.sh 10.99.87.249 "/opt/deploy/deploy-backend.sh --rollback"
# Контейнеры: vpn-back-blue (:8080) / vpn-back-green (:8081)
# nginx upstream: equal-weight 8080/8081, fail_timeout=10s
VPN Config Service (manual deploy)¶
Mass Server Drop alert
После рестарта worker'а — last_sync устареет на ~10 мин (пока идёт full_sync).
Это ожидаемое поведение, не баг.
Admin Frontend (manual deploy)¶
Push в main → auto build → manual trigger update_service_prod.
GitLab project ID: 39, target: 10.99.87.64:80.
Обслуживание¶
Ручная проверка состояния системы¶
# API layers
curl -I https://api.shivavpn.io # External
curl -I https://212.70.189.60 # Proxy direct
curl http://10.99.87.249:8080/actuator/health # Backend direct
# Docker containers
./scripts/ssh-internal.sh 10.99.87.249 \
"docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.RunningFor}}'"
# Resources
./scripts/ssh-internal.sh 10.99.87.249 "free -h; df -h; docker stats --no-stream"
Продлить подписку пользователю¶
./scripts/ssh-internal.sh 10.99.87.249 'TOKEN=$(curl -s -X POST \
http://localhost:8080/api/auth/login/admin \
-H "Content-Type: application/json" \
-d "{\"login\":\"admin@shivavpn.io\",\"password\":\"<см. Vaultwarden → Databases → MySQL PROD>\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)[\"accessToken\"])") && \
curl -s -X POST \
"http://localhost:8080/api/admin/maintenance/accounts/ACCOUNT_NUMBER/add-days?days=30" \
-H "Authorization: Bearer $TOKEN"' 2>/dev/null
Заменить ACCOUNT_NUMBER (16 цифр) и days=30.
Принудительная синхронизация аккаунта¶
# Через Backend Admin API
TOKEN=<получить через login>
curl -X POST \
"http://10.99.87.249:8080/api/admin/maintenance/sync/account/by-login/<ACCOUNT>" \
-H "Authorization: Bearer $TOKEN"
# Через VPN Config Service
curl -X POST "http://10.99.87.249:8000/api/v1/sync/fast?force=true" \
-H "X-API-Key: [API key: см. Vaultwarden]"
Рестарт VPN сервера (XUI)¶
Никогда не делай docker restart xray
Это отключит ВСЕХ пользователей на сервере. Для изменения конфигов используй X-UI API (addClient/delClient), xray подхватывает через gRPC hot-reload.
# Только если сервер реально не работает:
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519_shivavpn root@<IP>
systemctl restart xray
journalctl -u xray --tail 50
Бэкап MySQL¶
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysqldump -uvpn -p'$(см. Vaultwarden → Databases)' \
--single-transaction --quick vpn" | gzip > shiva_backup_$TIMESTAMP.sql.gz
Диагностика VPN сервера (XUI)¶
# Проверить состояние xray
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519_shivavpn root@SERVER_IP
systemctl status xray
journalctl -u xray --tail 50
ss -tlnp | grep 443
xray -test -config /usr/local/etc/xray/config.json
docker restart xray
Отключает ВСЕХ пользователей на сервере. Для изменения конфигов используй X-UI API (hot-reload через gRPC). Рестартовать systemctl только если сервер реально не работает.
Обновление сертификатов¶
# Проверить срок
ssh shivavpn-proxy "docker exec nginx_app_1 certbot certificates"
# Обновить
docker exec nginx_app_1 certbot renew
docker exec nginx_app_1 nginx -s reload
# Проверить
openssl s_client -connect api.shivavpn.io:443 -servername api.shivavpn.io \
2>/dev/null | openssl x509 -noout -dates
DDoS атака¶
⚠️ L3 — блокировка IP на прокси
# 1. Проверить подключения
ssh shivavpn-proxy
netstat -anp | grep :443 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n
# 2. Заблокировать IP
iptables -A INPUT -s BAD_IP -j DROP
Безопасность¶
Реагирование на инцидент безопасности¶
⚠️ L3 only — немедленно связаться с DevOps Lead
# 1. Изолировать скомпрометированную систему
iptables -A INPUT -j DROP # блокировать весь входящий трафик
# 2. Сохранить доказательства
tar -czf /tmp/incident_$(date +%Y%m%d).tar.gz /var/log
# 3. Проверить признаки компрометации
last -10 # последние логины
ps auxf # запущенные процессы
netstat -tulpn # открытые порты
find / -mtime -1 -type f # файлы изменённые за последние 24ч
# 4. Сброс учётных данных
# Сменить все пароли (см. Vaultwarden)
# Ротировать SSH ключи
# Отозвать API токены
# 5. Восстановление сервиса
# Пересобрать из чистого образа
# Восстановить из бэкапа
# Применить патчи безопасности
После инцидента: задокументировать инцидент (monorepo: incidents/INCIDENT-NAME-YYYY-MM-DD.md).
Ротация паролей¶
⚠️ L3 only — ротация паролей затрагивает все сервисы
# 1. MySQL root
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysql -uroot -p'\$MYSQL_ROOT_PASS' -e \
\"ALTER USER 'root'@'%' IDENTIFIED BY 'NEW_PASSWORD'; FLUSH PRIVILEGES;\""
# 2. Обновить в Vaultwarden → Databases → MySQL PROD
# 3. Обновить конфигурацию backend
./scripts/ssh-internal.sh 10.99.87.249 \
"grep DB_PASSWORD /opt/deploy/.env"
# Заменить значение, затем:
./scripts/ssh-internal.sh 10.99.87.249 "docker restart vpn-back-blue vpn-back-green"
# 4. Обновить VPN Config Service
./scripts/ssh-internal.sh 10.99.87.249 \
"nano /opt/vpn-config-service/.env && docker restart vpn-config-service vpn-config-worker"
# 5. Проверить доступность
curl http://10.99.87.249:8080/actuator/health
curl http://10.99.87.249:8000/health
Окно обслуживания¶
# 1. За 24 часа до начала
# - Обновить статус-страницу
# - Отправить анонс в Telegram
# 2. Подготовка (перед началом)
./scripts/ssh-internal.sh 10.99.87.249 \
"docker tag vpn-back-blue:latest vpn-back-blue:rollback"
./scripts/ssh-internal.sh 10.99.87.62 \
"docker exec vpn-db mysqldump -uvpn -p'\$MYSQL_PASS' \
--single-transaction vpn | gzip > /tmp/backup_pre_maintenance.sql.gz"
# 3. Выполнить обслуживание
# (следовать конкретному runbook)
# 4. Проверка после завершения
curl -I https://api.shivavpn.io
curl http://10.99.87.249:8080/actuator/health
curl http://10.99.87.249:8000/health
# 5. Мониторинг 2 часа
watch -n 30 'docker logs vpn-back-blue --tail 5'
# Проверять Grafana на аномалии (online_sessions, error rate)
Уровни критичности¶
| Уровень | Описание | Время реакции |
|---|---|---|
| P0 | Полный аутейдж, потеря данных | < 15 мин |
| P1 | Частичный аутейдж, деградация > 50% | < 1 час |
| P2 | Отдельные фичи не работают | < 4 часа |
| P3 | Косметические проблемы | Следующий спринт |
Контакты¶
- Max — Product Owner (бизнес-решения)
- Nikita — Ops (доступ к инфраструктуре)
- zardes — DevOps + Backend (техническая часть)