Automatización de backups incrementales con rsync y snapshots LVM
La gestión de datos en entornos de producción exige estrategias de backup que minimicen la ventana de respaldo, reduzcan el consumo de almacenamiento y garanticen la consistencia de los datos. La combinación de rsync para la transferencia eficiente de diferencias a nivel de archivo y los snapshots LVM (Logical Volume Manager) para obtener puntos de restauración consistentes a nivel de bloque constituye una de las arquitecturas más robustas, flexibles y económicas para servidores Linux.
Este artículo desglosa la implementación de un sistema de backups incrementales que aprovecha la atomicidad de LVM y la eficiencia de rsync, proporcionando un flujo de trabajo profesional, reproducible y auditable.
Fundamentos Técnicos: Por qué LVM + rsync
Antes de sumergirnos en la implementación, es crucial entender el "por qué" de cada componente.
El problema del backup en caliente (Hot Backup)
Realizar una copia de seguridad de un sistema de archivos montado mientras está en uso activo (por ejemplo, una base de datos MySQL o un directorio de correos) puede resultar en una copia inconsistente. Los archivos pueden estar siendo escritos en el momento exacto de la copia, resultando en datos corruptos o transacciones incompletas.
Snapshot LVM resuelve esto proporcionando una vista congelada y consistente del volumen lógico en un instante T. No es una copia física de los datos, sino un "enlace" a los bloques originales que registra los cambios posteriores (Copy-on-Write). Esto permite montar el snapshot como un sistema de archivos de solo lectura y realizar el backup sin detener los servicios.
La eficiencia de rsync vs. copias completas
cpotar: Cada backup es una copia completa. Ineficiente en espacio y tiempo.rsync: Transfiere solo los bloques de datos que han cambiado entre el origen y el destino. Utiliza un algoritmo de checksum para identificar diferencias a nivel de bloque y luego a nivel de archivo. Esto reduce drásticamente el tráfico de red y el tiempo de ejecución.
La sinergia
- LVM proporciona la instantánea consistente.
- Montamos el snapshot como solo lectura.
rsyncsincroniza el contenido del snapshot con un directorio de destino (local o remoto).- Eliminamos el snapshot para liberar espacio.
Este flujo garantiza que rsync siempre vea un sistema de archivos consistente, incluso si el servidor está bajo alta carga de escritura.
Arquitectura de la Solución
Diseñaremos un sistema que realiza backups incrementales diarios con retención configurable (por ejemplo, 7 días, 4 semanas, 12 meses).
Estructura de Directorios
/backup/
├── daily/
│ ├── 2023-10-27/
│ ├── 2023-10-26/
│ └── ...
├── weekly/
│ ├── 2023-10-23/
│ └── ...
└── monthly/
└── 2023-10-01/
Flujo de Trabajo General
- Pre-snapshot: Forzar sync de FS y bloquear escrituras (si es necesario, ej:
FLUSH TABLES WITH READ LOCKen MySQL). - Crear snapshot LVM:
lvcreate --snapshot. - Post-snapshot: Liberar bloqueos de escritura.
- Montar snapshot:
mount -o ro. - Ejecutar rsync: Sincronizar snapshot con el directorio
daily/YYYY-MM-DD/. - Desmontar y eliminar snapshot:
umountylvremove. - Rotación: Mover backups diarios a semanales y mensuales según política.
Implementación Paso a Paso
1. Preparación del Entorno LVM
Asumimos que los datos a respaldar residen en un volumen lógico llamado datos dentro del grupo de volúmenes vg_servidor.
# Verificar la estructura LVM
sudo lvs
sudo vgs
sudo pvs
Salida esperada:
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
datos vg_servidor -wi-ao---- 100.00g
Nota técnica: El atributo
-wi-ao----indica que el volumen está "writeable", "active" y "open". Es fundamental que el volumen esté activo.
2. Script de Backup Incremental (Backup.sh)
Crearemos un script en /usr/local/bin/backup_lvm_rsync.sh.
#!/bin/bash
# Script de backup incremental con LVM snapshot y rsync
# Versión: 2.1
# Autor: Sysadmin
# --- Configuración ---
VG_NAME="vg_servidor"
LV_NAME="datos"
SNAPSHOT_NAME="datos_snap"
SNAPSHOT_SIZE="10G" # 10% del tamaño del LV original es una regla general
MOUNT_POINT="/mnt/snapshot"
BACKUP_ROOT="/backup"
RSYNC_USER="backup_user"
RSYNC_SERVER="192.168.1.100" # Servidor de backups remoto
RSYNC_PATH="/remote_backup" # Ruta en el servidor remoto
LOG_FILE="/var/log/backup_lvm.log"
RETENTION_DAYS=7
RETENTION_WEEKS=4
RETENTION_MONTHS=12
# --- Funciones ---
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
cleanup() {
log_message "Iniciando limpieza por error..."
# Desmontar si está montado
if mountpoint -q "$MOUNT_POINT"; then
umount "$MOUNT_POINT" && log_message "Snapshot desmontado."
fi
# Eliminar snapshot si existe
if lvs --noheadings -o lv_name | grep -q "^$SNAPSHOT_NAME$"; then
lvremove -f "/dev/$VG_NAME/$SNAPSHOT_NAME" && log_message "Snapshot eliminado."
fi
exit 1
}
# --- Ejecución Principal ---
log_message "=== INICIO BACKUP ==="
# 1. Crear el snapshot LVM
log_message "Creando snapshot LVM de $LV_NAME..."
lvcreate --snapshot --name "$SNAPSHOT_NAME" --size "$SNAPSHOT_SIZE" "/dev/$VG_NAME/$LV_NAME"
if [ $? -ne 0 ]; then
log_message "ERROR: No se pudo crear el snapshot."
cleanup
fi
log_message "Snapshot creado: /dev/$VG_NAME/$SNAPSHOT_NAME"
# 2. Montar el snapshot como solo lectura
mkdir -p "$MOUNT_POINT"
mount -o ro "/dev/$VG_NAME/$SNAPSHOT_NAME" "$MOUNT_POINT"
if [ $? -ne 0 ]; then
log_message "ERROR: No se pudo montar el snapshot."
cleanup
fi
log_message "Snapshot montado en $MOUNT_POINT"
# 3. Ejecutar rsync incremental
# La fecha se usa para el directorio de destino
BACKUP_DATE=$(date +%Y-%m-%d)
DEST_DIR="$BACKUP_ROOT/daily/$BACKUP_DATE"
log_message "Iniciando rsync hacia $DEST_DIR..."
# Opciones de rsync explicadas:
# -a: Modo archivo (recursivo, preserva permisos, timestamps, dueños, etc.)
# -v: Verboso para logs detallados
# -z: Comprime datos durante la transferencia
# --delete: Elimina archivos en destino que ya no existen en origen
# --link-dest: Crea enlaces duros a backups anteriores para ahorrar espacio
# -e: Especifica el shell remoto (ssh en este caso)
# Buscar el último backup exitoso para usar como base para enlaces duros
LAST_BACKUP=$(ls -1dt "$BACKUP_ROOT/daily"/*/ 2>/dev/null | head -n 1)
if [ -n "$LAST_BACKUP" ]; then
LINK_DEST_OPTION="--link-dest=$LAST_BACKUP"
log_message "Usando backup anterior como base: $LAST_BACKUP"
else
LINK_DEST_OPTION=""
log_message "Primer backup. No hay backup anterior para enlazar."
fi
# Ejecutar rsync hacia destino local
rsync -avz --delete $LINK_DEST_OPTION "$MOUNT_POINT/" "$DEST_DIR/" 2>&1 | tee -a "$LOG_FILE"
# Opción remota (descomentar si se usa servidor remoto)
# rsync -avz --delete -e "ssh -i /root/.ssh/backup_key" $LINK_DEST_OPTION "$MOUNT_POINT/" "$RSYNC_USER@$RSYNC_SERVER:$RSYNC_PATH/daily/$BACKUP_DATE/" 2>&1 | tee -a "$LOG_FILE"
if [ $? -ne 0 ]; then
log_message "ERROR: rsync falló."
cleanup
fi
log_message "rsync completado exitosamente."
# 4. Limpieza del snapshot
umount "$MOUNT_POINT"
lvremove -f "/dev/$VG_NAME/$SNAPSHOT_NAME"
log_message "Snapshot eliminado."
# 5. Rotación de backups (política de retención)
log_message "Iniciando rotación de backups..."
# Rotación diaria: eliminar backups de hace más de RETENTION_DAYS días
find "$BACKUP_ROOT/daily" -maxdepth 1 -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \; -print >> "$LOG_FILE"
# Promover backup semanal (cada domingo)
if [ "$(date +%u)" -eq 7 ]; then
WEEKLY_DIR="$BACKUP_ROOT/weekly/$(date +%Y-%m-%d)"
cp -al "$DEST_DIR" "$WEEKLY_DIR" # Enlace duro, no copia datos
log_message "Backup promovido a semanal: $WEEKLY_DIR"
# Rotación semanal
find "$BACKUP_ROOT/weekly" -maxdepth 1 -type d -mtime +$((RETENTION_WEEKS * 7)) -exec rm -rf {} \; -print >> "$LOG_FILE"
fi
# Promover backup mensual (primer día del mes)
if [ "$(date +%d)" -eq 1 ]; then
MONTHLY_DIR="$BACKUP_ROOT/monthly/$(date +%Y-%m)"
cp -al "$DEST_DIR" "$MONTHLY_DIR"
log_message "Backup promovido a mensual: $MONTHLY_DIR"
# Rotación mensual
find "$BACKUP_ROOT/monthly" -maxdepth 1 -type d -mtime +$((RETENTION_MONTHS * 30)) -exec rm -rf {} \; -print >> "$LOG_FILE"
fi
log_message "=== FIN BACKUP EXITOSO ==="
Explicación de Opciones Críticas de rsync
| Opción | Función | Impacto en el Backup |
|---|---|---|
-a | Modo archivo. Preserva todo (permisos, dueños, grupos, timestamps, enlaces simbólicos, dispositivos). | Garantiza que la restauración sea idéntica al original. |
-v | Verboso. Muestra progreso en stdout. | Útil para depuración y monitoreo en tiempo real. |
-z | Compresión durante la transferencia. | Reduce el ancho de banda usado, pero consume CPU. Recomendado para WAN. |
--delete | Elimina archivos en el destino que no existen en el origen. | Esencial para mantener el backup como un espejo exacto. Sin esto, los archivos eliminados en origen persistirían en el backup. |
--link-dest | Crea enlaces duros a archivos idénticos en un directorio de referencia. | Clave para backups incrementales eficientes en espacio. Si un archivo no ha cambiado, no se copia, solo se crea un enlace duro. |
3. Automatización con Cron
Ejecutaremos el script diariamente en un horario de baja actividad.
# Editar crontab del root
sudo crontab -e
Añadir la línea:
# Backup incremental LVM+rsync todos los días a las 2:00 AM
0 2 * * * /usr/local/bin/backup_lvm_rsync.sh
Mejora profesional: Implementa
flockpara evitar ejecuciones concurrentes si el backup se alarga más de 24 horas. Ejemplo:0 2 * * * /usr/bin/flock -n /var/lock/backup.lock /usr/local/bin/backup_lvm_rsync.sh
Gestión de Consistencia de Aplicaciones (Pre/Post Snapshot)
Para bases de datos como MySQL/MariaDB, es necesario garantizar que todas las transacciones en memoria se escriban en disco antes del snapshot.
Ejemplo para MySQL
# En el script, justo antes de lvcreate:
log_message "Bloqueando tablas MySQL..."
mysql -u root -p"$MYSQL_PASSWORD" -e "FLUSH TABLES WITH READ LOCK;"
# Inmediatamente después, crear el snapshot
lvcreate --snapshot ...
# Una vez creado, liberar el lock
mysql -u root -p"$MYSQL_PASSWORD" -e "UNLOCK TABLES;"
log_message "Tablas MySQL liberadas."
Alternativa para XFS: Si usas XFS, puedes usar xfs_freeze antes del snapshot para forzar la consistencia del sistema de archivos.
xfs_freeze -f /mount/point
lvcreate --snapshot ...
xfs_freeze -u /mount/point
Verificación y Restauración
Un backup sin verificación no es un backup. Implementa una rutina de prueba de restauración.
Script de Verificación (Check_restore.sh)
#!/bin/bash
# Verificar integridad del backup más reciente
LATEST_BACKUP=$(ls -1dt /backup/daily/*/ | head -n 1)
echo "Verificando backup: $LATEST_BACKUP"
# 1. Listar archivos críticos
echo "Verificando archivos de configuración..."
if [ -f "$LATEST_BACKUP/etc/nginx/nginx.conf" ]; then
echo "OK: nginx.conf presente"
else
echo "ERROR: nginx.conf faltante"
fi
# 2. Verificar integridad con tar (si se usó tar) o con rsync --dry-run
echo "Simulando restauración con rsync..."
rsync -avz --dry-run "$LATEST_BACKUP/" /dev/null
# 3. Verificar checksums (si se generaron)
if [ -f "$LATEST_BACKUP/checksums.md5" ]; then
echo "Verificando checksums..."
cd "$LATEST_BACKUP"
md5sum -c checksums.md5 --quiet
fi
Monitoreo y Alertas
Integra el script de backup con un sistema de monitoreo como Nagios, Zabbix o simplemente notificaciones por correo.
Notificación por Email
Añadir al final del script:
# Enviar notificación por correo
if [ -x /usr/bin/mail ]; then
tail -n 5 "$LOG_FILE" | mail -s "Backup Status - $(hostname) - $(date +%Y-%m-%d)" sysadmin@example.com
fi
Métricas Clave a Monitorear
| Métrica | Herramienta | Umbral de Alerta |
|---|---|---|
| Duración del backup | time en script | > 4 horas |
| Tamaño del snapshot LVM | lvs -o data_percent | > 80% de SNAPSHOT_SIZE |
| Espacio en disco del destino | df -h | < 20% libre |
| Código de salida del script | $? | != 0 |
Consideraciones de Seguridad
- Permisos del script:
chmod 700 /usr/local/bin/backup_lvm_rsync.shy propiedadroot:root. - Clave SSH: Si usas backup remoto, genera una clave SSH sin passphrase para
rooty limita los comandos permitidos enauthorized_keysdel servidor remoto usandocommand=yfrom=para restringir IP. - Cifrado: Para datos sensibles, considera cifrar el backup con
gpgantes de la transferencia remota. - Aislamiento del snapshot: El tamaño del snapshot debe ser suficiente para contener
