Versionnez votre infrastructure avec Ansible

Tom Spreters
05/09/2017


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).

Premiers tests : les commandes Ad-Hoc

Installation

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

Les modules Ansibles

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)
    ...

Dépendances des modules

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) !

Les Playbook

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

Syntaxe de base d’un Playbook

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.

1. Nginx

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:
    [...]

Les Handlers

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.

2. PHP et MySQL

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)

3. Symfony

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 !

4. Roles

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 :

D’autres exemples d’applications :

Ou encore, la documentation complète :

SLICKTEAM