Разработка плана аварийного восстановления (Disaster Recovery Plan)
Disaster Recovery Plan (DRP) — документированный набор процедур для восстановления IT-инфраструктуры после катастрофического сбоя. Без DRP команда в панике, данные теряются, восстановление занимает дни вместо часов. Хороший DRP существует в двух форматах: детальный документ для анализа и краткие runbook'и для выполнения в 3 часа ночи.
Структура DRP
1. Классификация сценариев по приоритету
| Сценарий | RTO | RPO | Вероятность |
|---|---|---|---|
| Падение сервера приложений | 15 мин | 0 | Высокая |
| Отказ primary БД | 30 мин | 5 мин | Средняя |
| Потеря дата-центра (регион) | 4 ч | 1 ч | Низкая |
| Атака ransomware / удаление данных | 8 ч | 24 ч | Низкая |
| Ошибка деплоя (критическая регрессия) | 30 мин | 0 | Высокая |
2. Ответственные и контакты
# drp/contacts.yml
incident_commander: "CTO"
primary_oncall: "DevOps Team"
contacts:
- role: "Incident Commander"
name: "Aleksei Petrov"
phone: "+7-xxx-xxx-xxxx"
telegram: "@apetrov"
- role: "DB Admin"
name: "Marina Sidorova"
phone: "+7-xxx-xxx-xxxx"
- role: "AWS Support"
account_id: "123456789"
support_url: "https://console.aws.amazon.com/support"
tier: "Business"
- role: "Cloudflare"
api_token_location: "Vault: cloudflare/api-token"
3. Инвентаризация критических компонентов
# drp/inventory.yml
critical_systems:
- name: "PostgreSQL Primary"
host: "db-primary.internal"
backup_location: "s3://backups/postgres/"
backup_frequency: "hourly"
replication: "streaming to db-replica-1, db-replica-2"
runbook: "runbooks/db-failover.md"
- name: "Application Servers"
hosts: ["app-1", "app-2", "app-3"]
ami_id: "ami-0abc123def456" # последний golden image
auto_scaling_group: "app-asg-prod"
runbook: "runbooks/app-restore.md"
- name: "Redis"
host: "redis-primary.internal"
persistence: "RDB + AOF"
backup_location: "s3://backups/redis/"
runbook: "runbooks/redis-restore.md"
- name: "S3 Media Bucket"
bucket: "myapp-media-prod"
replication: "Cross-region to eu-west-1"
runbook: "runbooks/s3-restore.md"
Runbook: потеря primary БД
# RUNBOOK: PostgreSQL Primary Failure
**Время выполнения:** 15-30 минут
**Требования:** доступ к AWS, ssh к серверам
## Признаки
- Алерт: "PostgreSQL Primary Down" в PagerDuty
- Приложение возвращает 500 для операций записи
- pg_stat_replication показывает 0 реплик
## Шаги
### 1. Подтвердить отказ (2 минуты)
```bash
ssh db-primary.internal
# Если нет соединения — primary недоступен
psql -h db-primary.internal -U postgres -c "SELECT 1;"
2. Выбрать лучшую реплику (3 минуты)
# На каждой реплике проверить отставание
psql -h db-replica-1.internal -U postgres -c "
SELECT pg_is_in_recovery(),
pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn(),
pg_last_wal_receive_lsn() - pg_last_wal_replay_lsn() AS lag_bytes;"
Выбрать реплику с наименьшим lag_bytes.
3. Промоут реплики в primary (5 минут)
# Patroni (рекомендуется — автоматически)
patronictl -c /etc/patroni/patroni.yml failover cluster-name --master db-replica-1
# Вручную (если без Patroni)
ssh db-replica-1.internal
pg_ctl promote -D /var/lib/postgresql/data
4. Перенаправить трафик (5 минут)
# Обновить DNS или HAProxy
aws route53 change-resource-record-sets --hosted-zone-id Z123 --change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "db-primary.example.com",
"Type": "A",
"TTL": 60,
"ResourceRecords": [{"Value": "NEW-PRIMARY-IP"}]
}
}]
}'
5. Перезапустить приложение (2 минуты)
kubectl rollout restart deployment/api -n production
6. Верификация
# Проверить работоспособность
curl https://api.example.com/health
psql -h db-primary.example.com -U app -c "SELECT count(*) FROM users;"
7. Пост-инцидент
- Создать новую реплику из промоутированного primary
- Расследовать причину отказа original primary
- Обновить мониторинг если необходимо
### Автоматизация DR-процедур
```bash
#!/bin/bash
# scripts/dr/db-failover.sh
# Автоматический failover при подтверждённом отказе primary
set -euo pipefail
LOG_FILE="/var/log/dr/failover-$(date +%Y%m%d-%H%M).log"
exec > >(tee -a $LOG_FILE) 2>&1
echo "[$(date -u)] Starting PostgreSQL failover procedure"
# 1. Подтвердить отказ
if pg_isready -h "$DB_PRIMARY_HOST" -U postgres -t 5; then
echo "[$(date -u)] Primary is actually up. Aborting."
exit 1
fi
# 2. Найти лучшую реплику
BEST_REPLICA=""
BEST_LAG=999999999
for replica in $DB_REPLICAS; do
LAG=$(psql -h $replica -U postgres -tAc "
SELECT extract(epoch from (now() - pg_last_xact_replay_timestamp()))::int
" 2>/dev/null || echo 999999999)
if [ "$LAG" -lt "$BEST_LAG" ]; then
BEST_LAG=$LAG
BEST_REPLICA=$replica
fi
done
echo "[$(date -u)] Best replica: $BEST_REPLICA (lag: ${BEST_LAG}s)"
# 3. Промоут (Patroni)
patronictl -c /etc/patroni/patroni.yml failover \
postgres-cluster --master $BEST_REPLICA --force
echo "[$(date -u)] Failover complete. New primary: $BEST_REPLICA"
# 4. Уведомить Slack
curl -s -X POST "$SLACK_WEBHOOK" -H 'Content-type: application/json' \
-d "{\"text\": \"🔴 DB Failover completed. New primary: $BEST_REPLICA (lag was ${BEST_LAG}s)\"}"
DRP тестирование (DR Drill)
# Расписание учений
drp_tests:
quarterly:
- name: "Database failover drill"
procedure: "runbooks/db-failover.md"
success_criteria:
- "Failover completed in < 30 min"
- "Zero data loss"
- "Application recovered automatically"
annually:
- name: "Full region failover"
procedure: "runbooks/region-failover.md"
success_criteria:
- "Recovery in < 4 hours"
- "Data loss < 1 hour"
Срок выполнения
Разработка полного DRP с runbook'ами для 5–10 сценариев, инвентаризацией и скриптами автоматизации — 3–5 рабочих дней.







