La administración de bases de datos PostgreSQL requiere soluciones confiables para garantizar la seguridad y disponibilidad de la información. En este artículo, exploraremos cómo implementar un sistema automatizado para respaldar bases de datos PostgreSQL utilizando scripts en Bash y Python, incluyendo notificaciones por correo electrónico en caso de errores.
El Script Principal: Automatización en Bash
El siguiente script en Bash se encarga de automatizar el proceso de respaldo para bases de datos PostgreSQL y realizar tareas de mantenimiento. Aquí te explicamos las principales funcionalidades:
1. Configuración inicial y estructura de directorios
- Define variables importantes como las rutas locales y remotas para almacenar los respaldos.
- Organiza los respaldos por fecha en directorios específicos, facilitando la gestión y recuperación.
DIR_BASE_RESPALDO="/Respaldos/respaldo-local" # Ruta base para los respaldos FECHA=$(date +"%d-%m-%Y") DIR_RESPALDO="$DIR_BASE_RESPALDO/$FECHA"
2. Obtención de bases de datos PostgreSQL
Se ejecuta una consulta en el sistema PostgreSQL para listar todas las bases de datos disponibles que no sean plantillas, utilizando el comando psql
como usuario postgres
:
DATABASES=$(su - postgres -c "$PSQL -d postgres -t -c \"SELECT datname FROM pg_database WHERE datistemplate = false;\"")
3. Generación de respaldos comprimidos
Por cada base de datos obtenida, se utiliza el comando pg_dump
para generar un respaldo comprimido en formato gzip
:
ARCHIVO_RESPALDO="$DIR_RESPALDO/${DB_NAME}_$FECHA.sql.gz" su - postgres -c "$PG_DUMP --format=c --no-owner --no-acl $DB_NAME" | gzip > "$ARCHIVO_RESPALDO"
4. Copia remota de los respaldos
Una vez generados los respaldos, el script copia la carpeta de respaldo a un directorio remoto, asegurando una segunda copia en caso de fallos en el servidor local:
cp -r "$DIR_RESPALDO" "$DIR_BACKUP_REMOTO"
5. Limpieza de respaldos antiguos
El script elimina automáticamente respaldos locales y remotos con más de 7 días de antigüedad:
find "$DIR_BASE_RESPALDO" -type d -mtime +7 -exec rm -rf {} + find "$DIR_BACKUP_REMOTO" -type d -mtime +7 -exec rm -rf {} +
6. Registro de logs
Todos los eventos y errores se registran en un archivo de log para facilitar el monitoreo del sistema:
LOG_FILE="$DIR_RESPALDO/respaldo_$FECHA.log" echo "[$FECHA $HORA] Inicio del respaldo de las bases de datos." > "$LOG_FILE"
Gestión de Errores y Notificaciones: Script en Python
Para informar al administrador sobre posibles errores durante el proceso, se utiliza un script en Python que envía notificaciones por correo electrónico. A continuación, describimos sus características principales.
1. Envío de notificaciones por correo
El script utiliza la biblioteca smtplib
para conectarse a un servidor SMTP y enviar correos electrónicos:
servidor_smtp = 'xxx.xxx.xxx.xx' puerto_smtp = 587 usuario = 'usuario' contrasena = 'contraseña'
2. Notificación de estado
El cuerpo del mensaje detalla si el proceso de respaldo fue exitoso o si se encontraron errores, incluyendo un resumen del log generado:
if estado_salida == 0: cuerpo_mensaje = "El proceso se completó sin errores." else: cuerpo_mensaje = "Se encontraron errores en el proceso:\n\n" + mensaje_error
3. Enlace con el script Bash
El script Bash llama al script Python en caso de detectar errores, pasando como argumentos el correo del destinatario, el estado del proceso y el mensaje de error:
python enviar_correo.py "$EMAIL" "$ESTADO" "$ERROR_MESSAGE"
Scripts completos
Script que realiza el backup:
respaldar.sh
#!/bin/bash # Variables DIR_BASE_RESPALDO="/Respaldos/respaldo-local" # Ruta base para los respaldos FECHA=$(date +"%d-%m-%Y") HORA=$(date +"%H:%M:%S") DIR_RESPALDO="$DIR_BASE_RESPALDO/$FECHA" DIR_BACKUP_REMOTO="/BKP_REMOTO/" LOG_FILE="$DIR_RESPALDO/respaldo_$FECHA.log" PG_DUMP="/usr/bin/pg_dump" PSQL="/usr/bin/psql" EMAIL="[email protected]" # Correo destinatario # Crear directorios de respaldo y log si no existen mkdir -p "$DIR_RESPALDO" || { echo "[$FECHA $HORA] Error al crear el directorio de respaldo." >> "$LOG_FILE"; exit 1; } # Inicializar el archivo de log echo "[$FECHA $HORA] Inicio del respaldo de las bases de datos." > "$LOG_FILE" # Variable de estado ESTADO=0 # Obtener todas las bases de datos como usuario postgres DATABASES=$(su - postgres -c "$PSQL -d postgres -t -c \"SELECT datname FROM pg_database WHERE datistemplate = false;\"") if [ $? -ne 0 ]; then echo "[$FECHA $HORA] Error al obtener la lista de bases de datos." >> "$LOG_FILE" ESTADO=1 else echo "[$FECHA $HORA] Lista de bases de datos obtenida exitosamente." >> "$LOG_FILE" fi # Respaldar cada base de datos for DB_NAME in $DATABASES; do ARCHIVO_RESPALDO="$DIR_RESPALDO/${DB_NAME}_$FECHA.sql.gz" if su - postgres -c "$PG_DUMP --format=c --no-owner --no-acl $DB_NAME" | gzip > "$ARCHIVO_RESPALDO"; then echo "[$FECHA $HORA] Respaldo completado exitosamente: $ARCHIVO_RESPALDO" >> "$LOG_FILE" else echo "[$FECHA $HORA] Error en el respaldo de la base de datos: $DB_NAME" >> "$LOG_FILE" ESTADO=1 fi done # Copiar la carpeta de respaldo a la ubicación remota if cp -r "$DIR_RESPALDO" "$DIR_BACKUP_REMOTO"; then echo "[$FECHA $HORA] Copia de respaldos a $DIR_BACKUP_REMOTO completada." >> "$LOG_FILE" else echo "[$FECHA $HORA] Error al copiar respaldos a $DIR_BACKUP_REMOTO." >> "$LOG_FILE" ESTADO=1 fi # Eliminar respaldos de más de 7 días en el directorio local if find "$DIR_BASE_RESPALDO" -type d -mtime +7 -exec rm -rf {} +; then echo "[$FECHA $HORA] Eliminación de respaldos antiguos en local completada." >> "$LOG_FILE" else echo "[$FECHA $HORA] Error al eliminar respaldos antiguos en local." >> "$LOG_FILE" ESTADO=1 fi # Eliminar respaldos de más de 7 días en el directorio remoto if find "$DIR_BACKUP_REMOTO" -type d -mtime +7 -exec rm -rf {} +; then echo "[$FECHA $HORA] Eliminación de respaldos antiguos en remoto completada." >> "$LOG_FILE" else echo "[$FECHA $HORA] Error al eliminar respaldos antiguos en remoto." >> "$LOG_FILE" ESTADO=1 fi # Registrar el estado de salida y enviar correo si hay errores if [ $ESTADO -eq 0 ]; then echo "[$FECHA $HORA] Proceso completado sin errores." >> "$LOG_FILE" else echo "[$FECHA $HORA] Proceso finalizado con errores." >> "$LOG_FILE" # Enviar correo en caso de error usando Python ERROR_MESSAGE=$(cat "$LOG_FILE") # Captura el contenido del log para enviar python enviar_correo.py "$EMAIL" "$ESTADO" "$ERROR_MESSAGE" # Llama al script de envío de correo fi echo "[$FECHA $HORA] Fin del respaldo" >> "$LOG_FILE" # Copiar el log al directorio remoto al finalizar LOG_COPY_PATH="$DIR_BACKUP_REMOTO/$FECHA/respaldo_$FECHA.log" if cp "$LOG_FILE" "$LOG_COPY_PATH"; then echo "[$FECHA $HORA] Log copiado a $LOG_COPY_PATH." >> "$LOG_FILE" else echo "[$FECHA $HORA] Error al copiar el log a $LOG_COPY_PATH." >> "$LOG_FILE" ESTADO=1 fi exit $ESTADO
Script para enviar correos:
enviar_correo.py
#!/usr/bin/env python2.6 # -*- coding: utf-8 -*- import smtplib import sys from email.mime.text import MIMEText def enviar_correo(correo_destinatario, estado_salida, mensaje_error): # Cuerpo del mensaje if estado_salida == 0: cuerpo_mensaje = "El proceso se completó sin errores." else: cuerpo_mensaje = "Se encontraron errores en el proceso:\n\n" + mensaje_error msg = MIMEText(cuerpo_mensaje) msg['Subject'] = 'Informe de Respaldo' msg['From'] = '[email protected]' msg['To'] = correo_destinatario # Configuración de autenticación servidor_smtp = 'xxx.xxx.xxx.xxx' # Servidor SMTP puerto_smtp = 587 # Usa 587 para TLS o 465 para SSL usuario = 'usuario' # Usuario SMTP contrasena = 'contraseña' # Contraseña SMTP servidor = None try: servidor = smtplib.SMTP(servidor_smtp, puerto_smtp) servidor.starttls() # Inicia la conexión TLS servidor.login(usuario, contrasena) # Autenticación servidor.sendmail(msg['From'], [correo_destinatario], msg.as_string()) # Enviar el correo print "Correo enviado a %s" % correo_destinatario # Uso de % para formatear except smtplib.SMTPException as e: print "Error al enviar correo: %s" % e # Uso de % para formatear finally: if servidor is not None: try: servidor.quit() # Asegúrate de cerrar la conexión except Exception as e: print "Error al cerrar la conexión SMTP: %s" % e # Uso de % para formatear if __name__ == '__main__': if len(sys.argv) != 4: print "Uso: python send_email.py <correo_destinatario> <estado_salida> <mensaje_error>" sys.exit(1) correo_destinatario = sys.argv[1] estado_salida = int(sys.argv[2]) mensaje_error = sys.argv[3] enviar_correo(correo_destinatario, estado_salida, mensaje_error)