# 2022/11/11 (atelier) : Ansible * [[https://doc.neutrinet.be/atelier-ansible-2022-09-24|Réunion précédente]] * [[https://doc.neutrinet.be/atelier-ansible-2022-11-11#|Pad de la réunion]] Présences : - HgO - Célo ## Météo //Moment informel durant lequel on exprime **en peu de mots** comment on se sent et si on a une attente forte pour la réunion.// //**Ce n'est pas un moment de discussion mais d'expression individuelle et ce n'est pas obligatoire :-)**// ### Attente(s) forte(s) //Si l'une ou l'autre personne exprime une attente forte, merci de vous en occuper en priorité ou de la noter dans le hub ou dans un point approprié//. - Être en état de faire le playbook pour Mobilizon - Avoir fini la partie Molécule. ## Création d'un rôle ### Qu'est-ce qu'un rôle ? Un rôle, dans Ansible, c'est en quelque sorte un paquet d'un gestionnaire de paquets : cela fait tout ce qu'il faut pour que ça soit installé. Que ce soit les fichiers de config, les variables par défaut, la liste des tâches ansible, les handlers propre au déploiement de l'application, etc. Le playbook va appeler un ou plusieurs rôles. L'avantage c'est qu'on peut utiliser un rôle pour plusieurs choses. Par exemple, on a un rôle qui ne fait qu'installer nginx et qu'on mobilisera partout. Qui ne gérera que l'installation, et non la configuration du vhost qui sera propre à chaque cas, mais qui pourra être utilisé pour beaucoup d'applications différentes. La configuration du vhost sera par exemple faite dans le rôle qui déploie l'application en elle-même (exemple: un rôle pour Mobilizon) Un autre avantage, c'est de cloisonner. Si tout est dans le même playbook, cela ferait beaucoup de variable et sera difficilement lisible. Quand on fait un rôle, on va toujours préfixer les variables du nom du rôle. Par exemple, pour mobilizon : `mobilizon_domain` au lieu de `domain` Cela évite qu'un autre rôle utilise la même variable. C'est aussi plus lisible : on sait tout de suite de quelle variable il s'agit. ### Différences entre module et rôle La différence entre module (apt par exemple) et rôle est qu'un module est écrit en python, il est fourni par Ansible. Même si on peut écrire des modules, on le fait rarement car la plupart du temps ils existent déjà et il faut ensuite maintenir le code en python. Par contre, pour coder correctement avec Ansible, on doit écrire des rôles, pour toutes les raisons qu'on a expliqué plus haut. ### Structure d'un rôle Dans le cours https://supports.uptime-formation.fr/06-ansible/cours3/, il y a une image qui montre la structure d'un rôle. Quand on lance un playbook, Ansible va d'abord regarder les hosts puis va ensuite regarder les variables. Ensuite, il va regarder les rôles. Le rôle common va s'appliquer à un groupe de serveurs (sur le schéma, il s'applique à tous). Il contient des tasks, des handlers, des templates, des files. Le rôle common, ça va être les configs de bases : ssh, etc. Ensuite on a les rôles plus spécialisés, dans l'ordre de leur spécialisation. Par exemple : - common - nginx - mobilizon Tous les rôles doivent être dans le dossier roles. Chaque rôle est un sous dossier : ``` roles/ common/ # this hierarchy represents a "role" tasks/ # main.yml # <-- tasks file can include smaller files if warranted handlers/ # main.yml # <-- handlers file templates/ # <-- files for use with the template resource ntp.conf.j2 # <------- templates end in .j2 files/ # foo.sh # <-- script files for use with the script resource vars/ # main.yml # <-- variables associated with this role defaults/ # main.yml # <-- default lower priority variables for this role meta/ # main.yml # <-- role dependencies library/ # roles can also include custom modules module_utils/ # roles can also include custom module_utils lookup_plugins/ ``` Pour chaque dossier, Ansible va regarder dans le fichier main.yml. Pour les variables, on a default et vars. Vars sont les variables qu'on écrit, default les variables par défaut. Les variables par défaut sont modifiées dans de très rares cas (par exemple si on a besoin d'une version de php par défaut). Mais chez Neutrinet, dans ce cas, on préfère souvent hardcoder plutôt qu'utiliser les variables par défaut. Le reste est moins important : meta permet de mettre des propriétés aux rôles, licence... C'est important si on met à disposition son rôle de tout le monde. Chez Neutrinet, on n'utilise Ansible qu'en interne donc on n'a pas besoin de s'en occuper. ### Ansible Galaxy et rôles externes https://galaxy.ansible.com/ Ansible Galaxy est un annuaire de rôles et de modules. C'est une mine d'or pour trouver des rôles sans trop se casser la tête. Par contre, la plupart sont fait par des gens, et donc ne sont pas toujours maintenus. Il y a des étoiles et des nombres de téléchargement, donc on peut avoir une indication. En général, on va regarder ce qui existe déjà comme rôle avant de créer un nouveau, ceci pour ne pas réinventer la roue. Mais la plupart du temps, les rôles proposés sont trop génériques ou trop spécifiques et impose une certaine structure pour le déploiement de l'application. On les réécrit donc généralement pour les mettre à notre sauce. ### Conversion du playbook précédent vers un rôle On avait ceci comme structure la dernière fois: ``` ├── ansible.cfg ├── inventories │ ├── group_vars │ │ └── all.yml │ ├── hosts.ini │ └── host_vars │ ├── debian1.yml │ └── debian2.yml ├── playbooks │ ├── files │ ├── playbook1.yml │ └── templates │ └── nginx.conf.j2 └── Vagrantfile ``` On va rajouter notre rôle: ``` ├── ansible.cfg ├── inventories │ ├── group_vars │ │ └── all.yml │ ├── hosts.ini │ └── host_vars │ ├── debian1.yml │ └── debian2.yml ├── playbooks │ ├── files │ ├── playbook1.yml │ └── templates │ └── nginx.conf.j2 ├── roles │   └── nginx │   ├── defaults │   │   └── main.yml │   ├── handlers │   │   └── main.yml │   ├── tasks │   │   └── main.yml │   └── templates │   └── nginx.conf.j2 └── Vagrantfile ``` On ne met que la liste des tâches dans `roles/nginx/tasks/main.yml`, la liste des handlers dans `roles/nginx/handlers/main.yml`, et le dictionnaire contenant les variables (ici juste la variable `domains`) dans `roles/nginx/defaults/main.yml`. On enlève l'en-tête task ou handler, et on enlève l'indentation. On copie le template de la config nginx dans `roles/nginx/templates/nginx.conf.j2`. On renomme la variable `domains` en `nginx_domains` pour respecter les bonnes pratiques. On le fait dans les variables par défaut (`defaults`) et dans la tâche qui configure nginx. Pour l'instant on a notre rôle, mais on ne peut pas l'exécuter. Pour ce faire, il nous faut l'inclure dans un playbook. On va reprendre notre playbook et le simplifier. On enlève la partie `tasks`, `vars`, et `handlers`, et on ajoute (attention à l'indentation!): ```yaml roles: - nginx ``` Pourquoi c'est si simple ? Parce que Ansible sait où aller chercher les rôles (on peut le définir dans `ansible.cfg`, mais par défaut c'est le dossier `roles` au même niveau que là où on exécute la commande `ansible-playbook`). Donc il va chercher dans le dossier `roles/nginx` et puis le fichier de tasks `tasks/main.yml` de ce rôle-là. Dans le cas de nginx, cela a peu d'intérêt, mais on peut séparer la partie installation et la partie configuration dans deux fichiers séparés : install.yml et config.yml. Ensuite, dans main.yml, on utilise le module `import_tasks` avec le lien vers le fichier. Dans `roles/nginx/tasks/main.yml`, on a donc: ```yaml - name: Installation de nginx import_tasks: install.yml - name: Configuration de nginx import_tasks: config.yml ``` Dans `roles/nginx/tasks/install.yml`: ```yaml - name: Installation de nginx package: name: nginx state: present update_cache: true ``` Et dans `roles/nginx/tasks/config.yml`: ```yaml - name: Configuration de nginx template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/{{ domain }} owner: root group: root mode: "0644" # équivalent à "u=rw,g=r,o=r" # validate: nginx -T -c %s notify: Restart nginx loop: "{{ domains }}" loop_control: loop_var: domain ``` À présent, on va lancer le playbook... mais visiblement il ne trouve pas le rôle :/ Dans le fichier `ansible.cfg`, dans la section `defaults`, on rajoute ceci: ```ini roles_path=./roles ``` Lors de l'exécution du playbook, Ansible préfixe les tâches avec `nginx :` pour indiquer qu'il travaille dans le rôle `nginx`. ### Différence entre import et include L'import est statique. En informatique, cela veut dire que c'est fait avant l'exécution du playbook et plus jamais après - c'est plus optimisé. Les include sont dynamiques. Dans la tâche config, on fait une boucle. On pourrait aussi la faire au niveau de main.yml, dans la partie import des tâches de la configuration de nginx. On importerait alors la config une fois par domaine. Cela permet de simplifier la config dans config.yml. Cela permet aussi d'avoir un ensemble de tâche qui seront appliquées à chaque domaine. Au final, on déplace ce bout de code qui était dans le fichier `config.yml` vers `main.yml` en dessous de l'import de `config.yml`: ```yaml loop: "{{ nginx_domains }}" loop_control: loop_var: domain ``` On voit qu'on a une erreur, car les `import_tasks` sont statiques, or on ne peut utiliser une boucle qui est par définition dynamique ici. Il faut utiliser le `include_tasks`. En gros, il faut retenir que si on n'est pas dans une boucle on va faire un import, et sinon ce sera un include. ## Molecule Pour l'explication de ce qu'est Molecule, voir le [[fr:rapports:2021:06-12#presentation_de_molecule|Neutriton du 12/06/2021]] ### Installation de molecule On va installer molecule: ```bash pip install molecule ``` On installe un plugin pour que molecule puisse gérer des VMs vagrant (c'est un provider): ```bash pip install molecule-vagrant ``` ### Création d'une VM via molecule Molecule se configure toujours au niveau d'un rôle. Donc on va reprendre le dossiers `roles` qu'on avait créé, et on va rajouter des bouts de config pour molecule. Pour faire les opérations molécules, on se place dans le dossier du rôle qu'on est en train d'écrire. Donc chez nous dans le dossier nginx. On doit créer un dossier `molecule/default`. Ici default représente le scénario par défaut dans Molécule. On peut avoir plusieurs scénario, typiquement le scénario par défaut va créer une seule VM et appliquer le rôle ansible dessus. Mais on pourrait avoir un scénario `cluster` qui va générer trois VMs pour tester si tout fonctionne bien en mode cluster. On peut avoir un scénario `debian` et un autre `archlinux` qui vont tester le rôle sur les distributions. Une fonction, `molecule init`, permet de faire la conf à notre place en proposant de choisir le nom du scénario, le type de driver (on utilisera vagrant plutôt que delegated). ``` > molecule init scenario -r nginx -d vagrant INFO Initializing new scenario default... INFO Initialized scenario in ./molecule/default successfully. ``` On voit que molecule crée différents fichiers: ``` ├── molecule │   └── default │   ├── converge.yml │   ├── INSTALL.rst │   ├── molecule.yml │   └── verify.yml ``` - converge.yml va appliquer le playbook - molecule.yml est la configuration de la vm. - verify.yml permet de tester le rôle. On l'utilise plus rarement. On va un peu modifier la configuration de molecule.yml : ```yaml dependency: name: galaxy driver: name: vagrant provider: name: libvirt platforms: - name: bullseye-nginx-molecule box: debian/bullseye64 cpu: 2 memory: 1024 provisioner: name: ansible config_options: defaults: interpreter_python: /usr/bin/python3 ssh_connection: pipelining: true verifier: name: ansible ``` Le driver permet d'indiquer si on utilise vagrant ou docker par exemple. Le provider indique qu'est-ce que vagrant va utiliser (libvirt, virtualbox, etc) La partie platforms indique le nombre de VMs et quels types de VM. La box, c'est comme dans vagrant, cela indique quelle image utiliser. La partie provisionner permet de configurer ansible (utile pour forcer la version de python) La partie verifier c'est pour effectuer les tests, mais on ne l'utilise pas. A partir de ces infos, molecule va générer un vagrantfile. Pour créer la VM, c'est très compliqué: ```bash molecule create ``` Note: Il râle sur l'option `pipelining: true` donc on va commenter ça comme ce n'est pas nécessaire. On voit bien que Molécule crée la VM `default_bullseye-nginx-molecule` :) Molécule génère tout un tas de fichier dans le dossier suivant: ```bash ls -a ~/.cache/molecule/nginx/default ``` On voit par exemple qu'il a généré un fichier Vagrantfile. On voit aussi qu'il y a un dossier `.vagrant` et si on creuse dedans, on retrouve la private key associée à la VM ! Ce sera utile si jamais on doit y accéder en ssh directement. Par exemple si on veut faire du port forwarding, cela peut être pratique. ```bash ls ~/.cache/molecule/nginx/default/.vagrant/machines/bullseye-nginx-molecule/libvirt ``` ### Comment se connecter à la VM ? À partir du dossier du rôle nginx, on lance: ```bash molecule login ``` Si on a plusieurs VMs, on devra utiliser: ```bash molecule login -h ``` Mais de toute façon, molecule l'explique quand c'est nécessaire. C'est tout pareil qu'avec vagrant \o/ ### Destruction de la VM C'est encore plus pareil qu'avec vagrant: ```bash molecule destroy ``` En cas de bug, on peut aussi effacer les fichiers du cache avec la commande `reset`. Mais normalement, `destroy` le fait aussi. ### Comment exécuter son rôle avec molecule ? Dans le dossier molécule, on a un fichier converge.yml. Il est correct tel quel mais on va le modifier pour que ce soit mieux : ```yaml - name: Converge hosts: all become: true roles: - nginx ``` On va lancer un `molecule converge`, ce qui va exécuter le rôle sur la VM. Si elle n'est pas créée, il va la créer pour nous :) `molecule converge` fonctionne comme `ansible-playbook`, on peut rajouter des options, par exemple pour débugguer. Par exemple: ```bash molecule converge -- -vvv ``` Mais en vrai toutes les options de `ansible-playbook` sont disponibles :) ### Installer des dépendences avant d'exécuter le rôle On va faire un nouveau rôle qui va simplement cloner un repo git, celui-ci: https://github.com/e-lie/flask_hello_ansible.git Cependant, on part du principe que git est déjà installé sur le host ! On va voir comment faire pour que ça marche dans molecule. On reprend la structure de notre précédent rôle, et on va modifier le fichier `tasks/main.yml` dans notre nouveau rôle. On utilise le module git: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html# On ne garde que les dossiers `molecule` et `tasks`. ```yaml - name: Clonage du repo git ansible.builtin.git: repo: 'https://github.com/e-lie/flask_hello_ansible.git' dest: /opt/flask version: master ``` Dans `molecule/default/converge.yml`, on modifie le rôle par `git` (le nom du nouveau rôle). Dans `molecule/default/molecule.yml`, on change le nom de la machine. Ansible râle parce qu'il ne trouve pas le binaire `git` (surprenant!) Pour cela, molécule à tout prévu... On va créer un nouveau fichier prepare.yml, dans lequel on va copier converge.yml et le modifier. Cela devient un autre playbook qui s'exécutera juste après la création de la machine, mais avant converge.yml :) Surtout, il ne s'exécute qu'une seule fois (après la création de la VM), et plus jamais après. Ce qui permet d'alléger le converge. C'est l'intérêt de mettre des informations comme l'installation des dépendance dans ce second playbook. ```yaml - name: Prepare hosts: all become: true tasks: - name: Installation de git package: name: git update_cache: true ``` On lance la commande `molecule prepare`, mais il nous dit que tout est déjà fait... En effet, on a déjà créé les machines. Même si cela a échoué, ce n'est pas la première fois qu'on lance molecule et molecule le sait. Soit on fait un destroy, soit on force. Et là on force: ```bash molecule prepare -f ``` Maintenant que tout est prêt, on peut faire le converge: ```bash molecule converge ``` ### Remarque pour le repo de Neutrinet Là on a tout ce qu'il faut pour utiliser molecule. Pour l'utiliser avec l'infra de Neutrinet, il faudra installer préalablement les requierments pour avoir la même version, dans un environnement virtuel évidemment :p ```bash pip install -r requirements.txt ``` ## Secrets - Qu'est-ce qu'un vault ? - Où placer ses secrets ? - Comment afficher le contenu d'un vault ? - Où placer la clé de chiffrement ? - Comment garder la clé de chiffrement secrète dans un repo git ? - Comment ne pas logguer des secrets ? Commande ansible-doc pour avoir la doc. -> équivalent au man page ## Keycloak ## Prochaine réunion Prochain atelier Ansible : À définir Lieu : Chez Célo (Crainhem) ## Météo de fin //Moment informel durant lequel on exprime **en peu de mots** comment, à titre personnel, la réunion a été vécue que ce soit positif ou négatif.// //Si une ou plusieurs **tension est née** durant la réunion, il est peut-être nécessaire d'envisager l'une ou l'autre réunion pour y remédier.// {{tag>infra atelier}}