Ansible :  exemple d'une configuration simple d'ansible pour automatiser l'installation de nginx sur un serveur distant

Ansible

Ansible (ansible.com) se révèle aujourd'hui être un outil de choix dans le domaine de l'automatisation. Nous utilisons cette technologie depuis plusieurs mois maintenant et après Europython 2017, pendant lequel nous avons pu voir plusieurs cas d'utilisation, nous avons décidé de mettre les bouchées doubles sur cette technologie et faire monter en compétence une partie de l'équipe qui ne l'utilise encore que peu.

Un des plus gros avantage d'Ansible est sa simplicité d'utilisation et notamment ses fichiers de configuration relativement simples.

Il existe un service payant, ansible tower, mais ce qui nous interesse ici est la documentation sur leur site.

Voyons ensemble une configuration simple d'ansible pour automatiser l'installation de nginx sur un serveur distant. (Exemple simple)

Installation (http://docs.ansible.com/ansible/latest/intro_installation.html)

Via apt ubuntu :

sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible

Créons un fichier avec la liste de nos serveurs, par example "hosts" :

[web]
192.168.33.10
192.168.33.11
192.168.33.12
[database]
192.168.33.13
192.168.33.14

Cela permet de lancer des recettes par groupe.

Prenons pour l'instant un exemple simple avec un unique serveur

[web]
192.168.33.10

On va écrire une recette en "yaml", créons un fichier playbook.yml :

- name: Installation du serveur
  hosts: web
  remote_user: root
  tasks:
    - name: Installation de git
     (...)

remote_user étant l'utilisateur que l'on veut utiliser.

Nous pouvons dès à présent définir certaines tâches à effectuer.
Pour cela, ansible fournit un certain nombre de module pour faciliter la mise en place de ces taches. Par exemple vous pouvez en apprecier un certain nombre dans leur documentation officielle : docs.ansible.com/ansible/modules_by_category.html

Ici, nous voulons installer "git". Nous pouvons alors utiliser le module "apt" qui demande comme argument "name" et "update-cache".
Ajoutons "vim" également tant que nous y sommes.

- name: Installation du serveur
  hosts: web
  remote_user: root
  tasks:
    - name: Installation de git
      apt: name=git update_cache=yes
    - name: installation de Vim
      apt: name=vim

Pour lancer ce playbook, nous devons utiliser la commande suivante :

ansible-playbook -i hosts playbook.yml

La sortie en console de cette commandedevrait être similaire à :

PLAY [Installation du servuer] ***********************************************
TASK [setup] ***************************************************
ok: [192.168.33.10]
TASK [Installation de git] ***************************************************
changed: [192.168.33.10]
TASK [Installation de Vim] ***************************************************
changed: [192.168.33.10]
PLAY RECAP *******************************************************************
192.168.33.10    : ok=3   changed=2   unreachable=0   failed=0

En relancant la commande, ansible va d'abord vérifier si les paquets sont installés et sera donc plus rapide.

Nous pouvons gagner un peu de temps et spécifier en une fois les paquets qu'il faut installer. (Et ajoutons au passage "htop")

- name: Installation du serveur
  hosts: web
  remote_user: root
  tasks:
    - name: Installation des dépendances
      apt: name={{ item }} update_cache=yes state=latest
      with_items:
        - vim
        - htop
        - git

Cela reste relativement simple et peu utile comme playbook.

Ajoutons la création d'un nouvel utilisateur. Pour cela, nous allons avoir besoin d'un nouveau module : "user".

- name: Installation du serveur
  hosts: web
  remote_user: root
  tasks:
    - name: Installation des dépendances
      apt: name={{ item }} update_cache=yes state=latest
      with_items:
        - vim
        - htop
        - git
    - name: Création d'un utilisateur
      user: name=adrien comment='Mon nouvel utilisateur' shell=/usr/bin/bash
    - name: Ajout de la clé SSH
      authorized_key: user=adrien key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

Les modules sont très bien documentés et ces lignes viennent directement de la documentation. Pas de panique si vous pensez être perdu : il faut s'en tenir à la documentation.

Après avoir éxecuté notre playbook, nous pouvons essayer de nous connecter à notre serveur avec l'utilisateur "adrien" et nous devrions bien accéder au serveur.

Nous avons cependant ici une répétition dans notre fichier de configuration. En effet, nous spécifions deux fois le nom de l'utilisateur ("adrien").

Factorisons notre fichier avec l'extraction de la variable du nom d'utilisateur :

- name: Installation du serveur
  hosts: web
  remote_user: root
  vars:
    user: adrien
  tasks:
    - name: Installation des dépendances
      apt: name={{ item }} update_cache=yes state=latest
      with_items:
        - vim
        - htop
        - git
    - name: Création d'un utilisateur
      user: name={{ user }} comment='Mon nouvel utilisateur' shell=/usr/bin/bash
    - name: Ajout de la clé SSH
      authorized_key: user={{ user }} key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

Ainsi, cette recette est maintenant beaucoup plus facilement réutilisable par quelqu'un d'autre, il suffit de changer le nom de l'utilisateur.

Par ailleurs, nous pouvons également ajouter des conditions à nos tâches. Prenons l'exemple ou l'utilisateur n'a pas été défini, alors la recette pourrait être :

- name: Installation du serveur
  hosts: web
  remote_user: root
  vars:
  tasks:
    - name: Installation des dépendances
      apt: name={{ item }} update_cache=yes state=latest
      with_items:
        - vim
        - htop
        - git
    - name: Création d'un utilisateur
      when: user is defined
      user: name={{ user }} comment='Mon nouvel utilisateur' shell=/usr/bin/bash
    - name: Ajout de la clé SSH
      when: user is defined
      authorized_key: user={{ user }} key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

Si on lance notre playbook, alors les deux tâches seront ignorées ("skipping").

Notre utilisateur fraichement créé n'a pas les droits utilisateurs, ajoutons le au groupe des "sudoers". Pour cela nous allons créer un template et utiliser notre premier fichier jinja !

Créons templates/sudoers.j2 :

{{ user }} ALL=(ALL:ALL) NOPASSWSD: ALL

Et au sein de notre playbook :

- name: Installation du serveur
  hosts: web
  remote_user: root
  vars:
  tasks:
    - name: Installation des dépendances
      apt: name={{ item }} update_cache=yes state=latest
      with_items:
        - vim
        - htop
        - git
    - name: Création d'un utilisateur
      when: user is defined
      user: name={{ user }} comment='Mon nouvel utilisateur' shell=/usr/bin/bash
    - name: Ajout de la clé SSH
      when: user is defined
      authorized_key: user={{ user }} key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
    - name: "{{ user }}" devient un sudoer
      when: user is defined
      template: src=templates/sudoers.j2 dest=/etc/sudoers.d/{{ user }}-sudoer validate='visudo -cf %s'

Passons maintenant à notre objectif initial : l'installation de nginx et vérifions que le nom de domaine fonctionne.

Avant de se lancer tête baissée, prenons un peu de recul. Si nous continuons notre script, nous risquons vite d'obtenir un fichier un peu long et nous allons perdre la réutilisabilité de notre script.

Pour éviter ce genre de problème, ansible utilise ce qu'on appelle des "roles".

Créons un dossier roles/utils/tasks et ajoutons le fichier main.yml. Son contenu sera juste un extrait de notre playbook précédent :

- name: Installation des dépendances
  apt: name={{ item }} update_cache=yes state=latest
  with_items:
    - vim
    - htop
    - git

De même créons roles/user/tasks et ajoutons le fichier main.yml :

- name: Création d'un utilisateur
  when: user is defined
  user: name={{ user }} comment='Mon nouvel utilisateur' shell=/usr/bin/bash
- name: Ajout de la clé SSH
  when: user is defined
  authorized_key: user={{ user }} key="{{ lookup('file', '~/ssh/id_rsa.pub') }}"
- name: "{{ user }} devient un sudoer"
  when: user is defined
  template: src=templates/sudoers.j2 dest=/etc/sudoers.d/{{ user }}-sudoer validate='visudo -cf %s'

et notre playbook devient :

- name: Installation du serveur
  hosts: web
  remote_user: root
  vars:
  roles:
    - utils
    - user

Vous devez vous douter que nous ne sommes pas les premiers explorateurs d'ansible à écrire nos roles. Il existe une quantité affolante de rôles prêts à être utilisés sans que vous ayez à tous les réécrire. Vous pouvez explorer le site galaxy.ansible.com .

Attention toutefois : n'oubliez pas que vous risquez d'éxécuter un code que vous ne connaissez pas et que vous ne maitrisez potentiellement pas sur votre machine si vous utilisez directement un rôle écrit par quelqu'un d'autre. Cependant, vous pouvez toujours allez explorer leur code sur github et vous en inspirer.

Maintenant que notre playbook est découpé en rôles, ajoutons le rôle nginx :

- name: Installation du serveur
  hosts: web
  remote_user: root
  vars:
  roles:
    - utils
    - user
    - nginx

et créons le fichier roles/nginx/tasks/main.yml :

- name: Installation
  become: yes
  apt: name=nginx state=latest
- name: Start
  become: yes
  service: name=nginx state=started enabled=true
become: yes permet de s'assurer que la commande essaie de se lancer en tant qu'administrateur.

En lançant ce playbook, vous devriez donc avoir installé nginx. En vous rendant sur l'URL de votre serveur, vous devez maintenant observer la page d'accueil de nginx.

Souvent, nous ne voulons pas laisser cette page visible. Pour cela, nous pouvons supprimer la configuration par défaut.
Ensuite, nous devrions redemarrer nginx. Cela ferait deux nouvelles taches et toute modification imposera un rechargement de nginx.

Plutôt que de créer toujours plus de tâches qui se répètent, nous pouvons créer des "handlers". Chaque tâche peut notifier un handler et ceux ci seront déclencher une fois toutes les tâches effectuées s'ils ont été notifié. Le redémarrage de nginx rentre parfaitement dans ce cas d'utilisation !

Réécrivons ce role nginx:

- name: Installation
  become: yes
  apt: name=nginx state=latest
- name: Start
  become: yes
  service: name=nginx state=started enabled=true
- Suppression de la configuration par defaut
  become: yes
  file: path=/etc/nginx/sites-enabled/default state=absent
  notify: reload nginx

et créons le fichier roles/nginx/handlers/main.yml :

- name: reload nginx
  action: service name=nginx state=reloaded

Cette fois-ci, la page par défaut de nginx ne devrait plus être accessible.

Nous pouvons continuer ainsi et automatiser toute la chaîne de mise en production. A travers cet exemple simple, nous avons pu aborder les tasks, les roles, les templates et les handlers.

Cela devrait être suffisant pour commencer à s'amuser avec Ansible ! A vous de jouer !