Uno de los usos mas básicos de Ansible es la confección de ficheros de configuración, como veremos en este mismo artÃculo estos se basan en templates jinja2 de Python , esto nos permitirá una gran flexibilidad pudiendo adaptar nuestos ficheros a las necesidades que tengamos.
En mi caso voy a utilizar Ansible para configurar los backups de un servidor fÃsico y de las jails contenidas en él, como no tengo un servidor DNS interno lo primero será modificar mi fichero de hosts para acceder a estos por nombre.
192.168.69.6 drwho
192.168.69.14 gdrive
192.168.69.12 infinity
192.168.69.5 potras
192.168.69.13 prometheus
192.168.69.15 synapse
192.168.69.10 rxwod
192.168.69.11 haproxy
192.168.69.2 mighty
Ahora instalamos Ansible:
Creamos el directorio donde tendremos la configuración global de Ansible:
chown -R root:kr0m /usr/local/etc/ansible/
chmod 775 /usr/local/etc/ansible/
Ahora como ya podremos realizar el resto de pasos con nuestro usuario regular.
Definimos la lista de hosts indicando el tamaño mÃnimo de los backups en cada uno de los servidores y unas variables del grupo tobackup_servers donde definimos las polÃticas de retención de los backups:
[tobackup_servers]
mighty min_backup_size=21
drwho min_backup_size=249
potras min_backup_size=17873
infinity min_backup_size=2749625
haproxy min_backup_size=9
prometheus min_backup_size=17
rxwod min_backup_size=153657
[tobackup_servers:vars]
local_retention=30
digi_retention=10
gdrive_retention=30
Configuramos algunos parámetros para que no se queje sobre el
path de Python
ni el cambio de keys SSH.
[defaults]
interpreter_python=auto_silent
host_key_checking = False
Algunos comandos útiles de Ansible son:
ansible all -m ping -> Ping a los hosts
ansible all -a "uptime" -> Comando en los hosts
ansible all -m setup -> Variables de cada host
Todos los servidores partirán de un script base, donde se añadirá una parte especÃfica según sus necesidades, por ejemplo si se trata de un servidor postgresql se dumpeará la base de datos, si es un servidor web se copiarán los ficheros del documentroot, etc, etc. Además al final del script se añadirá un parte adicional si se trata del equipo fÃsico ya que tiene un requirimiento especial.
#! /usr/local/bin/bash
# Script managed by Ansible
# pkg install ccrypt
function sendTelegramMessage {
message=${@:1}
curl -s -X POST https://api.telegram.org/botYYYYYYYYYYYY/sendMessage -d chat_id=XXXXXXXXXX -d text="$message"
}
# Max: 50MB
function sendTelegramFile {
path=${@:1}
curl -s -F "chat_id=XXXXXXXXXX" -F document=@"$path" https://api.telegram.org/botYYYYYYYYYYYY/sendDocument
}
DATE=$(date +%d_%m_%Y)
SHORT_HOSTNAME=$(hostname -s)
BACKUPSDIR="/storage/backups/$SHORT_HOSTNAME/BACKUP"
rm -rf $BACKUPSDIR 2>/dev/null
mkdir -p $BACKUPSDIR
echo "-- Copying backup files"
# -- Common data --
cp -rp /root/.scripts/ $BACKUPSDIR/scripts
crontab -l > $BACKUPSDIR/crontab
cp /etc/hosts $BACKUPSDIR/hosts
cp /etc/rc.conf $BACKUPSDIR/rc.conf
# -----------------
# -- Custom data --
{% include inventory_hostname + '_custom_data.j2' %}
# ------------------
# -- Kr0m data --
crontab -u kr0m -l > $BACKUPSDIR/kr0m_crontab
cp -r /home/kr0m/.scripts $BACKUPSDIR/kr0m_scripts
# -----------------
# -- Packaging backup --
DIRNAME=$(dirname $BACKUPSDIR)
cd $DIRNAME
FILENAME="$SHORT_HOSTNAME"_backup_"$DATE".tar.gz
echo "-- Compressing backup: $FILENAME"
tar czf $FILENAME BACKUP
chmod 600 $FILENAME
echo "-- Done"
# ------------------
# -- Encrypting backup --
echo "-- Encrypting backup"
ccrypt -f -e -k ~/.scripts/backup_passkey $FILENAME
CRYPTED_FILENAME=$FILENAME.cpt
# To decrypt: ccrypt -d FILENAME
echo "-- Done"
# ------------------
# -- Telegram Upload --
echo "-- Uploading backup to Telegram"
sendTelegramFile $CRYPTED_FILENAME
echo ""
echo "-- Done"
# ------------------
# -- DIGI Upload --
echo "-- Uploading backup to DIGI"
# DONT CREATE DIRECTORY BECAUSE ITS PARENT FSTAB ENTRY AND SHOULD EXIST
# IF DOESNT AND YOU CREATE IT, BACKUP DOESNT FAIL AND YOU GET A LOCAL COPY ONLY
#if [ ! -d /mnt/DIGI/Backups/$SHORT_HOSTNAME ]; then
# mkdir -p /mnt/DIGI/Backups/$SHORT_HOSTNAME
#fi
cp $CRYPTED_FILENAME /mnt/DIGI/Backups/$SHORT_HOSTNAME/
echo "-- Done"
# ------------------
# -- GDrive Upload --
echo "-- Uploading backup to GDrive"
# DONT CREATE DIRECTORY BECAUSE ITS PARENT FSTAB ENTRY AND SHOULD EXIST
# IF DOESNT AND YOU CREATE IT, BACKUP DOESNT FAIL AND YOU GET A LOCAL COPY ONLY
#if [ ! -d /mnt/gdrive/Backups/$SHORT_HOSTNAME ]; then
# mkdir -p /mnt/gdrive/Backups/$SHORT_HOSTNAME
#fi
cp $CRYPTED_FILENAME /mnt/gdrive/Backups/$SHORT_HOSTNAME/
echo "-- Done"
# ------------------
# -- Size check --
echo "-- Checking backup size"
sleep 10
BACKUPSIZE=$(du $CRYPTED_FILENAME|awk '{print$1}')
BACKUPSIZEHUMAN=$(du -h $CRYPTED_FILENAME|awk '{print$1}')
echo "BACKUPSIZE: $BACKUPSIZE"
echo "BACKUPSIZE HUMAN: $BACKUPSIZEHUMAN"
# 249 -> 249K
if [ $BACKUPSIZE -lt {{ min_backup_size }} ]; then
message="$SHORT_HOSTNAME: Abnormal backup size -> /storage/backups/$SHORT_HOSTNAME/$CRYPTED_FILENAME $BACKUPSIZEHUMAN"
sendTelegramMessage $message
fi
echo "-- Done"
# ------------------
# -- Clear temp files --
echo "-- Deleting temp backup files"
rm -rf BACKUP
echo "-- Done"
# ------------------
# -- Remove old local backups --
echo "-- LOCAL_CHECK: Checking if there are old backups to remove"
DAYS_OF_BACKUP='{{ hostvars[groups['tobackup_servers'][0]].local_retention }}'
# Make cleaning of old backups:
TOTAL=$(ls -lt *_backup_*.tar.gz.cpt|wc -l|awk '{print$1}')
if [ $TOTAL -gt $DAYS_OF_BACKUP ]; then
let TO_DELETE=$TOTAL-$DAYS_OF_BACKUP
for BACKUP in $(ls -lt *_backup_*.tar.gz.cpt|awk '{print$9}'|tail -n $TO_DELETE); do
echo "-- Deleting backup: $BACKUP"
rm $BACKUP
done
fi
echo "-- Done"
# ------------------
# -- Remove old DIGI backups --
echo "-- DIGI_CHECK: Checking if there are old backups to remove"
DAYS_OF_BACKUP='{{ hostvars[groups['tobackup_servers'][0]].digi_retention }}'
# Make cleaning of old backups:
TOTAL=$(ls -lt /mnt/DIGI/Backups/$SHORT_HOSTNAME/*_backup_*.tar.gz.cpt|wc -l|awk '{print$1}')
if [ $TOTAL -gt $DAYS_OF_BACKUP ]; then
let TO_DELETE=$TOTAL-$DAYS_OF_BACKUP
for BACKUP in $(ls -lt /mnt/DIGI/Backups/$SHORT_HOSTNAME/*_backup_*.tar.gz.cpt|awk '{print$9}'|tail -n $TO_DELETE); do
echo "-- Deleting backup: $BACKUP"
rm $BACKUP
done
fi
echo "-- Done"
# ------------------
# -- Remove old GDrive backups --
echo "-- GDrive_CHECK: Checking if there are old backups to remove"
DAYS_OF_BACKUP='{{ hostvars[groups['tobackup_servers'][0]].gdrive_retention }}'
# Make cleaning of old backups:
TOTAL=$(ls -lt /mnt/gdrive/Backups/$SHORT_HOSTNAME/*_backup_*.tar.gz.cpt|wc -l|awk '{print$1}')
if [ $TOTAL -gt $DAYS_OF_BACKUP ]; then
let TO_DELETE=$TOTAL-$DAYS_OF_BACKUP
for BACKUP in $(ls -lt /mnt/gdrive/Backups/$SHORT_HOSTNAME/*_backup_*.tar.gz.cpt|awk '{print$9}'|tail -n $TO_DELETE); do
echo "-- Deleting backup: $BACKUP"
rm $BACKUP
done
fi
echo "-- Done"
# ------------------
{% if inventory_hostname == "mighty" %}
{% include 'mighty_additional_backup.j2' %}
{% endif %}
Por cada server generaremos un fichero custom_data:
Mighty es un poco especial, además del custom_data anterior también necesita un additional_backup:
Ahora que ya tenemos los templates procedemos con el playbook:
---
- name: Ansible backup script
hosts: tobackup_servers
remote_user: kr0m
tasks:
- name: Create backup script
template:
src: "~/ansible/templates/backup/backup.j2"
dest: "/root/.scripts/backup.sh"
owner: "root"
group: "wheel"
mode: 0700
become: true
become_method: su
vars:
ansible_su_pass: "ZZZZZZZZZZ"
El nombre del parámetro ansible_su_pass lo he consultado mediante el siguiente comando:
- become_pass
Password to pass to su
[Default: (null)]
set_via:
env:
- name: ANSIBLE_BECOME_PASS
- name: ANSIBLE_SU_PASS
ini:
- key: password
section: su_become_plugin
vars:
- name: ansible_become_password
- name: ansible_become_pass
- name: ansible_su_pass
Finalmente ejecutamos el playbook y si todo ha ido bien tendremos desplegado el script de backup en todos los servidores: