Fini le temps où les serveurs prenaient la poussière avec des vieilles versions d’apache ou php. Avec la démocratisation des VM et du cloud, il n’est pas rare d’avoir des machines dont la durée de vie n’est que de quelques jours ou heures. Il devient alors indispensable d’automatiser la configuration de celles-ci.
Ansible est un outil de déploiement permettant d’automatiser l’exécution de commandes sur un parc de machines ainsi que le déploiement d’applications et de configurations.
Ansible n’est pas le seul outils à répondre à cette problématique, on peut par exemple citer Puppet ou Chef. La principale différence entre Ansible et ses compétiteurs est qu’il ne nécessite pas d’installer un agent sur les machines distantes. Toute la configuration se fait par connexion SSH et modules Python.
Dans cet article, nous verrons les bases d’Ansible ainsi qu’un exemple d’utilisation pour le déploiement d’une stack LEMP (Linux, NGINX, MySQL & PHP).
Ansible est disponible dans la plupart des gestionnaires de paquets mais le plus simple reste d’utiliser pip (qui servira aussi à installer les modules supplémentaires) :
$ pip install ansible Et c’est tout. Il n’y a rien à installer sur les machines que l’on va administrer. Inventaire Le fichier d’inventaire ( /etc/ansible/hosts ) liste les hôtes utilisables par ansible. On peut les regrouper par catégories ce qui permettra de cibler l'exécution de certaines commandes.
[webservers]
foo.example.com
bar.example.com
[dbservers]
one.example.com
two.example.com
three.example.com
On va commencer par quelque chose de plus simple :
localhost
Et maintenant, si on exécute la commande ansible all -m ping, Ansible va se connecter à toutes les machines de l'inventaire :
$ ansible all -m ping
> localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}
Ou en ciblant une parties des machines : ansible webservers -m ping
Hello World !
$ ansible all -a "/bin/echo hello"
La connexion aux différentes machines se fait par SSH. Cela implique que l’utilisateur exécutant ansible doit pouvoir s'y connecter. Ansible part du principe que des clés ssh sont déjà configurées mais si vous préférez utiliser un mot de passe, il suffit de passer l'argument --ask-pass.
$ ansible all -a "/bin/echo hello" --ask-pass
Il est aussi possible de spécifier l’utilisateur à utiliser :
$ ansible all -a "/usr/bin/whoami" -u username [--ask-pass]> username Et d’en changer, une fois connecté (qui est la méthode recommandé en matière de sécurité) : $ ansible all -a "/usr/bin/whoami" -u username --become-user other_user [--ask-become-pass] > other_user $ ansible all -a "/usr/bin/whoami" -u username --become [--ask-become-pass] > root
Ansible permet donc d’exécuter des commandes à distances mais cela semble encore assez limité. La puissance d’Ansible réside dans la création de playbooks (que l’on verra dans la partie suivante) et l’utilisation de modules.
$ ansible all -m module_name -a arguments
Et oui, jusqu’à maintenant, on utilisait sans le savoir le module par défault : command
Je ne vais pas lister tous les modules proposés par ansible (il y en a plus de 1000, liste complète ici) mais en voici quelques uns pour vous donner une idée des possibilités :
Copier un fichier local vers les machines distantes
$ ansible all -m copy -a “src=/etc/hosts dest=/tmp/hosts”
Installer un paquet (avec respectivement apt, yum/dnf, le gestionnaire par défaut)
$ ansible all -m apt -a “name=nginx state=present”$ ansible all -m yum -a “name=nginx state=present”$ ansible all -m package -a “name=nginx state=present”
Envoyer un fichier sur Amazon S3 (module aws_s3)
Créer une maintenance via Zabbix (zabbix_maintenance)
Gérer des containers LXC (lxc_container)
Ajouter des items LDAP (ldap_entry)
...
Bien qu’ansible ne nécessite que python sur les machines à administrer, certains modules peuvent avoir des dépendances supplémentaires.
C’est le cas par exemple des modules apt (qui utilise bien évidemment apt), irc (qui requiert socket) ou boto3 pour aws_s3.
Vous pourrez retrouver ces dépendances sur la page de chaque module dans la documentation d’ansible.
Astuce : La plupart des dépendances sont des modules python et peuvent donc être installées en utilisant ansible (module pip) !
Un Playbook Ansible est un ensemble de commandes utilisant les modules d’ansible et leurs configurations. Il est composé d’un ou plusieurs fichiers au format yaml.
Dans cette partie, nous allons utiliser l’exemple du déploiment d’un site Symfony 3 avec Nginx et MySQL.
Vous pouvez retrouver les fichiers complets correspondants à chaque étape sur ce dépôt : ansible-poc.git
site.yml
---
- hosts: all
tasks:
- name: task 1
modulename: parameter=value
- name: task 2
modulename: parameter1=value parameter2=value
- name: task 3
modulename:
- parameter1: value
- parameter_2: value
Chaque tâche exécute un module ansible avec les paramètres spécifiés.
Pour exécuter un playbook, on utilise :
ansible-playbook site.yml -i hosts
Ici, l’option -i permet d'utiliser un fichier d'inventaire autre que celui par défaut. Il est aussi possible d'ajouter -v ou -vv pour avoir plus de détails sur le déroulement de chaque tâche / module.
On commence par installer la dernière version d’nginx :
name: ensure nginx is at the latest version
package: name=nginx state=latest Il reste à créer le fichier de configuration. Pour cela, on utilise le module template qui parse des fichiers au format .j2 (Jinja2).
- name: write the nginx config file template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf notify: - restart nginx
Si vous regardez le fichier nginx.conf.j2 vous remarquerez que le port et le serveur d'écoute ({{ httpport }}, {{ servername }}) sont des variables. On peut les définir à la racine de notre playbook :
---
- hosts: all
vars:
httpport: 80
servername: localhost
tasks:
[...]
Dernière subtilité de cette tâche :
notify:
- restart nginx
Cette option a pour effet d’exécuter le handler "restart nginx" si la tâche "write the nginx config file" s'est exécutée sans erreur.
Les handlers sont définis comme des tâches et peuvent utiliser tous les modules d’ansible.
handlers:
- name: restart nginx
service: name=nginx state=restarted
Ils sont exécutés à la fin de toutes les tâches, si et seulement si une tâche avec notify correspondante est exécutée.
Pour rappel, ansible est idempotent, c’est à dire qu’une tâche ne sera exécutée (et par extension, le handler) que si nécessaire. Par exemple, le module template ne s'exécute que si les fichiers ont été modifiés.
Pour installer PHP et MySQL, on suit le même principe :
- name: ensure php-fpm is at the latest version
package: name=php-fpm state=latest
- name: ensure php-fpm is running (and enable it at boot)
service: name=php-fpm state=started enabled=yes
- name: ensure mariadb is at the latest version
package: name=mariadb state=latest
- name: ensure mariadb is running (and enable it at boot)
service: name=mariadb state=started enabled=yes
On remarquera que les tâches php, nginx et mysql ont été séparées pour permettre d’utiliser des paramètres différents comme piste d’amélioration (notamment on pourrait installer le serveur mysql sur une machine différente).
On rajoute aussi un fichier d’index pour tester notre installation de php :
- name: write the demo index file
copy: src=templates/index.php dest=/var/www/index.php
notify:
- restart nginx
(Le fichier nginx.conf.j2 a aussi été mis à jour)
Cet exemple utilise les configurations par défault de php et mysql par simplicité mais il est bien sûr possible de les modifier. (Utilisation par exemple du module mysql_user pour gérer les utilisateurs MySQL)
On va maintenant installer le projet de démonstration de Symfony. Pour cela, il faut cloner le dépôt git et installer les dépendances.
- name: clone symfony demo, and keep it updated git: repo: 'https://github.com/symfony/symfony-demo' dest: /var/www/demo version: v1.0.4
Le module git permet de télécharger un dépôt à une version spécifique. Il possède plein d’autres options comme la création d’archives ou la récupération de pull-requests.
- name: write symfony config file template: src=templates/parameters.yml.j2 dest=/var/www/demo/app/config/parameter.yml
Même principe que pour nginx, avec cette fois les identifiants de connexion à la bdd en paramètre.
- name: ensure all dependencies all satisfied using composer composer: command: install workingdir: /var/www/demo nodev: false
Composer est un gestionnaire de dépendances php utilisé par symfony mais on ne va pas rentrer dans les détails, ce n’est pas le but de cet article.
Et voilà, vous avez maintenant une stack LEMP fonctionnelle et déployable en quelques instants !
Petit problème : notre Playbook commence à être long et illisible, il est temps de le découper en rôles.
Un rôle ansible est un répertoire regroupant des tâches, handlers et fichiers templates. Il permet d’organiser la configuration d’un playbook et d’être réutilisé par d’autres playbooks.
Exemple de structure d’un rôle :
/ handlers/
main.yml
tasks/
main.yml
...
templates/
...
vars/
main.yml
Dans notre exemple, le playbook est découpé en 3 rôles : www (installation nginx et php), mysql et demo (clonage du dépot symfony et configuration).
On remarquera que les variables (comme database_user) ont été déplacées dans des fichiers séparés.
Le fichier yml principal devient alors :
---- hosts: all
remote_user: root
roles:
- www
- mysql
- demo
Ansible Galaxy
Comme évoqué précédemment, les playbook peuvent être partagés à l’aide des rôles. C’est là que l’Ansible Galaxy entre en jeu.
En vous rendant sur https://galaxy.ansible.com/list vous trouverez tous les rôles publiés et maintenus par la communauté.
Dans l’exemple LEMP, des rôles comme bennojoy.mysql ou bennojoy.nginx offrent beaucoup plus d'options et une configuration plus fine que notre petit exemple.
Pour en installer, on utilise la commande ansible-galaxy :
$ ansible-galaxy install bennojoy.mysql
Ou, pour installer plusieurs rôles en une fois et maintenir une liste des rôles requis :
user1.role1,v1.0.0
user2.role2,v0.5
user2.role3
$ ansible-galaxy install -r roles.txt
Pour aller plus loin
Quelques concepts plus avancés :
Commandes conditionnelles (when)
Boucles (withitems, withfile, ...)
D’autres exemples d’applications :
Ou encore, la documentation complète :
SLICKTEAM