Skip to main content

Advanced automation


Table of contents

  1. Automation patterns
  2. Robust scripts
  3. Parallelization
  4. Introduction to Ansible
  5. Multi-server orchestration
  6. Practical exercises


1 - Automation patterns

Automation levels

LevelToolsCharacteristics
ScriptsBash, PythonAd-hoc, fast
Config ManagementAnsible, PuppetIdempotent, declarative
IaCTerraform, PulumiFull infrastructure
GitOpsArgoCD, FluxGit as the source of truth

Idempotence

# NON idempotent - problème si relancé
echo "config=value" >> /etc/myapp.conf

# Idempotent - safe à relancer
grep -q "config=value" /etc/myapp.conf || echo "config=value" >> /etc/myapp.conf

# Ou avec sed
sed -i '/^config=/d' /etc/myapp.conf
echo "config=value" >> /etc/myapp.conf

Project structure

automation/
├── inventory/
│ ├── production
│ └── staging
├── roles/
│ ├── common/
│ ├── webserver/
│ └── database/
├── playbooks/
│ ├── deploy.yml
│ └── backup.yml
├── scripts/
│ ├── utils.sh
│ └── deploy.sh
└── README.md

🔝 Back to table of contents



2 - Robust scripts

Professional script template

#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

#######################
# Configuration
#######################
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.*}.log"
readonly LOCK_FILE="/var/run/${SCRIPT_NAME%.*}.lock"

#######################
# Logging
#######################
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

log_info() { log "INFO" "$@"; }
log_warn() { log "WARN" "$@"; }
log_error() { log "ERROR" "$@" >&2; }

die() {
log_error "$@"
exit 1
}

#######################
# Locking
#######################
acquire_lock() {
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
die "Une autre instance est en cours d'exécution"
fi
echo $$ > "$LOCK_FILE"
}

#######################
# Cleanup
#######################
cleanup() {
local exit_code=$?
log_info "Nettoyage..."
# Vos actions de nettoyage ici
rm -f "$LOCK_FILE"
exit $exit_code
}

trap cleanup EXIT INT TERM

#######################
# Main
#######################
main() {
acquire_lock
log_info "Démarrage de $SCRIPT_NAME"

# Votre logique ici

log_info "Terminé"
}

main "$@"

Dependency management

check_dependencies() {
local deps=("jq" "curl" "docker")
local missing=()

for cmd in "${deps[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
missing+=("$cmd")
fi
done

if [ ${#missing[@]} -ne 0 ]; then
die "Dépendances manquantes: ${missing[*]}"
fi
}

Retry pattern

retry() {
local max_attempts="${1:-3}"
local delay="${2:-5}"
shift 2
local cmd=("$@")

local attempt=1
while true; do
if "${cmd[@]}"; then
return 0
fi

if (( attempt >= max_attempts )); then
log_error "Échec après $max_attempts tentatives: ${cmd[*]}"
return 1
fi

log_warn "Tentative $attempt échouée, nouvelle tentative dans ${delay}s..."
sleep "$delay"
(( attempt++ ))
done
}

# Utilisation
retry 5 10 curl -sf http://service/health

🔝 Back to table of contents



3 - Parallelization

With background jobs

# Exécuter en parallèle
for host in server{1..10}; do
ssh "$host" "apt update && apt upgrade -y" &
done
wait
echo "Tous les serveurs mis à jour"

With xargs

# Parallèle avec limite
cat servers.txt | xargs -P 5 -I {} ssh {} "uptime"

# Avec plusieurs commandes
cat servers.txt | xargs -P 5 -I {} bash -c 'ssh {} "hostname && uptime"'

With GNU Parallel

# Installation
apt install parallel

# Exécution parallèle
parallel -j 4 ssh {} uptime ::: server1 server2 server3 server4

# Depuis un fichier
parallel -j 4 -a servers.txt ssh {} uptime

# Avec commande complexe
parallel -j 4 'ssh {} "df -h | grep /dev/sda"' ::: $(cat servers.txt)

Concurrency control

#!/bin/bash
MAX_JOBS=5
current_jobs=0

run_with_limit() {
while (( current_jobs >= MAX_JOBS )); do
wait -n
(( current_jobs-- ))
done

"$@" &
(( current_jobs++ ))
}

for server in $(cat servers.txt); do
run_with_limit ssh "$server" "some_command"
done

wait

🔝 Back to table of contents



4 - Introduction to Ansible

Installation

apt install ansible
# ou
pip install ansible

Inventory

# /etc/ansible/hosts ou inventory.ini
[webservers]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11

[databases]
db1 ansible_host=192.168.1.20

[production:children]
webservers
databases

[all:vars]
ansible_user=admin
ansible_python_interpreter=/usr/bin/python3

Ad-hoc commands

# Ping tous les serveurs
ansible all -m ping

# Exécuter une commande
ansible webservers -m shell -a "uptime"

# Installer un package
ansible webservers -m apt -a "name=nginx state=present" --become

# Copier un fichier
ansible all -m copy -a "src=/local/file dest=/remote/path"

Basic playbook

# deploy.yml
---
- name: Configurer les serveurs web
hosts: webservers
become: yes

vars:
app_name: myapp
app_port: 8080

tasks:
- name: Mettre à jour apt cache
apt:
update_cache: yes
cache_valid_time: 3600

- name: Installer Nginx
apt:
name: nginx
state: present

- name: Copier la configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: Restart Nginx

- name: Activer le site
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link

handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted

Run a playbook

# Exécuter
ansible-playbook deploy.yml

# Avec inventaire spécifique
ansible-playbook -i inventory.ini deploy.yml

# Mode check (dry-run)
ansible-playbook deploy.yml --check

# Limiter aux hosts
ansible-playbook deploy.yml --limit webservers

# Avec variables
ansible-playbook deploy.yml -e "app_port=9090"

🔝 Back to table of contents



5 - Multi-server orchestration

Deployment script

#!/bin/bash
set -euo pipefail

SERVERS=("web1" "web2" "web3")
DEPLOY_DIR="/var/www/myapp"
ARTIFACT="myapp-$(date +%Y%m%d%H%M%S).tar.gz"

deploy_to_server() {
local server="$1"

echo "Déploiement sur $server..."

# Upload
scp "$ARTIFACT" "$server:/tmp/"

# Deploy
ssh "$server" bash << EOF
set -e
cd $DEPLOY_DIR
tar -xzf /tmp/$ARTIFACT
systemctl restart myapp
rm /tmp/$ARTIFACT
EOF

echo "✓ $server déployé"
}

# Rolling deployment
for server in "${SERVERS[@]}"; do
deploy_to_server "$server"

# Health check
sleep 5
if ! curl -sf "http://$server/health" > /dev/null; then
echo "Échec health check sur $server, rollback..."
exit 1
fi
done

echo "Déploiement terminé sur tous les serveurs"

Orchestration with Fabric (Python)

# fabfile.py
from fabric import Connection, SerialGroup

hosts = ['web1', 'web2', 'web3']

def deploy():
for host in hosts:
c = Connection(host)
c.run('cd /var/www/myapp && git pull')
c.run('systemctl restart myapp')
print(f"Deployed to {host}")

def parallel_update():
group = SerialGroup(*hosts)
group.run('apt update && apt upgrade -y')

🔝 Back to table of contents



6 - Practical exercises

Exercise 1: Multi-server backup script

Create a script that backs up several servers in parallel:

Solution
#!/bin/bash
set -euo pipefail

SERVERS=("server1" "server2" "server3")
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"

backup_server() {
local server="$1"
echo "Backup de $server..."
ssh "$server" "tar czf - /var/www /etc" > "$BACKUP_DIR/${server}.tar.gz"
echo "✓ $server"
}

for server in "${SERVERS[@]}"; do
backup_server "$server" &
done

wait
echo "Tous les backups terminés dans $BACKUP_DIR"

Exercise 2: Ansible playbook

Create a playbook that installs and configures a LAMP server:

Solution
---
- name: Install LAMP stack
hosts: webservers
become: yes

tasks:
- name: Install packages
apt:
name:
- apache2
- mysql-server
- php
- php-mysql
state: present
update_cache: yes

- name: Start Apache
service:
name: apache2
state: started
enabled: yes

- name: Start MySQL
service:
name: mysql
state: started
enabled: yes

Quiz

Q1. What is idempotence?

Answer

An idempotent script/playbook produces the same result whether it is run once or several times. The system always ends up in the desired state after execution.

Q2. How do you run tasks in parallel with xargs?

Answer

xargs -P N where N is the number of parallel processes.

🔝 Back to table of contents



Key takeaways

  • Idempotence: scripts that can be re-run without side effects
  • set -euo pipefail: always in production
  • Locking: avoid concurrent executions
  • xargs -P and GNU Parallel for parallelization
  • Ansible for configuration management
  • Always include health checks after deployment

🔝 Back to table of contents


← Previous chapter | Next chapter: Troubleshooting →