Backup y Recuperación en Linux: Estrategias Profesionales para Proteger Datos
Los backups son la última línea de defensa contra la pérdida de datos. Una estrategia de backup bien diseñada puede ser la diferencia entre un pequeño inconveniente y un desastre catastrófico.
Fundamentos de Backup y Recuperación
Tipos de Backup
1. Backup Completo (Full Backup)
1
2
3
4
5
| # Backup completo con tar
tar -czf backup_completo_$(date +%Y%m%d).tar.gz /home /etc /var/www
# Backup completo con rsync
rsync -avH --delete /home/ /backup/home/
|
2. Backup Incremental
1
2
3
4
5
6
| # Solo archivos modificados desde el último backup
find /home -newer /var/log/last_backup.timestamp -type f | \
tar -czf incremental_$(date +%Y%m%d).tar.gz -T -
# Actualizar timestamp
touch /var/log/last_backup.timestamp
|
3. Backup Diferencial
1
2
3
| # Archivos modificados desde el último backup completo
find /home -newer /var/log/full_backup.timestamp -type f | \
tar -czf diferencial_$(date +%Y%m%d).tar.gz -T -
|
Principios Fundamentales
Regla 3-2-1
- 3 copias de los datos importantes
- 2 medios de almacenamiento diferentes
- 1 copia offsite (fuera del sitio)
RTO y RPO
1
2
3
4
5
6
7
8
9
| # RTO (Recovery Time Objective): Tiempo máximo aceptable de downtime
# RPO (Recovery Point Objective): Pérdida máxima de datos aceptable
# Configurar backups según RPO
# RPO = 1 hora -> Backup cada hora
0 * * * * /script/backup_incremental.sh
# RPO = 1 día -> Backup diario
0 2 * * * /script/backup_completo.sh
|
Herramientas de Backup Nativas
rsync: Sincronización Eficiente
Configuración Básica
1
2
3
4
5
6
7
8
9
10
11
12
| # Backup local
rsync -avH --progress /home/ /backup/home/
# Backup remoto
rsync -avH --progress -e ssh /home/ usuario@servidor:/backup/home/
# Backup con exclusiones
rsync -avH --progress \
--exclude='*.tmp' \
--exclude='*.log' \
--exclude='.cache' \
/home/ /backup/home/
|
Script de Backup con rsync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| #!/bin/bash
# backup_rsync.sh
ORIGEN="/home"
DESTINO="/backup/home"
LOG="/var/log/backup.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$DATE] Iniciando backup..." >> $LOG
# Crear directorio de backup con fecha
BACKUP_DIR="$DESTINO/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Backup con hard links para ahorrar espacio
rsync -avH --link-dest="$DESTINO/latest" "$ORIGEN/" "$BACKUP_DIR/"
# Actualizar enlace a último backup
rm -f "$DESTINO/latest"
ln -s "$BACKUP_DIR" "$DESTINO/latest"
if [ $? -eq 0 ]; then
echo "[$DATE] Backup completado exitosamente" >> $LOG
else
echo "[$DATE] Error en backup" >> $LOG
exit 1
fi
|
tar: Archivos Comprimidos
Backup Completo con tar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # Backup con compresión gzip
tar -czf backup_$(date +%Y%m%d).tar.gz \
--exclude='/proc' \
--exclude='/sys' \
--exclude='/dev' \
--exclude='/tmp' \
--exclude='/var/cache' \
/
# Backup con compresión xz (mejor compresión)
tar -cJf backup_$(date +%Y%m%d).tar.xz /home
# Backup con verificación
tar -czf backup.tar.gz /home && tar -tzf backup.tar.gz > /dev/null
|
Restauración con tar
1
2
3
4
5
6
7
8
| # Listar contenido del archivo
tar -tzf backup.tar.gz | head -20
# Extraer archivos específicos
tar -xzf backup.tar.gz home/usuario/documento.txt
# Restaurar todo
tar -xzf backup.tar.gz -C /restore/
|
dd: Backup a Nivel de Bloque
Clonado de Discos
1
2
3
4
5
6
7
8
| # Clonar disco completo
dd if=/dev/sda of=/dev/sdb bs=64K status=progress
# Crear imagen de disco
dd if=/dev/sda of=/backup/disk_image.img bs=64K status=progress
# Backup con compresión on-the-fly
dd if=/dev/sda bs=64K | gzip > /backup/disk_image.img.gz
|
Backup de Particiones
1
2
3
4
5
| # Backup de partición boot
dd if=/dev/sda1 of=/backup/boot_partition.img bs=64K
# Backup de MBR
dd if=/dev/sda of=/backup/mbr.img bs=512 count=1
|
Herramientas Profesionales
Bacula: Enterprise Backup Solution
Instalación de Bacula
1
2
3
4
5
6
7
| # Ubuntu/Debian
apt-get install bacula-server bacula-client
# Configurar base de datos
sudo -u postgres createuser bacula
sudo -u postgres createdb bacula
/usr/share/bacula/make_postgresql_tables
|
Configuración Director (bacula-dir.conf)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| Director {
Name = "servidor-dir"
DIRport = 9101
QueryFile = "/etc/bacula/scripts/query.sql"
WorkingDirectory = "/var/lib/bacula"
PidDirectory = "/var/run/bacula"
Maximum Concurrent Jobs = 4
Password = "password_director"
Messages = Daemon
}
JobDefs {
Name = "DefaultJob"
Type = Backup
Level = Incremental
Client = servidor-fd
FileSet = "Full Set"
Schedule = "WeeklyCycle"
Storage = File
Messages = Standard
Pool = File
Priority = 10
Write Bootstrap = "/var/lib/bacula/%c.bsr"
}
Job {
Name = "BackupCliente1"
JobDefs = "DefaultJob"
Client = cliente1-fd
}
FileSet {
Name = "Full Set"
Include {
Options {
signature = MD5
compression = GZIP
}
File = /home
File = /etc
File = /var/www
}
Exclude {
File = /var/lib/bacula
File = /tmp
File = /proc
File = /sys
}
}
|
Configuración Storage Daemon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| Storage {
Name = servidor-sd
SDPort = 9103
WorkingDirectory = "/var/lib/bacula"
Pid Directory = "/var/run/bacula"
Maximum Concurrent Jobs = 20
}
Device {
Name = FileStorage
Media Type = File
Archive Device = /backup/bacula
LabelMedia = yes;
Random Access = Yes;
AutomaticMount = yes;
RemovableMedia = no;
AlwaysOpen = no;
}
|
Amanda: Advanced Maryland Automatic Network Disk Archiver
Configuración Amanda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # Instalación
apt-get install amanda-server amanda-client
# Configuración básica
# /etc/amanda/DailySet1/amanda.conf
org "DailySet1"
mailto "admin@ejemplo.com"
dumpcycle 4 weeks
runspercycle 20
tapecycle 25 tapes
define dumptype global {
comment "Global definitions"
auth "bsdtcp"
}
define dumptype root-tar {
global
program "GNUTAR"
comment "root partitions dumped with tar"
compress none
index yes
}
|
Duplicity: Encrypted Backup
Backup Encriptado
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Backup encriptado a S3
export AWS_ACCESS_KEY_ID="tu_access_key"
export AWS_SECRET_ACCESS_KEY="tu_secret_key"
export PASSPHRASE="tu_passphrase"
duplicity /home s3://tu-bucket/backup/home
# Backup incremental
duplicity --full-if-older-than 1M /home s3://tu-bucket/backup/home
# Restaurar archivo específico
duplicity restore --file-to-restore home/usuario/archivo.txt \
s3://tu-bucket/backup/home /tmp/restaurado/
|
Strategies de Backup Automatizadas
Scripts de Backup Inteligentes
Backup Rotativo con Retención
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| #!/bin/bash
# backup_inteligente.sh
BACKUP_DIR="/backup"
RETENTION_DAYS=30
SOURCE_DIRS="/home /etc /var/www"
EXCLUDE_FILE="/etc/backup/excludes.txt"
# Crear directorio con timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_PATH="$BACKUP_DIR/$TIMESTAMP"
mkdir -p "$BACKUP_PATH"
# Función de logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/backup.log
}
# Función de backup
perform_backup() {
log "Iniciando backup en $BACKUP_PATH"
tar -czf "$BACKUP_PATH/system_backup.tar.gz" \
--exclude-from="$EXCLUDE_FILE" \
$SOURCE_DIRS
if [ $? -eq 0 ]; then
log "Backup completado exitosamente"
echo "$TIMESTAMP" > "$BACKUP_DIR/latest.txt"
else
log "ERROR: Backup falló"
return 1
fi
}
# Función de limpieza
cleanup_old_backups() {
log "Iniciando limpieza de backups antiguos"
find "$BACKUP_DIR" -type d -name "20*" -mtime +$RETENTION_DAYS -exec rm -rf {} \;
log "Limpieza completada"
}
# Verificar espacio en disco
check_disk_space() {
AVAILABLE=$(df "$BACKUP_DIR" | awk 'NR==2 {print $4}')
REQUIRED=1000000 # 1GB en KB
if [ "$AVAILABLE" -lt "$REQUIRED" ]; then
log "ERROR: Espacio insuficiente en disco"
exit 1
fi
}
# Ejecutar backup
check_disk_space
perform_backup
cleanup_old_backups
# Enviar notificación
if [ $? -eq 0 ]; then
echo "Backup completado en $(hostname)" | mail -s "Backup OK" admin@ejemplo.com
else
echo "Backup falló en $(hostname)" | mail -s "Backup ERROR" admin@ejemplo.com
fi
|
Backup con MySQL/PostgreSQL
Backup de Base de Datos MySQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| #!/bin/bash
# mysql_backup.sh
DB_USER="backup_user"
DB_PASS="backup_password"
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
# Crear directorio
mkdir -p "$BACKUP_DIR"
# Backup de todas las bases de datos
mysqldump --user="$DB_USER" --password="$DB_PASS" \
--all-databases \
--routines \
--triggers \
--single-transaction \
--master-data=2 | gzip > "$BACKUP_DIR/all_databases_$DATE.sql.gz"
# Backup individual por base de datos
mysql --user="$DB_USER" --password="$DB_PASS" -e "SHOW DATABASES;" | \
grep -Ev '^(Database|information_schema|performance_schema|mysql|sys)$' | \
while read db; do
mysqldump --user="$DB_USER" --password="$DB_PASS" \
--routines \
--triggers \
--single-transaction \
"$db" | gzip > "$BACKUP_DIR/${db}_$DATE.sql.gz"
done
|
Backup de PostgreSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #!/bin/bash
# postgresql_backup.sh
export PGPASSWORD="password"
BACKUP_DIR="/backup/postgresql"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# Backup global
pg_dumpall -h localhost -U postgres | gzip > "$BACKUP_DIR/global_$DATE.sql.gz"
# Backup por base de datos
psql -h localhost -U postgres -l -t | cut -d'|' -f1 | \
sed -e 's/ //g' -e '/^$/d' | \
grep -v template | \
while read db; do
pg_dump -h localhost -U postgres -Fc "$db" > "$BACKUP_DIR/${db}_$DATE.dump"
done
|
Backup Remoto y Cloud
Backup a Servidores Remotos
rsync sobre SSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #!/bin/bash
# backup_remoto.sh
REMOTE_USER="backup"
REMOTE_HOST="backup.ejemplo.com"
REMOTE_PATH="/backup/$(hostname)"
LOCAL_PATH="/home"
# Configurar SSH sin contraseña
# ssh-keygen -t rsa
# ssh-copy-id $REMOTE_USER@$REMOTE_HOST
# Backup con rsync
rsync -avz --delete \
-e "ssh -o StrictHostKeyChecking=no" \
"$LOCAL_PATH/" \
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/"
|
Backup a Cloud Providers
AWS S3 con aws-cli
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #!/bin/bash
# backup_s3.sh
AWS_PROFILE="backup"
S3_BUCKET="mi-bucket-backup"
LOCAL_DIR="/backup/daily"
# Configurar perfil AWS
# aws configure --profile backup
# Sincronizar con S3
aws s3 sync "$LOCAL_DIR" "s3://$S3_BUCKET/$(hostname)/" \
--profile "$AWS_PROFILE" \
--delete \
--storage-class STANDARD_IA
# Backup con encriptación
aws s3 cp backup.tar.gz "s3://$S3_BUCKET/encrypted/" \
--sse AES256 \
--profile "$AWS_PROFILE"
|
Google Cloud Storage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #!/bin/bash
# backup_gcs.sh
GCS_BUCKET="gs://mi-bucket-backup"
LOCAL_DIR="/backup/daily"
# Instalar y configurar gcloud
# gcloud auth login
# gcloud config set project mi-proyecto
# Sincronizar con GCS
gsutil -m rsync -r -d "$LOCAL_DIR" "$GCS_BUCKET/$(hostname)/"
# Backup con compresión
tar -czf - /home | gsutil cp - "$GCS_BUCKET/home_$(date +%Y%m%d).tar.gz"
|
Recuperación de Datos
Estrategias de Restauración
Restauración Granular
1
2
3
4
5
6
7
8
| # Restaurar archivo específico desde tar
tar -xzf backup.tar.gz home/usuario/archivo.txt --strip-components=2
# Restaurar desde rsync backup
rsync -avH /backup/home/latest/usuario/archivo.txt /home/usuario/
# Restaurar base de datos MySQL
gunzip < database_backup.sql.gz | mysql -u root -p database_name
|
Restauración Completa del Sistema
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #!/bin/bash
# restauracion_completa.sh
BACKUP_FILE="/backup/system_backup.tar.gz"
RESTORE_ROOT="/mnt/restore"
# Montar sistema de archivos destino
mount /dev/sdb1 "$RESTORE_ROOT"
# Restaurar sistema base
cd "$RESTORE_ROOT"
tar -xzf "$BACKUP_FILE"
# Restaurar bootloader
mount --bind /dev "$RESTORE_ROOT/dev"
mount --bind /proc "$RESTORE_ROOT/proc"
mount --bind /sys "$RESTORE_ROOT/sys"
chroot "$RESTORE_ROOT" grub-install /dev/sdb
chroot "$RESTORE_ROOT" update-grub
# Ajustar fstab si es necesario
vim "$RESTORE_ROOT/etc/fstab"
|
Disaster Recovery Planning
Documento de DR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # Crear inventario del sistema
#!/bin/bash
# inventario_sistema.sh
echo "=== INVENTARIO DEL SISTEMA $(date) ===" > inventario.txt
echo "" >> inventario.txt
echo "HARDWARE:" >> inventario.txt
lscpu | grep -E "Model name|CPU\(s\)|Architecture" >> inventario.txt
free -h >> inventario.txt
lsblk >> inventario.txt
echo "" >> inventario.txt
echo "SISTEMA OPERATIVO:" >> inventario.txt
cat /etc/os-release >> inventario.txt
uname -a >> inventario.txt
echo "" >> inventario.txt
echo "SERVICIOS ACTIVOS:" >> inventario.txt
systemctl list-units --type=service --state=active >> inventario.txt
echo "" >> inventario.txt
echo "PUERTOS ABIERTOS:" >> inventario.txt
netstat -tlnp >> inventario.txt
echo "" >> inventario.txt
echo "APLICACIONES INSTALADAS:" >> inventario.txt
dpkg -l >> inventario.txt
|
Monitoreo y Alertas
Verificación de Backups
Script de Verificación
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| #!/bin/bash
# verificar_backups.sh
BACKUP_DIR="/backup"
LOG_FILE="/var/log/backup_verification.log"
MAX_AGE_HOURS=25 # Backup debe ser menor a 25 horas
check_backup_age() {
local backup_file="$1"
local file_age_seconds=$(( $(date +%s) - $(stat -c %Y "$backup_file") ))
local file_age_hours=$(( file_age_seconds / 3600 ))
if [ $file_age_hours -gt $MAX_AGE_HOURS ]; then
echo "ALERTA: Backup antiguo detectado: $backup_file ($file_age_hours horas)" | \
tee -a "$LOG_FILE"
return 1
else
echo "OK: Backup reciente: $backup_file ($file_age_hours horas)" | \
tee -a "$LOG_FILE"
return 0
fi
}
verify_backup_integrity() {
local backup_file="$1"
case "$backup_file" in
*.tar.gz)
if tar -tzf "$backup_file" > /dev/null 2>&1; then
echo "OK: Integridad verificada: $backup_file"
return 0
else
echo "ERROR: Backup corrupto: $backup_file"
return 1
fi
;;
*.sql.gz)
if gunzip -t "$backup_file" 2>/dev/null; then
echo "OK: Integridad verificada: $backup_file"
return 0
else
echo "ERROR: Backup corrupto: $backup_file"
return 1
fi
;;
esac
}
# Verificar todos los backups
find "$BACKUP_DIR" -name "*.tar.gz" -o -name "*.sql.gz" | \
while read backup_file; do
check_backup_age "$backup_file"
verify_backup_integrity "$backup_file"
done
|
Alertas y Notificaciones
Sistema de Alertas con Nagios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # check_backup_age.sh para Nagios
#!/bin/bash
BACKUP_FILE="$1"
WARNING_HOURS="$2"
CRITICAL_HOURS="$3"
if [ ! -f "$BACKUP_FILE" ]; then
echo "CRITICAL: Archivo de backup no encontrado: $BACKUP_FILE"
exit 2
fi
file_age_seconds=$(( $(date +%s) - $(stat -c %Y "$BACKUP_FILE") ))
file_age_hours=$(( file_age_seconds / 3600 ))
if [ $file_age_hours -gt $CRITICAL_HOURS ]; then
echo "CRITICAL: Backup demasiado antiguo: $file_age_hours horas"
exit 2
elif [ $file_age_hours -gt $WARNING_HOURS ]; then
echo "WARNING: Backup antiguo: $file_age_hours horas"
exit 1
else
echo "OK: Backup reciente: $file_age_hours horas"
exit 0
fi
|
Best Practices y Seguridad
Seguridad en Backups
Encriptación de Backups
1
2
3
4
5
6
7
8
9
| # Backup encriptado con GPG
tar -czf - /home | gpg --cipher-algo AES256 --compress-algo 1 \
--symmetric --output backup_encrypted.tar.gz.gpg
# Restaurar backup encriptado
gpg --decrypt backup_encrypted.tar.gz.gpg | tar -xzf -
# Backup con password
tar -czf - /home | openssl enc -aes-256-cbc -salt > backup_encrypted.tar.gz
|
Control de Acceso
1
2
3
4
5
6
| # Configurar permisos restrictivos
chmod 700 /backup
chown backup:backup /backup
# Usuario dedicado para backups
useradd -r -s /bin/bash -d /var/lib/backup backup
|
Testing de Recuperación
Plan de Testing Regular
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| #!/bin/bash
# test_restauracion.sh
TEST_DIR="/tmp/test_restore_$(date +%s)"
BACKUP_FILE="/backup/latest.tar.gz"
mkdir -p "$TEST_DIR"
# Restaurar backup de prueba
tar -xzf "$BACKUP_FILE" -C "$TEST_DIR"
# Verificar archivos críticos
critical_files=(
"etc/passwd"
"etc/fstab"
"etc/ssh/sshd_config"
"home/usuario/.bashrc"
)
for file in "${critical_files[@]}"; do
if [ -f "$TEST_DIR/$file" ]; then
echo "OK: $file restaurado correctamente"
else
echo "ERROR: $file no encontrado en backup"
fi
done
# Limpiar
rm -rf "$TEST_DIR"
|
Conclusiones
Una estrategia de backup efectiva combina:
- Automatización: Scripts programados y herramientas profesionales
- Redundancia: Múltiples copias en diferentes ubicaciones
- Verificación: Testing regular de integridad y recuperación
- Seguridad: Encriptación y control de acceso
- Documentación: Procedimientos claros de recuperación
- Monitoreo: Alertas proactivas sobre fallos
Los backups no son un lujo, son una necesidad fundamental en cualquier infraestructura seria.
Andrés Nuñez - t4ifi