Creando un plan de contigencia con AWS S3

En el post anterior estuvimos Conociendo los hooks de Git y Usando ficheros Makefile para automatizar tareas de desarrollo. Ahora veremos cómo automatizar, de una manera muy simple, los backup de nuestros proyectos una vez desplegados y así protegernos ante posibles hackeos, pérdidas de datos o un incendio en el data center.

Introducción

Esta semana muchos han vivido una situación desagradable con la pérdida del datacenter de OVH más grande de Europa. Una situación excepcional porque no es frecuente que un datacenter termine literalmente ardiendo, pero que ha destapado un hecho aún más preocupante: la falta de planes de contingencia ante una crisis de esta magnitud.

Esta ausencia de protocolos de actuación suele achacarse al exceso de confianza, creyendo que un datacenter no va a esfumarce de la noche a la mañana; o manteniendo las copias de seguridad en el mismo servidor de la aplicación para reducir los costes. En cualquier caso, cuando publicamos un proyecto estamos exponiendo nuestra infraestructura ante posibles hackeos, DDoS, etc.

Definir un plan de contingencia no tiene por qué suponer un coste elevado, ni una inversión execisa en herramientas o configuración de terceros.

¿Qué es un plan de contingencia?

Un Plan de Contingencia (Diasaster Recovery Service) es un protocolo de actuación que facilita la total recuperación ante desastres en el ámbito informático y que detalla las acciones que deben tomarse a nivel de sistemas:

  • Inmediatamente tras suceder el "desastre".
  • Durante la resolución de la crisis.
  • Después de haberse restablecido el servicio.

Todo plan de contingencia se basa en una correcta política de copias de seguridad y un plan de recuperación de datos que minimice el tiempo en que nuestra aplicación está sin servicio. Así pues hay DRS que se basan en copias de seguridad realizadas periódicamente y otros planes mucho más efectivos que se apoyan en la sincronización de datos en tiempo real.

Dos de los parámetros más relevantes de un DRS son:

El RPO (Recovery Point Objective)

El Recovery Point Objective es la cantidad máxima de datos que podemos perder antes de que se llegue a afectar a la continuidad del negocio. Hace referencia al momento en que se llevó a cabo el último backup previo a la parada.

Se podría decir que definiendo el RPO, medimos la cantidad de información a la que podemos llegar a renunciar para reanudar nuestra actividad cuanto antes y nos ayudará a decidir también la frecuencia con la que se deberían realizar las copias de seguridad en un futuro.

El RTO (Recovery Time Objective)

El Recovery Time Objective es el máximo tiempo de parada que puede aceptarse dado que, más allá de este punto, el negocio se ve perjudicado por no poder dar servicio.

Es el tiempo que pasa desde que se produce el parón hasta que se reanuda el servicio.

En resumen:

  • A mayor cantidad de datos en nuestras copias de seguridad o snapshots, mayor será el consumo de recursos para almacenar dicha información y mayor será el tiempo necesario para recuperarlos.
  • Cuanta mayor sea la frecuencia de copia de seguridad, menor será la probabilidad de pérdida de datos pero aumentará los costes de transferencia.

Plan de contingencia básico

Veamos cómo hacer un plan de contingencia básico que nos permita:

  • Definir la frecuencia con la que queremos hacer nuestras copias de seguridad
  • Realizar copias de seguridad de nuestra aplicación
  • Realizar copias de seguridad de la base de datos
  • Almacenar dichas copias de seguridad en local o en una máquina de la misma montada como una unidad de almacenamiento
  • Almacenar dichas copias de seguridad en un servicio de almacenamiento cloud como S3 de Amazon Web Services

Para desarrollar una RDS más completo, el Instituto Nacional de Ciberseguridad ha elaborado un informe sobre Plan de Contingencia y Continuidad de Negocio con recursos explicativos y guías de acción.

Cómo hacer backups de nuestros proyectos

Para automatizar nuestros backups haremos uso de:

  • Un shell script por proyecto que nos permita definir qué información queremos hacer copia de seguridad.
  • Crontab para definir el mejor momento para dicha copia de seguridad, que generalmente coincidirá con la franja horaria de menor carga de tráfico y, por tanto, menor probabilidad de variabilidad de la información.

backup-projectx.sh

#!/bin/sh

#
# Setup: crontab -e
#
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7)  OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  *  command to be executed
# *  *  *  *  *  command --arg1 --arg2 file1 file2 2>&1
#
# 0  1  *  *  *  ./backup-project-x.sh
#

RESET='\e[0m'
RED='\e[31m'
GREEN='\e[32m'
YELLOW='\e[33m'

#----------------------------------------------------------------------------------------------------------------------
# DEFINITIONS
#----------------------------------------------------------------------------------------------------------------------

# Application name
project_name="ProjectX"

# Application path
project_path="/var/www/ProjectX"

# Empty string "" for standalone applications
project_database="db_projectx"

# Where local snapshots will be stored
target="/media/BACKUP/ProjectX"

# AWS S3 bucket name
aws_bucket="s3://privated-bucket-name/"

#----------------------------------------------------------------------------------------------------------------------
# VARIABLES
#----------------------------------------------------------------------------------------------------------------------

date=$(date +"%Y%m%d%H%M%S")

hostname=$(hostname -s)

archive_file_application="$hostname-application-$project_name-$date.tgz"

if [ -z "$project_database" ]
then
    archive_file_database=""
else
    archive_file_database="$hostname-mysql-$project_database-$date.sql.gz"
fi

#----------------------------------------------------------------------------------------------------------------------
# CHECKPOINT: MAKE SURE TARGET FOLDER EXISTS
#----------------------------------------------------------------------------------------------------------------------

mkdir -p $target

#----------------------------------------------------------------------------------------------------------------------
# CLEAN UP: REMOVE OLD SNAPSHOTS
#----------------------------------------------------------------------------------------------------------------------

echo "┌─────────────────────────────────────────────────────────────────────────────┐"
echo "│ REMOVING OLDER SNAPSHOTS FROM LOCAL FOLDER                                  │"
echo "└─────────────────────────────────────────────────────────────────────────────┘"
echo ""
echo "  ❗ Removing files from [ ${YELLOW}$target${RESET} ]"

find $target/ -mtime +14 -exec rm {} \;

echo ""
echo "  🟢 Task done!"
echo ""

#----------------------------------------------------------------------------------------------------------------------
# BACKUP: APPLICATION
#----------------------------------------------------------------------------------------------------------------------

echo "┌─────────────────────────────────────────────────────────────────────────────┐"
echo "│ LOCALLY BACKUP THE APPLICATION                                              │"
echo "└─────────────────────────────────────────────────────────────────────────────┘"
echo ""
echo "  📦 Backing up [ ${YELLOW}$project_path${RESET} ] to [ ${YELLOW}$target/$archive_file_application${RESET} ]"

tar czf $target/$archive_file_application $project_path 2>/dev/null

echo ""
echo "  🟢 Task done!"
echo ""

#----------------------------------------------------------------------------------------------------------------------
# BACKUP: DATABASE
#----------------------------------------------------------------------------------------------------------------------

echo "┌─────────────────────────────────────────────────────────────────────────────┐"
echo "│ LOCALLY BACKUP THE DATABASE                                                 │"
echo "└─────────────────────────────────────────────────────────────────────────────┘"
echo ""

if [ -z "$project_database" ]
then
    echo "  ⚠️  Current project does not have any database to backup!"
else
    echo "  📦 Backing up [ ${YELLOW}$project_database${RESET} ] to [ ${YELLOW}$target/$archive_file_database${RESET} ]"

    mysqldump --login-path=local --triggers --routines $project_database | gzip -9 -c > $target/$archive_file_database
fi

echo ""
echo "  🟢 Task done!"
echo ""

#----------------------------------------------------------------------------------------------------------------------
# UPLOAD TO AWS S3
#----------------------------------------------------------------------------------------------------------------------

echo "┌─────────────────────────────────────────────────────────────────────────────┐"
echo "│ UPLOAD BACKUPS TO AWS S3                                                    │"
echo "└─────────────────────────────────────────────────────────────────────────────┘"
echo ""
echo "  🚀 Uploading [ ${YELLOW}$target/$archive_file_application${RESET} ] to [ ${YELLOW}AWS S3${RESET} ]"

aws s3 cp --quiet $target/$archive_file_application $aws_bucket

if [ -z "$project_database" ]
then
    echo "  ⚠️  Current project does not have any database to backup!"
else
    echo "  🚀 Uploading [ ${YELLOW}$target/$archive_file_database${RESET} ] to [ ${YELLOW}AWS S3${RESET} ]"

    aws s3 cp --quiet $target/$archive_file_database $aws_bucket
fi

echo ""
echo "  🟢 Task done!"
echo ""

#----------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------

Para que dicho shell script funcione debemos asignarle permisos de ejecución:

$ chmod +x backup-projectx.sh

Crontab

Para automatizar nuestro backup basta con configurar el crontab de nuestro servidor y definir la periodicidad de ejecución de nuestras copias de seguridad:

$ crontab -e

Para evitar posibles confusiones con respecto al órden de los parámetros dejaremos esta plantilla al comienzo de nuestro crontab:

# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7)  OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  *  command to be executed
# *  *  *  *  *  command --arg1 --arg2 file1 file2 2>&1

Así pues, bastaría con añadir una nueva línea a nuestro crontab con el script que hemos generado para, por ejemplo, hacer copiade seguridad todos los días a la 1:30 AM:

 30  1  *  *  *  ./scripts/backup-projectx.sh 2>&1

Descarga

Puedes descargar el fichero de ejemplo desde aquí.

Versión del documento

[^v1.0]: Última Modificación: 15/03/2021