Ansible

Logo Ansible

La gestion automatisée de tout un parc de serveurs,

De l'installation d'applications à la configuration en passant par toutes les tâches de maintenance.

Tout est automatique grâce aux playbooks Ansible.

Pré-requis: maitriser le YAML, disposer d'un environnement virtuel en Python et disposer sur son système du programme sshpass.


Pour les systèmes à base de dnf

$ sudo dnf install python3 sshpass

Pour les systèmes à base de apt

$ sudo apt install python3 sshpass

Pour les systèmes à base de zypper

$ sudo zypper install python3 sshpass

 
 

 

 

 

Fichier attachéTaille
Image icon logo ansible34.32 Ko

Ansible: Installer son environnement virtuel Python

Pour utiliser Ansible, il est nécessaire de disposer d'un environnement virtuel Python.

Pour ce faire, rien de plus simple:

1 - Création de l'environnement virtuel dans son dossier personnel

$ cd ~
$ python3.11 -m venv ansible

2 - Activation de l'environnement virtuel et effectuer la mise à jour de l'outil python pip

$ cd ansible/
$ source bin/activate
$ python3 -m pip install -U pip

3 - Installation du module Ansible

$ python3 -m pip install -U ansible

4 - Vérifier la bonne installation de Ansible

$ ansible --version
ansible [core 2.16.7]
  config file = None
  configured module search path = ['/home/ronan/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/ronan/ansible/lib/python3.11/site-packages/ansible
  ansible collection location = /home/ronan/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/ronan/ansible/bin/ansible
  python version = 3.11.3 (main, Aug  9 2023, 09:30:45) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/home/ronan/ansible/bin/python3)
  jinja version = 3.1.4
  libyaml = True

$ ansible -m ping localhost
[WARNING]: No inventory was parsed, only implicit localhost is available
localhost | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Et voilà, l'environnement virtuel Python pour Ansible est prêt et opérationnel.

Etiquettes: 

Ansible: le fichier inventaire

Pour fonctionner, Ansible a besoin de connaitre la liste exhaustive de tous les serveurs qu'il doit administrer.

La documentation Ansible en ligne

1 - Création du fichier hosts

$ vim hosts

2 - Le contenu du fichier doit ressembler à ceci

$ cat hosts
[nginx]
SERVER1
SERVER2

[rabbitmq]
SERVER3

[glpi]
SERVER4

[docker]
SERVER5
SERVER6

[prod]
SERVER1
SERVER3
SERVER4
SERVER5

[dev]
SERVER2
SERVER6

Il est possible d'indiquer entre [...] un terme permettant de regrouper les différents serveurs en catégorie.

La commande suivante permet d'afficher tous les hosts présents dans le fichier inventaire.

$ ansible-inventory --graph
@all:
  |--@ungrouped:
  |--@nginx:
  |  |--SERVER1
  |  |--SERVER2
  |--@rabbitmq:
  |  |--SERVER3
  |--@glpi:
  |  |--SERVER4
  |--@docker:
  |  |--SERVER5
  |  |--SERVER6
  |--@prod:
  |  |--SERVER1
  |  |--SERVER3
  |  |--SERVER4
  |  |--SERVER5
  |--@dev:
  |  |--SERVER2
  |  |--SERVER6

... d'un groupe en particulier

$ ansible-inventory --graph nginx
@nginx:
  |--SERVER1
  |--SERVER2

 
 

 

 

Etiquettes: 

Ansible: le fichier de configuration ansible.cfg

Le fichier ansible.cfg n'est pas forcément nécessaire pour l'utilisation d'Ansible, mais il est fortement conseillé pour la simplifier.

Documentation Ansible en ligne

Voici un exemple limité des principaux paramètres à renseigner.

$ cat ansible.cfg
[defaults]
inventory          = ./hosts
interpreter_python = /usr/bin/python3
host_key_checking  = False
log_path           = ./ansible.log
remote_user        = root

- On y indique l'emplacement du fichier inventaire hosts
- L'interpréteur python à utiliser sur les serveurs distants
- On ne vérifie pas les clés des serveurs distants. Cela évite d'avoir tous les serveurs de renseignés dans le fichier ~/.ssh/know_hosts
- On renseigne le fichier pour les logs
- Le user utilisé pour se connecter aux différents serveurs

Il est possible de générer automatiquement le fichier de configuration via cette commande

$ ansible-config init --disabled > ansible.cfg

 

Etiquettes: 

Ansible: utilisation avec une clé ssh

Pour éviter, à chaque fois, de saisir le mot de passe (option -k) permettant de se connecter aux serveurs distants, il est possible d'utiliser une connexion par clé ssh.

La documentation Ansible en ligne

Tout est indiqué ici pour générer une clé ssh.

Une fois la clé ssh générée, il faut la copier sur tous les serveurs distants.
Pour cela, nous allons utiliser Ansible pour le faire à grande échelle.

Nous allons donc utiliser la commande authorized_key pour le faire.

$ ansible -m authorized_key -u mon_user -a "user=mon_user exclusive=true state=present key=""{{ lookup('file', '~/.ssh/id_rsa.pub') }}""" all -k

- L'option -m authorized_keys permet d'indiquer que nous souhaitons utiliser la commande authorized_keys
- L'option -u mon_user permet d'indiquer avec quel utilisateur se connecter aux serveurs distants.
- L'option -a "..." permet de renseigner les arguments nécessaires pour la commande authorized_keys. On y indique le user concerné, le terme exclusive permet d'indiquer qu'il doit y avoir une seule clé ssh d'autorisée, le terme state permet d'indiquer que la clé publique doit être présente (absent si l'on souhaite la supprimer) et enfin, le terme key permet, grâce à la fonction lookup, de copier le contenu du fichier ~/.ssh/id_rsa.pub du serveur local dans le fichier authorized_keys du ou des serveurs distants.
- Le mot clé all permet d'indiquer que l'opération doit être effectuée sur tous les hosts présents dans l'inventaire.
- L'option -k permet de saisir le mot de passe pour se connecter aux serveurs distants.

Ansible: les commandes ...

Voici quelques exemples d'utilisation des différentes commandes disponibles avec Ansible

Etiquettes: 

Ansible: la commande ping

La première commande Ansible que nous allons utiliser est la commande ping.

La documentation Ansible en ligne

Cette commande va permettre de tester si tous les serveurs sont accessibles et disponibles.

L'option -k permet de saisir le mot de passe pour se connecter aux serveurs. Le mot de passe doit être identique à tous les serveurs. Pour éviter cette manipulation, il est possible d'utiliser une connexion via une clé ssh.

L'option -m permet d'indique la commande à utiliser (ping).

Le mot clé all permet d'indiquer que nous souhaitons exécuter la commande sur tous les hosts présents dans le fichier inventaire.

$ ansible -k -m ping all
SSH password:
SERVER5 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
SERVER2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
SERVER1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
SERVER3 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
SERVER4 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
SERVER6 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Si je souhaite exécuter la commande ping uniquement sur mes hosts nginx

$ ansible -k -m ping nginx
SSH password:
SERVER2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
SERVER1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Rien de plus simple.

Etiquettes: 

Ansible: la commande setup

La comande setup permet d'obtenir un inventaire exhaustif des hosts de l'inventaire.

La documentation Ansible en ligne

Attention, cette commande retourne un json de plus de 1000 lignes contenant toutes les informations du ou des hosts interrogés.

Chaque clé du json peut être utilisée dans les playbooks Ansible grâce à la variable ansible_facts.

$ ansible -m setup SERVER1
SERVER1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.50.2.253",
            "172.17.0.1"
        ],
...
       "module_setup": true
    },
    "changed": false
}

Il est possible de filtrer directement sur un paramètre précis.

$ ansible -m setup SERVER1 -a "filter=ansible_processor_nproc"
SERVER1 | SUCCESS => {
    "ansible_facts": {
        "ansible_processor_nproc": 2
    },
    "changed": false
}

Pour info, tout comme les hosts présents dans le fichier inventaire, le serveur Ansible lui-même peut être utilisé grâce au mot clé localhost.

$ ansible -m setup localhost -a "filter=ansible_loadavg"
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_loadavg": {
            "15m": 0.43,
            "1m": 1.9,
            "5m": 0.78
        }
    },
    "changed": false
}

Etiquettes: 

Ansible: la commande "command"

La commande command permet d'exécuter une commande simple.

Voir la commande shell pour des exécutions plus complexes.

La documentation Ansible en ligne

Le commande n'étant pas exécutée par l'interpréteur de commande, l'utilisation des caractères spéciaux tels que * < > | ; & ne fonctionne pas.

Quelques exemples:

$ ansible -m command -a "uptime" SERVER1
SERVER1 | CHANGED | rc=0 >>
 13:37:18 up 12 days,  3:58,  2 users,  load average: 0,16, 0,11, 0,25

$ ansible -m command -a "w" SERVER2
SERVER2 | CHANGED | rc=0 >>
 13:41:22 up 12 days,  4:02,  2 users,  load average: 0,10, 0,09, 0,20
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
user pts/0    10.99.17.12      lun.10    2.00s  1.31s  0.00s w
user pts/1    10.99.17.12      09:53   34:37   0.25s  0.01s sshd: user [priv]

$ SERVER3 -m command -a "date" SERVER3
localhost | CHANGED | rc=0 >>
mer. juin  5 13:55:55 CEST 2024

 

Etiquettes: 

Ansible: la commande shell

La commande shell permet d'exécuter des commandes simples mais aussi complexes et également sous forme de scripts.

Documentation Ansible en ligne

Exemples:

$ ansible -m shell -a 'find /var/log/nginx/ -type f -name "access.log*" | xargs stat --printf "%-80n%-15s%-35w\n"' nginx
SERVER2 | CHANGED | rc=0 >>
/var/log/nginx/access.log-20240116                                              7052050        2023-11-15 17:37:33.620096531 +0100
/var/log/nginx/access.log                                                       10191          2024-01-16 09:28:20.137458998 +0100
SERVER1 | CHANGED | rc=0 >>
/var/log/nginx/access.log-20240116                                              2101711        2023-11-15 16:03:11.480149331 +0100
/var/log/nginx/access.log                                                       22278          2024-01-16 09:29:28.959290202 +0100

$ ansible -m shell -a 'cat /etc/passwd | grep root' SERVER1:SERVER2
SERVER1 | CHANGED | rc=0 >>
root:x:0:0:root:/root:/bin/bash
SERVER2 | CHANGED | rc=0 >>
root:x:0:0:root:/root:/bin/bash

$ ansible -m shell -a 'echo "Hello world!" > /tmp/hello_world ; ls -l /tmp/hello_world ; cat /tmp/hello_world' localhost
localhost | CHANGED | rc=0 >>
-rw-r--r-- 1 user group 13  5 juin  14:20 /tmp/hello_world
Hello world!

Etiquettes: 

Ansible: les playbooks ...

Quelques exemples de playbooks.

Ansible: playbooks: Template basique

Voici un template basique pour démarrer un nouveau playbook Ansible.

- name: Description du playbook
  hosts: all
  gather_facts: true
  tasks:
    ...


Le format est en YAML.
Il faut être très rigoureux concernant l'indentation.
Enormément d'erreurs sont dues à une mauvaise indentation.

La clé name permet de nommer le playbook
La clé hosts permet d'indiquer la liste des hosts présents dans l'inventaire à utiliser
La clé gather_facts permet d'obtenir un inventaire complet des hosts (true ou false)
La clé tasks permet de renseigner toutes les tâches que le playbook doit exécuter.

Concernant la clé hosts, la sélection est multiple.

Par exemple, le mot clé all permet d'indiquer la liste complète de tous les hosts.
Si je veux un host en particulier
    hosts: SERVER1 # (attention à la casse pour les nom de hosts).
Si je veux deux hosts
    hosts: SERVER1:SERVER2
Si je veux tous les hosts exceptés le SERVER2
    hosts: all:!SERVER2
Si je veux mes hosts nginx de prod
    hosts: nginx:&prod

Etiquettes: 

Ansible: playbooks: Template élaboré

Voici un template plus élaboré pour démarrer un nouveau playbook Ansible et prenant en compte des variables.

---
# Description
- name: Description du playbook
  hosts: localhost
  gather_facts: true
  gather_subset: all
  become: true
  serial: false
  collections:
    - community.vmware
  vars:
    bar: 1
    foo: "bar"
    bar_dict: {
      foo: {bar: "foo", foo: 2},
      bar: {bar: "bar", foo: 3}
    }
    date: "{{lookup('pipe','date +%Y%m%d')}}"
    bar_list:
      - bar
      - foo
    tmp_dir: /tmp
    tmp_file: "{{ tmp_dir }}/toto"
    enc_vault_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          32326566396363316431393632363934396534323464343139636430386337633930383363303531
          3765303765326332316633316565356533623630383337320a306131613737336662616234343135
          37373938646264653264653639386262303662366464396163346366666230646332393639653532
          3761333761623763610a656337316333393963386466303734663066313334396234656166383731
          3830
    ansible_distro: "{{ ansible_distribution | lower }}"
  vars_prompt:
    - name: install_git
      prompt: Faut-il installer le programme git (o/n) ?
      private: false
      default: o
  pre_tasks:
      - include_vars: vars.yaml
  tasks:
    - name: the first task
      # Comment
      ansible.builtin.debug:
        msg: |
          let's go
          inventory_hostname: {{ inventory_hostname }}
          bar: {{ bar }}
          foo: {{ foo }}
          bar_dict: {{ bar_dict }}
          bar_dict['bar']['foo']: {{ bar_dict['bar']['foo'] }}
          date: {{ date }}
          bar_list: {{ bar_list }}
          tmp_dir: {{ tmp_dir }}
          tmp_file: {{ tmp_file }}
          dec_vault_password: {{ enc_vault_password }}
          install_git: {{ install_git }}
          include_vars__other: {{ other }}
          ansible_distribution: {{ ansible_distribution }}
          ansible_distro: {{ ansible_distro }}
        verbosity: 0
 

 

Quelques petites explications:

- gather_facts (true/false) permet d'obtenir l'inventaire du ou des hosts
- gather_subset (liste exhaustive)  permet d'obtenir des informations supplémentaire concernant le ou les hosts
- become (true/false) permet d'obtenir les droits SUDO
- serial (true/false) permet d'indiquer s'il faut exécuter les tâches en série, host par host ou simultanément sur tous les hosts
- collections permet d'inclure des plugins Ansible supplémentaires
- vars permet d'inclure différentes variables (string, int, dict, list)
- vars_prompt permet d'inclure des variables initialisées par un prompt lors de l'exécution du playbook
- pre_tasks permet d'exécuter des tâches avant l'exécution des tâches du playbook (j'inclus des variables globales grâce à include_vars)

Pour info, la commande lookup('pipe', ...) permet de récupérer le résultat de la sortie standard de la commande passée en paramètre.
Plus d'info ici

Etiquettes: