miércoles, 24 de agosto de 2011

Rsync y optimización del espacio con enlaces duros o hard links

ACTUALIZACION 24-Agosto-2011 16:41: He añadido una nota al final sobre los links duros.

Hola a todos. Hace bastante tiempo que no incluyo una entrada nueva en el blog y creo que esta le va a gustar a muchos administradores de sistemas que necesitan optimizar el espacio porque los datos que necesitan guardar son muy pesados.

Hace poco me encontré con un problema de espacio que tenia en un servidor web. El caso es que, por contrato, la empresa donde trabajo necesitaba guardar un cierto número de rotaciones que no podíamos mantener ni por asomo. Cada copia eran 154GB, para que os hagáis una idea. La única forma de mantener un sistema de copias de seguridad así es haciendo copias incrementales, por supuesto, pero ¿como podríamos hacer que gastando un mínimo espacio pudiésemos tener copias de seguridad completas en cada copia y tener rotaciones cada dos horas?

La respuesta la tenemos en el uso de los links duros o hard links y en rsync.

Cuando tenemos un fichero en el disco, sus datos ocupan un espacio de bits en el. A grandes rasgos, el nombre, inodo, comienzo del bloque de datos en el disco y final, etc. vamos los metadatos del fichero, apuntan a ese espacio de disco. Si los datos del disco del fichero no cambian, podríamos crear un link duro que apuntara a esos datos e incluirlo en la copia de seguridad en vez de copiar el fichero con la duplicidad de los mismos datos en disco que eso conlleva. Un link duro es, a todos los efectos, el mismo fichero que el fichero original y comparte el mismo inodo que el fichero original. Podemos ver el inodo de un fichero con el comando ls y la opción -i.

Imaginaros que, de los 154 GB de los que tengo que hacer la copia de seguridad, solo cambian, realmente, 2 o 3 MB cada 2 horas, o incluso menos (imágenes no cambian, vídeos no cambian,etc.)... ¿Os vais dando cuenta del espacio que podemos ahorrar creando links duros en vez de copias, no? :-D y, por tanto, también os dais cuenta de la frecuencia que podemos tener de copias de seguridad en comparación con otros sistemas de copia. Pues bien, para poder conseguir esto he creado un script que realiza una copia de seguridad usando rsync y links duros y generando un directorio nuevo "incremental" con la frecuencia que pongáis ejecutándolo con cron. ¿Por que pongo incremental con comillas? por que en realidad, cada directorio con la nueva copia donde solo se crearán copias reales de los ficheros que cambien o sean nuevos, serán copias de seguridad completas del directorio de origen. No hará falta copiar varias copias incrementales en orden para tener la copia completa que queremos recuperar, podemos copiar el contenido del directorio del día/hora que queramos directamente y listo (otra ventaja, sin duda)

#!/bin/bash

# Nombre: rsync_hard_link.sh
# Localización del script: HOST /root/bin 
# Versión: 1.0.0
# Permisos: 770 usuario: root grupo: root
# Autor: Francisco J. Bejarano
# Descripción:
# Script que genera copias de seguridad completas "incrementales"
# de un directorio.
#
# Log: DIR_LOG
#
# Comentarios: usar una version de rsync que permita la opcion
# --link-dest=DIR. En GNU\Linux CentOS 5.6 tenemos:
# rsync  version 2.6.8  protocol version 29
# Con la que funciona sin problemas. Si usáis Fedora, Ubuntu, etc. (más nuevos) no deberíais de
# preocuparos.
#
# Frecuencia: crontab -u root -l
# # Ejecución del script cada dos horas en punto.
# 00 */2 * * * /root/bin/rsync_hard_link.sh


################################ Parámetros de configuración

# Inicializamos las variables que vayamos a usar y los directorios.
FECHA=`date +%Y%m%d%-H%M%S`
DIR_BACKUP=/var/backup/
DIR_BASE_PREVIOUS=${DIR_BACKUP}web/
ORIGEN=${DIR_BACKUP}www/
DESTINO=${DIR_BASE_PREVIOUS}${FECHA}/
DIR_LOG=${DIR_BACKUP}web/log/
LOG_FILE=${DIR_LOG}incremental_www-${FECHA}.log

# Los directorios y logs de copias con más antigüedad que los días que pongamos aquí serán
# eliminados. Por defecto 180 días o 6 meses. Ajustar a lo deseado.
DIAS=180

################################ Aplicaciones

# Detectamos la ubicación de la aplicación rsync por si lo ejecutamos en diferentes sistemas.
APP_RSYNC=`whereis rsync | cut -d" " -f2`

################################ Funciones

# Crea el nuevo directorio con la copia de seguridad nueva usando los hard links y copias 
# de ficheros nuevos o modificados.

function rsync_incremental {
   
   # Los directorios que tenemos aquí son lo que más puede confundir. El directorio que ponemos
   # en --link-dest debe ser el directorio de la última copia que realizamos previamente.
   # Vamos la anterior a la que vamos a hacer ahora. En este caso, crearemos un link blando
   # al directorio anterior cada vez que ejecutemos el script llamado previous.
   # El directorio de origen (acabado en /, importante) es el directorio del que queremos hacer
   # la copia de seguridad.
   # El directorio destino es al que vamos a hacer la copia de seguridad nueva con los hard
   # links que sean necesarios.
   # La opcion delete elimina los ficheros/directorios en el destino que se hayan eliminado
   # en el origen.

   $APP_RSYNC -avh --stats --delete --link-dest=${DIR_BASE_PREVIOUS}previous $ORIGEN $DESTINO &> $LOG_FILE

   # Borramos el anterior link blando que apuntaba a la copia previa
   rm -f ${DIR_BASE_PREVIOUS}previous
   
   # Creamos el link blando nuevo que ahora apunta a la copia de seguridad que acabamos de hacer
   # y que será la previa para la siguiente.
   ln -s $DESTINO ${DIR_BASE_PREVIOUS}previous
   
   # Comprimimos el log con bzip2. Con bzcat podemos ver el contenido sin descomprimir.
   # Así optimizamos más a un el espacio.
   bzip2 $LOG_FILE
}

# Eliminamos los ficheros necesarios para controlar el crecimiento sin control.

function eliminar_ficheros {

   # Eliminamos los directorios con antigüedad mayor a los días que pongamos en los parámetros
   # Además solo borramos los directorios que empiecen por 2 (no creo que este script siga
   # funcionando en el año 3000 :D) y solo inspeccionamos el nivel 1 de directorios no queremos
   # que borre directorios dentro de los que hay en el 1 nivel que empiecen por 2 y que sean 
   # de una copia actual que debamos mantener, ¿no?

   find $DIR_BASE_PREVIOUS -maxdepth 1 -type d -iname "2*" -ctime +${DIAS} -exec rm -rf {} \; &> /dev/null

   # Eliminamos los ficheros de log comprimidos con antigüedad mayor a los días que pongamos.
   find $DIR_LOG -type f -iname "*.bz2" -ctime +${DIAS} -exec rm -rf {} \; &> /dev/null

}

################################ Programa Principal

# Si no existe una copia previa hacemos la primera copia completa
# Esto solo se hace la primera vez. Si existe hacemos la incremental.

if [ ! -h ${DIR_BASE_PREVIOUS}previous ] ; then
   $APP_RSYNC -azh --stats --delete $ORIGEN ${FECHA}_origen/ &> $LOG_FILE
   ln -s ${FECHA}_origen/ ${DIR_BASE_PREVIOUS}previous   
else
   rsync_incremental
   eliminar_ficheros
fi

################################ Fin de Script

Y listo. Cada vez que lo ejecutemos veremos como tenemos un nuevo directorio con una copia completa que solo ocupa el espacio de disco nuevo de los ficheros nuevos o modificados en el origen.

Podemos ejecutar el script, hacer un df, modificar el contenido de ORIGEN, ejecutar de nuevo el script y hacer otro df y veremos como el consumo es mínimo y tenemos copias completas en cada directorio. También usad ls -li para ver los inodos en diferentes directorios de destino y comprobar que son los mismos si el fichero no ha cambiado.

Imaginad un desastre, se ha ido al garete todo el directorio origen de la web. Tenemos copias cada 2 horas.

# rsync --azh --delete DIR_DESTINO_O_COPIA/ DIR_ORIGEN_O_DOCUMENTROOT/

Esperáis a que se sincronice y tendréis de nuevo la copia exacta realizada en el momento en que se creo DIR_DESTINO_O_COPIA.

Saludos y espero que os sea útil ;-) y recordad que las copias de seguridad de las bases de datos debéis realizarlas también a parte si las usáis en vuestra web jeje... eso os lo dejo a vosotros. Si solo son ficheros no hay problema.

NOTA: Los links duros solo se pueden usar en el mismo sistema de ficheros. Esta limitación debemos tenerla en cuenta. Primero deberíamos hacer una sincronización del servidor web al de backup con ssh (por ejemplo) y después realizar esta copia. Esta sincronización ssh habría que hacerla cada 2 horas si usáis un servidor remoto de producción antes de la copia para que refleje los cambios o cada frecuencia que queráis. Podéis añadirlo al script mediante otra función como ejercicio jeje. De hecho, asi funcionan mis servidores.
Related Posts with Thumbnails