Esta pagina se ve mejor con JavaScript habilitado

Uso de templates en Ansible

 ·  🎃 kr0m

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.

vi /etc/hosts

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:

pkg install py39-ansible

Creamos el directorio donde tendremos la configuración global de Ansible:

mkdir /usr/local/etc/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:

vi /usr/local/etc/ansible/hosts

[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.

vi /usr/local/etc/ansible/ansible.cfg

[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.

mkdir -p ~/ansible/templates/backup
vi ~/ansible/templates/backup/backup.j2
#! /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:

vi ~/ansible/templates/backup/HOSTNAME_custom_data.j2

Mighty es un poco especial, además del custom_data anterior también necesita un additional_backup:

vi ~/ansible/templates/backup/mighty_additional_backup.j2

Ahora que ya tenemos los templates procedemos con el playbook:

mkdir -p ~/ansible/playbooks/backup

vi ~/ansible/playbooks/backup/backup.yml

---
- 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:

ansible-doc -t become su

- 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:

ansible-playbook ~/ansible/playbooks/backup/backup.yml

Si te ha gustado el artículo puedes invitarme a un RedBull aquí