灾备与故障恢复
大约 10 分钟约 2918 字
灾备与故障恢复
简介
灾备(Disaster Recovery, DR)与故障恢复是企业 IT 基础设施保障业务连续性的关键能力。在云计算和分布式系统时代,系统面临硬件故障、网络中断、自然灾害、人为操作失误等多种风险,建立完善的灾备体系和故障恢复机制对于保障业务连续性至关重要。本文将从 RTO/RPO 指标定义、备份策略设计、故障演练实践和多云灾备架构四个方面,深入探讨如何构建企业级的灾备与故障恢复体系。
特点
RTO/RPO 指标体系
RTO(Recovery Time Objective)和 RPO(Recovery Point Objective)是衡量灾备能力的两个核心指标。
# 灾备等级与 RTO/RPO 对照表
# | 灾备等级 | 场景描述 | RTO 目标 | RPO 目标 | 技术方案 | 成本等级 |
# |---------|----------------|-----------|-----------|---------------------|---------|
# | L1 | 核心交易系统 | < 5 分钟 | 0(零丢失) | 同步复制 + 自动切换 | 极高 |
# | L2 | 重要业务系统 | < 30 分钟 | < 1 分钟 | 半同步复制 + 快速切换 | 高 |
# | L3 | 一般业务系统 | < 4 小时 | < 1 小时 | 异步复制 + 定期备份 | 中 |
# | L4 | 辅助支撑系统 | < 24 小时 | < 24 小时 | 定期备份 + 手动恢复 | 低 |
# 灾备配置管理
apiVersion: v1
kind: ConfigMap
metadata:
name: dr-config
namespace: disaster-recovery
data:
# 全局灾备配置
PRIMARY_REGION: "us-east-1"
DR_REGION: "us-west-2"
DNS_FAILOVER_TTL: "60"
# 数据库复制配置
DB_REPLICATION_MODE: "semi-sync" # sync / semi-sync / async
DB_FAILOVER_MODE: "automatic" # automatic / manual
DB_FAILOVER_THRESHOLD: "30" # 秒
# 备份配置
BACKUP_SCHEDULE_FULL: "0 2 * * 0" # 每周日全量
BACKUP_SCHEDULE_INCR: "0 */6 * * *" # 每 6 小时增量
BACKUP_RETENTION_DAYS: "90"
# 健康检查配置
HEALTH_CHECK_INTERVAL: "10" # 秒
HEALTH_CHECK_TIMEOUT: "5" # 秒
HEALTH_CHECK_THRESHOLD: "3" # 连续失败次数触发切换#!/bin/bash
# dr-monitor.sh - 灾备监控脚本
set -euo pipefail
PRIMARY_API="https://api-primary.example.com/healthz"
DR_API="https://api-dr.example.com/healthz"
ALERT_WEBHOOK="${SLACK_WEBHOOK_URL}"
LOG_FILE="/var/log/dr-monitor.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
alert() {
local level="$1"
local message="$2"
local color="danger"
[ "$level" == "WARN" ] && color="warning"
curl -s -X POST "$ALERT_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{
\"attachments\": [{
\"color\": \"$color\",
\"title\": \"[灾备告警] $level\",
\"text\": \"$message\",
\"fields\": [
{\"title\": \"主区域状态\", \"value\": \"$PRIMARY_STATUS\", \"short\": true},
{\"title\": \"灾备区域状态\", \"value\": \"$DR_STATUS\", \"short\": true},
{\"title\": \"时间\", \"value\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\", \"short\": true}
]
}]
}" > /dev/null
}
# 检查主区域健康状态
check_primary() {
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 "$PRIMARY_API")
echo "$http_code"
}
# 检查灾备区域健康状态
check_dr() {
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 "$DR_API")
echo "$http_code"
}
# 主监控循环
FAIL_COUNT=0
while true; do
PRIMARY_STATUS=$(check_primary)
DR_STATUS=$(check_dr)
if [ "$PRIMARY_STATUS" != "200" ]; then
FAIL_COUNT=$((FAIL_COUNT + 1))
log "WARN: 主区域健康检查失败 ($FAIL_COUNT/3), HTTP=$PRIMARY_STATUS"
if [ $FAIL_COUNT -ge 3 ]; then
log "CRITICAL: 主区域连续 3 次健康检查失败,触发灾备切换"
alert "CRITICAL" "主区域不可用,需要立即进行灾备切换"
# 执行灾备切换流程
/opt/dr/scripts/failover.sh
break
fi
else
if [ $FAIL_COUNT -gt 0 ]; then
log "INFO: 主区域恢复正常"
fi
FAIL_COUNT=0
fi
sleep 10
done备份策略设计
企业级备份需要覆盖数据库、文件存储、配置和状态数据等多个维度。
#!/bin/bash
# backup-postgres.sh - PostgreSQL 备份脚本
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/../lib/utils.sh"
# 配置
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
DB_USER="${DB_USER:-backup_user}"
BACKUP_DIR="/data/backups/postgres"
S3_BUCKET="s3://my-backups/postgres"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
# 获取数据库列表
DATABASES=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;" | tr -d ' ')
for DB_NAME in $DATABASES; do
log INFO "备份数据库: $DB_NAME"
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz"
# 使用 pg_dump 自定义格式(支持并行恢复)
pg_dump \
-h "$DB_HOST" \
-p "$DB_PORT" \
-U "$DB_USER" \
-d "$DB_NAME" \
--format=custom \
--compress=6 \
--verbose \
--file="${BACKUP_DIR}/${DB_NAME}_${DATE}.dump"
# 同时导出纯 SQL 格式(用于跨版本恢复)
pg_dump \
-h "$DB_HOST" \
-p "$DB_PORT" \
-U "$DB_USER" \
-d "$DB_NAME" \
--format=plain \
--no-owner \
--no-privileges | gzip > "$BACKUP_FILE"
# 计算校验和
sha256sum "$BACKUP_FILE" > "${BACKUP_FILE}.sha256"
sha256sum "${BACKUP_DIR}/${DB_NAME}_${DATE}.dump" > "${BACKUP_DIR}/${DB_NAME}_${DATE}.dump.sha256"
# 上传到 S3
aws s3 cp "$BACKUP_FILE" "${S3_BUCKET}/${DB_NAME}/$(date +%Y/%m/)/" --storage-class STANDARD_IA
aws s3 cp "${BACKUP_DIR}/${DB_NAME}_${DATE}.dump" "${S3_BUCKET}/${DB_NAME}/$(date +%Y/%m/)/" --storage-class STANDARD_IA
log INFO "数据库 $DB_NAME 备份完成"
done
# 验证备份完整性
log INFO "验证备份完整性..."
LATEST_BACKUP=$(ls -t "${BACKUP_DIR}"/*.dump | head -1)
if pg_restore --list "$LATEST_BACKUP" > /dev/null 2>&1; then
log INFO "备份验证通过"
else
log ERROR "备份验证失败,请立即检查"
notify "备份验证失败" "PostgreSQL 备份验证未通过"
exit 1
fi
# 清理过期备份
log INFO "清理 ${RETENTION_DAYS} 天前的本地备份"
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -name "*.dump" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -name "*.sha256" -mtime +${RETENTION_DAYS} -delete#!/bin/bash
# backup-k8s-resources.sh - Kubernetes 资源备份脚本
set -euo pipefail
BACKUP_DIR="/data/backups/kubernetes"
DATE=$(date +%Y%m%d_%H%M%S)
S3_BUCKET="s3://my-backups/kubernetes"
# 备份所有命名空间的资源定义
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
log INFO "备份命名空间: $ns"
mkdir -p "${BACKUP_DIR}/${ns}"
# 备份该命名空间下所有资源
kubectl get all,configmaps,secrets,ingresses,pvc,serviceaccounts,roles,rolebindings \
-n "$ns" -o yaml > "${BACKUP_DIR}/${ns}/${ns}_${DATE}.yaml"
done
# 备份集群级别资源
mkdir -p "${BACKUP_DIR}/cluster"
kubectl get clusterroles,clusterrolebindings,storageclasses,namespaces \
-o yaml > "${BACKUP_DIR}/cluster/cluster_${DATE}.yaml"
# 备份 Helm releases
mkdir -p "${BACKUP_DIR}/helm"
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
for release in $(helm list -n "$ns" -q 2>/dev/null); do
helm get values "$release" -n "$ns" > "${BACKUP_DIR}/helm/${ns}_${release}_values_${DATE}.yaml"
helm get manifest "$release" -n "$ns" > "${BACKUP_DIR}/helm/${ns}_${release}_manifest_${DATE}.yaml"
done
done
# 备份 etcd(需要直接访问 etcd 集群)
ETCDCTL_API=3 etcdctl snapshot save "${BACKUP_DIR}/cluster/etcd_${DATE}.db" \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# 上传到 S3
aws s3 sync "$BACKUP_DIR" "${S3_BUCKET}/${DATE}/" --storage-class STANDARD_IA
log INFO "Kubernetes 资源备份完成"故障演练实践
故障演练(混沌工程)是验证灾备方案有效性的关键手段。
# Chaos Mesh 配置 - 网络故障注入
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: network-delay-primary
namespace: chaos-testing
spec:
action: delay
mode: one
selector:
namespaces:
- production
labelSelectors:
app: web-platform
delay:
latency: "500ms"
correlation: "50"
jitter: "100ms"
direction: to
duration: "5m"
---
# Pod 故障注入
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: pod-failure-drill
namespace: chaos-testing
spec:
action: pod-failure
mode: fixed-percent
value: "30"
selector:
namespaces:
- production
labelSelectors:
app: web-platform
duration: "3m"
---
# 磁盘故障注入
apiVersion: chaos-mesh.org/v1alpha1
kind: IOChaos
metadata:
name: disk-fault-drill
namespace: chaos-testing
spec:
action: fault
mode: one
selector:
namespaces:
- production
labelSelectors:
app: postgres
volumePath: /var/lib/postgresql/data
path: "/var/lib/postgresql/data/**/*"
errno: 5
percent: 50
duration: "2m"#!/bin/bash
# dr-drill.sh - 灾备演练脚本
set -euo pipefail
DRILL_ID="drill-$(date +%Y%m%d-%H%M%S)"
REPORT_FILE="/data/drill-reports/${DRILL_ID}.md"
log_info() {
echo "[INFO] [$DRILL_ID] $*" | tee -a "$REPORT_FILE"
}
log_result() {
local test_name="$1"
local result="$2"
local rto="$3"
local rpo="$4"
echo "| $test_name | $result | ${rto}s | ${rpo}s |" | tee -a "$REPORT_FILE"
}
# 初始化演练报告
cat > "$REPORT_FILE" << EOF
# 灾备演练报告
- 演练编号: $DRILL_ID
- 演练时间: $(date '+%Y-%m-%d %H:%M:%S')
- 演练类型: 计划性故障切换
| 测试项 | 结果 | 实际 RTO | 实际 RPO |
|--------------|--------|---------|---------|
EOF
# 测试 1: 数据库故障切换
log_info "=== 测试 1: 数据库故障切换 ==="
START_TIME=$(date +%s)
# 模拟主库故障
kubectl scale statefulset postgres-primary -n production --replicas=0
log_info "主库已缩容为 0,等待自动切换..."
# 等待切换完成
while true; do
if kubectl exec -n production statefulset/postgres-standby -- pg_isready -q 2>/dev/null; then
break
fi
sleep 5
done
END_TIME=$(date +%s)
ACTUAL_RTO=$((END_TIME - START_TIME))
log_result "数据库故障切换" "通过/失败" "$ACTUAL_RTO" "0"
# 恢复主库
kubectl scale statefulset postgres-primary -n production --replicas=1
log_info "数据库演练完成"
# 测试 2: 应用层故障切换
log_info "=== 测试 2: 应用层故障切换 ==="
START_TIME=$(date +%s)
# 模拟应用故障
kubectl scale deployment web-platform -n production --replicas=0
log_info "应用已缩容为 0,等待自动恢复..."
# HPA 会自动恢复
sleep 60
kubectl get pods -n production -l app=web-platform
END_TIME=$(date +%s)
ACTUAL_RTO=$((END_TIME - START_TIME))
log_result "应用层自动恢复" "通过/失败" "$ACTUAL_RTO" "N/A"
log_info "=== 灾备演练完成 ==="
log_info "详细报告: $REPORT_FILE"多云灾备架构
# 多云灾备架构配置
# 主区域: us-east-1 (AWS)
# 灾备区域: us-west-2 (AWS) 或 Azure East Asia
# Terraform 灾备基础设施定义
# primary/ - 主区域基础设施
# dr/ - 灾备区域基础设施
# DNS 故障切换配置 (Route53)
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: app-failover
spec:
endpoints:
- dnsName: app.example.com
recordTTL: 60
recordType: A
targets:
- "10.0.1.100" # 主区域 LB IP
providerSpecific:
- name: failover
value: "true"
- name: health-check-id
value: "abc-123-def"
- name: failover-target
value: "10.1.1.100" # 灾备区域 LB IP
---
# Velero 备份配置(跨集群灾备)
apiVersion: velero.io/v1
kind: Schedule
metadata:
name: daily-full-backup
namespace: velero
spec:
schedule: "0 2 * * *"
template:
includedNamespaces:
- production
storageLocation: aws-dr-backup
volumeSnapshotLocations:
- aws-primary
ttl: 720h
labels:
type: full
---
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: aws-dr-backup
namespace: velero
spec:
provider: aws
objectStorage:
bucket: my-dr-backups
prefix: velero
config:
region: us-west-2
---
# 灾备切换流程配置
apiVersion: v1
kind: ConfigMap
metadata:
name: failover-procedure
namespace: disaster-recovery
data:
failover-steps: |
1. 确认主区域故障状态
- 检查监控告警
- 确认故障范围和影响
- 通知相关团队
2. 决策是否执行灾备切换
- 评估 RTO 影响
- 确认主区域恢复时间预期
- 获得灾备切换授权
3. 执行灾备切换
- DNS 流量切换到灾备区域
- 提升灾备区域数据库为主库
- 验证灾备区域服务可用性
- 通知上下游依赖方
4. 验证灾备切换结果
- 检查所有核心业务功能
- 验证数据一致性
- 监控错误率和性能指标
5. 主区域恢复后回切
- 同步灾备区域数据到主区域
- 验证数据一致性
- DNS 流量切换回主区域
- 确认服务正常
## 关键知识点
- DevOps 主题的核心是让交付更快、更稳、更可审计。
- 自动化不是把命令脚本化,而是把失败、回滚、权限和观测一起设计进去。
- 生产链路必须明确制品、环境、凭据、配置和责任边界。
## 项目落地视角
- 把流水线拆成构建、测试、制品、部署、验证和回滚几个阶段。
- 为关键步骤补齐日志、指标、通知和人工兜底点。
- 定期演练扩容、回滚、故障注入和灾备切换。
## 常见误区
- 只关注部署成功,不关注失败恢复和审计追踪。
- 把环境差异藏在临时脚本或人工操作里。
- 上线频率高了以后,没有标准化制品和配置管理。
## 进阶路线
- 继续补齐 GitOps、可观测性、平台工程和成本治理。
- 把主题和应用架构、安全、权限、备份恢复联动起来理解。
- 形成团队级平台能力,而不是每个项目重复造轮子。
## 适用场景
- 当你准备把《灾备与故障恢复》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合构建自动化交付、基础设施治理、监控告警和生产发布体系。
- 当团队规模扩大、发布频率提升或环境变多时,这类主题会显著影响交付效率。
## 落地建议
- 所有自动化流程尽量做到幂等、可审计、可回滚。
- 把制品、变量、凭据和执行权限分层管理。
- 定期演练扩容、回滚、密钥轮换和灾备恢复。
## 排错清单
- 先定位失败发生在代码、构建、制品、环境还是权限层。
- 检查流水线变量、凭据、镜像标签和目标环境配置是否一致。
- 如果问题偶发,重点看并发发布、资源争抢和外部依赖抖动。
## 复盘问题
- 如果把《灾备与故障恢复》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《灾备与故障恢复》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《灾备与故障恢复》最大的收益和代价分别是什么?
## 延伸阅读
- [Docker volume](/dir/devops/docker_volume.md)
- [Docker networking deep](/dir/devops/docker_networking_deep.md)
- [Docker compose override](/dir/devops/docker_compose_override.md)
- [Docker 安全实践](/dir/devops/docker_security.md)