Dans le cadre du défi “100 jours de DevOps” de la plateforme KodeKloud, une des tâches fournies consistait à installer cronie sur trois serveurs et à créer la même tâche cron pour l’utilisateur root :

The Nautilus system admins team has prepared scripts to automate several day-to-day tasks. They want them to be deployed on all app servers in Stratos DC on a set schedule. Before that they need to test similar functionality with a sample cron job. Therefore, perform the steps below:
a. Install cronie package on all Nautilus app servers and start crond service.
b. Add a cron */5 * * * * echo hello > /tmp/cron_text for root user.

À la lecture de l’énoncé j’entrevoyais déjà la procédure attendue, manuelle et répétitive :

  • récupérer les identifiants du premier serveur et s’y connecter en SSH
  • installer le paquet cronie
  • vérifier l’état du service
  • éditer le crontab root
  • déconnexion et répétition avec les deux serveurs suivants.

J’ai une horreur viscérale de ce genre de répétitions robotiques sans intérêt ; ça fait crier quelque chose au fond de moi : “Je ne suis pas une machine !” Car c’est à la machine que revient d’effectuer ce genre de tâches : le mot informatique n’est-il pas la contraction de information automatique après tout ?

Ce qui m’intéresse dans l’approche DevOps c’est exactement ça : mettre un terme à cette manière manuelle et fastidieuse de procéder, et parachever enfin la raison d’être de l’informatique. On veut de l’industrialisation, de l’automatisation, de l’Infra-as-Code enfin !


J’ai donc commencé par envisager mes alternatives :

  • installer Ansible et créer un playbook : overkill pour un lab unique, entre l’installation et l’écriture ça aurait introduit une complexité inutile à ce stade ; je suis sûr que je dois pouvoir faire cela avec les outils à ma disposition.
  • mon petit chouchou, GNU parallel : pas installé dans l’environnement du lab, suppose une connexion par clé SSH pour se passer de mot de passe.
  • reste le script Bash traditionnel.

J’ai commencé par établir la procédure opératoire précise pour la traduire en commandes impératives :

  • pour se connecter à chaque serveur : ssh user1@server1, acceptation de la clé d’hôte puis entrée du mot de passe de l’utilisateur

  • exécution des trois commandes pour répondre à l’énoncé :

    sudo dnf install -y cronie
    sudo systemctl start crond # pas nécessaire car le service est démarré automatiquement
    echo "*/5 * * * * echo hello > /tmp/cron_text" | sudo tee -a /etc/crontab
    

Pour automatiser cela :

  1. Je peux utiliser l’options -o StrictHostKeyChecking=no pour contourner l’acceptation de la clé d’hôte
  2. Je peux recourir à sshpass pour fournir le mot de passe de manière non-interactive (par miracle il est installé sur la machine du lab)
  3. Une petite recherche m’apprend que sudo accepte l’option -S, --stdin pour “lire le mot de passe depuis l’entrée standard”.

Tout ceci mis ensemble donne pour le premier serveur :

sshpass -p Ir0nM@n ssh -o StrictHostKeyChecking=no tony@stapp01 \
    'sudo -S dnf install -y cronie <<< "Ir0nM@n" &&\
     echo "*/5 * * * * echo hello > /tmp/cron_text" | sudo tee -a /etc/crontab'

Ou sur une seule ligne :

sshpass -p Ir0nM@n ssh -o StrictHostKeyChecking=no tony@stapp01 'sudo -S dnf install -y cronie <<< "Ir0nM@n" && echo "*/5 * * * * echo hello > /tmp/cron_text" | sudo tee -a /etc/crontab'

Ok, c’est un peu hacky mais à ma bonne surprise ça fonctionne !

La deuxième étape a été d’extraire les parties communes dans des variables :

SSH_OPTIONS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR'
COMMAND='sudo -S dnf install -y cronie <<< $PASSWORD && echo "*/5 * * * * echo hello > /tmp/cron_text" | sudo tee -a /etc/crontab'

sshpass -p Ir0nM@n ssh $SSH_OPTIONS tony@stapp01 $COMMAND
sshpass -p Am3ric@ ssh $SSH_OPTIONS steve@stapp02 $COMMAND
sshpass -p BigGr33n ssh $SSH_OPTIONS banner@stapp03 $COMMAND

Restait à intégrer le mot de passe comme variable, utilisée à la fois pour se connecter en SSH et pour le premier sudo une fois connecté. Le petit défi est :

  1. de faire se correspondre le binôme utilisateur@machine avec le bon mot de passe, et de les changer à chaque itération
  2. de passer le mot de passe à l’intérieur de la variable contenant la commande…

À ce stade je voyais bien qu’il ne me manquait plus qu’un petit effort pour parvenir à automatiser cette procédure et qu’elle fonctionne sur les trois serveurs.

Mon intuition d’une boucle for ne convient pas : même en en imbriquant plusieurs les différents paramètres associés ne se conjugueraient pas correctement.

Attendez un instant, “paramètres associés”…Associatif… Comme un tableau associatif ?

Voilà, c’est ça qu’il me faut : établir un tableau associatif ‘utilisateur-serveur-mot de passe’ et itérer dessus pour exécuter les commandes !

N’utilisant pas souvent cette forme de variable dans Bash, c’était le moment pour moi de recourir à un LLM pour accélérer la conception du script. Je vous laisse admirer le résultat :

SSH_OPTIONS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR'

declare -A HOSTS=(
  ["tony@stapp01"]="Ir0nM@n"
  ["steve@stapp02"]="Am3ric@"
  ["banner@stapp03"]="BigGr33n"
)

for USERHOST in "${!HOSTS[@]}"; do
  PASSWORD="${HOSTS[$USERHOST]}"
  COMMAND="echo '$PASSWORD' | sudo -S dnf install -y cronie &&
           sudo systemctl enable --now crond &&
           echo '*/5 * * * * root echo hello > /tmp/cron_text' | sudo tee -a /etc/crontab"
  echo "→ Deploying to $USERHOST..."
  sshpass -p "$PASSWORD" ssh $SSH_OPTIONS "$USERHOST" "$COMMAND"
done

Malin : c’est à l’intérieur de l’itération que se fait l’affection de la variable PASSWORD, en adéquation avec le binôme utilisateur-machine sur lequel on est actuellement, et ensuite la variable COMMAND intègre le mot de passe variabilisé en inversant l’utilisation des guillemets simples et doubles. Du propre dont j’apprends beaucoup !

Voilà, c’est ici que s’achève ce récit d’automatisation. On termine avec quelques titres accrocheurs suggérés par ce même LLM :

Fini le copier-coller et le SSH manuel, on peut tout à fait orchestrer plusieurs serveurs sans Ansible ni configuration complexe !

Pourquoi taper la même suite de commandes X fois quand Bash peut le faire pour vous ?

Apprenez à vous servir de la paresse comme d’un levier de développement !

😁

Happy hacking!