Administration Linux et automatisation Bash
Ceci couvre les habitudes défensives en bash, set -euo pipefail, la rotation des logs, les health checks, et l'interception d'erreurs, qui empêchent un script ou une tâche planifiée sans supervision d'échouer silencieusement.
set -euo pipefail, et pourquoi ce n'est pas optionnel
Chaque script Bash que j'écris commence par cette ligne, juste après le shebang :
#!/usr/bin/env bash
set -euo pipefailChaque option ferme une vraie faille :
-earrête le script dès qu'une commande échoue. Sans cette option, uncd /app/releases/currentqui échoue laisse quand même la ligne suivante s'exécuter dans le mauvais répertoire.-utraite une variable non définie comme une erreur. Cela détecte la faute de frappe classique ($RELEASE_DIRmal orthographiée en$RELEASE_DR) avant qu'elle ne se transforme silencieusement en chaîne vide et ne changerm -rf "$RELEASE_DR/old"enrm -rf /old.pipefailfait échouer un pipeline si l'une de ses commandes échoue, plutôt que de ne vérifier que le code de sortie de la dernière.cat fichier_absent.txt | grep foorapporterait sinon un succès, puisquegrepse termine correctement.
set -e ne vous protège pas de toutes les erreurs. Cette option ne se déclenche pas dans une condition if, dans une chaîne &&/||, ni dans une fonction appelée depuis un de ces contextes. Testez vos véritables chemins d'échec.
Un script de rotation de logs qui n'emporte pas le disque avec lui
L'autre incident récurrent : une application (ou un script) écrit dans un fichier plat indéfiniment, personne ne le remarque, et un jour /var/log, ou pire, /, atteint 100 % et fait tomber toute la machine avec lui. logrotate gère cela pour la plupart des services système, mais beaucoup de logs applicatifs, de sorties de cron et de logs de jobs maison n'y sont jamais raccordés. Voici le script que j'installe en solution de repli :
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/var/log/myapp"
RETENTION_DAYS=14
if [[ ! -d "$LOG_DIR" ]]; then
echo "ERREUR : le répertoire de logs $LOG_DIR n'existe pas" >&2
exit 1
fi
# Compresse les logs de plus d'un jour qui ne sont pas déjà compressés
find "$LOG_DIR" -maxdepth 1 -type f -name "*.log" -mtime +1 -exec gzip {} \;
# Supprime les logs compressés au-delà de la période de rétention
find "$LOG_DIR" -maxdepth 1 -type f -name "*.log.gz" -mtime "+${RETENTION_DAYS}" -print -delete
echo "Nettoyage des logs terminé : $(date -Iseconds)"Mettez toujours vos variables entre guillemets : "$LOG_DIR", jamais $LOG_DIR seul. Une variable non protégée qui contient un espace, ou qui s'étend en chaîne vide, se fait découper en mots par Bash, et c'est exactement ainsi qu'une combinaison find/rm construite sur un chemin vide ou erroné finit par supprimer des choses qu'on ne voulait pas supprimer. Combinez cette discipline avec -maxdepth 1 et une variable de chemin explicite plutôt que quoi que ce soit dérivé d'une entrée utilisateur.
Un script de health-check que vous pouvez réellement planifier
Pour tout ce qui touche au client final, je veux quelque chose qui tourne sur un planning et qui interroge le vrai endpoint, plutôt que de se contenter d'un systemctl status :
#!/usr/bin/env bash
set -euo pipefail
URL="https://api.example.com/healthz"
TIMEOUT=5
EXPECTED_CODE=200
status_code=$(curl --silent --output /dev/null --write-out "%{http_code}" \
--max-time "$TIMEOUT" "$URL" || echo "000")
if [[ "$status_code" -ne "$EXPECTED_CODE" ]]; then
echo "ÉCHEC DU HEALTH CHECK : $URL a renvoyé $status_code" >&2
exit 1
fi
echo "OK : $URL a renvoyé $status_code"
exit 0Ce qui compte ici, c'est le code de sortie : c'est lui qui permet à cron, à systemd, ou à un agent de supervision (Nagios, le blackbox exporter de Prometheus) de décider s'il faut alerter quelqu'un.
Cron ou timers systemd
Pour des plannings simples sur une seule machine, cron reste tout à fait valable :
*/5 * * * * /usr/local/bin/healthcheck.sh >> /var/log/healthcheck.log 2>&1Je vois encore souvent cette ligne partir sans le >> ... 2>&1, ce qui fait disparaître toute sortie, y compris le message d'erreur dont vous auriez besoin.
Mais dès qu'un job a besoin d'un ordre de dépendances, de tentatives de reprise ou de limites de ressources, je passe à un timer systemd. Pareil si vous voulez simplement retrouver ses logs dans journalctl plutôt que dans un fichier plat que vous devrez faire tourner vous-même :
# /etc/systemd/system/healthcheck.service
[Unit]
Description=Health check de l'API
[Service]
Type=oneshot
ExecStart=/usr/local/bin/healthcheck.sh# /etc/systemd/system/healthcheck.timer
[Unit]
Description=Exécute healthcheck.service toutes les 5 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.targetActivez-le avec systemctl enable --now healthcheck.timer. À partir de là, journalctl -u healthcheck.service vous donne chaque exécution, sa sortie et son code de retour, et il survit à un redémarrage sans entrée cron @reboot isolée.
Journalisation et interception d'erreurs pour que les échecs ne restent pas silencieux
Cette dernière habitude, c'est ce qui empêche un échec de rester silencieux pendant des semaines : rendre l'échec bruyant, volontairement.
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="/var/log/myapp/etl.log"
log() {
echo "$(date -Iseconds) $*" | tee -a "$LOG_FILE"
}
on_error() {
local exit_code=$?
local line_no=$1
log "ERREUR : le script a échoué à la ligne $line_no avec le code de sortie $exit_code"
# envoyez une alerte Slack/PagerDuty ici, plutôt que d'espérer que quelqu'un lise le log
exit "$exit_code"
}
trap 'on_error $LINENO' ERR
log "Démarrage du run ETL"
# ... le vrai travail se passe ici ...
log "Run ETL terminé avec succès"trap ... ERR se déclenche dès qu'une commande échoue (avec -e actif), et $LINENO vous indique exactement où. Cet unique ajout transforme un « le job s'est arrêté quelque part il y a trois semaines » en « le job a échoué à la ligne 42 ce matin à 3h14, et voici l'alerte qui s'est déclenchée à l'instant même ».
Ce sont six habitudes, appliquées à chaque fois. La différence, c'est d'apprendre une panne par une alerte de supervision quelques minutes après qu'elle survient, ou par quelqu'un d'autre plusieurs semaines plus tard.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez