El problema#
Tengo varios contenedores Docker con datos críticos en volúmenes. Un día se corrompió una base de datos y perdí días de información. Después de eso decidí automatizar los backups incrementales a almacenamiento remoto. Aquí comparto cómo lo hice.
Preparación#
Primero instalé duplicity en el servidor anfitrión:
apt-get install duplicity python3-pip
pip3 install boto3 # si usas S3Necesitaba acceso a almacenamiento remoto. En mi caso usé B2 de Backblaze, que es económico. También puedes usar S3, Google Cloud Storage o incluso un servidor SFTP propio.
Generé las credenciales necesarias y las guardé en un archivo seguro:
cat > /root/.duplicity_env << 'EOF'
export B2_ACCOUNT_ID="tu_account_id"
export B2_APP_KEY="tu_app_key"
EOF
chmod 600 /root/.duplicity_envEstructura de backups#
Decidí hacer backup de los volúmenes por separado. Para un contenedor con PostgreSQL, primero detenía el contenedor para garantizar consistencia:
docker stop mi_postgresLuego ejecutaba duplicity:
source /root/.duplicity_env
duplicity \
--full-if-older-than 30D \
--include=/var/lib/docker/volumes/postgres_data/_data \
--exclude='**' \
/var/lib/docker/volumes/postgres_data/_data \
b2://bucket_name/postgres_backup
docker start mi_postgresEste comando hace backups completos cada 30 días e incrementales el resto del tiempo. Sin --full-if-older-than, los incrementales se acumulan y la restauración es más lenta.
Automatización con cron#
Creé un script de backup completo:
cat > /usr/local/bin/backup-docker-volumes.sh << 'EOF'
#!/bin/bash
source /root/.duplicity_env
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/docker-backups-$TIMESTAMP.log"
backup_volume() {
local volume_name=$1
local b2_path=$2
local container_name=$3
echo "[$(date)] Iniciando backup de $volume_name" >> $LOG_FILE
docker stop $container_name 2>/dev/null
sleep 5
duplicity \
--full-if-older-than 30D \
--include=/var/lib/docker/volumes/${volume_name}/_data \
--exclude='**' \
/var/lib/docker/volumes/${volume_name}/_data \
b2://$b2_path >> $LOG_FILE 2>&1
docker start $container_name
echo "[$(date)] Backup de $volume_name completado" >> $LOG_FILE
}
backup_volume "postgres_data" "mi_bucket/postgres" "postgres"
backup_volume "appdata" "mi_bucket/app" "webapp"
duplicity cleanup --force b2://mi_bucket/postgres 2>/dev/null
duplicity cleanup --force b2://mi_bucket/app 2>/dev/null
EOF
chmod +x /usr/local/bin/backup-docker-volumes.shLo agregué a crontab para que corra cada noche:
0 2 * * * /usr/local/bin/backup-docker-volumes.shRestauración ante fallos#
Cuando un contenedor falla, la restauración es sencilla:
# Crear directorio temporal
mkdir -p /tmp/restore
cd /tmp/restore
source /root/.duplicity_env
# Restaurar desde b2
duplicity restore b2://mi_bucket/postgres .
# Los datos están en /tmp/restore
ls -laLuego puedo copiar los datos al volumen:
docker stop postgres
cp -r /tmp/restore/* /var/lib/docker/volumes/postgres_data/_data/
docker start postgresVerificación#
Ejecuto verificaciones mensuales:
duplicity verify b2://mi_bucket/postgres /var/lib/docker/volumes/postgres_data/_dataEsto comprueba que los backups sean válidos sin restaurarlos completamente.
Lecciones aprendidas#
- Los backups sin verificación no sirven. He encontrado problemas en restauraciones de prueba.
- Detener el contenedor antes de backup es crítico para bases de datos.
- Los backups incrementales después de 30 días se vuelven difíciles de restaurar. Por eso limito el intervalo de completos.
- Guarda credenciales con
chmod 600siempre.
Esta configuración me ha salvado varias veces. La automatización es la clave: sin cron, nunca hubiera tenido backups consistentes.