Journal Test de vie et Ansible : un exemple de réalisation pour mieux comprendre l'outil

Posté par  . Licence CC By‑SA.
Étiquettes :
29
23
mar.
2022

Sommaire

Ansible est un outil fantastique, que l'éditeur (Red Hat) et sa communauté - présente comme "radicalement simple" ("Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain.". C'est vrai par son approche descriptive d'un état désiré ("as code" : on décrit un état désiré et non des opérations à effectuer), par la généralisation des "livres de recettes" (playbook) et autres rôles qui sont réutilisables (presque) sans restriction.

Mais ce n'est pas seulement un outil de déploiement : sa généricité va au-delà de la volonté de ne pas réinventer la roue pour une action d'installation.

Les tests de vie - en résumé, savoir si un service est opérationnel ou pas -, rentre parfaitement dans ses cordes. Ce sont souvent les indicateurs avancés pour l'état de santé d'une infra : niveau de consommation des ressources (CPU, RAM, disques), si un processus est exécuté ou non, etc.

Ce sont aussi des besoins différents suivant notre casquette, particulièrement en entreprise où les rôles sont bien séparés :

  • comme responsable d'infra, nous allons privilégié le bon état des machines ;
  • comme responsable de dév celle des processus ;
  • comme responsable d'affaire celle des applications.

La différence est parfois subtile mais c'est bien cet ensemble qui compte. Imaginez par exemple que vous mettez en ligne un site Wordpress personnel : votre premier souhait est de savoir si le site est bien accessible. Vous êtes en qualité ici de "responsable d'affaire" : vos "clients" (visiteurs) peuvent-ils y accéder ? Peut-être que oui, peut-être que non ; avec quelle performance pour les pages appelées… peu importe que vous soyez salarié, bénévole, amateur. La position critique est la même.

S'il n'est pas accessible ou difficilement (délai, erreur), pourquoi cette situation ? Un test sur une URL n'est pas suffisante : est-ce le Nginx frontal, est-ce PHP-FPM, est-ce la base de données, est-ce un parefeu ou tout autre chose… ? Est-ce votre développement ou votre exploitation qui pêche ? La machine-même, est-elle accessible ; est-elle suffisamment dimensionnée ?

Si vous travaillez en direct sur cette dernière, vous allez parcourir chaque point : c'est long, laborieux, non-satisfaisant pour plus d'une ou deux machines. On voit alors l'intérêt d'Ansible pour automatiser ces tâches et les jouer avec régulièrement via AWX - Ansible Tower communautaire -, ou via une simple tâche CRON.

C'est cet exercice mental que je vous propose, comme prétexte pour servir de support afin de comprendre / approfondir certains aspects d'Ansible sans se perdre.

Préparation de notre dossier de travail

Il est de bon goût d'avoir un environnement virtuel Python pour travailler en démo / dév / test. Ansible est en Python, alors ne cherchons pas d'excuse:

julien@julien-Vostro-7580:~/Developpement$ virtualenv ansible-test-vie 
created virtual environment CPython3.8.10.final.0-64 in 75ms
  creator CPython3Posix(dest=/home/julien/Developpement/ansible-test-vie , clear=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/julien/.local/share/virtualenv)
    added seed packages: pip==20.1.1, pkg_resources==0.0.0, setuptools==44.0.0, wheel==0.34.2
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
julien@julien-Vostro-7580:~/Developpement$ cd ansible-test-vie 
julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ source ./bin/activate
(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ python3 -m pip install ansible
Collecting ansible
  Downloading ansible-4.8.0.tar.gz (36.1 MB)
     |████████████████████████████████| 36.1 MB 459 kB/s 
Collecting ansible-core<2.12,>=2.11.6
  Downloading ansible-core-2.11.6.tar.gz (7.0 MB)
     |████████████████████████████████| 7.0 MB 657 kB/s 
Collecting PyYAML
  Downloading PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (701 kB)
     |████████████████████████████████| 701 kB 597 kB/s 
Collecting cryptography
  Downloading cryptography-35.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.7 MB)
     |████████████████████████████████| 3.7 MB 775 kB/s 
Collecting jinja2
  Downloading Jinja2-3.0.3-py3-none-any.whl (133 kB)
     |████████████████████████████████| 133 kB 800 kB/s 
Collecting packaging
  Downloading packaging-21.2-py3-none-any.whl (40 kB)
     |████████████████████████████████| 40 kB 863 kB/s 
Collecting resolvelib<0.6.0,>=0.5.3
  Downloading resolvelib-0.5.4-py2.py3-none-any.whl (12 kB)
Collecting cffi>=1.12
  Downloading cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (446 kB)
     |████████████████████████████████| 446 kB 330 kB/s 
Collecting MarkupSafe>=2.0
  Using cached MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl (30 kB)
Collecting pyparsing<3,>=2.0.2
  Using cached pyparsing-2.4.7-py2.py3-none-any.whl (67 kB)
Collecting pycparser
  Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
     |████████████████████████████████| 118 kB 677 kB/s 
Building wheels for collected packages: ansible, ansible-core
  Building wheel for ansible (setup.py) ... done
  Created wheel for ansible: filename=ansible-4.8.0-py3-none-any.whl size=59454442 sha256=ac2be274d30521793513160041ae9cf88d06283060672d85dc62071fffb97e8f
  Stored in directory: /home/julien/.cache/pip/wheels/3d/db/ed/6c8b0ffe3008371db04f2098374dfa21b22823bd7bf30fe5b1
  Building wheel for ansible-core (setup.py) ... done
  Created wheel for ansible-core: filename=ansible_core-2.11.6-py3-none-any.whl size=1959295 sha256=0ee112235f0eb31fcf398b43042c6d6c46abe65835d0d552b96857dbe1326786
  Stored in directory: /home/julien/.cache/pip/wheels/11/e3/41/39946a5f418fe614f955a9ba4de0edee55a209f46a53089846
Successfully built ansible ansible-core
Installing collected packages: PyYAML, pycparser, cffi, cryptography, MarkupSafe, jinja2, pyparsing, packaging, resolvelib, ansible-core, ansible
Successfully installed MarkupSafe-2.0.1 PyYAML-6.0 ansible-4.8.0 ansible-core-2.11.6 cffi-1.15.0 cryptography-35.0.0 jinja2-3.0.3 packaging-21.2 pycparser-2.21 pyparsing-2.4.7 resolvelib-0.5.4
(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ subl . 
(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$

Notre poste et notre éditeur de texte préférés sont prêts ! Vous pouvez créé avec VirtualBox, un Windows Server quelconque, qui sera notre machine cible. Vous comprendrez tout à l'heure pourquoi j'ai utilisé des machines Windows dans cette article.

"Gather facts"

C'est ce qui frappe la première fois qu'on lance l'outil. Une ligne apparaît, non souhaitée, et de prime abord on tend à l'oublier : "TASK [Gathering Facts]…". C'est pourtant le coeur nucléaire du système.

Le recueil des faits en bon français, est une opération qui est exécutée par défaut lorsque vous lançez Ansible en mode playbook - sauf à spécifier le contraire donc. C'est notre premier appui à l'exercice. Réalisation d'un livre de recettes rapide :

lancement.yml

- name: Tester l'activité de mes applicatifs 
  gather_facts: yes 
  hosts: all 

Avec un inventaire tout aussi rapide (n'oubliez cependant pas, de créer une VM configurée avant…) :

inventaire.yml

all:
  hosts: 
    WIN-AD: 
      ansible_host: 192.168.56.3 
  vars: 
    ansible_user: "Administrateur"
    ansible_password: "Azerty01"
    ansible_connection: winrm
    ansible_port: 5985
    ansible_winrm_transport: ntlm

Lancez-le :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement.yml 

PLAY [Tester l'activité de mes applicatifs] ************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************
ok: [WIN-AD]

PLAY RECAP *********************************************************************************************************************************************************************************************************************************
WIN-AD                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Si l'on modifie maintenant le reccueil à "non"…

lancement.yml

- name: Tester l'activité de mes applicatifs 
  gather_facts: no 
  hosts: all 

… , cette ligne habituelle disparaît (et le livre de recette va considérablement plus vite !) :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement.yml 

PLAY [Tester l'activité de mes applicatifs] ************************************************************************************************************************************************************************************************

PLAY RECAP *********************************************************************************************************************************************************************************************************************************

Mais que fait-elle, cette tâche ? Et bien exactement ce que son nom dit : elle recueil des faits sur les machines cibles, des faits stockés dans une variables Ansible liées à l'hôte. Tout simplement, Ansible prend en charge directement ce qui est courant et essentiel de savoir sur celle-ci. Suivant l'OS, ces données peuvent varier en nature et volume (pour Windows, aucune notion de montage par exemple, mais plus d'information sur la jonction à un domaine).

Une étape de débogue vous permettra d'y voir clair :

lancement.yml

- name: Tester l'activité de mes applicatifs 
  gather_facts: yes 
  hosts: all 
  tasks: 
    - debug: msg="{{ ansible_facts }}"  

Voici le résultat (j'ai gardé toutes les lignes pour le côté exhaustif, mais on se fiche des valeurs ici) :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement.yml 

PLAY [Tester l'activité de mes applicatifs] ************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************
ok: [WIN-AD]

TASK [debug] *******************************************************************************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": {
        "architecture": "64 bits",
        "bios_date": "12/01/2006",
        "bios_version": "VirtualBox",
        "date_time": {
            "date": "2021-11-10",
            "day": "10",
            "epoch": "1636532337,09129",
            "hour": "08",
            "iso8601": "2021-11-10T07:18:57Z",
            "iso8601_basic": "20211110T081857091287",
            "iso8601_basic_short": "20211110T081857",
            "iso8601_micro": "2021-11-10T07:18:57.091287Z",
            "minute": "18",
            "month": "11",
            "second": "57",
            "time": "08:18:57",
            "tz": "Romance Standard Time",
            "tz_offset": "+01:00",
            "weekday": "Wednesday",
            "weekday_number": "3",
            "weeknumber": "44",
            "year": "2021"
        },
        "distribution": "Microsoft Windows Server 2016 Standard Evaluation",
        "distribution_major_version": "10",
        "distribution_version": "10.0.14393.0",
        "domain": "",
        "env": {
            "ALLUSERSPROFILE": "C:\\ProgramData",
            "APPDATA": "C:\\Users\\Administrateur\\AppData\\Roaming",
            "COMPUTERNAME": "WIN-EMCILDHJ6MJ",
            "ComSpec": "C:\\Windows\\system32\\cmd.exe",
            "CommonProgramFiles": "C:\\Program Files\\Common Files",
            "CommonProgramFiles(x86)": "C:\\Program Files (x86)\\Common Files",
            "CommonProgramW6432": "C:\\Program Files\\Common Files",
            "HOMEDRIVE": "C:",
            "HOMEPATH": "\\Users\\Administrateur",
            "LOCALAPPDATA": "C:\\Users\\Administrateur\\AppData\\Local",
            "LOGONSERVER": "\\\\WIN-EMCILDHJ6MJ",
            "NUMBER_OF_PROCESSORS": "3",
            "OS": "Windows_NT",
            "PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL",
            "PROCESSOR_ARCHITECTURE": "AMD64",
            "PROCESSOR_IDENTIFIER": "Intel64 Family 6 Model 158 Stepping 10, GenuineIntel",
            "PROCESSOR_LEVEL": "6",
            "PROCESSOR_REVISION": "9e0a",
            "PROMPT": "$P$G",
            "PSExecutionPolicyPreference": "Unrestricted",
            "PSModulePath": "C:\\Users\\Administrateur\\Documents\\WindowsPowerShell\\Modules;C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules",
            "PUBLIC": "C:\\Users\\Public",
            "Path": "C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Users\\Administrateur\\AppData\\Local\\Microsoft\\WindowsApps",
            "ProgramData": "C:\\ProgramData",
            "ProgramFiles": "C:\\Program Files",
            "ProgramFiles(x86)": "C:\\Program Files (x86)",
            "ProgramW6432": "C:\\Program Files",
            "SystemDrive": "C:",
            "SystemRoot": "C:\\Windows",
            "TEMP": "C:\\Users\\ADMINI~1\\AppData\\Local\\Temp",
            "TMP": "C:\\Users\\ADMINI~1\\AppData\\Local\\Temp",
            "USERDOMAIN": "WIN-EMCILDHJ6MJ",
            "USERDOMAIN_ROAMINGPROFILE": "WIN-EMCILDHJ6MJ",
            "USERNAME": "Administrateur",
            "USERPROFILE": "C:\\Users\\Administrateur",
            "windir": "C:\\Windows"
        },
        "fqdn": "WIN-EMCILDHJ6MJ",
        "gather_subset": [
            "all"
        ],
        "hostname": "WIN-EMCILDHJ6MJ",
        "interfaces": [
            {
                "connection_name": "Ethernet",
                "default_gateway": null,
                "dns_domain": null,
                "interface_index": 2,
                "interface_name": "Intel(R) PRO/1000 MT Desktop Adapter",
                "macaddress": "08:00:27:18:B8:1C"
            }
        ],
        "ip_addresses": [
            "192.168.56.3",
            "fe80::c55c:4c4a:f16b:63b"
        ],
        "kernel": "10.0.14393.0",
        "lastboot": "2021-11-10 00:42:18Z",
        "machine_id": "S-1-5-21-160161560-88724368-22562144",
        "memtotal_mb": 4096,
        "module_setup": true,
        "netbios_name": "WIN-EMCILDHJ6MJ",
        "nodename": "WIN-EMCILDHJ6MJ",
        "os_family": "Windows",
        "os_name": "Microsoft Windows Server 2016 Standard Evaluation",
        "os_product_type": "server",
        "owner_contact": "",
        "owner_name": "Utilisateur Windows",
        "powershell_version": 5,
        "processor": [
            "GenuineIntel",
            "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
            "GenuineIntel",
            "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
            "GenuineIntel",
            "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz"
        ],
        "processor_cores": 3,
        "processor_count": 1,
        "processor_threads_per_core": 1,
        "processor_vcpus": 3,
        "product_name": "VirtualBox",
        "product_serial": "0",
        "reboot_pending": null,
        "swaptotal_mb": 0,
        "system": "Win32NT",
        "system_description": "",
        "system_vendor": "innotek GmbH",
        "uptime_seconds": 27400,
        "user_dir": "C:\\Users\\Administrateur",
        "user_gecos": "",
        "user_id": "Administrateur",
        "user_sid": "S-1-5-21-160161560-88724368-22562144-500",
        "virtualization_role": "guest",
        "virtualization_type": "VirtualBox",
        "windows_domain": "WORKGROUP",
        "windows_domain_member": false,
        "windows_domain_role": "Stand-alone server"
    }
}

PLAY RECAP *********************************************************************************************************************************************************************************************************************************
WIN-AD                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Avec cette ligne sur votre console, même sans le débogue, lorsque l'étape de recueil est valide, vous savez déjà plusieurs choses :

  • votre machine est accessible par le réseau déclaré (elle est donc allumée et opérationnelle sur l'accès distant via l'IP déclarée) ;
  • votre inventaire est donc correct, car on a pu s'authentifier ;
  • vous pouvez démarrer un shell, vous avez donc un minimum de droits pour agir…

Car évdemment ces faits ne viennent pas de WinRM ou SSH : ce sont des scripts qui sont joués localement et dont les résultats "remontent" à la machine Ansible. C'est l'intérêt d'avoir ici du Windows : Ansible ne gère pas directement du Python pour l'exécution locale sur la machine cible, mais du Powershell. On comprend bien qu'il y a autre chose que simplement "une connexion" qui est ouverte.

Pour Linux, un interpréteur Python est cherché - car il est généralement disponible en standard sur cet OS (au moins feu Python 2).

Ainsi même un simple "ping", même sans passer par un livre de recettes, suit cette logique. Exemple toujours avec Windows :

  • la machine est contactée, via le port cible de connexion à distance (WinRM dans notre cas) ;
  • Ansible s'authentifie (grâce à NTML dans notre cas) ;
  • Ansible envoie un emballage ("wrapper") pour les modules des tâches en Powershell, puis les différents modules Powershell eux-même, ceux qui sont appelés via des tâches ou en direct dans notre cas ;
  • les scripts sont exécutés localement ;
  • le résultat est récupéré, sérialisé en JSON et renvoyé à Ansible, qui le traite à son tour.

C'est cela, un fonctionnement "sans agent" local : il n'est pas installé, mais il y a bien une exécution local au travers d'un ensemble coordonné. L'absence d'installation d'agent, ne veut pas dire pour autant anarchie ou une bête commmande.

Qu'est-ce qui est effectivement joué ?

Pour bien comprendre que cette distinction entre Shells, modules et connexion est loin d'être mineure, si je tente un "ping" avec le module par défaut vers mon inventaire Windows, cela échoue :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible -i inventaire.yml -m ping all
[WARNING]: No python interpreters found for host WIN-AD (tried ['/usr/bin/python', 'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6',
'/usr/libexec/platform-python', '/usr/bin/python3', 'python'])
WIN-AD | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "module_stderr": "Exception lors de l'appel de « Create » avec « 1 » argument(s) : « Au caractère Ligne:4 : 21\r\n+ def _ansiballz_main():\r\n+                     ~\r\nUne expression est attendue après « ( ».\r\nAu caractère Ligne:13 : 27\r\n+     except (AttributeError, OSError):\r\n+                           ~\r\nArgument manquant dans la liste de paramètres.\r\nAu caractère Ligne:15 : 7\r\n+     if scriptdir is not None:\r\n+       ~\r\nParenthèse ouvrante « ( » manquante après « if » dans l’instruction If.\r\nAu caractère Ligne:22 : 7\r\n+     if sys.version_info < (3,):\r\n+       ~\r\nParenthèse ouvrante « ( » manquante après « if » dans l’instruction If.\r\nAu caractère Ligne:22 : 30\r\n+     if sys.version_info < (3,):\r\n+                              ~\r\nExpression manquante après « , ».\r\nAu caractère Ligne:22 : 25\r\n+     if sys.version_info < (3,):\r\n+                         ~\r\nL’opérateur « < » est réservé à une utilisation future.\r\nAu caractère Ligne:27 : 34\r\n+     def invoke_module(modlib_path, temp_path, json_params):\r\n+                                  ~\r\nArgument manquant dans la liste de paramètres.\r\nAu caractère Ligne:28 : 40\r\n+         z = zipfile.ZipFile(modlib_path, mode='a')\r\n+                                        ~\r\nArgument manquant dans la liste de paramètres.\r\nAu caractère Ligne:31 : 33\r\n+         zinfo = zipfile.ZipInfo()\r\n+                                 ~\r\nUne expression est attendue après « ( ».\r\nAu caractère Ligne:34 : 25\r\n+         z.writestr(zinfo, sitecustomize)\r\n+                         ~\r\nArgument manquant dans la liste de paramètres.\r\nLes erreurs d’analyse n’ont pas toutes été signalées. Corrigez les erreurs signalées, puis recommencez. »\r\nAu caractère Ligne:6 : 1\r\n+ $exec_wrapper = [ScriptBlock]::Create($split_parts[0])\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException\r\n    + FullyQualifiedErrorId : ParseException\r\n \r\nL’expression située après «&» dans un élément de pipeline a généré un objet non valide. Elle doit générer un nom de \r\ncommande, un bloc de script ou un objet CommandInfo.\r\nAu caractère Ligne:7 : 2\r\n+ &$exec_wrapper\r\n+  ~~~~~~~~~~~~~\r\n    + CategoryInfo          : InvalidOperation : (:) [], RuntimeException\r\n    + FullyQualifiedErrorId : BadExpression\r\n ",
    "module_stdout": "",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}

Si par contre je passe par le module "win_ping", celui dédié à l'OS de Microsoft, plus de problème… et plus d'alerte "No python interpreters found for host WIN-AD".

Ce module est d'ailleurs très facile de reproduire localement, ce que nous allons faire, pour bien comprendre ce qui se passe.

Si vous êtes attentif sur le dépôt Git du projet, vous aurez noté que le module win_ping a deux fichiers :

Le fichier Python n'est pas jouée sur la machine cible dans notre cas : lorsqu'un interpréteur Python n'est pas trouvé mais dans le cas présent, il existe l'alternative d'un script pouvant être exécuté sur le shell local, c'est celui-ci qui est privilégié. Simple, pratique. Le script Python pour win_ping n'existe que pour porter la documentation.

Jouons un peu autour de ça :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ mkdir -p plugins/modules
(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ touch plugins/modules/win_ping.ps1
(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ANSIBLE_LIBRARY=./ ansible -i inventaire.yml -m win_ping all
WIN-AD | FAILED! => {
    "msg": "module (win_ping) is missing interpreter line"
}

Cette erreur nous montre bien comment on peut très facilement surcharger un module interne à Ansible, quelque soit la cible. Donne-nous lui du corps :

#!powershell

# j'insère la bibliothèque Ansible Windows 
#AnsibleRequires -CSharpUtil Ansible.Basic

$spec = @{
    options = @{
        data = @{ type = "str"; default = "pong" }
        # "data2" est ajouté, en facultatif ("required" sinon) 
        data2 = @{ type = "str" ; default = "toto" } 
    }
    supports_check_mode = $true
}
# j'instancie mon module - notez que '$args' est fourni par l'emballage 
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

# je récupères des données du modules, celles sérialisées initialement via le contrôle Ansible 
$data = $module.Params.data
$data2 = $module.Params.data2

$module.Result.ping = "$data - $data2" 

# j'indique de manière factice que le système cible a changé (pour la situation d'erreur, utiliser "failed") 
$module.Result.changed = $true 

# je sors, en produisant un JSON valide 
$module.ExitJson()

L'exécution se joue sans erreur et l'orangé indique bien un changement sur le système cible (le vert indiquant une action réussite mais sans nécessairement changement d'état, différent de "skipped" qui est la non-réalisation pour une cause conditionnelle).

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ANSIBLE_LIBRARY=./ ansible -i inventaire.yml -m win_ping all
WIN-AD | CHANGED => {
    "changed": true,
    "ping": "pong - toto"
}

Pour nos tests de vie, nous avons donc une piste sur le 'comment' : l'usage de modules existants (et la liste est longue), comme produire les siens, propres à son métier / à son besoin. Que ce soit directement en Python, soit en Powershell (ou en ce que vous voulez !).

Gérer les faits d'un parc

Nous avons vu deux points essentiels :

  • récupérer automatiquement un grand nombre d'informations, à partir l'équivalent du module par défaut "gather_facts" ;
  • créer les siens et les stocker localement sur la machine Ansible.

Cependant cette histoire de faits récupérés, d'exécution locale, peut s'avérer compliquée au quotidien. Il faut comprendre que la sérialisation des variables et du contexte de la machine Ansible vers la machine cible, et la remontée des résultats, ne veulent pas dire que les machines cibles communiquent directement elles (pas du tout). Elles communiquent exclusivement vers et via la machine Ansible, qui agit comme le contrôleur dans notre cas (ou le client, si l'on se place dans une dénomination réseau).

De la même façon, la portée des faits est particulière : la machine Ansible est appelable comme machine cible, "indifféremment" des autres, via "hosts: localhost". Elle n'a donc pas par défaut l'environnement d'une autre machine cible, avec qui elle ne partage pas des variables autres que celles globales et dans l'instant où elles sont au moment de l'appel.

Illustrons-ça (petite précision sur les noms en commentaire du code) :

lancement2.yml

- name: Tester quelque chose (1) 
  gather_facts: yes 
  hosts: WIN-AD, localhost # attention, là c'est le nom d'inventaire... 
  vars: 
    ma_variable: "toto" 
  tasks: 
    - debug: msg="{{ ma_variable }}"
    - set_fact: 
        ma_variable: "titi"
      when: ansible_hostname == 'WIN-EMCILDHJ6MJ' # et ici c'est le nom d'hôte local (sinon prendre 'inventory_hostname')
    - set_fact: 
        ma_variable: "tata"
      when: ansible_hostname == 'julien-Vostro-7580'
    - debug: msg="{{ ma_variable }}"

L'exécution est sans appel, les deux sont bien divergents :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement2.yml 

PLAY [Tester quelque chose (1)] *******************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]
ok: [WIN-AD]

TASK [debug] **************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": "toto"
}
ok: [localhost] => {
    "msg": "toto"
}

TASK [set_fact] ***********************************************************************************************************************************************************
ok: [WIN-AD]
skipping: [localhost]

TASK [set_fact] ***********************************************************************************************************************************************************
skipping: [WIN-AD]
ok: [localhost]

TASK [debug] **************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": "titi"
}
ok: [localhost] => {
    "msg": "tata"
}

PLAY RECAP ****************************************************************************************************************************************************************
WIN-AD                     : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

C'est-à-dire que suivant l'appel, soit on prend la machine Ansible comme une instance indépendante (c-à-d le contrôleur Ansible), soit comme une machine cible. Pour preuve, si vous relance en mode plus verbeux (-vvv), vous pouvez suivre la connexion à la machine cible "localhost" comme vers celle Windows :

# ( ... ) 
TASK [Gathering Facts] ****************************************************************************************************************************************************
task path: /home/julien/Developpement/ansible-test-vie/lancement2.yml:3
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: julien
<127.0.0.1> EXEC /bin/sh -c 'echo ~julien && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/julien/.ansible/tmp `"&& mkdir /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433 && echo ansible-tmp-1636541247.5407162-99491-187805259433="` echo /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433 `" ) && sleep 0'
Using module file /usr/lib/python3/dist-packages/ansible/modules/windows/setup.ps1
Pipelining is enabled.
<192.168.56.3> ESTABLISH WINRM CONNECTION FOR USER: Administrateur on PORT 5985 TO 192.168.56.3
EXEC (via pipeline wrapper)
Using module file /usr/lib/python3/dist-packages/ansible/modules/system/setup.py
<127.0.0.1> PUT /home/julien/.ansible/tmp/ansible-local-99486k5ok0xvy/tmp0l38mgx1 TO /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433/AnsiballZ_setup.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433/ /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433/AnsiballZ_setup.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python3 /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433/AnsiballZ_setup.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /home/julien/.ansible/tmp/ansible-tmp-1636541247.5407162-99491-187805259433/ > /dev/null 2>&1 && sleep 0'
ok: [localhost]
ok: [WIN-AD]
META: ran handlers

Avoir une remontée d'information vers le contrôleur passe donc par le retour de votre module et de son emballage (son "wrapper", qui est un mécanisme interne à Ansible). Et la machine cible "localhost" n'est pas réellement dans l'inventaire déclaré, mais une sorte d'entrée fantôche, toujours présente. Ne vous y méprenez pas, cette distinction a un impact. Ainsi voyons le passage des modifications entre les différentes recettes ("plays") :

lancement2.yml

- name: Tester quelque chose (1) 
  gather_facts: yes 
  hosts: WIN-AD, localhost 
  vars: 
    mon_inventaire: "toto" 
  tasks: 
    - debug: msg="{{ mon_inventaire }}"
    - set_fact: 
        mon_inventaire: "titi"
      when: ansible_hostname == 'WIN-EMCILDHJ6MJ'
    - set_fact: 
        mon_inventaire: "tata"
      when: ansible_hostname == 'julien-Vostro-7580'
    - debug: msg="{{ mon_inventaire }}" 
- name: Tester quelque chose (2) 
  hosts: localhost 
  gather_facts: yes
  tasks: 
    - with_items: "{{ hostvars }}" 
      debug: msg="{{ hostvars[item]['mon_inventaire'] }}" 

Vous vous attendez à avoir deux entrées de débogue ? Que nenni :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement2.yml 

PLAY [Tester quelque chose (1)] *******************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]
ok: [WIN-AD]

TASK [debug] **************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": "toto"
}
ok: [localhost] => {
    "msg": "toto"
}

TASK [set_fact] ***********************************************************************************************************************************************************
ok: [WIN-AD]
skipping: [localhost]

TASK [set_fact] ***********************************************************************************************************************************************************
skipping: [WIN-AD]
ok: [localhost]

TASK [debug] **************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": "titi"
}
ok: [localhost] => {
    "msg": "tata"
}

PLAY [Tester quelque chose (2)] *******************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]

TASK [debug] **************************************************************************************************************************************************************
ok: [localhost] => (item=WIN-AD) => {
    "msg": "titi"
}

PLAY RECAP ****************************************************************************************************************************************************************
WIN-AD                     : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
localhost                  : ok=6    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Si l'on voulait véritablement considérer la machine locale (celle qui Ansible) comme une autre, il faudrait la déclarer comme les autres :

inventaire.yml

all:
  hosts: 
    WIN-AD: 
      ansible_host: 192.168.56.3 
      ansible_user: "Administrateur"
      ansible_password: "Azerty01"
      ansible_connection: winrm
      ansible_port: 5985
      ansible_winrm_transport: ntlm 
    LINUX-LOCAL: 
      ansible_connection: local  

… Et elle apparaîtrait bien, comme ses modifications de valeur propres. Notez bien ici que j'ai bien spécifié "hosts: localhost" et non "host: LOCAL-LINUX". Notez bien aussi le rapport dans les dernières lignes, qui distingue la connexion locale ("LOCAL-LINUX") de l'hôte générique local ("localhost") :

lancement2.yml

- name: Tester quelque chose (1) 
  gather_facts: yes 
  hosts: WIN-AD, LINUX-LOCAL 
  vars: 
    mon_inventaire: "toto" 
  tasks: 
    - debug: msg="{{ mon_inventaire }}"
    - set_fact: 
        mon_inventaire: "titi"
      when: ansible_hostname == 'WIN-EMCILDHJ6MJ'
    - set_fact: 
        mon_inventaire: "tata"
      when: ansible_hostname == 'julien-Vostro-7580'
    - debug: msg="{{ mon_inventaire }}" 

- name: Tester quelque chose (2) 
  hosts: localhost
  gather_facts: yes
  tasks: 
    - with_items: "{{ hostvars }}" 
      debug: msg="{{ hostvars[item]['mon_inventaire'] }}"

Et voici le résultat :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement2.yml 

PLAY [Tester quelque chose (1)] *******************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
[DEPRECATION WARNING]: Distribution Ubuntu 20.10 on host LINUX-LOCAL should use /usr/bin/python3, but is using /usr/bin/python for backward compatibility with prior 
Ansible releases. A future Ansible release will default to using the discovered platform python for this host. See 
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information. This feature will be removed in version 2.12. Deprecation 
warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
ok: [LINUX-LOCAL]
ok: [WIN-AD]

TASK [debug] **************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": "toto"
}
ok: [LINUX-LOCAL] => {
    "msg": "toto"
}

TASK [set_fact] ***********************************************************************************************************************************************************
ok: [WIN-AD]
skipping: [LINUX-LOCAL]

TASK [set_fact] ***********************************************************************************************************************************************************
skipping: [WIN-AD]
ok: [LINUX-LOCAL]

TASK [debug] **************************************************************************************************************************************************************
ok: [WIN-AD] => {
    "msg": "titi"
}
ok: [LINUX-LOCAL] => {
    "msg": "tata"
}

PLAY [Tester quelque chose (2)] *******************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]

TASK [debug] **************************************************************************************************************************************************************
ok: [localhost] => (item=WIN-AD) => {
    "msg": "titi"
}
ok: [localhost] => (item=LINUX-LOCAL) => {
    "msg": "tata"
}

PLAY RECAP ****************************************************************************************************************************************************************
LINUX-LOCAL                : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
WIN-AD                     : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

C'est cet aspect qui est intéressant : notre test de vie va remonter des informations des machines cibles, vers l'hôte Ansible, en passant par des valeurs (des faits), qui sont liés à la déclaration des machines.

Produire un rapport dans un format déterminé (ici XML)

Bien, nous avons désormais pleins d'astuces pour (comprendre et) assurer l'organisation de nos tests de vie avec cet outil fabuleux. Ce sont des modules par défaut ou communautaires (ou les nôtres) qui assureront les tests (connexions à la base de données, ping de machine ou encore CURL d'URL, etc.). Les données remonteront au contrôleur Ansible et passeront de tâche en tâche, pour déterminer un état suivant une logique métier.

Mais ces résultats, qu'en faire ? Faut-il envoyer par exemple une alerte pour chaque machine, même en cas de retour positif ? Si vous avez suivi des cours de logique floue (ou un peu de Bayes) et des théories de l'info-com', plusieurs points sont à considérer pour répondre à cette question :

  • l'information, c'est ce qui sort de "l'ordinaire" dans un environnement donné : dans notre cas, on alerte lors d'un changement d'état (une perte de service alors que tout allait bien ; ou inversement en cas de reprise). Inutile d'alerte pour dire "ça va toujours (pas) bien" ;
  • il faut des états intermédiaires, particulièrement si vous n'avez pas une machine formant un noeud unique. En cas de distribution, on alertera différemment l'état d'une machine ou d'un service de noeud, de l'ensemble d'un parc ; en privilégiant dans le second, l'alerte en cas de dépassement à la hausse ou à la baisse d'un seuil. Exemple : 80% d'activité, en baisse, de mes machines Web.
  • on peut définir un état de santé d'un SI (donc d'au moins deux composants, eux-même pouvant être 1 ou n processus), au travers d'une formule. Pour l'exemple précédent, l'état d'un SI, cela peut être la multiplication du taux de réussite des tests individuels, addionné ensuite pour chacune des machines (et ramené à 1, pour la lisibilité, en pondérant durant l'addition, chaque note ou composant). En dessous d'un certain délai, de tous les tests, et ce taux serait considéré valide à partir d'une certaine valeur.

On voit donc l'intérêt d'Ansible :

  • ne pas ré-inventer la roue : un module par grand test (BdD, service Web, etc.) ;
  • variabiliser pour que le réusage des modules, s'adapte au travers des rôles ;
  • définir des livres de recettes qui utilisent intelligemment les rôles, pour cibler un intérêt métier (dont les 3 classiques : responsabilité d'affaire ; d'exploitation ; de développement) ;
  • agir sur de nombreuses machines à la fois et remontée - ou non - la bonne information, au bon moment, pour la bonne cible.

L'articulation technique et architecturale ou organisationnelle, est complète, cohérente, lisible.

Cependant chaque lancement d'un livre de recette doit alimenter un jeu de données, par définition un jeu historisé. Ansible peut aussi s'en charger, même si c'est loin d'être sa cible première. Pour l'exercice, j'ai choisi XML, car la manipulation via CRUD est disponible nativement.

lancement.yml

- name: Tester l'activité de mes applicatifs 
  gather_facts: yes 
  hosts: all 
  tasks: 
    - name: Simulation d'un test de vie qui renvoi une valeur...  
      set_fact: 
        mon_retour: "toto !" 

- name: Générer un rapport 
  gather_facts: yes 
  hosts: localhost 
  tasks: 
    - name: Ecrire le retour en cas de succès 
      when: "hostvars[item]['mon_retour'] is defined "
      with_items: "{{ hostvars }}" 
      xml: 
        path: ./rapport.xml 
        xpath: /hotes 
        input_type: xml 
        add_children: 
          - "
            <{{ item }} date=\"{{ ansible_date_time.iso8601 }}\" erreur=\"non\">
              <particulier>
                {{ hostvars[item]['mon_retour'] }}
              </particulier>
              <general>
                {{ hostvars[item]['ansible_windows_domain_role'] }} ( et d'autres valeurs si on veut...) 
              </general>
            </{{ item }}>" 
    - name: Ecrire le retour en cas d'échec 
      when: "hostvars[item]['mon_retour'] is not defined "
      with_items: "{{ hostvars }}" 
      xml: 
        path: ./rapport.xml 
        xpath: /hotes 
        input_type: xml 
        add_children: 
          - "<{{ item }} date=\"{{ ansible_date_time.iso8601 }}\" erreur=\"oui\" />" 

Nous aurons besoin d'un caneva XML pour que les noeuds puissent ajoutés :

rapport.xml

<?xml version='1.0' encoding='UTF-8'?><hotes></hotes>

Si vous lancer la première fois le livre de recettes, vous allez vous heurtez à une erreur banale de module manquant :

TASK [Remove the 'subjective' attribute of the 'rating' element] ***************************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ModuleNotFoundError: No module named 'lxml'
failed: [localhost] (item=WIN-AD) => {"ansible_loop_var": "item", "changed": false, "item": "WIN-AD", "msg": "Failed to import the required Python library (lxml) on julien-Vostro-7580's Python /usr/bin/python3. Please read module documentation and install in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter"}

PLAY RECAP *********************************************************************************************************************************************************************************************************************************
WIN-AD                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Au choix : résoudre le cas sur la machine Ansible une fois pour toute… ou rajouter une étape pour valider que le paquet est bien présent. Il va s'en dire que ce second choix est celui à privilégier : il est un poil plus lent, mais rend votre livre de recettes générique. Ce serait bien un drame s'il n'était pas fichu de gérer son propre environnement !

Cela étant fait, l'exécution se déroule désormais sans difficulté :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement.yml 

PLAY [Tester l'activité de mes applicatifs] *******************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [WIN-AD]

TASK [simulation d'un test de vie qui renvoi une valeur...] ***************************************************************************************************************
ok: [WIN-AD]

PLAY [Générer un rapport] *************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]

TASK [Ecrire le retour en cas de succès] **********************************************************************************************************************************
changed: [localhost] => (item=WIN-AD)

TASK [Ecrire le retour en cas d'échec] ************************************************************************************************************************************
skipping: [localhost] => (item=WIN-AD) 

PLAY RECAP ****************************************************************************************************************************************************************
WIN-AD                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

C'est parfait, notre rapport a été alimenté :

rapport.xml

<?xml version='1.0' encoding='UTF-8'?>
<hotes><WIN-AD date="2021-11-10T11:54:37Z" erreur="non"> <particulier> toto ! </particulier> <general> Stand-alone server ( et d'autres valeurs si on veut...) </general> </WIN-AD></hotes>

Voyons maintenant si l'on ajoute un hôte en erreur, par exemple si ce dernier n'est pas encore (ou plus) accessible :

inventaire.yml

all:
  hosts: 
    WIN-AD: 
      ansible_host: 192.168.56.3 
    WIN-IIS: # je l'ai éteinte, elle n'est donc pas accessible 
      ansible_host: 192.168.56.4 
    # LINUX-LOCAL:
    #   ansible_connection: local 
  vars: 
    ansible_user: "Administrateur"
    ansible_password: "Azerty01"
    ansible_connection: winrm
    ansible_port: 5985
    ansible_winrm_transport: ntlm

L'exécution indique bien une erreur, mais notre rapport restera complet :

(ansible-test-vie) julien@julien-Vostro-7580:~/Developpement/ansible-test-vie$ ansible-playbook -i inventaire.yml ./lancement.yml 

PLAY [Tester l'activité de mes applicatifs] *******************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
fatal: [WIN-IIS]: UNREACHABLE! => {"changed": false, "msg": "ntlm: HTTPConnectionPool(host='192.168.56.4', port=5985): Max retries exceeded with url: /wsman (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f40996c9460>: Failed to establish a new connection: [Errno 113] No route to host'))", "unreachable": true}
ok: [WIN-AD]

TASK [simulation d'un test de vie qui renvoi une valeur...] ***************************************************************************************************************
ok: [WIN-AD]

PLAY [Générer un rapport] *************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]

TASK [Ecrire le retour en cas de succès] **********************************************************************************************************************************
changed: [localhost] => (item=WIN-AD)
skipping: [localhost] => (item=WIN-IIS) 

TASK [Ecrire le retour en cas d'échec] ************************************************************************************************************************************
skipping: [localhost] => (item=WIN-AD) 
changed: [localhost] => (item=WIN-IIS)

PLAY RECAP ****************************************************************************************************************************************************************
WIN-AD                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
WIN-IIS                    : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0   
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

On note d'ailleurs qu'il y a le résultat de l'exécution précédente. J'ai mis les tabulations pour la lisibilité :

rapport.xml

<?xml version='1.0' encoding='UTF-8'?>
<hotes>

    <!-- ici la première exécution --> 
    <WIN-AD date="2021-11-10T11:57:42Z" erreur="non"> 
        <particulier> toto ! </particulier> 
        <general> Stand-alone server ( et d'autres valeurs si on veut...) </general> 
    </WIN-AD>

    <!-- ici la deuxième exécution --> 
    <WIN-AD date="2021-11-10T11:58:02Z" erreur="non"> 
        <particulier> toto ! </particulier> 
        <general> Stand-alone server ( et d'autres valeurs si on veut...) </general> 
    </WIN-AD>
    <WIN-IIS date="2021-11-10T11:58:02Z" erreur="oui"/>

    <!-- les prochaines s'ajouteront après ce commentaire --> 

</hotes>

Voilà, il est prêt à partir : envoi par courriel, par API ou simplement le journaliser localement.

Il existe encore d'autres notions essentielles, comme le mise en réserve des faits (pour accéler la récupération des faits, éventuellement sans passer par "gather_facts") ainsi que les notions de délégation. Cependant vous avez ici tout le caneva pour réussir les premiers pas d'un SI dont les tests de vie sont intelligents, efficaces et centralisés.

Bon code !


PS : je ne porte pas Windows dans mon coeur, loin de là ; c'est juste un support.

  • # Horreur

    Posté par  . Évalué à 6.

    Juste après l'envoi, je m'aperçois de deux boulettes :

    "Si l'on voulait véritablement considéré"
    -> considérER

    "Ainsi voyons le passage des modifications entre les différentes recettes ("plays") : (…)"
    -> la partie suivante devrait être en bloc de code "```"

    Si un admin/modérateur du site a la possibilité de faire la modif ou m'indiquer comment la faire (de mémoire, on ne peut pas éditer un journal après publication ?). D'avance merci !

  • # Ansible

    Posté par  (site web personnel) . Évalué à 7.

    On voit donc l'intérêt d'Ansible :

    À titre personnel, je mettrais en premier l'interface ok/changed/failed plus riche que le ok/ko historique du code de retour shell/C. Ce qui amène l'idempotence et le test à blanc (check mode/dry run). Ça permet par exemple d'espérer que lancer une seconde fois va résoudre le souci transitoire, ou de tester avant (vérifier ce qu'on va faire) ou après un déploiement (tout est conforme au dépôt de code ?).

    • [^] # Re: Ansible

      Posté par  (site web personnel, Mastodon) . Évalué à 2.

      Tout à fait. Ce n'est pas juste pour remplacer les scripts (comme j'ai lu ailleurs) mais aussi un cadriciel .

      Maintenant, pour en revenir à la collection de facts on (les programmes qui sont instruits dans ce sens) peut en poser de brutes (ce sont juste de fichiers JSON avec l'extension .fact) ou de dynamiques (fichiers de scripts exécutables avec l'extension .fact et renvoyant du JSON)
      https://www.tecmint.com/ansible-variables-and-facts/
      https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html#parameter-fact_path
      OpenShift, par exemple, fournit ce genre de « custom facts »

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

    • [^] # Re: Ansible

      Posté par  . Évalué à 4.

      Pour compléter : cela permet aussi, lorsque certains éléments techniques ne sont pas centralisés, où le dry run ou une exécution réelle, retourne la "réalité" dans la définition des variables (cf. "Gather facts").

      Si l'enjeu de sécurité est fort, Ansible est alors parfait pour "ré-aligner" un serveur / un service d'après un gabarit centralisé et remonter l'alerte.

      À titre personnel, je mettrais en premier l'interface ok/changed/failed plus riche que le ok/ko historique du code de retour shell/C.

      En Shell ou C, un processus retourne un entier (signé ou non), donc tu as par convention tu as 0 qui vaut "tout va bien"… et 255 autres valeurs pour dire qu'un truc s'est plus ou moins mal passé (ou bien passé, mais avec un truc en plus).

      Cependant la richesse relative d'un entier, ne vaut évidemment l'aspect de retours complexes et faciles d'états d'Ansible.

      • [^] # Re: Ansible

        Posté par  (site web personnel, Mastodon) . Évalué à 4. Dernière modification le 25 mars 2022 à 02:27.

        Le framework va plus loin que « tout va bien » et « quelque chose s'est mal passé » puisqu'il distingue :

        • (ok) l'état est nominal et je ne fais aucune modification (c'est là la notion d'idempotence)
        • (changed) l'état n'était pas celui désiré et les actions d'alignement ont été entreprises et se sont bien passées : ça correspond au rc=0
        • (failed) l'état n'était pas celui désiré et les actions de correction ont été entreprises mais ne sont pas bien passées : ça correspond au rc≠0
        • (skipped) l'état nominal n'a pas été vérifié et c'est voulu/demandé
        • (unreachable) rien n'a pu être fait parce-qu'il y a des soucis de connectivité, là c'est pas voulu/demandé.

        Et le framework unifie la présentation (fini les commandes dont les retours ne sont pas homogènes) et le fonctionnement (un module est une charge utile avec le même mode de fonctionnement global hormis les détail d'action) et permet normalement le test à blanc comme tu l'as mentionné (dry run)

        “It is seldom that liberty of any kind is lost all at once.” ― David Hume

  • # il faudrait le réécrire <s>en Rust</s> !

    Posté par  (site web personnel) . Évalué à 6.

    Ansible fait son boulot, mais:

    • la syntaxe est atroce (yaml beurk) ;
    • malgré tout le discours sur l'idempotence, c'est un mauvais langage de script (playbook = script, role = fonction, task = instruction) ;
    • il dépends des paquets installés sur l'OS, par exemple si je dois récupérer un artefact maven, il faut installer un parseur xml python…

    Vivement ansible 42.0 sans ces défauts !

    Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

      Posté par  . Évalué à 2.

      Et les logs "tout sur la même ligne avec des \n au milieu", une horreur à lire.

      On peut quand même améliorer ça avec un "callback plugin", par exemple json.

    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

      Posté par  . Évalué à 7.

      J'ai beaucoup utilisé Ansible à $job-1, je fais du puppet maintenant. J'ai l'impression d'être passé de la peste au choléra.

      Ansible a des concepts horribles :
      - sa syntaxe (coder en yaml… faut être maso),
      - l'idée qu'on lance les playbooks localement, sans pouvoir facilement suivre qui a exécuté quoi, avec quels diffs locaux…

      Et on était contraints de compléter ansible par des scripts qui remplissaient de l'inventaire automatiquement : sans ça, impossible de représenter proprement une ressource de plus haut niveau (les clusters PostgreSQL dans mon cas)

      Puppet… j'apprends à le haïr à sa façon :
      - quel est l'analphabète comme ses pieds qui se dit qu'une ressource ne peut être présente qu'une seule fois, y compris les demandes d'installation de paquets ?! Du coup, adieu les modules vraiment réutilisables, si y'a une dépendance commune ça va planter, et bonjour la complexité même au sein d'un module si il itère sur plusieurs ressources et choisit selon d'installer des paquets.
      - les modules en général sont des putain de chats. Par exemple le module lvm exigera qu'on lui décrive tout pour qu'il accepte de créer un LV, dans un VG qu'il veut créer avec des PVs qu'il veut connaître… mais je veux pas lui dire ça, l'OS est dans un VG, je veux pas qu'il y touche, je veux juste qu'il accepte de créer un LV…

      Je préfère encore écrire des scripts shell…

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (site web personnel) . Évalué à 5.

        • l'idée qu'on lance les playbooks localement, sans pouvoir facilement suivre qui a exécuté quoi, avec quels diffs locaux…

        Y a Ansible Tower pour ça, mais sinon, des gens utilisent des CI (jenkins, buildbot, woodpecker) pour lancer ansible quand il y a un commit.

        Pour ma part, j'ai un script de post commit git qui analyse le commit et lance le(s) playbook(s) impactés automatiquement.

        Ansible de base fait les choses simplement, ce qui implique aussi de ne pas forcer un workflow spécifique. Tu as raison de dire que déployer depuis son PC, c'est naze, mais dans ce cas, ne le fait pas, force les gens à mettre un commit git et un CI avant, fait en sorte que le déploiement soit pas depuis le PC.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 2.

          Y a Ansible Tower pour ça, mais sinon, des gens utilisent des CI (jenkins, buildbot, woodpecker) pour lancer ansible quand il y a un commit.

          Jenrkins root sur toute l'infra… je vois pas ce qui pourrait mal se passer tiens.
          Comment tu fais ça avec des règles de sécu strictes ?

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel) . Évalué à 7.

            Je suppose que ça dépend exactement des régles de secu strictes en question. Si on commence à faire une partie de Calvinball, il faut au moins l'annoncer.

            Fondamentalement, quelque chose doit être root sur l'infra pour l'administrer. J'aime pas Jenkins non plus, c'est pour ça que j'ai pas mis ça en place, mais fondamentalement, tu as un outil qui va faire des choses en root, et à partir du moment ou l'outil que ça soit ansible, puppet ou n'importe quoi d'autres peut exécuter des commandes en root, c'est game over.

            Je ne pense pas que ansible lancer par jenkins soit pire que ansible depuis le PC random d'un admin. Et si le souci est que l'admin ne doit pas faire de modif local avant de lancer, alors faut le lancer depuis une machine ou ça n'arrive pas par erreur.

            Sinon, il reste aussi le mode "ansible-pull".

            • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

              Posté par  . Évalué à 3.

              Si on commence à faire une partie de Calvinball, il faut au moins l'annoncer.

              Ben ça me semble évident, on parle d'une infra type entreprise, pas du blog de mamie…

              Je ne pense pas que ansible lancer par jenkins soit pire que ansible depuis le PC random d'un admin.

              Un accès en plus, qui va faire des actions qu'on ne peut pas facilement lier à un humain… Si, c'est pire.

              Sinon, il reste aussi le mode "ansible-pull".

              La moins mauvaise des solutions dispos dans ce que j'avais vu, mais quand tu commences à avoir beaucoup de playbooks l'ordonnancement devient fun.
              Mon idéal eut été une machine dédiée, mais dont l'accès au parc (et au vault ansible) dépende d'un agent ssh transmis quand nécessaire par un admin. Je n'ai pas eu le temps de faire un poc à l'époque.

            • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

              Posté par  (site web personnel) . Évalué à 2.

              En terme de sécurité, ça change quand même beaucoup.

              Un poste d'admin propre, qui ne sert qu'à de l'administration et vers lequel aucun flux n'est ouvert, est normalement beaucoup plus dur à compromettre qu'un serveur avec des ports ouverts.
              De plus, toujours si tu fais les choses proprement (par exemple avec du SSH avec authentification forte), il faudra que l'admin soit devant son poste pour qu'il y ait une action.
              Avec un Jenkins, le serveur peut être compromis dans un premier temps et le reste de l'infra compromise à n'importe quelle moment (idéalement le week-end).

              Donc si, passer par un Jenkins est bien pire que depuis un PC d'admin.

              • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                Posté par  . Évalué à 4.

                En soi n'importe quel ordinateur est vecteur d'attaque : un serveur AWX/Tower ou un poste d'administrateur.

                Si on centralise sur l'un ou l'autre, tout l'accès au SI avec des privilèges élevés, le résultat sera le même : pas bon.

                Au-delà de l'usage d'AWX, le principe d'avoir un serveur dédié est de ne pas avoir l'effet oups lors que l'administrateur part en vacances, que son poste crame pour x raison(s). Un serveur peut avoir aussi ses difficultés, mais si on respectueux de ne pas lui faire porter la terre entière, et qu'on a des serveurs physiques ou VM dédiés par usage, voire des pools, ça limite la casse.

                Maintenant sur le fond : un poste d'admin ou d'AWX, ça se surveille. Si la sécurité est vraiment importante, ce n'est certainement pas en direct qu'on fait pointer le poste de commandement de déploiement vers l'infra : on passe par un bastion (pour Linux hein, pour Windows… non mais faut juste retirer Windows). Là c'est ce qui est autorisé qui passe, même en commandes envoyées. Pas parfait, mais mieux que rien.

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (site web personnel) . Évalué à 3.

        Il y a beaucoup de magie dans le Yaml d'Ansible, mais alors vraiment trop, à tel point que ce n'est pas du Yaml standard.
        Parfois on a du texte sans guillemet, et parfois il faut mettre des guillemets alors que ça ne sera pas du texte (!).

        L'autre aspect vraiment regrettable est qu'Ansible n'est pas du tout pensé pour être API-isable, avec simplement un ansible-runner qui fait semblant de l'être.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 5.

          Parfois on a du texte sans guillemet, et parfois il faut mettre des guillemets alors que ça ne sera pas du texte (!).

          Ce n'est pas "la faute" à Ansible, mais au respect de la grammaire YAML. Déconcertant certes. Mais pas illogique.

          L'autre aspect vraiment regrettable est qu'Ansible n'est pas du tout pensé pour être API-isable, avec simplement un ansible-runner qui fait semblant de l'être.

          Tout à fait d'accord. Mais parce que le principe est d'avoir soit l'accès en une "ligne de commande" (enfin un panel restreint), soit via un outil graphique type AWX/Tower.

          Après on peut automatiser Ansible en faisant un playbook Ansib-… heu… attendez…

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel, Mastodon) . Évalué à 7.

          Il y a beaucoup de magie dans le Yaml d'Ansible, mais alors vraiment trop, à tel point que ce n'est pas du Yaml standard.

          C'est quoi du YAML standard ??? Parce-que je n'ai pas vu ce qui est rajouté par Ansible qui serait rejeté par un validateur YAML (je passe tous mes playbooks, et plus généralement tous mes fichiers YAML sous yamllint et je n'ai jamais rien vu de non standard dans Ansible, donc j'aimerais satisfaire ma curiosité.)

          Parfois on a du texte sans guillemet, et parfois il faut mettre des guillemets alors que ça ne sera pas du texte (!).

          La règle c'est que tu mets le texte entre guillemets, point. YAML offre la possibilité, par mauvaise facilité de ne pas mettre de guillemets : ce que tu considères comme la règle est en fait l'exception voir une mauvais pratique parce-que tu vas faire faire des interprétations fausses…
          Et si on a pris le temps de lire la foutue doc, Ansible met en garde contre ce qu'ils appellent les YAML gotchas !

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (site web personnel, Mastodon) . Évalué à 9. Dernière modification le 24 mars 2022 à 23:35.

        Conclure par :

        Je préfère encore écrire des scripts shell…

        Après s'être plaint que :

        • l'idée qu'on lance les playbooks localement, sans pouvoir facilement suivre qui a exécuté quoi, avec quels diffs locaux…

        Bref, n'avoir rien compris à l'affaire.
        L'outil se dit simple parce-que n'imposant rien (là où Puppet par exemple impose une façon de s'organiser.) Tu peux faire des lancements locaux (et faire tout presque comme tu continuerais à faire avec tes scripts) ou tout centraliser (ce qu'il faudrait faire quand on travaille en équipe.) Forcément, comme ça contraint pas (et en cela ces fidèle à la philosophie unixienne), on a vite fait de voir des gens faire n'importe quoi puis raconter des conneries.

        Même en mode perso, j'ai tous mes lancements qui sont journalisés (c'est une bête ligne de configuration qui n'est pas imposée.) Et quand je doit utiliser Ansible en entreprise je demande qu'il y a un serveur commun d'administration (à défaut d'avoir un AWX) où j'active cette journalisation.

        Pour les diffs locaux, ça fait des lustres qu'il y a l'option --diff à côté duquel tu es visiblement passé. Partout où je suis passé, je l'ai toujours manqué dans la documentation qui va bien (tout les playbooks n'en ont pas besoin mais pour certains j'estime qu'il faut et c'est consigné noir sur blanc.)

        • sa syntaxe (coder en yaml… faut être maso),

        Cette mécompréhension fait qu'on appréhende l'outil de la plus mauvaise manière qui soit. Ce n'est pas du codage mais de la description : il faut se le répéter jusqu'à ce que ça rentre sinon on va faire et dire n'importe quoi. Tu décris des états, tu ne les codes pas (enfin, tu codes si tu écris toi-même les modules sous-jacent.)
        La documentation (bien qu'exécutable, d'où la comparaison avec les recettes) n'est pas du code (ni de la littérature remarque.) On décrit des étapes et non la mécanique sous-jacente (si on veut vraiment faire l'analogie avec la programmation, c'est du descriptif comme SQL ou Prolog et non de l'impératif comme Java ou Rust ou Haskell ou même tes scripts shell…)

        Et on était contraints de compléter ansible par des scripts qui remplissaient de l'inventaire automatiquement : sans ça, impossible de représenter proprement une ressource de plus haut niveau (les clusters PostgreSQL dans mon cas)

        Ce n'est pas un gestionnaire d'inventaire, au contraire ça s'appuie sur un inventaire. Ceci dit, j'ai déjà fait des playbooks pour mettre à jour divers types d'inventaires.)
        J'ai l'impression que vous avez passé le temps à utiliser des outils sans vraiment les comprendre, ce qui est vraiment dommage. (ça s'applique à tout, même aux scripts shells : les gens qui viendront juste les lancer sans comprendre ce qu'ils/elles font iront aussi de ce genre de réponses.)

        “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 0.

          Cette mécompréhension fait qu'on appréhende l'outil de la plus mauvaise manière qui soit. Ce n'est pas du codage mais de la description : il faut se le répéter jusqu'à ce que ça rentre sinon on va faire et dire n'importe quoi. Tu décris des états, tu ne les codes pas (enfin, tu codes si tu écris toi-même les modules sous-jacent.)

          Dac, comment tu dis à ansible «je veux avoir un cluster PostgreSQL 13 sur le port 5433», à part en scriptant l'installation des paquets, le lancement du pg_createcluster, le remplissage des bons fichiers de conf (selon la version ça va changer) ?
          C'est mensonger d'affirmer qu'en décrivant l'état on n'est pas déjà en train de coder, et tu n'abordes pas l'éventuelle complexité du changement d'état qui demande beaucoup d'actions à automatiser (puisque «coder» ou «scripter» fait peur…)

          Ce n'est pas un gestionnaire d'inventaire, au contraire ça s'appuie sur un inventaire.

          Et je dis que son inventaire est moisi, celui de puppet est bien mieux pour ça. Ansible pour mon cas c'est «codez un inventaire dynamique, regardez le code source pour la doc de l'api» (et encore, y'a des trucs qui étaient pas dispos à l'époque, et la doc est plus complète qu'elle ne l'était)
          Puppet a des mécanismes qui me permettent de dire sur l'hôte A «prend le cluster X et le cluster Z», et sur le B «cluster Y et Z». Sans écrire de plugins et python ou ruby ou autre…

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel, Mastodon) . Évalué à 6.

            C'est mensonger d'affirmer qu'en décrivant l'état on n'est pas déjà en train de coder, et tu n'abordes pas l'éventuelle complexité du changement d'état qui demande beaucoup d'actions à automatiser (puisque «coder» ou «scripter» fait peur…)

            Décrire l'état c'est dire : « à cette étape, s'assurer de l'existence du répertoire trucmuch »
            Le coder, ce serait faire en shell : mkdir trucmuch (si tu dois faire la même chose que Ansible sous le capot, il faudrait d'abord vérifier si le répertoire existe et sinon le créer, et tracer les actions que tu fais etc. en général les gens qui disent que c'est plus simple et plus rapide de faire un script ne font pas les trois quarts de ce à quoi ils/elles comparent mais bon)
            Dans le premier cas (descriptif) tu n'indiques que l'état final recherché (et dans un format qui parle à plus de gens que les personnes qui codent.) Dans le second cas (impératif) tu indiques les différentes commandes (et donc t'adresses aux gens qui comprennent le langage de scripting utilisé.) La personne qui lit ton playbook ne se préoccupe pas de l'implémentation ; ce n'est pas une question de coder ou scripter qui ferait peur, c'est juste que ce n'est pas le lieu. Bien sûr que le changement d'état est complexe et justement quand tu fais de la description d'état tu n'abordes pas la difficulté sous-jacente : donc tu ne codes pas…

            Et je dis que son inventaire est moisi, celui de puppet est bien mieux pour ça. Ansible pour mon cas c'est «codez un inventaire dynamique, regardez le code source pour la doc de l'api» (et encore, y'a des trucs qui étaient pas dispos à l'époque, et la doc est plus complète qu'elle ne l'était)
            Puppet a des mécanismes qui me permettent de dire sur l'hôte A «prend le cluster X et le cluster Z», et sur le B «cluster Y et Z». Sans écrire de plugins et python ou ruby ou autre…

            C'est bien que admettes qu'il y a de plus en plus de plugins d'inventaire …plus le temps passe et plus t'en as. C'est bien aussi de faire le parallèle qu'un autre outil qui a pratiquement le double de son âge ait plus de plugins d'inventaire déjà prêts.

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel) . Évalué à 1.

          Cette mécompréhension fait qu'on appréhende l'outil de la plus mauvaise manière qui soit. Ce n'est pas du codage mais de la description : il faut se le répéter jusqu'à ce que ça rentre sinon on va faire et dire n'importe quoi. Tu décris des états, tu ne les codes pas (enfin, tu codes si tu écris toi-même les modules sous-jacent.)

          Ça c'est la théorie, en pratique les modules sont souvent mal foutus et tu es obligé de passer en mode script :(

          Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel, Mastodon) . Évalué à 6.

            Et chaque fois qu'on creuse (en tout cas la majorité des questions que je croise sur les forums) c'est parce-que en pratique on veut mal l'utiliser (souvent comme un langage de script que ça n'est pas —on reboucle.)
            Sinon, tant qu'à scripter tu pourrais proposer des modules « mieux » faits.

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à -1.

          Bref, n'avoir rien compris à l'affaire.
          L'outil se dit simple parce-que n'imposant rien

          J'oubliais de te remercier pour ton mépris.

          Prenons une page de la doc :
          https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html

          Quels paramètres sont indispensables pour qu'ansible respecte son principe de pouvoir lancer les playbooks à l'infini ? creates et removes. Ils sont optionnels, et guère mis en avant alors qu'ils méritent a minima une place dans le synopsis, voire un warning du type :

          if creates is None and removes is None and register is None:
            logging.warn("tu fous quoi là ?")
          

          Quand toute une boite utilise un outil, y'a deux options :
          - croire que tout le monde apprendra à bien l'utiliser
          - avoir un outil contraint

          À $job-1, ansible était déjà là, mais l'option 1 n'a jamais été approuvée, «it works don't fix it» (grosso modo). Et ça semble être la norme plus que l'exception en entreprise, ce qui me fait envier l'option 2, la contrainte, ou l'option 1 avec des outils dont la courbe d'apprentissage est un mur dans la face.

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel, Mastodon) . Évalué à 5.

            Ce n'est pas du mépris et ton exemple ne fait que redire ce que je dis : ça n'impose rien (malheureusement pour toi). Et je dis aussi que c'est dans l'esprit unixien qui ne contraint pas : il faut bien apprendre à utiliser les commandes sinon le système n'empêche pas root de faire rm -rf / contrairement à un autre qui veut te contraindre et te demandant dix validations pour supprimer un fichier anodin ou parfois en te refusant carrément de le faire pour une raison obscure. Donc pas de warning quand tu utilises le module command à ta guise car tu es sensé avoir lu la fichue doc[1] et savoir ce que tu fais…
            Tu arrives toi-même à la conclusion que \$job-1 on faisait ce qu'il ne fallait pas faire et ce n'est pas du mépris que de faire ce triste constat (qui concerne beaucoup d'entreprises…)[2]

            PS : Si tu veux t'assurer que les bonnes pratiques sont respectées dans tes playbooks, passe ansible-lint dessus… Mais, encore une fois, tu resteras libre de ne pas suivre ses avertissements et consignes.

            [1] Doc qui te dit justement de bien chercher s'il n'y a pas un meilleur module, un module dédié, et que celui-là ne doit s'utiliser qu'en dernier recours… Doc aussi disponible hors ligne mais beaucoup l'ignorent.

            [2] C'est du même acabit quand les boîtes développent en C (zéro garde-four) au lieu d'utiliser Ada ou Java ou Rust par exemple.

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 4.

          L'outil se dit simple parce-que n'imposant rien

          Il n'impose pas d'architecture, mais il est tout de même opiniated (rien que pour le côté déclaratif par exemple).

          sa syntaxe (coder en yaml… faut être maso),

          Cette mécompréhension fait qu'on appréhende l'outil de la plus mauvaise manière qui soit.

          Pas besoin de chercher à faire des choses procédurales pour souffrir. Si je prends le premier playbook en suivant les liens dans la doc officielle (getting started → administrator getting started → playbook) on arrive ici : A system administrator's guide to getting started with Ansible - FAST!

          et on nous montre ça (je garde que ce qui est nécessaire à mon exemple) :

          ---
          - hosts: webservers
           become: yes
           tasks:
             - name: install Apache server
               yum:
                 name: httpd
                 state: latest
          #...
             - name: set content directory group/permissions 
               file:
                 path: /var/www/html
                 owner: root
                 group: web
                 state: directory
                 mode: u=rwx,g=rwx,o=rx,g+s
          
             - name: create default page content
               copy:
                 content: "Welcome to {{ ansible_fqdn}} on {{ ansible_default_ipv4.address }}"
                 dest: /var/www/html/index.html
                 owner: webadm
                 group: web
                 mode: u=rw,g=rw,o=r

          Et voila jinja2. Je comprends le choix (c'est LE moteur de template de python), mais tu as 2 syntaxe dans un même fichier, je n'ai jamais trouvé d'éditeur en mesure de représenter correctement les 2 syntaxes (comme ça existe pour html/js/css) et tu as un moteur de template complet (on manipule des structures de données fréquement) dans un langage de description qui est plutôt subtile (indentation, multiline, etc).

          Tiens d'ailleurs la documentation redhat (qui est pointée par le site officiel d'ansible) ne respecte pas ce que tu décrit comme bonne pratique sur un getting started donc sur quelque chose qui est prévu pour être simple.

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel, Mastodon) . Évalué à 2.

            La doc d'introduction met l'accent sur la facilité de prise en main …ce qui peut être au détriment des bonnes pratiques :-S Probablement le prix à payer pour la bonne affiche (j'allais dire « pour être vendeur » avant de me rappeler que ce n'est pas vendu), en tout cas on ne cherche pas à décourager par certaines considérations.

            Ainsi, je ne trouve pas très propre le YAML qui devrait ressembler plutôt à

            - hosts: "webservers"
              become: yes
              tasks:
                - name: "install Apache server"
                  yum:
                    name: "httpd"
                    state: latest

            On va dire que je chipote, mais :

            • Avoir une indentation correcte et cohérente est, comme avec Python, plus facile à lire et limite les erreurs.
            • Il faut, systématiquement mettre les chaînes entre « quotes » pour ne pas donner l'impression comme on l'a vu que tantôt il en faut parfois par magie ou sans raison.
            • Je ne fais exception que pour différencier les mots clés de l'outil : "latest", "directory", etc. Par contre, dans tous les cas, en ne quotant pas "yes" je différencie bien le booléen de la chaîne de caractères (et certains linters veulent même qu'on utilise carrément "true" et rien d'autre)
            • Utiliser "latest" montre bien que l'outil n'impose rien et qu'on peut l'utiliser pour mettre à jour aussi sans trop de prise de tête, mais cette option va à l'encontre des bonnes pratiques si on veut être idempotent…

            Maintenant, venons au cas de Jinja2, où encore on a marché sur les bonnes pratiques au profit de la facilité… car l'utilisation de "content" dans le module copy est vraiment à réserver au dernier recours. Et dans cet exemple précisément, on était dans un cas où il aurait fallut faire appel au module template… Tout cela illustre mon propos (l'outil est permissif, ce qui permet de le prendre en main facilement mais avec le revers de pouvoir faire aussi des choses pas proprettes…) Le « FAST » dans le titre indique que les petites violations des bonnes pratiques sont assumées et ça me va : vite fait ne peut être toujours 100% bien fait…

            Je reviens au point qui préoccupe. Comme les éditeurs font de la coloration syntaxique pour du YAML c'est donc du YAML pur qui est vu et mis en évidence. Pour répondre à ton/ta besoin/demande, il faudrait rajouter une déclinaison qui serait la prise en compte du DSL… Pour faire une analogie, quand tu ouvres un document XHTML dans un éditeur qui le voit comme du XML, ça va certes reconnaitre qu'il y a des balises et des attributs et pouvoir indiquer les imbrications etc. Mais contrairement à un éditeur HTML pur, ça ne reconnait pas la sémantique qui lui permettrait de distinguer les balises valides des balises farfelues, et reconnaître proprement JS et CSS dedans. C'est exactement ce qu'il manque avec la prise en compte de Jinja2 dans du YAML (et c'est valable aussi si on utilise quelque autre mécanisme : j'ai perso déjà eu à faire des modèles YAML avec des variables shell mais les éditeurs ne savaient pas mettre ces intrus en évidence.)
            Je pense/espère que l'éditeur que tu utilises te permets de définir assez facilement ton fichier de coloration syntaxique. Auquel cas, il te faudra partir de celui du YAML et adapter jusqu'à obtenir ton bonheur. Perso, j'utilise essentiellement Vim mais n'ayant pas eu le besoin, je n'ai pas spécialement creusé la question. Je trouve cependant pearofducks/ansible-vim et chase/vim-ansible-yaml (ou erikzaadi/vim-ansible-yaml entre autres) qui mettent en évidence des éléments du DSL. Je n'ai pas testé car je reste plus avec le fonctionnement de base et quelques réglages. Il y a divers approches avec cet éditeur (on parle par exemple de rocannon/vim-assimilate/vim-polyglot par exemple.)

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

            • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

              Posté par  . Évalué à 2.

              Je reviens au point qui préoccupe. Comme les éditeurs font de la coloration syntaxique pour du YAML c'est donc du YAML pur qui est vu et mis en évidence. Pour répondre à ton/ta besoin/demande, il faudrait rajouter une déclinaison qui serait la prise en compte du DSL… Pour faire une analogie, quand tu ouvres un document XHTML dans un éditeur qui le voit comme du XML, ça va certes reconnaitre qu'il y a des balises et des attributs et pouvoir indiquer les imbrications etc. Mais contrairement à un éditeur HTML pur, ça ne reconnait pas la sémantique qui lui permettrait de distinguer les balises valides des balises farfelues, et reconnaître proprement JS et CSS dedans. C'est exactement ce qu'il manque avec la prise en compte de Jinja2 dans du YAML (et c'est valable aussi si on utilise quelque autre mécanisme : j'ai perso déjà eu à faire des modèles YAML avec des variables shell mais les éditeurs ne savaient pas mettre ces intrus en évidence.)

              C'est un long paragraphe pour me paraphraser. Je sais très bien ce qu'il manque et je l'ai dis dans mon commentaire. Comme tu le souligne il faudrait les mots clefs d'ansible le support de jinja2, voir une compréhension de la structure, des mots-clef, de jinja pour avoir de l'autocomplétion. Un linter qui va avec ça. Bref quand on superpose des syntaxes existantes, on obtiens pas le meilleur de chaque monde, mais le pire. On crée un nouveau format qui n'est que partiellement pris en charge un peu partout et cette prise en charge partielle et par effet goodenought ralenti fortement l'émergence d'outils agréables à utiliser.

              Et non la solution n'est pas de tripatouiller les fichiers de syntaxes de ton éditeur, tu va avoir un truc qui marchote plus ou moins. La solution c'est d'utiliser le LSP proposé par le projet, mais voila son développement n'a commencé que l'an dernier pour un projet qui a 10 ans.

              Le « FAST » dans le titre indique que les petites violations des bonnes pratiques sont assumées et ça me va : vite fait ne peut être toujours 100% bien fait…

              Tu veux dire qu'ils veulent que ce soit tellement rapide qu'ils ont viré les quotes non nécessaires ? En tout cas si je regarde les playbook les mieux notés d'ansible galaxy, ils appliquent tous la règles "on ne quotes que les chaines pour les quel c'est nécessaire". Bon est pour aller plus loin encore c'est aussi la règle qui est utilisée dans le dépôt de playbooks servant à illustrer les bonnes pratiques pointées par la doc d'ansible comme quoi.

              Avoir une indentation correcte et cohérente est, comme avec Python, plus facile à lire et limite les erreurs.

              Quel est le problème avec l'indentation ?

              https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

              • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                Posté par  (site web personnel, Mastodon) . Évalué à 2.

                Un linter qui va avec ça.

                Je l'ai mentionné dans un autre commentaire :

                pip install yamllint
                # c'est générique et servira pour tout YAML qu'on trouve ci et là
                # par défaut c'est assez strict et je ne pense pas que l'exemple passe
                pip install ansible-lint
                # c'est très laxiste sur le YAML, comparativement à l'autre
                # ça vérifie les bonnes pratiques émises au fil du temps

                Je mets ça en place aussi dans mes CI/CD.

                Bref quand on superpose des syntaxes existantes, on obtiens pas le meilleur de chaque monde, mais le pire. On crée un nouveau format qui n'est que partiellement pris en charge un peu partout et cette prise en charge partielle et par effet goodenought ralenti fortement l'émergence d'outils agréables à utiliser.

                Je dis justement que ça ne ralenti par certaines personnes (comme moi qui ne recherche pas de syntaxe particulière) mais sinon c'est bien une nouvelle syntaxe…
                On peut faire le même parallèle pour DocBook et d'autres comparé à XML au sens générique. J'ai des collègues qui ont du mal parce-que n'ayant pas de syntaxe spécifique alors que moi non.
                On peut faire le même parallèle pour tous les DSL (aussi bien celui de Puppet que celui de Structurizr avec lequel je travaille actuellement.)

                Et non la solution n'est pas de tripatouiller les fichiers de syntaxes de ton éditeur, tu va avoir un truc qui marchote plus ou moins. La solution c'est d'utiliser le LSP proposé par le projet, mais voila son développement n'a commencé que l'an dernier pour un projet qui a 10 ans.

                Ça revient au même pour moi. Si tu utilises lsp, bah il faut créer les fichiers de syntaxe lsp (ou attendre que quelqu'un fasse le tripatouillage pour toi.)
                Ensuite j'ai donné des exemples pour Vim qui ont cinq à sept ans d'existence.

                En tout cas si je regarde les playbook les mieux notés d'ansible galaxy, ils appliquent tous la règles "on ne quotes que les chaines pour les quel c'est nécessaire". Bon est pour aller plus loin encore c'est aussi la règle qui est utilisée dans le dépôt de playbooks servant à illustrer les bonnes pratiques pointées par la doc d'ansible comme quoi.

                La « bonne habitude » est de « juste mettre les quotes pour les chaînes de caractères. » Beaucoup d'erreurs auxquels j'ai répondu sur le forum étaient juste liées à ça : on a pris le raccourci autorisé par YAML et on n'a pas compris ce qui a merdé. Que de temps perdu en n'ayant pas fait les choses correctement dès le départ ?

                Quel est le problème avec l'indentation ?

                Seconde et troisième lignes (« become: yes et tasks: ») ; ça m'a piqué les yeux …et ça rend l'interprétation ambigüe (est-ce bien au même niveau que hosts: webservers ou au niveau de l'élément de liste - hosts: webservers ? certains diront qu'il y a une espace, mais comme pour Python il est mieux d'avoir un nombre paire d'espaces et surtout le même partout hors on en est à 2 sous tasks…)

                “It is seldom that liberty of any kind is lost all at once.” ― David Hume

                • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                  Posté par  . Évalué à 2.

                  On peut faire le même parallèle pour DocBook et d'autres comparé à XML au sens générique. J'ai des collègues qui ont du mal parce-que n'ayant pas de syntaxe spécifique alors que moi non.

                  Oui si ce n'est que l'outillage pour en faire avec xml est bien plus simple. Tu n'a pas besoin d'implémenter un parser, tu te contente de décrire ta grammaire et tu as une coloration, une autocompletion, une validation dans tout ce qui sait manipuler du xml. Tu peux avoir ton templating en xml ce qui va se marier ensemble et pas se marcher sur les pieds.

                  On peut faire le même parallèle pour tous les DSL (aussi bien celui de Puppet que celui de Structurizr avec lequel je travaille actuellement.)

                  Le fait d'avoir un bout de d'outils existant à fait qu'on a mis 10 ans à prendre le sujet ça n'aurait pas mis autant de temps sinon.

                  Ça revient au même pour moi. Si tu utilises lsp, bah il faut créer les fichiers de syntaxe lsp (ou attendre que quelqu'un fasse le tripatouillage pour toi.)

                  On ne parle pas de la même chose. Ansible a mis 10 ans à s'occuper d'avoir un outillage sympa pour ses playbooks. Ce qui rendait pénible tout usage de jinja ou qui pouvait te planter si tu n'échappe pas des choses qui ont du sens pour celui-ci. Le fait que tu puisse te créer ton outillage pour ne change pas. C'est comme dire que si tu sais faire tu ne fait pas d'erreur.

                  La « bonne habitude » est de « juste mettre les quotes pour les chaînes de caractères. » Beaucoup d'erreurs auxquels j'ai répondu sur le forum étaient juste liées à ça : on a pris le raccourci autorisé par YAML et on n'a pas compris ce qui a merdé. Que de temps perdu en n'ayant pas fait les choses correctement dès le départ ?

                  Ça n'a l'air partager par personne dans la communauté. Je ne suis pas allé voir s'il y a eu un ticket ou un mail là dessus. Mais c'est, je trouve significatif de difficultés avec le format que tu trouve si problématique les règles suivi par la communauté.

                  https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

                  • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                    Posté par  (site web personnel, Mastodon) . Évalué à 3. Dernière modification le 28 mars 2022 à 03:41.

                    La « bonne habitude » est de « juste mettre les quotes pour les chaînes de caractères. » Beaucoup d'erreurs auxquels j'ai répondu sur le forum étaient juste liées à ça : on a pris le raccourci autorisé par YAML et on n'a pas compris ce qui a merdé. Que de temps perdu en n'ayant pas fait les choses correctement dès le départ ?

                    Ça n'a l'air partager par personne dans la communauté. Je ne suis pas allé voir s'il y a eu un ticket ou un mail là dessus. Mais c'est, je trouve significatif de difficultés avec le format que tu trouve si problématique les règles suivi par la communauté.

                    C'est typiquement le genre de ticket, si ça arrive, sera fermé dans la foulée. Parce-que le souci n'est pas Ansible mais YAML. Les points problématiques ont été listés
                    https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html#gotchas
                    Et ma remarque est qu'il vaut mieux apprendre la règle générale des chaînes de caractères, plutôt que son cas spécial (certes sympa et simple d'apparence mais avec plein de particularités à connaître) et environ une dizaine d'exceptions.

                    “It is seldom that liberty of any kind is lost all at once.” ― David Hume

                    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                      Posté par  . Évalué à 2.

                      C'est typiquement le genre de ticket, si ça arrive, sera fermé dans la foulée. Parce-que le souci n'est pas Ansible mais YAML. Les points problématiques ont été listés

                      Parce qu'il est interdit de discuter des bonnes pratiques conseillées et appliquées par le projet ? Si c'est un sujet troll dans le projet, c'est dommage que la description des bonnes pratiques soient péremptoire plutôt que de refléter l'indécision (ce qu'elle fait au sujet de l'arborescence d'un playbook).

                      C'est un vrai sujet qui concerne le projet qui comme tu le dit ça pose régulièrement des difficultés sur les forums.

                      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

                      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                        Posté par  (site web personnel, Mastodon) . Évalué à 3.

                        Arf, j'avais compris que tu parlais d'ouvrir un ticket sur une erreur dans ton YAML (j'en ai vu passer, et c'est fermé en renvoyé vers le forum utilisateur ce qui fait sens s'il ne s'agit pas d'un problème dans le code de l'outil lui-même.)

                        Pour discuter des bonnes pratiques, ça doit pouvoir se faire en étant sur le projet d'exemples de playbooks (pas celui du cœur ou de quelque autre module.) Mais comme il y a peu de gens (l'éternel problème de ressources), les issues ne semble pas lus et répondus ; il faudrait probablement arriver avec des PR… (et déjà éviter de fâcher yamllint avec sa configuration par défaut indiquera que que les fichiers modèles sont vraiment canoniques/modèles et non juste que ça fonctionne dans leur exemple mais que si quelqu'un fait une adaptation sans connaître toutes les subtilités de YAML ça va lui péter au visage.)

                        Entre temps, un travail impression a été fait dans l'appli pour la remonté des erreurs. Quand je comparer certains messages de la version 2.2 et de la version 2.8 y a pas photo.
                        D'ailleurs, cela me fait penser que le dépôt des exemples est obsolète parce-que n'ayant pas de branches pour chaque version majeure et que des exemples vrais pour une version peuvent se révéler plus trop juste pour une autre.

                        “It is seldom that liberty of any kind is lost all at once.” ― David Hume

                  • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                    Posté par  (site web personnel, Mastodon) . Évalué à 3.

                    Je vais remonter à mes collègues qu'il leur faut faire des efforts. :-)

                    Revenons à Ansible. Je n'ai pas l'impression que ça a mis 10 ans à avoir un outillage sympa pour les playbooks. Tu places la priorité sur LSP alors qu'il y avait bien plus urgent à faire et que comme d'habitude il n'y a pas dix milles personnes qui bossent dessus. Rien que le travail sur la documentation me semblait plus important …parce-que des modules dont tous les paramètres n'étaient pas listés (c'est rare mais j'en avais vu) ou dont on ne comprenais pas les subtilités ça n'aide pas à faire le boulot en ayant un IDE aux petits oignons, alors qu'à l'inverse tu peux avancer sur la gestion de ton infra même si tu n'as pas un simili confort. Avoir rajouté les retours dans les docs est vachement plus utile et fait gagner du temps comparer à devoir déboguer chaque sortie pour pouvoir exploiter rapidement ce que tu register par exemple. Il y avait un certain nombre d'incohérence être certains modules, dans les sorties générés, etc., et il était plus urgent de corriger ces points que de faire de la coloration syntaxique sur un truc bancal. Je n'ai vraiment pas eu l'impression que les devs se sont tourné les pouces mais bon.
                    Colorier du Jinja me semble anecdotique parce-que le vrai souci est ailleurs d'une part, et que la coloration pour du .y(a)ml suffit en général (tu l'as vu par exemple avec l'exemple que tu as posté ici.) Et c'était ça le point que je soulevais, pas que je me suis fait mon outillage et tant pis pour le reste (d'ailleurs, j'ai bien dit que j'utilisais mon Vim avec ses fonctionnalités de base et sans ajout spécifique.)

                    “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (Mastodon) . Évalué à 5.

        • les modules en général sont des putain de chats. Par exemple le module lvm exigera qu'on lui décrive tout pour qu'il accepte de créer un LV, dans un VG qu'il veut créer avec des PVs qu'il veut connaître… mais je veux pas lui dire ça, l'OS est dans un VG, je veux pas qu'il y touche, je veux juste qu'il accepte de créer un LV…

        En même temps ça évite de faire des bêtises, tout comme le fait de ne pas déclarer deux fois la même ressource.

        Puppet est un très mauvais outil pour déployer un truc par dessus de la merde fait à la main. Il est fait pour gérer ses machines comme des troupeaux, pas comme des animaux de compagnie.

        Si tes machines sont gérées en troupeaux, elles auront toutes un VG qui a un nom déterminé, donc ça ne pose pas de problème qu'il soit déclaré explicitement. Et après tu utilises hiera ou tout autre node classifier pour gérer les différences entre les groupes de machines ou des exceptions.

        C'est quand même bien plus facile de faire de l'idempotence avec Puppet qu'avec Ansible (pas testé chef et salt je n'ai fais que lire un article sur ce dernier il y a de nombreuses années). Ansible c'est juste bien pour remplacer des scripts bash de déploiement par dessus des trucs pas forcément bien gérés/administrés et ça plait beaucoup aux sysadmins du dimanche parce qu'ils peuvent continuer à faire de la merde comme avant, sauf que c'est en YAML donc ils peuvent dire qu'ils sont Devops et prétendre à un salaire plus élevé parce que les RH adorent ce mot.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 5.

          Puppet est un très mauvais outil pour déployer un truc par dessus de la merde fait à la main. Il est fait pour gérer ses machines comme des troupeaux, pas comme des animaux de compagnie.

          Cas concrêt : j'ai un serveur sur lequel un disque flanche. Je profite de l'occasion pour ajouter une paire de disques, et en synchro avec mon fournisseur je fais l'intervention logicielle quand il fait la partie hard.
          Dans puppet, il faut que je maintienne en continu ma liste de PV. Le VG est là et ne bouge pas, mais non, il exige de connaître les PVs, et va donc devenir une nuisance pour l'intervention, voire, en cas d'intervention d'urgence le 1er janvier à 4H du matin, un danger si on oublie de le désactiver…
          J'aimerais lui dire «notre contrat à tous les deux : je te file une machine avec le VG machin». Mais il exige d'aller jusqu'au bout, savoir la constitution du VG. Et là c'est pas possible.

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (Mastodon) . Évalué à 4.

            Cas concrêt : j'ai un serveur sur lequel un disque flanche. Je profite de l'occasion pour ajouter une paire de disques, et en synchro avec mon fournisseur je fais l'intervention logicielle quand il fait la partie hard.
            Dans puppet, il faut que je maintienne en continu ma liste de PV. Le VG est là et ne bouge pas, mais non, il exige de connaître les PVs, et va donc devenir une nuisance pour l'intervention, voire, en cas d'intervention d'urgence le 1er janvier à 4H du matin, un danger si on oublie de le désactiver…

            puppet agent --disable "pv change" pour le désactiver, sinon quoi, Si ton PV d'origine n'est plus la il va se vautrer et rentrer en erreur et le lendemain matin tu vas corriger ta config.

            Du reste ça prend 2 minutes de mettre le nouveau pv dans hiera ou ton external node classifier, git commit + push compris. Si tu as l'habitude de travailler avec puppet, tu ne fais de toute façon plus grand chose à la popogne sur un serveur donc c'est devenu un automatisme de penser "puppet" avant tout autre chose et de faire un test en -noop après chaque intervention manuelle.

            Dans puppet, il faut que je maintienne en continu ma liste de PV.

            On ne peut pas dire que les PVs soient l'exemple du truc qui varie souvent.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 4.

          Et après tu utilises hiera ou tout autre node classifier pour gérer les différences entre les groupes de machines ou des exceptions.

          J'avoue ne pas comprendre : tu peux avoir des inventaires automatiques ou des inventaires statiques ; avec des notions de groupes hiérarchisés entre eux (idem pour la gestion des varibles). Tu peux avoir cette faculté directement au sein d'Ansible. Je ne dis pas que c'est mieux qu'un autre applicatif de la même famille, mais en soi je retrouve ce potentiel dans Ansible, sans l'ombre d'un problème.

          Ansible c'est juste bien pour remplacer des scripts bash de déploiement par dessus des trucs pas forcément bien gérés/administrés (…)

          Si tu navigues sur Ansible-Galaxy, tu trouveras du sale. Comme tous les produits applicatifs de la planète, qui ont l'équivalent d'une place de marché aux modules.

          A titre perso, je suis plutôt content que l'outil soit permissif sur les machines à qui il s'adresse. De plus ramener Ansible à un script Bash, comme cela a été dit dans les autres commentaires, c'est oublier la notion de description d'un état désiré lors de l'appel à un module - le fonctionnement normal - plutôt qu'un script impératif avec beaucoup d'embranchements.

          Derrière le code exécuté peut être le même (au sens où l'action réalisée serait la même), mais l'orchestration, la maintenance et les déploiements sont radicalement différent. Ce sujet est en préambule de la doc officielle de l'outil.

          Cela permet une généralisation très forte, en segmentant le module comme une opération ou un ensemble limité d'opérations dans un but déterminé. Les rôles permettent encore de produire des ensembles qui seront contextualisés par les playbooks. Ce but peut être technique ou davantage "métier".

          (…) et ça plait beaucoup aux sysadmins du dimanche parce qu'ils peuvent continuer à faire de la merde comme avant, sauf que c'est en YAML donc ils peuvent dire qu'ils sont Devops et prétendre à un salaire plus élevé parce que les RH adorent ce mot.

          Jugement de valeur non ? Les salariés de chez RedHat, c'est tous des sysadmins du dimanche ?

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (Mastodon) . Évalué à 2.

            Jugement de valeur non ? Les salariés de chez RedHat, c'est tous des sysadmins du dimanche ?

            Non mais que ils doivent pouvoir fournir un livrable fait pour s'installer sur n'importe quelle brêle mal administrée de leur clients.

            Raison pour laquellr chez mon précédent employeur on utilisait souvent un mix d'ansible et puppet. Puppet pour tout ce qui était non négociable et obligatoire, ansible pour tout ce qui était optionnel.

    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

      Posté par  (site web personnel) . Évalué à 7.

      C'est un mauvais langage de script parce que c'est justement pas censé être un langage de script. C'est comme dire qu'une Ferrari F50 est un mauvais véhicule pour transporter ses meubles.

      Sinon, il y avait déjà des outils comme fabric ou tu prends directement python, il n'y avait pas besoin de refaire la roue. Ou pour plus moderne, Bearstech a écrit Nuka.

      Limiter ce que l'outil permet est une décision consciente, que les devs n'aiment pas, mais curieusement, les devs ne se jettent pas sur les alternatives cités plus haut qui n'ont pas de limitation sur le script. C'est un peu tout le concept de "revelead preference".

      Ensuite, quand à yaml par rapport à un DSL qui plairement mieux à quelqu'un, je suis sur que ça changerait rien vu que si on arrive pas à se mettre d'accord sur les languages de script, un de plus ne va rien changer. Autant dire que c'est pas trés utile comme remarque.

      Par contre, ce qu'on peut regarder si on veut avoir une remarque utile, c'est l'impact de prendre yaml par rapport à un DSL custom. Par exemple, utiliser yaml permet de fairel'analyse statique de façon assez trivial. Exemple, tu peux avoir un règle de d'abord installer les paquets, et de lancer le service à la fin. Si tu as un DSL maison comme puppet/salt ou un language complet (chef, par exemple), tu dois refaire un parser. En utilisant quelque chose comme yaml/toml/xml/json, un dev normal va pouvoir le faire, pour des scripts de post commits, etc.

      Quand à "ansible a besoin de code", ouais, réinventer la roue n'est pas toujours une solution. Tu peux essayer d'aller convaincre l'upstream python de mettre le support de maven dans la lib standard ceci dit.

      On pourrait aussi noter qu'en prenant une solution intégré avec aucun OS comme maven, on se retrouve à devoir payer ce coût de non intégration à chaque fois. Si les paquets java avaient repris le format rpm, ou simplement pip, la question ne se poserait pas.

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  . Évalué à 0.

        C'est un mauvais langage de script parce que c'est justement pas censé être un langage de script.

        C'est exact. Corrigeons : bash est un excellent outil d'automatisation, ansible est mauvais.

      • [^] # Commentaire supprimé

        Posté par  . Évalué à 2.

        Ce commentaire a été supprimé par l’équipe de modération.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 4.

          C'est un mauvais langage de script parce que c'est justement pas censé être un langage de script.

          Ça ce discute. À partir du moment où cela gère des instructions, des variables, des conditions, des entrées et des sortie, je ne vois pas ce qui le différencie d'un langage de programmation.

          En soi Ansible ne gère rien, sinon du déclaratif : on lui donne une liste d'états désirés, et ce sont les modules (Python, PS1, ou autres) qui font le taff. La machine distante (qui peut être localhost à l'occasion), renvoie des états post-opération, et Ansible va donc superviser la suite en fonction de ce retour.

          Ansible n'a même pas connaissance des valeurs réelles sur les machines, vu qu'il envoie une charge utile à la machine distante et qu'il n'intervient donc pas durant cette phase (à de rares exceptions près, mais subsidiaires).

          Bash peut faire le même travail effectivement : tu gères tes accès à la machine distante, les commandes à envoyer, etc. Mais un dry run ou des opérations un peu complexes (au sens "métier" j'entends), dans une phase d'industrialisation du SI, c'est inimaginable avec Bash. Non que ça soit impossible - par Bash ou par le langage x -, mais parce que c'est juste imbitique par conception, car pas la finalité première.

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (site web personnel) . Évalué à 5.

        Limiter ce que l'outil permet est une décision consciente, que les devs n'aiment pas, mais curieusement, les devs ne se jettent pas sur les alternatives cités plus haut qui n'ont pas de limitation sur le script.

        C'est rarement un choix fait par les devs :-)

        Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 1. Dernière modification le 24 mars 2022 à 23:22.

          C'est rarement un choix fait par les devs :-)

          Ne me jetez pas la pierre, faut bien que je justifie mon salaire… ;)

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (Mastodon) . Évalué à 5.

        Sinon, il y avait déjà des outils comme fabric

        Tu viens de me faire découvrir un truc. J'ai des besoins ponctuels de reproduire des commandes simples sur une liste de cibles, et les quelques fois où je me suis motivé à apprendre Ansible… bin ça n'a pas bcp duré et j'ai fini soit par un script Bash soit par le faire à la main.

        Là j'ai vraiment l'impression d'avoir trouvé mon bonheur.

        En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel, Mastodon) . Évalué à 2.

          J'ai des besoins ponctuels de reproduire des commandes simples sur une liste de cibles,

          Pour ça (balancer des commandes sur une liste de machines), il y a encore plus simple depuis des lustres (en tout cas existaient longtemps avant Fabric) :

          et j'ai fini soit par un script Bash soit par le faire à la main.

          Avec l'utilitaire parallel pour ne pas faire du séquentiel ? Ou diverses astuces et de l'huile de coude ? https://www.baeldung.com/linux/processing-commands-in-parallel

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (Mastodon) . Évalué à 2. Dernière modification le 25 mars 2022 à 07:31.

            Avec l'utilitaire parallel pour ne pas faire du séquentiel ?

            Même pas, c'est ponctuel, je ne fais pas la course à la performance, le sequentiel me va très bien !

            En gros :

            for h in $hosts
            do
              ssh $h $cmd
            done

            Mais bon, dès que tu dois gérer les erreurs ça devient vite bordélique.

            En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

            • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

              Posté par  (site web personnel, Mastodon) . Évalué à 4.

              je ne fais pas la course à la performance, le sequentiel me va très bien !

              T'inquiètes, je fais parfois ce genre de boucle. C'est juste que le séquentiel c'est bien ici quand le nombre de machines se compte sur le bout des doigts et que la commande ne prend pas une plombe (ça c'est un autre souci mais qui entre en compte proportionnellement au nombre d'hôtes.)
              Le parallélisme offert par ces outils est intéressant quand on doit faire de la masse (ou grande échelle) mais est quand même limité pour ne pas pomper toutes les ressources (déjà sur la machine d'exécution, il est bon de limiter le nombre de sous-processus fork en fonction de la RAM et du CPU, et ne pas oublier que plus on a de connexions établies plus on consomme de bande passante à l'instant et au détriment des autres)

              Et du coup, pour répondre à

              et les quelques fois où je me suis motivé à apprendre Ansible

              Pour faire la même chose avec Ansible, avec ta liste de $hosts définie dans un fichier $hostslist (comme pour les autres outils mentionnés, avec une machine par ligne) :

              ansible '*' -i "$hostlist" -m command -a "$cmd"

              Il est possible d'utiliser ta liste $hosts sans passer par un fichier, mais ce n'est pas la méthode préconisée (et pas bien documentée mais on trouve la trace quelque part dans la documentation officielle.)

              Petit détail quand même qui peut avoir son importance, c'est bon pour des commandes toutes simples car les redirections et tubes ne seront pas permises ainsi. Pour faire sauter ces garde-fous, c'est encore un autre module.

              ansible '*' -i "$hostlist" -m shell -a "$cmd"

              Il y a un troisième encore plus permissif que je n'évoquerai pas.
              Et sinon, bien entendu faire gaffe aux variables d'environnement et ce genre de choses surprenantes.
              En tout cas tu as maintenant le mode d'emploi pour la prochaine fois. Bien que, comme dit ailleurs, je trouve qu'il y a peu d'intérêt à te farcir Ansible si c'est juste pour ce genre de besoin ponctuel. Et parlant d'ailleurs, tu peux rajouter le tricorder (pas de rapport avec ST…) à la liste

              “It is seldom that liberty of any kind is lost all at once.” ― David Hume

              • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                Posté par  (Mastodon) . Évalué à 3.

                Pour faire la même chose avec Ansible […]

                Hop ! C'est noté dans mon Joplin qui me sert de mémo :)

                Merci !

                En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (Mastodon) . Évalué à 7.

        Ah mais Yaml, couplé avec Jinja2, c'est le PHP des années 2000 all-over-again.
        PHP c'est - à l'origine - un préprocesseur de HTML.
        Sauf que c'est incidemment un langage turing-complet.

        Et on est passé du templating sympa au templating crados pour dépasser le templating, puis à l'application web mal foutue bourrée de trous et impossible à maintenir, et enfin aux applications PHP modernes qui sont quand même mieux faites, mais n'ont rigoureusement plus rien à voir avec le pré-processing des origines.

        Et là, quand on fait du Ansible avancé, on se retrouve à voir des trucs qui ressemblent au passage du templating sympa, au templating crados.
        Par pur chance on a les modules, généralement en Python, qui permettent de ralentir le passage à l'application ansible mal foutue fondée sur un langage de templating descriptif…

        Et franchement, faire de la programmation en Yaml/Jinja2 c'est du pur masochisme, sadique pour le reste de l'équipe.
        Sauf qu'il faut trouver le bon curseur entre complexificatifier son module pour gérer plein de cas, et traiter des cas différents depuis un bout de programmation Yaml/Jinja2, ce qui n'est pas toujours d'une complète évidence.

        Au bout du compte, dès qu'on commence à vraiment gérer des choses compliquées en Ansible, c'est terriblement difficile à lire et à maintenir :(
        Alors passer des arguments depuis un Job Jenkins à un playbook Ansible, qui appelle des rôles, et eux-mêmes des modules internes, c'est un champs de mine terrifiant à débugger pour savoir pourquoi cette p$£*% de variable a pas la bonne valeur là, bigre !

        En plus j'ai une question subsidiaire :
        La syntaxe du Python est basée sur l'indentation, et ça rend presque forcément le code lisible, qu'on aime ou pas, le code Python se ressemble et avec de l'habitude on arrive même à lire le code de Pierre T.
        La syntaxe de Yaml est basée sur l'indentation, et ça rend presque forcément le résultat illisible, qu'on aime ou pas, le code Yaml se ressemble, et même avec de l'habitude il faut s'y reprendre à plusieurs fois pour bien savoir quelle est la structure représentée.
        Pourquoi ?
        Ou plutôt : Comment avoir autant raté son design ?
        Comment réussir au final à être moins lisible que du bête JSON qui n'impose rien en terme de présentation du code ?

        Bref, Ansible, si je pouvais m'en passer, ça serait volontiers.
        cela dit, le journal est clair et bien présenté, j'approuve totalement, merci JulienG !

        • Yth.
        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  . Évalué à 7.

          cela dit, le journal est clair et bien présenté, j'approuve totalement, merci JulienG !

          De rien :)

          Sauf qu'il faut trouver le bon curseur entre complexificatifier son module pour gérer plein de cas, et traiter des cas différents depuis un bout de programmation Yaml/Jinja2, ce qui n'est pas toujours d'une complète évidence.

          Beaucoup de débats sur Ansible dans les commentaires, plus que j'aurais pensé. Je pense faire un journal supplémentaire sur ce sujet (état désiré vs programmation) dès ces prochains jours.

          La syntaxe de Yaml est basée sur l'indentation, et ça rend presque forcément le résultat illisible, qu'on aime ou pas, le code Yaml se ressemble, et même avec de l'habitude il faut s'y reprendre à plusieurs fois pour bien savoir quelle est la structure représentée.
          Pourquoi ?
          Ou plutôt : Comment avoir autant raté son design ?
          Comment réussir au final à être moins lisible que du bête JSON qui n'impose rien en terme de présentation du code ?

          Idem, ça nécessite un journal en soi pour répondre avec exhaustivité.

          Chaque format a été prévu avec un objectif - comme toute chose -, et souvent la créature dépasse le créateur. L'usage du Yaml pour Ansible, me semble intimement lié à cette approche d'état désiré. Cependant ce n'est là qu'un avis tout personnel.

          Pour le JSON justement, ce n'est pas prioritairement un langage de représentation "humain" (fusse pouvant être lu par lui), qu'un langage d'échange entre app'. Tu n'as pas non plus la complexité d'un XML : le JSON n'a peu de types, fixés à l'avance, aucune notion de définition d'élément, etc. Cette simplicité au moins apparente, le rend pour beaucoup plus lisible que le XML (et le LISP par définition).

          Le Yaml a tenté le compromis entre le peu de types à la JSON, mais la représentation lisible par l'homme à la Python - ce qui conduit à une complexité effectivement.

          Et franchement, faire de la programmation en Yaml/Jinja2 c'est du pur masochisme, sadique pour le reste de l'équipe.

          Ces derniers mois j'ai fait plus de la moitié de mon temps pro sur un projet complexe d'intégration en Ansible. Zéro (0, mais vraiment 0) "programmation" au sein de Jinja : pas d'usage de fonctions, pas de Python dissimulé, etc.

          Étant Tech Lead, j'ai pu par contre imposer à l'équipe - et elle ne s'est pas plainte, loin de là -, l'obligation d'avoir des modules dédiés pour tout (rien en Shell dans le Yaml). Le Yaml est donc là pour porter l'état désiré, jamais une commande (je bannis l'usage du module "Shell / Win_Shell"). Si un module n'existe pas, on le créé (Python ou PS1, selon la destination).

          De fait je peux t'assurer que toute la complexité est ramené à des scripts (modules) parfaitement clairs et correctement découpés, avec Ansible pour les appels et l'orchestration.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel, Mastodon) . Évalué à 4.

          Ah mais Yaml, couplé avec Jinja2, c'est le PHP des années 2000 all-over-again.
          PHP c'est - à l'origine - un préprocesseur de HTML.
          Sauf que c'est incidemment un langage turing-complet.

          Je me permets de ne pas être entièrement d'accord.
          Ce n'était pas un préprocesseur au sens des SSG (i.e. Statics Site Generator comme Hugo et Jeckill pour les plus connus) et autres convertisseurs de Markdown (ou autre en HTML ou XHTML).
          C'était un générateur de pages web et un outil de traitement de formulaires comme les CGI (i.e. Common Gateway Interface qui étaient souvent écrit en PERL mais aussi en C ou en Bash —j'en ai fait) : le terme préprocesseur est utilisé parce-que le serveur web devait le lancer avant de passer le résultat généré au client web. Il est normal que ce soit un langage de programmation complet (ou plutôt le devienne à partir de sa version 3) : on a la même chose avec Python ou Ruby pour faire des sites web « all-over-again »

          YAML c'est de la bête description, comme on ferait avec du SGML (les pages purement HTML, sans scripting dynamique, décrivent du contenu et ne sont pas des langages de programmation) ou du XML (c'est pour décrire et anoter des données, faut normalement se tourner vers XLT pour les manipuler et une feuille de style pour bien afficher les données.)
          Jinja2 c'est un moteur de template relativement simple, dans la lignée de Mustache : ça permet de générer du contenu en y plaçant des données tirées d'un dictionnaire JSON ou YAML (ce qui en fait un choix cohérent, en plus du fait que ce fut d'abord conçu pour l'écosystème Python même si le principe a été rapidement repris dans d'autres langages parfois avec un nom différent qui peut se justifier par les adaptations au langage –et donc éviter les confusions…) Ce n'est pas un langage de programmation (c'est pourquoi je dis que c'est simple et simplet, en tout cas comparé à d'autres avant lui) et beaucoup s'en sont plaint. Bon, il y a quand même une toute petite logique (boucle for et structure if) qui est reprise du langage (Python ici, mais chaque langage l'ayant adopté adapte cette partie à sa syntaxe) …mais ce n'est pas ce qui va permettre d'écrire une appli et il est un peu surfait de comparer ça à PERL/PHP/Python/Ruby/etc.

          Et franchement, faire de la programmation en Yaml/Jinja2 c'est du pur masochisme, sadique pour le reste de l'équipe.
          Sauf qu'il faut trouver le bon curseur entre complexificatifier son module pour gérer plein de cas, et traiter des cas différents depuis un bout de programmation Yaml/Jinja2, ce qui n'est pas toujours d'une complète évidence.

          Comme utiliser une syntaxe de description et un système de templating n'est pas faire de la programmation, c'est en effet masochiste. Encore une fois (je ne le dis pas pour toi mais juste par rapport aux autres commentaires que j'ai écrit et qui me donnent l'impression de me répéter), ce (couple) n'est pas un langage de programmation et on ne fait pas de codage avec l'outil ! Arrêtons de vouloir planter des clous avec des tournevis…

          Les modules n'ont pas non plus à être (très) complexes : il faut garder la philosophie Unix de faire KISS, fonctionnel et répondant aux cas identifiés …quitte à avoir plusieurs modules (une tâche un outil) ; exemple que donne le projet (il y a le ping pour le manchot et le diablotin, mais win_ping pour la Fenêtre ; il y a un module par type de gestionnaire de paquets…)
          Dès que l'on commence à de la « programmation Yaml/Jinja2 » de Sioux (les cas de contrôle prévus ne sont pas vraiment de la programmation) c'est signe qu'on : utilise mal l'outil, que le problème est mal posé, etc.

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel, Mastodon) . Évalué à 4.

          En plus j'ai une question subsidiaire :
          La syntaxe du Python est basée sur l'indentation, et ça rend presque forcément le code lisible, qu'on aime ou pas, le code Python se ressemble et avec de l'habitude on arrive même à lire le code de Pierre T.
          La syntaxe de Yaml est basée sur l'indentation, et ça rend presque forcément le résultat illisible, qu'on aime ou pas, le code Yaml se ressemble, et même avec de l'habitude il faut s'y reprendre à plusieurs fois pour bien savoir quelle est la structure représentée.
          Pourquoi ?
          Ou plutôt : Comment avoir autant raté son design ?
          Comment réussir au final à être moins lisible que du bête JSON qui n'impose rien en terme de présentation du code ?

          Je rencontre pas mal de code Python pas aussi lisible qu'on nous le vend, tout comme on peut retrouver la même structuration dans d'autres langages qui ne l'imposent pas, Mais là n'est pas le débat.
          Je lis le YAML avec la même difficulté/facilité que Python, du coup j'ai du mal à comprendre comment l'un serait illisible et l'autre lisible alors qu'il n'y a pas de différence conceptuelle. Pire, dire que JSON est plus lisible me laisse perplexe.

          Ceci est du JSON

          {"ints":{"odd":[1,3,5],"even":[2,4,6]},"str":"foo\nbar","ok":true}

          Un autre commentaire se plaignait des lignes interminables et des \n pourtant c'est bien ce que tu trouves plus lisible…
          Ceci n'est pas du JSON comme l'indique https://www.json.org/json-en.html ; il s'agit d'une/un représentation/formatage pour tenter de le rendre digeste (c'est fait par un formatter et il faut le rectifier en retour pour que l'application qui va le désérialiser l'accepte et surtout le traite bien…)

          {
             "ints":{
                "odd":[
                   1,
                   3,
                   5
                ],
                "even":[
                   2,
                   4,
                   6
                ]
             },
             "str":"foo\nbar",
             "ok":true
          }

          Par contre, cette représentation est bien du YAML que tu trouves illisible… (enfin, pour être canonique faudrait rajouter le « document start » au début)

          Comme c'est prévu pour être lu et écrit par les humains, et ne pas être « developpers centrist » on peut alternativement juste écrire (et penser à corriger les indentations) :

          ---
             "ints":
                "odd": [
                   1,
                   3,
                   5
                ]
                "even": [
                   2,
                   4,
                   6
                ]
             "str": "foo\nbar"
             "ok": true

          Est-ce le fait que ce soit à portée des non geeks qui le rend compliqué ? Parce-que ce qui a été fait por les hash/map/dict put s'appliquer aussi aux listes/tuples (une des syntaxe de liste de Markdown et d'autres) :

          ---
          "ints":
             "odd":
                - 1
                - 3
                - 5
             "even":
                - 2
                - 4
                - 6
          "str": "foo\nbar"
          "ok": true

          C'est vrai que c'est gonflé de se mettre à la portée des gens lambdas, parce-que même true peut s'écrire True ou yes, et que avec juste un mot avec ces chaîne de caractères ne prêtant pas à confusion on peut carrément se passer de guillemets (c'est même recommandé, comme ça on ne met pas d'identifiant aevc des symboles problématiques –sous cette forme, pas de deux-points par exemple et les linters refusent les espaces et tout ce qui n'est pas lettre ou chiffre)

          ---
          ints:
             odd:
                - 1
                - 3
                - 5
             even:
                - 2
                - 4
                - 6
          str: "foo\nbar"
          ok: yes

          Sapristi, c'est devenu trop simple (ou plutôt trop compliqué pour Yth et devnewton.)
          Pourtant ça semble vachement pythonique (tant qu'on n'utilise pas de truc exotérique) et on peut même mettre des commentaires dis donc. Ça devient trop compliqué en ne ressemblant plus à du tout C…

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (Mastodon) . Évalué à 3.

            Trop simple, trop compliqué…
            Ton exemple est trop simple pour représenter un cas réel.
            La réalité te confronte rapidement à des structures de données un poil plus complexe qu'un dictionnaire de deux listes de trois éléments, avec deux clé/valeur au bout.

            Et là… Là ça devient très vite illisible.
            Mais en vrai, du Python avec de forts degrés d'indentations et sans découpage du code, c'est insupportable aussi. En fait n'importe quel langage.
            Et pour le coup une syntaxe ouvrante/fermante est plus lisible (même si parfois marginalement).

            Sauf que, pour des états, des structures de données, la complexité peut être normale, alors que dans du code source, la normalité est de faire des fonctions, de la factorisation, pour garder des ensembles cohérent de code assez petits, et sans trop de niveaux d'imbrication.

            Et un include task bidule en yaml ansible, qui exploite des variables héritées sans prendre de paramètres, te donne rapidement un sac de fichiers imbitables à dérouler.
            Donc tu fais quoi ? Tu essaies d'être plus explicite, de passer des paramètres à tes sub-tasks, de faire de boucles, … de programmer ta description d'état…

            Et tu sors du templating descriptif pour entrer dans le templating crados.
            Et là parfois tu te demandes bien pourquoi tu n'es pas en train de coder directement en Python.
            La différence entre :

            tasks:
            - name: "install Apache server"
            yum:
            name: "httpd"
            state: latest

            Et :

            tasks(name="install Apache server").yum(name="httpd", state="latest")

            Est moins évidente, mais permettrait d'éviter la « programmation en Jinja2 ».

            Mais bref, je trouve que c'est une dérive qu'on retrouve un peu trop dans Ansible, un peu sur le principe de quand on a un marteau, tous les problèmes ressemblent à des clous, si on a déjà une infra Ansible en place, on a envie de tout faire en Ansible.
            Et cette dérive me fait penser au PHP des années 2000.
            Le PHP des années 2000 a abouti au PHP 8 d'aujourd'hui, qui est efficace et fonctionnel, rien à dire de ce côté là, donc ce n'était pas forcément un mal en soi, juste une phase d'adolescence brouillonne et qui a laissé des traumatismes.

            Mais quelle adolescence aujourd'hui dans Ansible, qui a clairement dépassé le stade de la prime jeunesse ?
            Quelle stabilité sera conquise dans le futur, quelle évolution attendre ?
            Puisque le côté outil simple et descriptif d'un état clairement énoncé ne suffit pas (sinon on n'aurait pas les dérives que je décris, et décrie), quelle est la réponse ?

            Ansible doit-il se contenter d'être un outil de description d'état désiré, ou doit-il évoluer vers un outil de programmation distante comme on peut le voir de plus en plus ?
            Et s'il évolue vers un outil de programmation distante, quelle évolution du couple yaml/Jinja2 ? Un autre format plus adapté à côté, ou des hacks imbitable qui finiront par donner un langage de programmation originellement pensé pour faire du templating, et qui deviendra utilisable dans 10 ans ?

            Le parallèle avec PHP 3 se fait exactement ici, on est dans la période brouillonne et crados, et on est en train de construire les traumatismes futurs.
            Mais quel futur est souhaitable ? Quel futur sera construit ?

            • Yth.
            • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

              Posté par  (site web personnel, Mastodon) . Évalué à 4. Dernière modification le 27 mars 2022 à 03:27.

              Mais en vrai, du Python avec de forts degrés d'indentations et sans découpage du code, c'est insupportable aussi. En fait n'importe quel langage.

              Je préfère déjà :-D Ce n'est plus Y est râté et P est mieux alors qu'ils font la même.

              Et pour le coup une syntaxe ouvrante/fermante est plus lisible (même si parfois marginalement).

              Qu'entends-tu par « syntaxe ouvrante/fermante » ? Les formatages à la C par oppositions aux formatages par indentation ?

              Sauf que, pour des états, des structures de données, la complexité peut être normale, alors que dans du code source, la normalité est de faire des fonctions, de la factorisation, pour garder des ensembles cohérent de code assez petits, et sans trop de niveaux d'imbrication.

              Ansible est allé dans ce sens avec les roles et différentes include
              Mais intrinsèquement, les structures de données sont complexes et les host_vars/group_vars permettent un certain découpage (et pourtant je continue à en voir qui foutent tout dans un fichier d'inventaire ini qui contient déjà une ou deux centaines de machines)

              […] Et là parfois tu te demandes bien pourquoi tu n'es pas en train de coder directement en Python.
              […] Est moins évidente, mais permettrait d'éviter la « programmation en Jinja2 ».

              C'est souvent que le problème est mal posé ou son approche l'est. (Parfois j'ai l'impression que les gens pratiquent le « pourquoi faire simple quand on peut faire compliqué ? » et c'est encore plus vrai quand on se met martèle en tête que Ansible est un langage de programmation …sauf que là c'est le début du masochisme)
              Mais il y a des cas effectivement pas simples qui indique qu'on a atteint les limites d'Ansible de base : il faut soit créer ses propres modules ou y aller directement dans le langage qu'on maîtrise, sinon on fait des playbooks difficiles à maintenir et qui en plus vont rapidement vous péter au visage.

              Dans ton exemple, il n'y a pas de Jinja2. Et l'alternative que tu préconises, indique que tu as plutôt besoin de Fabric ou Nuka ou similaire.

              Je suis bien d'accord avec ce qui suit :

              Mais bref, je trouve que c'est une dérive qu'on retrouve un peu trop dans Ansible, un peu sur le principe de quand on a un marteau, tous les problèmes ressemblent à des clous, si on a déjà une infra Ansible en place, on a envie de tout faire en Ansible.

              C'est d'autant plus dommage que l'utilisation d'Ansible ne t'enferme pas, et que l'esprit unixien veut qu'on utilise l'outil qui va bien dans l pléthore qu'on a à disposition. C'est toujours une erreur de faire une fixette sur un outil spécifique.

              Ansible doit-il se contenter d'être un outil de description d'état désiré, ou doit-il évoluer vers un outil de programmation distante comme on peut le voir de plus en plus ?

              Il y a un besoin qu'il rempli bien et il n'y a pas de raison de changer cela. Pourquoi vouloir que ça fasse le boulot d'autres outils ? On doit arrêter avec les fixettes windowsienne ; la seule évolution que j'attends perso de ces diverses solutions est qu'elles puissent (mieux) communiquer entre elles de sorte à pouvoir être complémentaires.

              “It is seldom that liberty of any kind is lost all at once.” ― David Hume

              • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                Posté par  (Mastodon) . Évalué à 1.

                Dans mon exemple il n'y a pas de Jinja2, non.
                Mais remplacer des bidouilles Jinja2 par du python rend les choses plus claires : moins de mélange de genre entre descriptif et impératif.
                Et dans les cas simples, ça ne change pas grand chose non plus, comme montré dans mon exemple : il faut quand même avoir une documentation des différents modules et de leurs paramètres, de la syntaxe générale, etc.

                Mais bon, mon questionnement ne tient pas tellement de comment il convient d'utiliser l'outil, mais plutôt d'une tentative de divination : comment ça va évoluer, au vu de comment c'est utilisé aujourd'hui ?

                • Yth.
              • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

                Posté par  . Évalué à 2.

                Et l'alternative que tu préconises, indique que tu as plutôt besoin de Fabric ou Nuka ou similaire.

                Je ne connais pas nuka et je réfléchis à utiliser fabric en ce moment(pour des usages , tu as un lien ? Mon moteur de recherche ne m'aide pas beaucoup.

                https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  . Évalué à 5.

        Tu décris des états, tu ne les codes pas (enfin, tu codes si tu écris toi-même les modules sous-jacent.)

        Je suis bien d'accord sur la notion d'état désiré. Pour la partie "pas coder dans le playbook", ce n'est hélas pas rare de le voir via les deux modules de l'enfer : "Shell" / "Win_Shell". Combien le font, par méconnaissance (ou flemme) ? On se retrouve avec des déploiements in-maintenables et pas d'idempotence…

        Probablement qu'une partie du problème vient de là, dans la culture et l'image que renvoient Ansible.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel, Mastodon) . Évalué à 4.

          C'est pour ça que je rappelle que ça n'impose rien (pour son malheur parfois/visiblement.) Même pour ces deux, il est clairement dit que c'est pour dépanner (cas d'une machine sans Python ou cas où il n'existerait pas de module adapté, deux points qui sont de moins en moins vrais aujourd'hui…) Même pour ces deux, le soin a été pris de donner les moyens de faire les choses proprement (et là on arrive au fait que je dis que c'est un cadriciel pour faire facilement de l'idempotence) mais rien n'est imposé (et du coup des sanguoins n'utilisent pas les conditionnements prévus – creates/removes/etc.)

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (site web personnel, Mastodon) . Évalué à 5.

        Petite correction : Salt-stack utilise aussi YAML et Jinja mais avec un paradigme un peu différent.

        Petit rappel : Ansible a été pensé pour utiliser des choses existantes, éprouvées et connues de ces auteurs (à un moment on fait un choix…) Donc

        • SSH comme transport au lieu de réinventer quelque avec toutes les contraintes, inconvénients et avantages. Mais ce choix a aussi ses bons et mauvais côtés qui sont connus et maîtrisés par les admins sys et la plupart des devs…
        • YAML comme langage de description d'états au lieu de se lancer dans un DSL maison. Cela permet d'avoir des recettes lisibles par des humains et auto-documentées tout en favorisant l'usage de JSON sous le capot…
        • Python comme langage pour les modules …parce-que justement c'est présent partout (y avait aussi l'alternative PERL mais comme il faut faire des choix et que ce n'était certainement pas connu des équipes qui ont créé l'outil, ou pas assez, en plus d'être un peu en perte de vitesse tandis que l'autre en gagne.)

        C'était (et ça reste encore) le plus simple. Tiens, Salt a fait les mêmes choix de briques/technos…

        “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel) . Évalué à 4.

          Petite correction : Salt-stack utilise aussi YAML et Jinja
          mais avec un paradigme un peu différent.

          Je dirais plus qu'un peu différent. Salt utilise un langage de template (jinja, mais d'autres sont dispos) qui va générer du yaml (ou d'autres). Tu as le choix des armes, et tu peux faire tout en python par exemple.

          À contrario, Ansible utilise du yaml avec parfois des bouts de jinja dans les chaines. C'est beaucoup plus encadré.

          SSH comme transport au lieu de réinventer quelque avec toutes
          les contraintes, inconvénients et avantages. Mais ce choix a
          aussi ses bons et mauvais côtés qui sont connus et maîtrisés
          par les admins sys et la plupart des devs…

          Ansible est en parti inspiré de Taboot, qui a lui même été fait pour aider à l'usage de Func. Le département IT de RH utilisait Taboot et Func il y a 10/15 ans pour l’exécution de commandes (en plus de Puppet), et Fedora utilisait Func/Puppet avant de passer à Ansible.

          Func avait son propre bus de messages, et Ansible pouvait l'utiliser via un plugin. Pour le fun, j'ai aussi écrit un plugin pour utiliser celui de Salt (vu que j'ai fait Ansible => Salt => Ansible sur un projet).

          Donc le choix de ssh a sans doute été fait en prenant en compte l'expérience acquise d'avoir un bus à maintenir.

          Python comme langage pour les modules …parce-que justement
          c'est présent partout (y avait aussi l'alternative PERL mais
          comme il faut faire des choix et que ce n'était certainement
          pas connu des équipes qui ont créé l'outil, ou pas assez, en
          plus d'être un peu en perte de vitesse tandis que l'autre en
          gagne.)

          RH, à l'époque ou Michael Dehaan y a bossé (eg, 2008/2010) était 4 à 10 fois plus petite que maintenant, et fortement orienté python. Les projets de l'époque comme Yum, Anaconda, Bodhi, Func, c’était du python.

          Ensuite, on peut faire des modules en perl ou en bash. Ou des binaires directement en go si on veut. C'est juste que l'upstream ne va pas accepter le code dans le dépôt principal pour des questions de maintenance.

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel, Mastodon) . Évalué à 4.

            Merci beaucoup pour ces précisions (moi qui m'intéresse à l'histoire de l'évolution des technos, dans un certain sillage, tu m'as plus que ravi.)

            Je me souviens de Func, pour avoir joué avec rapidement à titre personnel (j'expérimente pas mal et j'avais testé beaucoup de choses avant d'arrêter mon choix sur Ansible alors en 1.8.etquelque à l'époque.)
            C'est vrai que dans le fonctionnement de la commande func ça ressemble pas mal à ansible ad-hoc comme ils disent.
            Tiens, le certmaster rappelle l'enrôlement avec Puppet (coucou puppet-cert) ou SaltStack (coucou salt-key) et d'autres. Remarque, on fait pareil sauf que c'est délégué (ssh-keyscan -H par exemple, ou directement ssh-copy-id etc.)

            Je n'ai pas connu Taboot. Mais effectivement, leur premier exemple a l'air très familier ;-)

            - hosts:
                - 'java0*.web.qa.*'
                - 'someotherhost'
              concurrency: 2
              tasks:
                - yum.Update:
                    packages:
                      - some-rpm-package-name
                - service.Restart
                    service: httpd

            Cela devient aujourd'hui (en restant au plus proche)

            - hosts:
                - 'java0*.web.qa.*'
                - 'someotherhost'
              serial: 2
              tasks:
                - yum:
                    name:
                      - some-rpm-package-name
                    state: latest #update
                - service
                    name: httpd
                    state: restarted #restart

            Par contre, ils font l'erreur de parler de YAMLscript, ce qui devait déjà amener des gens à vouloir « programmer » avec…
            Et sinon, je vois qu'effectivement il était prévu de l'utiliser avec Puppet et Nagios, qui sont des citoyens de premier ordre comme Yum et AJP.

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

          • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

            Posté par  (site web personnel, Mastodon) . Évalué à 6. Dernière modification le 27 mars 2022 à 20:53.

            Petite correction : Salt-stack utilise aussi YAML et Jinja
            mais avec un paradigme un peu différent.

            Je dirais plus qu'un peu différent. Salt utilise un langage de template (jinja, mais d'autres sont dispos) qui va générer du yaml (ou d'autres). Tu as le choix des armes, et tu peux faire tout en python par exemple.

            J'oublie souvent qu'il est possible d'utiliser autre chose (certainement parce-que je suis resté à l'usage de Jinja). Mais en prenant juste ces deux composants, je notais juste les deux approches sans entrer dans les détails.

            À contrario, Ansible utilise du yaml avec parfois des bouts de jinja dans les chaines. C'est beaucoup plus encadré.

            Exactement. Chez Red Hat (c'est l'actuel propriétaire), on fait du YAML avec des bouts de Jinja2 dedans. Cela a obligé à rajouter des mécanismes d'itération pour aller au delà des limitations de YAML.

            - name: "Ensure groups exist"
              group:
                name: "{{ item.key }}"
                gid: "{{ item.value.id }}"
              with_dict: users
            - name: "Ensure users exist"
              user:
                name: "{{ item.key }}"
                uid: "{{ item.value.id }}"
                group: "{{ item.key }}"
                groups: "foo,bar"
                comment: "{{ item.value.fn }}"
                shell: "/bin/bash"
              with_dict: users

            À l'inverse, chez VMware (c'est l'actuel propriétaire), on fait du YAML à partir de Jinja2 ; du coup pas besoin de rajouter d'équivalent des with_ par exemple.

            {% for key, val in pillar['users'].items() %}
              "Ensure user {{ key }} exist":
                user.present:
                  - name: "{{ key }}"
                  - uid: "{{ val.id }}"
                  - gid_from_name: True
                  - shell: "/bin/bash"
                  - groups:
                    - "foo"
                    - "bar"
                    - fullname: "{{ val.fn }}"
            {% endfor %}

            C'est à dire que, pour le dictionnaire de users suivant

            users:
              idi:
                id: 9050
                fn: "Irvin Dirby"
              bob:
                id: 8141
                fn: "Barb Obiwan"

            Ansible va appeler les modules et les exécuter avec le dictionnaire qu'il aura préparé comme il se doit. Il a fallu rajouter des mots-clés pour passer les limitations natives du formation de description.
            SaltStack va d'abord évaluer le gabarit Jinja2 pour générer le YAML mis à plat

            "Ensure user idi exist":
              user.present:
                - name: "idi"
                - uid: "9050"
                - gid_from_name: True
                - shell: "/bin/bash"
                - groups:
                  - "foo"
                  - "bar"
                  - fullname: "Irvin Dirby"
            "Ensure user bob exist":
              user.present:
                - name: "bob"
                - uid: "8141"
                - gid_from_name: True
                - shell: "/bin/bash"
                - groups:
                  - "foo"
                  - "bar"
                  - fullname: "Barb Obiwan"

            Comme le Jinja2 est évalué avant le YAML, pour faire les équivalents de when il faut passer par le mini-langage de templating aussi.

            {% set myvar = salt[…] %}
            
            {% if myvar %}
            #conditionned YAML here
            {% endif %}
            

            Par contre quand on dépend du résultat de la tâche d'avant, là où Ansible offre register (pour exploitation dans le when), on n'a pas d'équivalent avec SaltStack. Par contre, quand on est amené à faire de la programmation Python (ou juste du Jinja2), on apprécie mieux Salt qui n'est pas aussi contraignant que l'autre.

            C'était juste une réponse illustrative pour les gens qui nous liraient en se demandant de quoi on parlait exactement au sujet de cette différence d'approche.

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

      Posté par  . Évalué à 7.

      malgré tout le discours sur l'idempotence, c'est un mauvais langage de script (playbook = script, role = fonction, task = instruction)

      Hum, je ne suis pas d'accord avec cette lecture. Factuellement Ansible ne fonctionne pas comme cela.

      La partie script, c'est bien le module qui le porte. Une tâche est un appel de module. On retrouve l'ensemble des appels (des tâches), dans un playbook. Le rôle n'est qu'une convention d'organisation pour faciliter les échanges.

      nb : Les modules ont une notion d'état désiré, et non de programmation au sens classique (c-à-d la suite d'instructions).

      • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

        Posté par  (site web personnel) . Évalué à 1.

        Les modules ont une notion d'état désiré, et non de programmation au sens classique

        Dans un langage de programmation classique, on peut fonctionner en mode descriptif ou impératif. On a souvent ce débat sur les API graphiques (retained mode ou immediate mode).

        On pourrait même faire pareil en shell avec des commandes qui décrivent un état plutôt qu'une action (ensuredirexists pour remplacer mkdir).

        Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

        • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

          Posté par  (site web personnel) . Évalué à 4.

          C'est aussi un point qui a été discuté sur les systèmes de paquets. Ce qui fait qu'on ne peux pas facilement faire de rollback avec un système classique (eg, rpm, deb), c'est les effets de bord des scripts de pre/post installation. Et même si la majorité des paquets n'ont pas ce souci, il y en a suffisament pour que ça soit impossible.

          Un paquet, c'est hautement déclaratif, sauf sur ça. Et visiblement, tout le monde s'oriente vers des solutions ou le paquet d'une qu'une brique de base et ce qu'on transporte, c'est le produit final après installation et application des scripts (ostree, Nix, mais aussi dans une moindre mesure les conteneurs via l'usage de Dockerfile).

    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

      Posté par  . Évalué à 4.

      On retrouve du yaml partout de nos jours et je préfère qu yaml à du XML.

      Ce code ne fait que décrire l'état souhaité. Ce sont les modules Ansible écrit en python qui font le job sur les machines.

      Je suis impatient de voir ta première release de ton outil écrit en Rust.

    • [^] # Re: il faudrait le réécrire <s>en Rust</s> !

      Posté par  . Évalué à 3.

      Ansible 42.0 est à 90% là : il suffit de faire quelques classes utilitaires autour de mitogen pour compléter les 10% restants, et ça roule. C’est ce que je fais au boulot, et après être passé par ansible, chef et puppet, je ne ferai demi tour pour rien au monde.

      Point bonus : avec un peu de boulot c’est possible de réutiliser les modules ansible existants.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.