Supongamos un server recién instalado, llevó horas configurarlo, dejarlo afinado como un violín, pero cuando creemos haber terminado aún hay como una sensación, algo que no está bien, pero... que será?
"Puede fallar" decía Tusam, y todos comprobamos la verdad de estas sabias palabras alguna vez en la vida.
Pero...qué pude fallar?
short answer: "todo puede fallar"
Lo bueno es que muchas de las cosas son reemplazables, mother, memorias, fuentes, discos se consiguen a buen precio hoy, pero que hay de las horas de configuración?, los datos acumulados?, los emails?, las bases de datos?
La posibilidad de que esa información valiosa se pierdan siempre ha sido un itch to scratch.
1- Objetivos
Hacer un script de backup que
- sea tan simple de usar como poner una linea en cron
- permita backup versionado en el tiempo
- optimice el espacio de los backups
- pueda hacer mirror remoto de los mismos
- borre automaticamente los viejos para hacer espacio a los nuevos
- salga limpiamente de los errores que se puedan producir y garantice la integridad
- avise a alguien del error enviandole un detalle del fallo
- que mientras funcione bien sea silencioso y pase desapercibido
2- Requisitos
- bash
- tar
- lftp
3- Desarrollo
Tener un itch to scratch nunca es garantía de que llegará el scratch, ahí es cuando el cliente exijente aparece y de alguna manera fuerza y/o financia el scratch:cliente- muy bueno como quedó todo, ahora tengo una pregunta, de esto hay backup?Todo comenzó con un simple script para salir del paso, y a medida que pasaron los años las necesidades de backup se volvieron cada vez más complejas.
geek- eh... sí, naturalmente, como no va a haber backup!
Varios checkpoints en el tiempo, mirror remoto, aviso en caso de fallos, backups incremental fueron algunas de las exigencias, y el pequeño script fue creciendo y evolucionando cada vez más.
3.1- Configuración
La primera parte del script está dedicada la configuración, esto es lo que da al script flexibilidad.Lo primero es elegir el directorio en donde se van a guardar los backups, la ventaja de esto es que ese directorio puede caer en un disco diferente al principal, o incluso puede ser una unidad de red montada via smbfs o nfs por ejemplo.
#!/bin/bash # directorio donde van a parar los backups backup_dir="/backup"Luego viene la parte de remote mirror de los bakcups, el script usa ftp para mantener en syncro el directorio de backups local con el remoto. Si dejamos en blanco estas variables el script realiza solo el backup en el directorio local.
# datos ftp, si están en blanco el backup es sólo local ftp_host=10.0.0.1 ftp_user=backup ftp_pass=******La siguiente variable nos pide el nombre de un usuario o un email al cual enviar un aviso en caso de que el script falle en alguno de sus pasos.
# mail a donde avisar si algo sale mal, puede ser un usuario del sistema mail_for_errors="root"backup_days representa el numero de días de los cuales vamos a tener backups, a mayor cantidad de días, más espacio en disco necesitaremos.
# tiempo en dias que conserva copia de los backups backup_days=15A continuación configuramos que cosas vamos a incluir y excluir dentro del bakcup. En un backup completo tradicional incluiremos el directorio raiz "/" y excluiremos cosas como /tmp,/proc y /sys entre otros.
Hay que acordarse de excluir cualquier directorio que contenga mucha información que pueda inflar innecesariamente los backups, por ejemplo el cache de squid o el fuente de un kernel en /usr/src, en fin,cosas que no vale la pena tener por duplicado.
Obviamente se excluye el $backup_dir para no crear una recursividad tratando de backupear el propio directorio de backups.
exclude=$(mktemp) include=$(mktemp) cat > $include <<EOF / EOF cat > $exclude <<EOF /tmp/* /sys/* /proc/* /var/spool/squid/* $backup_dir EOFDespués de esto comienza realmente el script, así que llegamos a la linea divisoria, si estás buscando una solución Copy and Paste, podés saltar todo esto y descargar el script completo
3.2- Setup inicial
Voy a explicar la solución paso a paso, en el orden de ejecución. Para esto me voy a tomar la libertad de explicar las funciones cuando son llamadas y no en el orden en que aparecen en el script original.Comenzamos creando un logfile donde redireccionar la stderr de los programas que usamos a lo largo del script, para poder recopilar los errores de los mismos y despues enviarlos por mail.
También creamos un directorio .tar, dentro de nuestro directorio de bakcups. Este directorio lo usaremos para guardar el archivo para backups incremental y unos lock que me aseguraran de que el script pueda recuperarse de un fallo.
# Scripts Begins Here logfile=$(mktemp) inc_dir="${backup_dir}/.tar" inc_file="${inc_dir}/incremental.info" [ -d "$backup_dir" ] || mkdir -p "$backup_dir" [ -d "$inc_dir" ] || mkdir -p "$inc_dir"El pŕoximo paso es borrar los backups que ya son viejos segun $backup_days
# borro los backups vencidos find $backup_dir -name "backup-$(hostname)-*" -ctime +$backup_days -a -type f -exec rm -f {} \;En esta linea hago la primera comprobación de error. Cuando comienzo a crear un backup completo, creo un archivo .lock, que se borrará al final si todo sale exitosamente.
Pero si algo falla, entonces el archivo .lock permanece creado hasta la próxima ejecución del script. Si encuentro ese archivo es porque algo salío mal y sé que debo hacer nuevamente el backup completo (full).
# si encuentro .lock es porque fallo el full backup, borro inc_file para que se haga de nuevo [ -f "${inc_file}.lock" -a -f "$inc_file" ] && rm -f "$inc_file"El script maneja dos tipos de backup, full e incremental.
El backup full se realiza una vez a la semana, y es un backup de tar autocontenido, que contiene toda la información necesaria para ser usado y extraido en cualquier lugar.
El backup incremental se realiza a diario, y se usa como referencia siempre el último backup full. Esto quiere decir que el lunes se realiza un backup incremental teniendo en cuenta el último backup full que es el del domingo. Y si quiciera restaurar el sistema al día lunes debería primero extraer el backup full del domingo y luego arriba de ese usar el del lunes.
Los backups incrementales son independientes, esto quiere decir que están siempre hechos contra el último backup full, por lo que si tengo un backup incremental del viernes y quiero restaurar el sistema a ese día, entonces sólo necesito el último backup full (domingo) más el incremental del viernes.
Esto está hecho así para asegurar la efectividad de los backups, ya que si para poder recuperar el viernes dependiera de que el lunes, martes, miércoles y jueves se hayan realizado con éxito estoy siendo muy optimista por default.
Cada backup tiene su nombre, que incluye el hostname, indica si es full o incremental y la fecha a la cual corresponde. En el caso de los incrementales se usa unicamente el día de la semana para que se vayan pisando con los anteriores y así tener sólo la última semana en formato incremental.
#setup full or incremental if [ $(date +%u) -eq 7 -o ! -f "$inc_file" ];then #backup-full backup_name="${backup_dir}/backup-$(hostname)-full-$(date +%Y-%m-%d).tar.gz" #borro inc_file para que el backup sea full [ -f "$inc_file" ] && rm -f "$inc_file" touch "${inc_file}.lock" else #backup-inc backup_name="${backup_dir}/backup-$(hostname)-inc-$(date +%A).tar.gz" cp $inc_file ${inc_file}.temp inc_file=${inc_file}.temp fi
3.3- Creando el backup
Finalmente una vez que todo está listo, hago el backup#finally, do the backup tarme "$inc_file" "$backup_name"tarme es una función, veamosla en detalle, lo que hace es invocar a tar con ciertos parámetros especiales, como que archivos incluir y excluir del backup, que preserve owners y permisos o que envíe stderr a logfile.
tarme() { local inc_file=$1 file=$2 tar --listed-incremental="$inc_file" \ --files-from="$include" \ --exclude-from="$exclude" \ --ignore-failed-read \ --absolute-names \ -zpcf "$file" 2>$logfile || do_exit "tar" $? $file return 0 }
3.4- A prueba de errores
Como se puede ver al final de la invocación de tar, hay un "|| do_exit ..."do_exit es otra función, que la vamos a utlizar en caso de que algo salga mal, en bash cuando un comando devuelve 0 es que todo fue bien en su ejecución, en cambio si el valor es != de cero es que algo salió mal. Al poner "|| do_exit" me estoy asegurando que si algo sale mal la función do_exit se ejecute.
do_exit va a ser usado por tar pero también por otros comandos que fallen, la idea es que haga un clenaup si el backup quedó inconsistente y que envíe un mail al administrador avisando que algo salío mal.
do_exit() { local app=$1 exit_status=$2 file=$3 case $app in tar) #exit status 1 en tar no es un error fatal: #If tar was given ‘--create’, ‘--append’ or ‘--update’ option, #this exit code means that some files were changed while being #archived and so the resulting archive does not contain the #exact copy of the file set. [ $exit_status -eq 1 ] && return #borro el backup que fallo xq esta incompleto [ -n "$file" -a -f "$file" ] && rm -f "$file" ;; esac cat $logfile | mail -s "Backup error host:$(hostname) app:$app exit_status:$exit_status file:$file" $mail_for_errors exit 1 }
3.5- Mirror remoto
Luego de que tar corrio exitosamente, hacemos el upload remoto si es que corresponde[ -n "$ftp_host" ] && uploadme "$backup_name"uploadme es otra función, lo que hace es hacer un mirror del directorio local de backups contra un ftp remoto, en este sentido el comando mirror de lftp es de gran ayuda.
Lo bueno de esto es que la sincronía puede fallar un día, pero en cuanto el script vuelva a correr el directorio local y remoto de backups terminaran perfectamente sincronizados.
uploadme() { local file=$1 lftp -c "open ftp://$ftp_user:$ftp_pass@$ftp_host;mirror -R --delete-first $backup_dir (hostname)" 2>>$logfile || do_exit "lftp" $? $file return 0 }
3.6- Check final
Una vez subido el backup, entonces estamos en condices de borrar el .lock file, si es que lo habiamos creado (sólo se usa para los full backups)# si todo fue bien borro el .lock [ -f "${inc_file}.lock" ] && rm -f "${inc_file}.lock"
3.7- El script completo(backup.sh)
Descargar el script completo: backup.sh4- Invocando el script desde crontab
Generalmente coloco el script en /usr/local/bin/backu.sh y luego agrego esta linea a alguan crontab, puede ser la de root(crontab -e) o en /etc/cron.d/ en alguan crontab personal.La linea de cron es simplemente para ejecutarlo una vez al día preferentemente en un horario de poco impacto para el servidor, como son las horas de la madrugada
0 2 * * * root /usr/local/bin/backup.sh
5- Ejemplo de salida
Pego la salida de ls sobre un directorio de bakcups, en donde se han acumlado tres semanas.Como se puede observar hay una gran diferencia de tamaño entre un backup full y uno incremental. También se ve como los incrementales solo cubren la primer semana y luego quedan sólo full 3 semanas hacia atras.
-rw-r--r-- 1 root root 1.4G 2009-04-19 03:16 backup-sequre-0001-full-2009-04-19.tar.gz -rw-r--r-- 1 root root 1.3G 2009-04-26 03:15 backup-sequre-0001-full-2009-04-26.tar.gz -rw-r--r-- 1 root root 16M 2009-04-27 03:02 backup-sequre-0001-inc-Monday.tar.gz -rw-r--r-- 1 root root 16M 2009-04-28 03:02 backup-sequre-0001-inc-Tuesday.tar.gz -rw-r--r-- 1 root root 30M 2009-04-29 03:02 backup-sequre-0001-inc-Wednesday.tar.gz -rw-r--r-- 1 root root 30M 2009-04-30 03:02 backup-sequre-0001-inc-Thursday.tar.gz -rw-r--r-- 1 root root 30M 2009-05-01 03:02 backup-sequre-0001-inc-Friday.tar.gz -rw-r--r-- 1 root root 50M 2009-05-02 03:02 backup-sequre-0001-inc-Saturday.tar.gz -rw-r--r-- 1 root root 1.3G 2009-05-03 03:16 backup-sequre-0001-full-2009-05-03.tar.gz
6- Conclusión
Tener backups es garantía de buen sueño para un sys-admin y, es además, una marca de calidad en lo que a infraestructura respecta.Si bien hay soluciones de backup muy interesantes como bacula o backuppc, hay escenarios en donde no se justifica hacer semejante despliegue de configuración e infraestructura.
Escenarios donde lo único que necesitamos es tener una copia fresca y con archivo de un servidor, en donde dedicar más de 5 miutos de setup sería perder tiempo y dinero.
Este script hace que tener backups sea algo sencillo de implementar, sin descuidar la calidad de los resultados, así que ya no hay más excusas para tener un sólo server sin backup.
Good luck and good backups :)