Regla 3-2-1. La importancia de un buen sistema de copias de seguridad (y cómo implementarlo en nuestro servidor)

¿Qué pasa si pierdes llaves de casa? Llamas al cerrajero o tiras la puerta abajo. ¿Qué pasa si se rompe la llave del coche? Llamas al taller y pides otra nueva. ¿Qué pasa si hay algún problema con los datos de nuestros servicios? Estamos jod***s.

O no, si tenemos una copia de seguridad.

Las copias de seguridad, las grandes olvidadas

Las cosas como son: depositamos demasiada confianza en que nunca pasará nada, hasta que pasa. Y en informática, todo puede pasar.

Los que me conocen dicen que estoy enfermo, porque tengo copias de seguridad de todo (y más de una), pero no me gustaría perder cierta información por no haber querido invertir unas pocas horas de mi vida (y aun así puede pasar algo que haga que lo pierda todo).

Ya hemos hablado de las ventajas de usar servicios OpenSource autogestionados, pero eso tiene un pequeño coste: las copias de seguridad no las hace nadie, a diferencia de si usamos los servicios proporcionados por un tercero. Y suficiente importantes son nuestros datos como para jugar a la ruleta rusa.

Pero si hablamos de datos de grandes empresas (o de los datos de toda la ciudadanía de un país), la cosa empeora (¿alguien ha dicho SEPE?). Un pequeño proceso automatizado puede salvarnos de una gran catástrofe.

Photo by Louis Hansel / Unsplash

Sí, con los avances actuales de infraestructura cloud (AWS o Azure, por ejemplo), podemos hacer esto de forma automática y casi sin tener que preocuparnos por nada, pero el coste de esta infraestructura está muy por encima de los recursos de un ser humano. Y puede que otras veces no utilicemos este tipo de infraestructura por diversos motivos, así que no nos queda hora que “hacerlo a mano”.

La regla 3-2-1

Hay una regla para las copias de seguridad que debería seguirse a rajatabla, y esa es la regla 3-2-1. ¿En qué consiste?

En tener al menos 3 copias de nuestra información

en al menos 2 soportes diferentes

y al menos 1 fuera del servidor principal:

De esta manera nos aseguramos que, por una parte, la tasa de fallo disminuya considerablemente (que 2 dispositivos diferentes fallen a la vez es muy poco probable), y por otra, que si por lo que sea el servidor principal sufre algún problema, al tener fuera del mismo una copia, podremos recuperarla.

Pero yo no tengo tanta infraestructura

Sí, la tienes. Otra cosa es que la forma de hacer esas copias sea más o menos tediosa, o lo tengas que hacer todo a mano.

Tienes un ordenador (medio 1), tienes memorias USB (medio 2), tienes discos duros externos (medio 3), y tienes Dropbox o Google Cloud (externo), aunque esto último tenga sus inconvenientes, tanto en espacio como en privacidad.

Automatizar las copias en nuestro VPS

Aquí hay que tener en cuenta algunas cosas: automatizar copias de nuestro ordenador puede ser relativamente fácil, ya que hay programas que nos ayudan a ello, el problema es trasladar esas copias a otros medios o externalizarlas. Pero si disponemos de un VPS donde tenemos alojados todos nuestros servicios, podemos automatizar este proceso de una forma sencilla.

Dejando de lado la opción de copias que muchos proveedores ofrecen (que son básicamente copias enteras de nuestro servidor), vamos a ver cómo podemos automatizar las copias de aquello que nos interesa.

Photo by Kaleidico / Unsplash

Preparar el entorno

Partiremos de la base que nuestros servicios están desplegados en contenedores Docker, y que los volúmenes de datos están en la raíz del proyecto, pero se puede extrapolar a otra estructura (u otro tipo de información a guardar).

Lo primero que haremos será crear una carpeta llamada backups donde más nos guste (en mi caso, la he creado en la carpeta raíz de mi usuario) y dentro de esta otra llamada old_backups.

Los scripts

💡
Al final del artículo tenéis los scripts completos, que se irán explicando en las siguientes líneas, y puede que no sean los más optimizados ni los más mejores, así que los podéis modificar a vuestro antojo; son sólo una de tantas opciones.

Empezamos creando un pequeño script como el siguiente, llamado por ejemplo backup.sh, en la carpeta raíz de cada uno de los proyectos de los cuales queramos hacer un backup:

#! /bin/bash

BACKUP_FOLDER="myproject1"
BACKUP_PATH="/home/USER/backups/"$BACKUP_FOLDER

cd /home/USER/projects/$BACKUP_FOLDER
echo "Stopping container..."
docker-compose stop
echo "Starting local backup..."
rsync -r . $BACKUP_PATH
echo "Starting container..."
docker-compose start
echo "Done!"

Lo que debemos modificar aquí es la variable BACKUP_FOLDER, que será el nombre de la carpeta del proyecto, y la ruta de BACKUP_PATH, para que coincida con la ruta de nuestra carpeta de backups. Pero lo que en realidad nos interesa aquí es la línea rsync -r . $BACKUP_PATH, que es la que se encarga de copiar el contenido de toda la carpeta.

Luego crearemos otro script, esta vez en la carpeta backups, llamado también backup.sh, con lo siguiente:

#!/bin/bash

echo "Project1..."
/home/user/projects/project1/backup.sh
echo "Project2..."
/home/user/projects/project2/backup.sh
echo "Project3..."
/home/user/projects/project3/backup.sh
# ...

Modificamos nuevamente los nombres de los diferentes proyectos y añadimos tantos proyectos como queramos para hacer el backup correspondiente de cada uno de ellos. De esta forma, podremos realizar todos los backups de una vez.

Sí, los añadimos a mano, pero es una forma muy sencilla y rápida de añadir y quitar copias según nos interese.

Con esto ya tendríamos 2 copias de nuestros datos, una en la carpeta original y otra en la carpeta backups, pero lo suyo es que esos datos estén en un fichero comprimido, ya que ocupará menos y será más fácil de manejar. Para ello, crearemos un nuevo script en la carpeta backups llamado compress_backups.sh, con lo siguiente:

#!/bin/bash
echo "Compressing files..."
DATE=$(date +"%d-%m-%Y")
BASE_PATH="/home/user/backups"
BACKUP_PATH="/home/user/backups/old_backups"

cd $BACKUP_PATH

echo "-----Project1-----"
tar -czf $BACKUP_PATH/project1.$DATE.tar -C $BASE_PATH/project1 .
echo "-----Project2-----"
tar -czf $BACKUP_PATH/project2.$DATE.tar -C $BASE_PATH/project2 .
echo "-----Project2-----"
tar -czf $BACKUP_PATH/project3.$DATE.tar -C $BASE_PATH/project3 .
#...

Aquí, como antes, debemos modificar los nombres de los proyectos para que encajen con el nombre de la carpeta del mismo, y lo que hará es crear un fichero comprimido con el nombre nombre_proyecto.FECHA_ACTUAL.tar dentro de la carpeta old_backups.

Perfecto, ahora ya tenemos 3 copias de nuestros datos: la original, la copia “en crudo”, y la comprimida. Pero aún nos quedan la regla 2 y la 1.

Externalizar las copias

En mi caso, la copia en otro medio y la externalización van juntas, ya que en un VPS no podemos conectar un USB o disco duro externo (por razones obvias). Lo que he hecho es, por una parte, contratar otro VPS sólo para copias, que tiene el ridículo coste de menos de 2 €/mes, y por otra, usar un NAS que tengo en casa para traer esas copias. Pero centrémonos en cómo externalizarlas a otro VPS.

Lo primero que tenemos que hacer es generar una clave SSH para poder conectarnos directamente a nuestro segundo servidor sin necesidad de contraseña. Para ello, accedemos a nuestro servidor principal y ejecutamos el siguiente comando:

ssh-keygen

Nos pedirá varias cosas, pero las podemos dejar todas en blanco, así que pulsamos enter varias veces hasta que lleguemos al final, y veremos algo similar a esto:

Vale, ya tenemos las claves generadas, ahora necesitamos añadirlas al servidor de backups. Para ello, ejecutaremos el siguiente comando desde el servidor principal:

ssh-copy-id -i /home/user/.ssh/id_rsa.pub user@XXX.XXX.XXX.XXX

Cambiamos las XXX por la IP del servidor de backups (recomiendo que se utilice la IP privada, que suele ser del tipo 10.X.X.X) y el user por nuestro usuario en dicho servidor. Luego nos pedirá las credenciales del usuario y, tras conectarnos, a partir de ese momento podremos acceder del servidor principal al servidor de backups sin necesidad de poner la contraseña.

Lo que haremos a continuación es modificar los scripts anteriores para trasladar las copias a ese nuevo servidor, pero primero accederemos al servidor de backups para crear la carpeta backups correspondiente.

En el script de cada proyecto, añadimos lo siguiente:

echo "Starting backup to external server..."
rsync -r $BACKUP_PATH user@XXX.XXX.XXX.XXX:/home/user/backups/
echo "Done!"

Y en el de compress_backups.sh, lo siguiente:

echo "Copying files to backup server..."
rsync -rl $BACKUP_PATH/*.$DATE.tar user@XXX.XXX.XXX.XXX:/home/user/backups/old_backups
echo "Done!"

Con esto copiaremos, por una parte, la información en crudo, y por otra, todos los backups comprimidos en ese día.

Añadir los scripts a cron

Ahora que ya tenemos nuestros scripts preparados que copian, comprimen y externalizan los datos, lo ideal sería hacer esto de forma automática. Para ello usaremos cron, que se encargará de lanzarlos cuando nosotros le digamos, y es tan sencillo como ejecutar crontab -e y añadir las siguientes líneas:

0 3 * * * /home/user/backups/backup.sh
0 4 * * * /home/user/backups/compress_backups.sh

Lo que hacen es ejecutar un backup a las 3 de la mañana, y comprimirlos a las 4. Esto podría hacerse en un mismo script, cierto, pero lo hice así en su día y así se quedó.

Y listo, con esto cada día entre las 3 y las 4 de la mañana se realizarán vuestras copias de forma automática.

Rotación de backups

Si habéis seguido los pasos hasta ahora, tendréis una copia nueva cada noche, pero esto tiene un problema: si no eliminamos las anteriores, nos quedaremos sin espacio en algún momento, y además puede que una copia de hace 3 meses no nos interese si tenemos copias diarias.

Para ello, o bien podemos añadir lo siguiente al script que comprime las copias o bien crear uno nuevo exclusivo para esta tarea, pero debemos ejecutarlo tanto en el servidor principal como en el de backups. En mi caso, en el servidor principal lo tengo integrado dentro del script de compresión, y en el servidor de backups como script independiente que se ejecuta cada día a las 6 de la mañana.

Lo que nos hace falta es el siguiente código:

#!/bin/bash
BACKUP_PATH="/home/user/backups/old_backups"
echo "Deleting older backups..."
files=$(find $BACKUP_PATH/*.tar -mtime +6 -type f | wc -l)
if [ $files -gt 0 ]; then
    find $BACKUP_PATH/*.tar -mtime +6 -exec rm {} \;
else
    echo "Not enough files to delete"
fi
echo "Done!"

Esto lo que hará es eliminar aquellos backups con una antigüedad superior a 6 días, por tanto, tendremos 7 copias (la de hoy + 6).

Pero eso sería aplicar una regla tipo 7-2-1, ¿no? Correcto, y tiene una explicación: al ser servicios que puede que no use a diario, si hay algún problema con ellos, a lo mejor no me doy cuenta hasta dentro de 2-3 días, por lo que si durante ese tiempo se han ido haciendo copias de datos corruptos, y la rotación elimina los que no están corruptos, estoy vendido.

Resumen de scripts

Os dejo los scripts completos para que se puedan copiar fácilmente:

Backup de proyecto

#! /bin/bash

BACKUP_FOLDER="myproject1"
BACKUP_PATH="/home/user/backups/"$BACKUP_FOLDER

cd /home/user/projects/$BACKUP_FOLDER
echo "Stopping container..."
docker-compose stop
echo "Starting local backup..."
rsync -r . $BACKUP_PATH
echo "Starting container..."
docker-compose start
echo "Starting backup to external server..."
rsync -r $BACKUP_PATH user@XXX.XXX.XXX.XXX:/home/user/backups/
echo "Done!"

Backup general

#!/bin/bash

echo "Project1..."
/home/user/projects/project1/backup.sh
echo "Project2..."
/home/user/projects/project2/backup.sh
echo "Project3..."
/home/user/projects/project3/backup.sh
# ...

Compress

#!/bin/bash
echo "Compressing files..."
DATE=$(date +"%d-%m-%Y")
BASE_PROJECT_PATH="/home/user/backups"
BACKUP_PATH="/home/user/backups/old_backups"

cd $BACKUP_PATH

echo "-----Project1-----"
tar -czf $BACKUP_PATH/project1.$DATE.tar -C $BASE_PROJECT_PATH/project1 .
echo "-----Project2-----"
tar -czf $BACKUP_PATH/project2.$DATE.tar -C $BASE_PROJECT_PATH/project2 .
echo "-----Project2-----"
tar -czf $BACKUP_PATH/project3.$DATE.tar -C $BASE_PROJECT_PATH/project3 .
#...
echo "Deleting older backups..."
files=$(find $BACKUP_PATH/*.tar -mtime +6 -type f | wc -l)
if [ $files -gt 0 ]; then
    find $BACKUP_PATH/*.tar -mtime +6 -exec rm {} \;
else
    echo "Not enough files to delete"
fi
echo "Copying files to backup server..."
rsync -rl $BACKUP_PATH/*.$DATE.tar user@XXX.XXX.XXX.XXX:/home/user/backups/old_backups
echo "Done!"

Clean backups (para el servidor de backups)

#!/bin/bash
BACKUP_PATH="/home/user/backups/old_backups"
echo "Deleting older backups..."
files=$(find $BACKUP_PATH/*.tar -mtime +6 -type f | wc -l)
if [ $files -gt 0 ]; then
    find $BACKUP_PATH/*.tar -mtime +6 -exec rm {} \;
else
    echo "Not enough files to delete"
fi
echo "Done!"

No tienes ningún backup hasta que lo has restaurado

Y para acabar este artículo (algo más extenso de lo que esperaba en un inicio), recordar que debéis probar a restaurar desde 0 cualquier backup que hagáis, ya que hasta ese momento no podremos estar seguros de que funciona correctamente.

Espero que os haya sido de utilidad y podáis estar un poco más tranquilos a la hora de montar vuestros propios servicios, puesto que algunos como Vaultwarden o Nextcloud tienen información que no conviene perder.

💡
Si veis algo raro, comunicádmelo plis, que puede que entre tanto copy-paste se haya colado algo.