Administration système – Automatisation

Administration système – Automatisation

5 juillet 2019 Admin système Non classé Support 0

Pour déployer automatiquement des services, les admins systèmes ont toujours utilisés “les scripts”, c’est à dire, une suite de commandes systèmes natives, permettant l’enchaînement d’opérations désirées.
Cela nécessite une culture, des recherches, de mettre en place des tests de bonne exécution, etc…
Est arrivé Ansible, que j’ai étudié dans ses grandes lignes car “imposé” maintenant dans beaucoup de structures informatiques. Pourquoi ????
Apparemment et d’après la lecture de nombreux posts, cela fonctionne. Ansible permet l’industrialisation, une fois écrit, un Playbook pourra être exécuté par Ansible sur n’importe quel serveur et à destination de plusieurs serveurs au moyen d’une communication SSH.
Ansible permet peut-être la réalisation de beaucoup d’autres choses ???
Dans tous les cas, je reste très insatisfait dans son utilisation : nécessité d’apprendre la structure Ansible et les commandes Ansible, nécessité d’enregistrer le contenu des playbooks “Ansible” dans un fichier au format Yaml. Beaucoup de choses pour à chaque fois, éviter l’utilisation des commandes natives, à mon avis, plus parlante pour un admin système.
Je vais reprendre l’exemple disponible à la page : https://www.ansible.com/blog/getting-started-writing-your-first-playbook
Cette page explique comment fabriquer votre premier playbook Ansible et concerne l’installation d’un service nginx, comprenant la mise en place d’un fichier index.html spécifique. Il s’agit par conséquent d’écrire ceci :

---
- name: Install nginx
  hosts: host.name.ip
  become: true

  tasks:
  - name: Add epel-release repo
    yum:
      name: epel-release
      state: present

  - name: Install nginx
    yum:
      name: nginx
      state: present

  - name: Insert Index Page
    template:
      src: index.html
      dest: /usr/share/nginx/html/index.html

  - name: Start NGiNX
    service:
      name: nginx
      state: started

Dans cet exemple, on peut voir la commande “yum”…Ok et en déduire que la commande yum sera utilisée, mais comment ?
La section epl-release indique que l’on va utiliser le repository software : epl-release
La section suivante installera le service “nginx” comment ? avec la commande yum –enablerepo=epel-release install nginx
On voit tout de suite ici qu’il y a un problème, enfin pour moi… Si tu es admin système, tu sais comment installer nginx dans ce cas précis :
“yum –enablerepo=epel-release install nginx” et je vais devoir écrire au format Yaml :

 tasks:
  - name: Add epel-release repo
    yum:
      name: epel-release
      state: present

  - name: Install nginx
    yum:
      name: nginx
      state: present

Si tu n’est pas admin système, tu ne peux ni exécuter les commandes natives, ni écrire un playbook, puisque tu ne connais pas cet environnement. Par contre si ton chef te demande d’exécuter un Playbook tout le monde sait le faire. Mais qui est chargé d’écrire les playbook ? L’admin système qui va devoir jongler avec les espaces interminables (les tabulations sont interdites en Yaml), lire la documentation Ansible en parallèle pour récupérer un mot clé, fabriquer une condition etc… Alors que l’admin système sait très bien fabriquer un shell d’enchaînement de commandes.

Bref si quelqu’un peut m’expliquer, l’utilité de ce genre d’appareillage dans une infrastructure ? Merci de laisser un commentaire.

Cela n’empêche que l’on va devoir automatiser, et pour réaliser cette opération, on a besoin d’un admin système, de ses connaissances, d’un espace de stockage pour y déposer l’information qu’il va créer et la capitaliser, en vue de la réutiliser.

Je veux utiliser des commandes natives, vérifier que chaque commande ait été exécutée et correctement, minimiser la future structure de “Capitalisation” en vue de sa réutilisation.

Je vous propose de réfléchir sur un système, très proche des commandes systèmes et d’essayer de fabriquer une solution alternative à Ansible pour les petites structures.
A ce stade de la rédaction, je ne sais pas si les contraintes imposées permettront de réaliser ce type d’implémentation. J’ai, jusqu’à maintenant, toujours créé et utilisé des shell pour l’automatisation. Que je stocke dans un répertoire, permettant la réutilisation. Le problème avec cette organisation est la répétition du code, à cause des tests effectués après chaque commande, etc.
Essayons de construire un système basé sur les commandes natives… Et réalisons quelques cas d’utilisations.

Ce que je souhaite

  • Exécuter des commandes systèmes explicites, sur le serveur local ou sur un/des serveur(s) distant(s) au travers d’une communication SSH (authentification par échange de clés)
  • Ajouter des commentaires (important)
  • Copier des fichiers (cp pour le local, scp pour les serveurs distant via SSH, pour simplifier j’utiliserai toujours la commande scp, dont le comportement est similaire à la commande cp pour une utilisation locale)
  • Effectuer des remplacements de chaînes dans un fichier (sed ou awk)
  • Permettre la capitalisation de ces enchaînements de commandes (espace de stockage, organisation, métadonnées)
  • ….

Traduction du script Ansible

Repartir du script de présentation Ansible initial et le transformer d’une manière très basique en instructions shell ( dans mon cadre, pour DEBIAN) donne l’équivalent de ce qu’on appelle un shell (fichier nommé “script”):

apt update
apt install nginx
scp index.html /usr/share/nginx/html/index.html
systemctl start nginx

Chacune des commandes indiquées vont s’enchaîner sans jamais savoir si la précédente s’est bien terminée, et bien évidemment, ce shell ne fonctionnera pas.
Par contre, ce bout de code, qui décrit l’ensemble des opérations nécessaires à l’exécution de la tâche “installation du service nginx” me semble déjà beaucoup plus explicite et ne nécessite, à priori, pas de commentaires.

Commentaires

Mais ajoutons quelques commentaires (pour vos collègues)

# Mise à jour de la liste des applications DEBIAN disponible
apt update
# Installation du service nginx
apt install nginx 
# copie du fichier interne installé par défaut 
# sur toutes les nouvelles installation de nginx afin de vérifier par automate qu'il fonctionne 
scp index.html /usr/share/nginx/html/index.html
# Démarrage du service nginx 
systemctl start nginx

Construction d’un interpréteur

Je vais en tout premier lieu fabriquer un programme qui va lire le contenu du fichier “script” et exécuter les commandes indiquées, une à une, en vérifiant que chacune des commandes ait été correctement exécutée.

Récupérer la liste des commandes dans l’ordre et exécution

Le POC se fera tout simplement avec le langage bash. Le nom du script : “execute.sh

#!/bin/bash
while IFS= read -r line; do
        #Ne traite pas les commentaires (démarre par space+++# ou bien #
        # Comparaison avec l'utilisation des expressions régulières
        if [[ $line =~ ^[[:space:]]*# ]];then
            continue
        fi
        #Execution de la commande
        $line
done < ./script

Enregistrez le fichier, puis : chmod 755 execute.sh qui permet de rendre le shell exécutable, et exécution du shell : ./execute.sh
OOps les commandes s’exécutent, une à une… Comme j’utilise un compte “non admin”, j’obtiens inévitablement une erreur !!!

Introduire un test de bon exécution†

#!/bin/bash
while IFS= read -r line; do
    #Ne traite pas les commentaires (démarre par space+++# ou bien #
    if [[ $line =~ ^[[:space:]]*# ]];then
        continue
    fi
    # Execution de la commande
    $line
    # Récupération code Erreur
    ERR=$?
    if [ ! "$ERR" = "0" ];then
        echo ""
        echo "---------------"
        echo "[**ERR] - Une erreur s'est produite  - [Commande :$line - code erreur: $ERR]"
        echo "---------------"
        echo ""
        # Arrêt du processus, code de sortie = dernier code Erreur intercépté
        exit $ERR
    fi
done < ./script

Enregistrer, puis exécuter le shell : ./execute.sh
Le script s’arrête maintenant à la première erreur rencontrée. Pour exécuter ce type d’opération, vous devez disposer des privilièges “root”. Vous pouvez donc exécuter l’opération en préfixant la commande par : “sudo” et paramétrer votre compte pour ce type d’utilisation, ou bien vous connectez “root”.

Exécution en mode “root”

Connexion root à la console, ou bien utilisation de la commande “sudo” (nécessite un paramétrage système pour votre compte)

Need to get 1,588 kB of archives.
After this operation, 2,865 kB of additional disk space will be used.
Do you want to continue? [Y/n] Abort.
[**ERR] – Une erreur s’est produite – [Commande :apt install nginx – code erreur: 1]

Une erreur se produit car la commande apt attend une réponse utilisateur : Y | N pour respectivement “Yes”, “No”. Je souhaite installer quoiqu’il arrive, je vais donc indiquer le paramètre “-y” à la commande “apt install nginx” dans le fichier script

# Mise à jour du contenu des dépôts DEBIAN pour le système
apt update
# Installation du service nginx
apt -y install nginx 
# copie du fichier interne installé par défaut sur toutes les nouvelles installation de nginx afin de vérifier par automate qu'il fonctionne 
scp index.html /usr/share/nginx/html/index.html
# Démarrage du service nginx 
systemctl start nginx

Enregistrer, puis exécuter le shell : ./execute.sh


Setting up nginx-full (1.10.3-1+deb9u2) …
Setting up nginx (1.10.3-1+deb9u2) …
cp: cannot stat ‘index.html’: No such file or directory
[**ERR] – Une erreur s’est produite – [Commande :cp index.html /usr/share/nginx – code erreur: 1]

Le script demande de copier un fichier index.html vers la destination /usr/share/nginx/html/index.html, mais ce fichier n’existe pas. Je vais donc prévoir et dans l’esprit “Package” un répertoire pour les ressources et permettre ainsi de conserver le contexte d’exécution de cet automate. On pourrais imaginer une structure tel que :

--[Nom du projet d'automatisation]
  file : script
  Directory : ressources
              file : ressource1.txt
              file : ressource2.txt
              file : ....

mais je ne vais pas changer le contenu du fichier “script” qui restera : scp index.html /usr/share/nginx/html/index.html et considérer que toutes les sources à copier sans préfixe (../, ./test/, /root/ etc) sont disponibles dans le répertoire “ressources” du projet. Vous êtes admin système, vous savez ce que vous réaliser, vous connaissez donc explicitement la destination du fichier sur l’hôte.
mkdir ressources
touch ressources/index.html
On a donc maintenant un fichier vide à copier

Modification du répertoire de travail de “execute.sh”

#!/bin/bash
# Répertoire des ressources
RESSOURCESDIR="./ressources"
# Nom du script d'automatisation à exécuter
SCRIPT="script"
#Déplacement dans le répertoire de travail "ressources" si existe
if [ -d "$RESSOURCESDIR" ];then
    SCRIPT="../$SCRIPT"
    cd "$RESSOURCESDIR"
else
    SCRIPT="./$SCRIPT"
fi
while IFS= read -r line; do
    #Ne traite pas les commentaires (démarre par space+++# ou bien #
    if [[ $line =~ ^[[:space:]]*# ]];then
        continue
    fi
    # Execution de la commande
    $line
    # Récupération code Erreur
    ERR=$?
    if [ ! "$ERR" = "0" ];then
        echo ""
        echo "---------------"         echo "[*ERR] - Une erreur s'est produite  - [Commande :$line - code erreur: $ERR]"
        echo "---------------"
        echo ""
        # Arrêt du processus, code de sortie = dernier code Erreur intercepté
        exit $ERR
    fi
done < $SCRIPT

Avertissement de fin d’exécution

J’ajoute une petit blabla à la fin de “execute.sh“, pour indiquer la fin et bonne exécution du script d’automatisation.

echo ""
echo "---------------"
echo "[*FINOK] - Le script d'automatisation s'est correctement exécuté"
echo "---------------"
echo ""
exit 0

Enregistrer, puis exécuter le shell : ./execute.sh
./execute.sh
Résultat obtenu :

[*FINOK] - Le script d'automatisation s'est correctement exécuté

Premier constat

Avec un script basique écrit en bash, et une liste de tâches à exécuter sur un serveur local, la méthode fonctionne parfaitement. Vous remarquerez aussi que la commande scp se comporte comme la commande “cp” lors d’une exécution locale. Je peux donc automatiser les tâches simples pour mon environnement local sans devoir me “taper” un fichier yaml de 25 lignes.

Exécution sur un hôte distant

ici on peut imaginer deux méthodes :

  • Passage de la liste des hosts en paramètres à la commande execute.sh
  • ou bien passage en paramètre à la commande execute.sh d’un fichier contenant la liste des serveurs à traiter.

On remarque que dans ces deux cas il faudra indiquer un ou plusieurs paramètres.

Passage d’un ou plusieurs hôtes en paramètres

Passer un hôte en paramètre revient à exécuter cette commande : execute.sh [nom hôte ou adresse IP] [nom hôte ou adresse IP]
Du fait de l’utilisation de ssh, il vous faudra copier votre clé ~/.ssh/id_rsa.pub sur l’hôte distant avant de tenter une connexion ssh automatique. Si cette clé n’existe pas utiliser la commande ssh-keygen pour générer la paire cles privé/publique associée à votre compte utilisateur. Considérons que la clé est installé et que la connexion SSH ne demande pas de mot de passe. Dans cet exemple, l’hôte distant pour moi est l’adresse IP : 192.168.1.56
Exécuter la suite d’opération de manière automatisée va nécessiter de se connecter, par conséquent je vais adapter le shell execute.sh

Détection des paramètres hôte dans “execute.sh”

Je vais considérer qu’il y a toujours au moins un hôte dans les paramètres. Si la commande ne dispose pas de paramètre, alors l’hôte sera le serveur local soit “local”. Si je veux traiter plusieurs hôtes dont le serveur local, j’ajouterai “local” à la liste des serveurs. Sans paramètre le serveur local sera le seul serveur traité.

#Hôtes à traiter
HOSTS=""
#Detection des hosts
for PARAMHOST in "$@"
do
    HOSTS="$HOSTS $PARAMHOST"
done
#Aucun paramètre pas de host donc traitement local
if [ "$HOST" == '' ];then
    HOST="local"
fi

Après ce traitement j’aurai accès à la liste des hôtes à traiter. Il suffit d’itérer cette liste et d’exécuter les commandes à destination sur l’hôte. J’ajoute au passage quelques contrôles supplémentaires :

#!/bin/bash
PROCESSERRFILE=dirname $0
PROCESSERRFILE=$PROCESSERRFILE/log-install.log
touch $PROCESSERRFILE
#Répertoire des ressources
RESSOURCESDIR="./ressources"
#Paramétres de commande SSH
PARAMSSH="-o PasswordAuthentication=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no"
SSHCMD="ssh $PARAMSSH"
SCPCMD="scp $PARAMSSH"
#Nom du script d'automatisation à exécuter
SCRIPT="script"
#Hôtes à traiter
HOSTS=""
# Détection des hosts
for PARAMHOST in "$@"
do
    HOSTS="$HOSTS $PARAMHOST"
done
# Aucun paramètre pas de host donc traitement local
if [ "$HOSTS" == '' ];then
    HOSTS="local"
fi
# Erreurs détectées
ERREURS=""
# Déplacement dans le répertoire de travail "ressources" si existe
if [ -d "$RESSOURCESDIR" ];then
    SCRIPT="../$SCRIPT"
    cd "$RESSOURCESDIR"
else
    SCRIPT="./$SCRIPT"
fi
for SERVER in $HOSTS;
do
ERREURSERVER=""
AUTOMATE=()
while IFS= read -r LINE; do
        AUTOMATE+=("$LINE")
done < $SCRIPT
for LINE in "${AUTOMATE[@]}"
do
    #Ne traite pas les commentaires (démarrés par space+++# ou bien #
    if [[ $LINE =~ ^[[:space:]]# ]];then
         continue
    fi
    # Execution de la commande pour chacun des hosts - trim de la commande
    CMD=echo $LINE|xargs
    if [ $SERVER" != "local" ];then
       CMD="$SSHCMD root@$SERVER $CMD"
    fi
    # Altération de la commande scp pour ajouter l'hôte final
    if [[ $LINE =~ ^scp ]];then
        if [ "$SERVER" != "local" ];then
                CMD=""
                I=0
                for word in $LINE;
                do
                    # substitution de la commande scp par la commande interne
                    if [ $I -eq 0 ];then
                        CMD="$SCPCMD"
                    else
                        #Ajout des infos hosts dans la destination
                        if [ $I -gt 1 ];then
                            # exception si déjà mentionné
                            if [[ ! $word =~ @ ]];then
                                CMD="$CMD root@$SERVER:$word"
                            fi
                        else
                            CMD="$CMD $word"
                        fi
                fi
                I=$(($I+1))
                done
         fi
     fi
     echo "  [Exec : $LINE - CMD : $CMD]"
     $CMD
     # Récupération code Erreur
     ERR=$?
     if [ "$ERR" != "0" ];then
         echo ""
         echo "---------------"
         echo "[ERR] - Une erreur s'est produite  - [Commande :$LINE - code erreur: $ERR]"
         echo "     Commande réellement exécutée : $CMD"
         echo "---------------" 
         echo ""
         # Arrêt du processus, code de sortie = dernier code Erreur intercepté
         ERREUR="$ERREUR###$SERVER###$LINE###$CMD###$ERR"
         ERREURSERVER=$ERR
         break
   fi
done
if [ "$ERREURSERVER" = "" ];then
echo "" 
echo "---------------" echo "[FINOK] - Le script d'automatisation s'est correctement exécuté pour : $SERVER"
echo "---------------"
echo ""
fi
done
if [ "$ERREUR" != "" ];then
    echo "Erreurs detéctées : $ERREUR"
fi
exit 0

Et toujours à partir du fichier script, j’exécute execute.sh en fournissant une liste de plusieurs serveurs. Les opérations se déroulent normalement.

Conclusion

Cela fait trop longtemps que j’entends : “On ne va pas réinventer la roue…”. A force de ne pas réinventer la roue, on obtient ce résultat : Les offres d’emploi pullulent de “expérience Ansible nécessaire”, ou autre techno”. Parce que l’IT d’outre Manche à transformé les admins systèmes en consommateurs de technos, pour toujours réduire les coûts, et s’approprier de-facto le savoir. J’ai entendu un admin système dire qu’il ne se posait même plus la question de connaître le contenu des Playbook, il les passe, point. Mais surtout qu’il est incapable de reproduire toutes ses étapes en ligne de commande, tellement la lecture de ses Playbook est abstraite.
Effectivement il est inutile de réinventer les fondamentaux mais dans ce cas précis, si les Google, Amazon, … s’en sortent avec Ansible très bien pour eux, mais n’oublions pas que ce sont les seuls au monde à supporter des charges qui ne seront jamais atteintes ailleurs et les moyens financiers de s’offrir une multitude d’équipes pour réaliser les tâches.
Alors pourquoi se faire ch… à écrire des blocs en Yaml (même si je sais paramétrer mon vi pour faire 4 espaces, en lieu d’une tabulation). Le boulot d’admin système est un boulot d’innovation, d’adaptation à la structure pour laquelle on travaille (on n’utilise pas une masse pour rentrer un clou) et ***SURTOUT*** cela rend tout de suite le boulot beaucoup plus intéressant. Beaucoup de sociétés aujourd’hui tentent l’industrialisation en faisant confiance aux technos et non plus aux individus susceptibles de les créer. Les DSI doivent savoir rendre le travail des équipes, agréable. Utiliser Bash, Python, Javascript, Java, Perl, PHP, peu importe, seul le résultat attendu compte.
Un autre exemple de “réinvention de roue” : Prometheus, le monitoring développé par Soundcloud. L’équipe aurait pu se contenter d’améliorer Nagios, maître dans ce secteur…

Aller plus loin

Ces premières lignes de code en Bash sont à l’origine du projet “Mytinydc-automation” (Cliquez ici pour accéder au Gitlab).

Licence de ce documentCreative Commons(CC BY-NC-ND 4.0)
CETTE DOCUMENTATION EST LIVRÉE “EN L’ÉTAT”, SANS GARANTIE D’AUCUNE SORTE ET DISTRIBUÉE DANS UN BUT ÉDUCATIF EXCLUSIVEMENT. L’AUTEUR, CONTRIBUTEURS DE CETTE DOCUMENTATION OU ©MYTINYDC.COM NE SAURAIENT EN AUCUN CAS ÊTRE TENUS RESPONSABLES DES DOMMAGES DIRECTS OU INDIRECTS POUVANT RÉSULTER DE L’APPLICATION DES PROCÉDURES MISES EN ŒUVRE DANS CETTE DOCUMENTATION, OU DE LA MAUVAISE INTERPRÉTATION DE CE DOCUMENT.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

− 4 = 4

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.