Ansible

chmux
15 novembre 2017

Installation

Après avoir mis à jour le système, activer le repo rhel-7-server-extras-rpms :

subscription-manager repos --enable rhel-7-server-extras-rpms

Ensuite pour installer ansible :

yum install ansible

Utilisation basique (dite Ad-Hoc)

Ansible se connecte et exécute les actions sur les différents nœuds via le protocole ssh (outil en python), il n’a donc pas besoin de client. Pour cette exemple, nous partons du principe qu’un user « admin » avec des droits sudo est déployé sur toutes les machines et avec la clé publique du serveur Ansible dans l’authorized_keys.

Avant toutes choses, il faut renseigner le fichier « /etc/ansible/hosts » dans lequel il faut ajouter tous les serveurs qui pourront être requêtés, exemple :

[test]
 serv-stockage-01
 serv-stockage-02
 serv-samba-01

La balise [test] permet de définir un groupe « test » qui contient toutes les machines définies dessous.
Nous pouvons donc demander à Ansible de lancer par exemple la commande « vgs » sur tous les serveurs du groupe test :

[root@serv-ansible-01 playbook]# ansible -m command -a "vgs" test -u admin -b -k
SSH password:
serv-stockage-01 | SUCCESS | rc=0 >>
VG     #PV #LV #SN Attr   VSize   VFree
rhel     1   5   0 wz--n- <15,51g <4,91g
vg_u01   1   1   0 wz--n-  <2,00g     0
 
serv-stockage-02 | SUCCESS | rc=0 >>
VG     #PV #LV #SN Attr   VSize  VFree
rhel     1   5   0 wz--n- 15,51g 4,91g
vg_u01   1   1   0 wz--n-  2,00g    0
 
serv-samba-01 | SUCCESS | rc=0 >>
VG   #PV #LV #SN Attr   VSize  VFree
rhel   2   5   0 wz--n- 45,50g 20,90g

« -m » (comme module) permet de spécifier un module à utiliser pour Ansible, ici on utilise le module « command » qui va permettre de spécifier une commande à exécuter via l’option « -a » (comme argument).
« -u » (comme user) permet de spécifier à Ansible avec quel user effectuer la connexion, il faudra le coupler à « -k » (comme keypass) afin de taper le mdp (si la clé publique du user admin n’étant pas défini dans l’authorized_keys des autres serveurs).
« -b » (comme become) permet d’effectuer la commande en tant que root (par defaut) via l’utilisation de sudo (par defaut aussi), car nous voulons effectuer la commande « vgs » qui n’est possible qu’avec des droits root (le user admin doit avoir le droit de faire un sudo pour toutes commandes et sans avoir à spécifier le mdp).

Remarque : On aurait aussi pu rajouter l’option « -l » suivit d’un nom d’host (du groupe test) afin qu’Ansible ne réalise les actions que sur ce dernier.
Il existe de nombreux modules disponibles ici : http://docs.ansible.com/ansible/latest/modules_by_category.html

Les playbooks

Un playbook est un simple fichier au format YAML qui au final représente une suite d’exécution un peu comme si on les avait faites-les une à la suite des autres via le mode Ad-Hoc.

Nous allons créer un playbook permettant de créer un petit serveur web. On créer donc par exemple le group « web » dans « /etc/ansible/hosts » :

[web]
 v-ansi-client1-gt
 v-ansi-client2-gt

Puis on créer le fichier « /etc/ansible/playbooks/apache.yml » avec dedans :

- hosts: web #ce playbook se lancera sur les host du groupe web
  become: true #les actions seront exécutées via sudo
  tasks: #balise task pour définir toutes les actions à faire
  - name: Installation d'Apache #chaque action débute par une balise « name »
    yum: name=httpd state=latest #utilisation du module « yum » pour installer le paquet httpd à la dernière version dispo
  - name: Copie du vhost pour l'application app1
    copy: src=/etc/ansible/files/vhost-app1.conf dest=/etc/httpd/conf.d/app1.conf mode=0644 #utilisation du module « copy » qui copie le vhost-app1.cfg sur les serveurs
    notify: restart_apache #si modification dans cette partie on utilisera le handlers « restart_apache » défini plus bas
  - name: Suppression des vhosts actuels
    file: dest=/etc/httpd/conf.d/{{ item }} state=absent #utilisation du module « file » qui va supprimer les fichiers « item »
    with_items: #définition des fichiers « item »
      - autoindex.conf
      - welcome.conf
      - userdir.conf
    notify: restart_apache
  - name: Création du dossier de log
    file: dest=/var/log/apache2 state=directory
    notify: restart_apache
  - name: Création du documentroot
    file: dest=/var/www/app1 state=directory
    notify: restart_apache
  - name: Copie du index.html
    copy: src=/etc/ansible/files/index.html dest=/var/www/app1/
    notify: restart_apache

  handlers: #on définit ici des handlers via un « name » et une action, ils sont appelés par les balises « notify » si il y a eu modification
  - name: restart_apache
    service: name=httpd state=restarted

Nous avons donc 2 fichiers sources à envoyer sur les serveurs (/etc/ansible/files/vhost-app1.conf et /etc/ansible/files/index.html).

/etc/ansible/files/vhost-app1.conf :

<VirtualHost *:80>
DocumentRoot /var/www/app1
Options -Indexes

ErrorLog /var/log/apache2/error.log
TransferLog /var/log/apache2/access.log
</VirtualHost>

/etc/ansible/files/index.html :

test

On lance le playbook :

[root@serv-ansible-01 ansible]# ansible-playbook -u admin -k playbooks/apache.yml
SSH password:

PLAY [web] *************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [v-ansi-client2-gt]
ok: [v-ansi-client1-gt]

TASK [Installation d'Apache] *******************************************************************************************************************************************
changed: [v-ansi-client2-gt]
changed: [v-ansi-client1-gt]

TASK [Copie du vhost pour l'application app1] **************************************************************************************************************************
changed: [v-ansi-client1-gt]
changed: [v-ansi-client2-gt]

TASK [Suppression des vhosts actuels] ***********************************************************************************************************************************
changed: [v-ansi-client1-gt] => (item=autoindex.conf)
changed: [v-ansi-client2-gt] => (item=autoindex.conf)
changed: [v-ansi-client1-gt] => (item=welcome.conf)
changed: [v-ansi-client2-gt] => (item=welcome.conf)
changed: [v-ansi-client1-gt] => (item=userdir.conf)
changed: [v-ansi-client2-gt] => (item=userdir.conf)

TASK [Création du dossier de log] **************************************************************************************************************************************
changed: [v-ansi-client1-gt]
changed: [v-ansi-client2-gt]

TASK [Création du documentroot] ****************************************************************************************************************************************
changed: [v-ansi-client1-gt]
changed: [v-ansi-client2-gt]

TASK [Copie du index.html] *********************************************************************************************************************************************
changed: [v-ansi-client1-gt]
changed: [v-ansi-client2-gt]

RUNNING HANDLER [restart_apache] ***************************************************************************************************************************************
changed: [v-ansi-client2-gt]
changed: [v-ansi-client1-gt]

PLAY RECAP *************************************************************************************************************************************************************
v-ansi-client1-gt          : ok=8    changed=7    unreachable=0    failed=0
v-ansi-client2-gt          : ok=8    changed=7    unreachable=0    failed=0

La première tâche « Gathering Facts » que fait Ansible est de tester si les hosts à modifier sont bien accessibles. Il déroule ensuite les tâches définies dans le playbook. Nous voyons à la fin qu’il y a 8 tâches OK et 7 changées, on a donc les 8 tâches qui se sont bien déroulées (la première tâche de test au début comprise) dont 7 qui ont effectuées un changement sur les machines.

On voit aussi que comme il y a eu changement, le handler a été appelé et a relancé apache.

Si on relance à nouveau le playbook, on peut voir que tout est en OK et rien en « changed » car tout est déjà paramétré comme stipulé sur le playbook (de plus le handler ne sera pas appelé) :

[root@serv-ansible-01 ansible]# ansible-playbook -u admin -k playbooks/apache.yml
SSH password:

PLAY [web] *************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [v-ansi-client2-gt]
ok: [v-ansi-client1-gt]

TASK [Installation d'Apache] *******************************************************************************************************************************************
ok: [v-ansi-client2-gt]
ok: [v-ansi-client1-gt]

TASK [Copie du vhost pour l'application app1] **************************************************************************************************************************
ok: [v-ansi-client1-gt]
ok: [v-ansi-client2-gt]

TASK [Suppression des vhosts actuels] ***********************************************************************************************************************************
ok: [v-ansi-client1-gt] => (item=autoindex.conf)
ok: [v-ansi-client2-gt] => (item=autoindex.conf)
ok: [v-ansi-client2-gt] => (item=welcome.conf)
ok: [v-ansi-client1-gt] => (item=welcome.conf)
ok: [v-ansi-client1-gt] => (item=userdir.conf)
ok: [v-ansi-client2-gt] => (item=userdir.conf)

TASK [Création du dossier de log] **************************************************************************************************************************************
ok: [v-ansi-client1-gt]
ok: [v-ansi-client2-gt]

TASK [Création du documentroot] ****************************************************************************************************************************************
ok: [v-ansi-client1-gt]
ok: [v-ansi-client2-gt]

TASK [Copie du index.html] *********************************************************************************************************************************************
ok: [v-ansi-client1-gt]
ok: [v-ansi-client2-gt]

PLAY RECAP *************************************************************************************************************************************************************
v-ansi-client1-gt          : ok=7    changed=0    unreachable=0    failed=0
v-ansi-client2-gt          : ok=7    changed=0    unreachable=0    failed=0

Les variables

Variables complexes

Imaginons que nous voulons pousser un fichier (via le module template) vers plusieurs serveurs et que nous voulons que certaines lignes de ce fichier soient remplies dynamiquement. Par exemple un fichier de conf d’un loadbalancer qui contiendrait une ligne indiquant l’ip du serveur où on l’installe et plusieurs lignes en fonction des membres.   Le fichier template serait de la sorte :

….
listen cluster
bind {{ ansible_eth1['ipv4']['address'] }}:80 #voir 1)
{% for mavariablehosts in groups['web'] %} #voir 2)
server {{ hostvars[mavariablehosts]['ansible_hostname'] }} {{ hostvars[mavariablehosts]['ansible_eth1']['ipv4']['address'] }} check port 80
{% endfor %}
….

1) {{ ansible_eth1[‘ipv4’][‘address’] }} va récupérer l’ip du serveur où sera poussé le fichier, on peut trouver ces variables via le module « setup » :

[root@serv-ansible-01 ansible]# ansible -m setup -u admin -k -b v-ansi-client1-gt
….
"ansible_eth0": {
  "active": true,
  "device": "eth0",
  "features": {
    "busy_poll": "off [fixed]",
    …
    …
    "vlan_challenged": "off [fixed]"
  },
  "hw_timestamp_filters": [],
  "ipv4": {
    "address": "10.251.28.23",
    "broadcast": "10.251.28.255",
    "netmask": "255.255.252.0",
    "network": "10.251.28.0"

Les éléments se notent les l’un après les autres en cascade (json).

2) {% for mavariablehosts in groups[‘web’] %}
server {{ hostvars[mavariablehosts][‘ansible_hostname’] }} {{ hostvars[mavariablehosts][‘ansible_eth1’][‘ipv4’][‘address’] }} check port 80
{% endfor %}

Pareil qu’au-dessus sauf que nous faisons une boucle for qui va prendre en argument tous les hosts du groupe « web » (défini dans le fichier /etc/ansible.hosts), il faut juste utiliser « hostvars[mavariablehosts] » devant les éléments à récupérer.

Attention : pour copier un template il faut utiliser le module « template » et non « copy ».

Variables statiques

On peut définir des variables directement dans un playbook comme ceci :

- hosts: webservers
  vars:
  http_port: 80

On peut aussi définir des variables pour des groupes, il faut alors créer le fichier /etc/ansible/group_vars/nomdugroupe. On créer ensuite les variables comme ceci dans le fichier :

mavariable: value

Pareil pour les hosts seul, on créer le fichier /etc/ansible/host_vars/nomduhost ou placer les variables après le nom d’host dans le fichier « hosts » comme ceci :

[test]
 serv-stockage-01 mavariable=value
 serv-stockage-02
 serv-samba-01

On appelle ensuite ces variables comme ceci :

{{ mavariable }}

Les conditions

Conditionner le résultat d’un module

En utilisant l’option -v avec ansible-playbook, on s’aperçoit que les modules remontent différentes informations :

TASK [apache : Création du DocumentRoot] *******************************************************************************************************************************
ok: [v-ansi-client2-gt] => {"changed": false, "failed": false, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/var/www/app1", "size": 23, "state": "directory", "uid": 0}

Il est possible d’enregistrer le résultat d’un module dans une variable comme ceci :

- name: Création du DocumentRoot
  file: dest=/var/www/app1 state=directory
  notify: restart_apache
  register: mavariabletest

Pour avoir par exemple le groupe du dossier que l’on vient de créer, on utilisera « {{ mavariabletest.group }} », cette variable nous remontera « root ».

On peut aussi se servir de cette variable pour conditionner le passage d’autres tasks :

- name: Création du DocumentRoot
  file: dest=/var/www/app1 state=directory
  notify: restart_apache
  register: mavariabletest
- name: Copie du vhost pour l'application app1
  copy: src=/etc/ansible/files/toto dest=/var/www/app1/toto.conf
  when: mavariabletest|succeeded

Ou alors :

- name: Installation de Yum-cron
  yum: name=yum-cron state=latest enablerepo=rhel-7-server-optional-rpms
  when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 7

Dans le cas de plusieurs task conditionnées, on peut utiliser un block :

- name: Configuration d'Apache
  block:
    - name: Création du DocumentRoot
      file: dest=/var/www/app1 state=directory
    - name: Copie du vhost pour l'application app1
      copy: src=/etc/ansible/files/toto dest=/var/www/app1/toto.conf
  when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 7

If dans un template

Imaginons que nous avons ce fichier hosts :

[test]
 serv-stockage-01 domaine=mondomaine.a
 serv-stockage-02 domaine=mondomaine.b
 serv-samba-01

Nous voulons déployer le fichier /etc/resolv.conf avec le module template en fonction du domaine, on aura un template de ce type :

search {{ domaine }}
{% if domaine == 'mondomaine.a' %}
nameserver 10.10.5.100
nameserver 10.10.5.101
{% else %}
nameserver 10.10.4.100
nameserver 10.10.4.101
{% endif %}

Les rôles

Au lieu de créer un playbook contenant l’installation de multiples outils et de configurer plusieurs choses, il est plus élégant de créer un rôle par outils et d’ensuite appeler ces rôles dans un playbook.

Pour créer un rôle il faut créer une arborescence, par exemple pour le rôle apache, il faut créer le dossier roles/apache avec en sous dossier :
– tasks : contient la liste des tâches à effectuer
– handlers : les handlers qu’on appelle via les « tasks »
– vars : les variables
– files : les fichiers à déployer
– templates : les fichiers template à déployer
– meta : les metadata

Remarque : il existe d’autres sous dossiers pouvant être créé afin d’accéder à d’autres fonction d’Ansible (voir http://docs.ansible.com/ansible/latest/playbooks_reuse_roles.html )

Dans chaque sous dossier les fichiers se nommeront « main.yml », nous pouvons donc transformer notre playbook apache vu plus haut en rôle.

Il faut alors se placer dans le dossier « /etc/ansible/roles » et utiliser la commande « ansible-galaxy init apache » qui va créer l’arborescence défini au-dessus.

Mettre les « tasks » dans « /etc/ansible/roles/apache/tasks/main.yml » :

---
# tasks file for apache
- name: Installation d'Apache
  yum: name=httpd state=latest
- name: Copie du vhost pour l'application app1
  copy: src=/etc/ansible/sources/vhost-app1.conf dest=/etc/httpd/conf.d/app1.conf mode=0644
  notify: restart_apache
- name: Supression des vhosts actuels
  file: dest="/etc/httpd/conf.d/{{ item }}" state=absent
  with_items:
    - autoindex.conf
    - welcome.conf
    - userdir.conf
  notify: restart_apache
- name: Création du dossier de log
  file: dest=/var/log/apache2 state=directory
  notify: restart_apache
- name: Création du documentroot
  file: dest=/var/www/app1 state=directory
  notify: restart_apache
- name: Copie du index.html
  copy: src=/etc/ansible/sources/index.html dest=/var/www/app1/
  notify: restart_apache

Mettre le handlers dans « /etc/ansible/roles/apache/handlers/main.yml » :

---
# handlers file for apache
- name: restart_apache
  service: name=httpd state=restarted

Puis créer le playbook qui appellera le rôle dans « /etc/ansible/playbooks/apache.yml » :

- hosts: web
  become: true
roles:
  - apache