Advanced automation
Table of contents
- Automation patterns
- Robust scripts
- Parallelization
- Introduction to Ansible
- Multi-server orchestration
- Practical exercises
1 - Automation patterns
Automation levels
| Level | Tools | Characteristics |
|---|---|---|
| Scripts | Bash, Python | Ad-hoc, fast |
| Config Management | Ansible, Puppet | Idempotent, declarative |
| IaC | Terraform, Pulumi | Full infrastructure |
| GitOps | ArgoCD, Flux | Git 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