revision.devops

Ansible

Ansible configure des machines existantes : paquets, fichiers, services, utilisateurs. Il se connecte en SSH, sans agent à installer, et pousse les changements depuis un poste de contrôle ou un runner CI.

Terraform et Ansible

TerraformAnsible
RôleCréer l'infrastructure (VM, réseau, buckets)Configurer ce qui tourne dessus
StyleDéclaratifDéclaratif dans les modules, mais exécuté dans l'ordre des tâches
MémoireStateAucune, il interroge la machine à chaque exécution
ConnexionAPI du cloudSSH (ou WinRM)

Le combo classique : Terraform crée les VM, Ansible installe et configure Kafka dessus.

Inventaire

Statique, en INI ou YAML :

all:
  children:
    kafka:
      hosts:
        kafka-1.prod.internal:
        kafka-2.prod.internal:
        kafka-3.prod.internal:
      vars:
        kafka_heap: 4g
    zookeeper:
      hosts:
        zk-[1:3].prod.internal:

Dynamique sur GCP, avec le plugin google.cloud.gcp_compute (fichier qui doit finir par gcp.yml) :

plugin: google.cloud.gcp_compute
projects:
  - dbb-prod
auth_kind: application
keyed_groups:
  - key: labels.role
    prefix: role

Les VM sont groupées automatiquement par label. Plus besoin de maintenir la liste à la main quand l'autoscaling ajoute des machines.

ansible-inventory -i inventory/ --graph
ansible all -i inventory/ -m ping

Playbook

- name: Installer et configurer nginx
  hosts: web
  become: true
  vars:
    nginx_port: 8080

  tasks:
    - name: Installer nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Déployer la configuration
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        mode: "0644"
        validate: nginx -t -c %s
      notify: Recharger nginx

    - name: Démarrer nginx
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Recharger nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
  • become: true : élévation de privilèges (sudo).
  • validate : teste le fichier avant de le mettre en place. Une config cassée n'arrive jamais sur le serveur.
  • Handler : ne s'exécute que si une tâche qui le notifie a produit un changement, et une seule fois à la fin du play, même si plusieurs tâches le notifient.

Idempotence

Lancer le playbook deux fois de suite : la deuxième exécution doit afficher changed=0. C'est la propriété la plus importante d'un bon playbook.

Ce qui la casse :

# Mauvais : s'exécute et affiche "changed" à chaque fois
- name: Créer le dossier
  ansible.builtin.shell: mkdir -p /opt/app

# Bon : module natif
- name: Créer le dossier
  ansible.builtin.file:
    path: /opt/app
    state: directory

Si shell ou command est inévitable :

- name: Initialiser la base
  ansible.builtin.command: /opt/app/bin/init-db
  args:
    creates: /opt/app/.db-initialized   # ne s'exécute pas si le fichier existe

- name: Vérifier la version
  ansible.builtin.command: app --version
  register: app_version
  changed_when: false                   # lecture seule, jamais "changed"

Rôles

Structure standard, générée par ansible-galaxy init kafka :

roles/kafka/
├── defaults/main.yml    # variables par défaut, priorité la plus faible
├── vars/main.yml        # variables internes au rôle, priorité forte
├── tasks/main.yml
├── handlers/main.yml
├── templates/server.properties.j2
├── files/
└── meta/main.yml        # dépendances

Mettre dans defaults/ tout ce que l'utilisateur du rôle doit pouvoir surcharger.

Variables et priorités

Il y a 22 niveaux de priorité. À retenir :

  • defaults/ du rôle : le plus faible.
  • group_vars/all, puis group_vars/<groupe>, puis host_vars/<hôte>.
  • Variables du play, vars/ du rôle.
  • -e (extra vars) en ligne de commande : gagne toujours.

Les facts sont collectés au début du play (gather_facts) : ansible_facts['os_family'], ansible_facts['default_ipv4']['address'], mémoire, disques...

Templates Jinja2

# {{ ansible_managed }}
broker.id={{ broker_id }}
listeners=PLAINTEXT://{{ ansible_facts['default_ipv4']['address'] }}:9092
log.dirs={{ kafka_log_dirs | join(',') }}
{% if kafka_enable_tls %}
ssl.keystore.location=/etc/kafka/keystore.jks
{% endif %}

Boucles et conditions

- name: Créer les utilisateurs
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
  loop:
    - { name: alice, groups: wheel }
    - { name: bob, groups: dev }

- name: Paquet spécifique RHEL
  ansible.builtin.dnf:
    name: policycoreutils-python-utils
  when: ansible_facts['os_family'] == "RedHat"

ansible-vault

ansible-vault create group_vars/prod/vault.yml
ansible-vault edit group_vars/prod/vault.yml
ansible-vault encrypt_string 'MonMotDePasse' --name 'db_password'
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Convention : vault.yml contient vault_db_password, et vars.yml en clair contient db_password: "{{ vault_db_password }}". On voit quelles variables existent sans déchiffrer.

En CI, le mot de passe du vault vient d'une variable protégée et masquée, ou mieux, Ansible lit directement les secrets dans Secret Manager ou Vault via un lookup.

Exécution prudente

ansible-playbook site.yml --check --diff     # simulation avec les différences
ansible-playbook site.yml --limit kafka-1    # une seule machine
ansible-playbook site.yml --tags config      # seulement les tâches taguées
ansible-playbook site.yml --start-at-task "Déployer la configuration"
ansible-playbook site.yml -vvv               # debug

Pour un cluster, serial met à jour les machines par lots et évite de tout couper en même temps :

- hosts: kafka
  serial: 1                 # un broker à la fois
  max_fail_percentage: 0    # stop au premier échec

Qualité

  • ansible-lint en CI.
  • Molecule pour tester un rôle dans des conteneurs avant de le passer en prod.
  • Collections Ansible versionnées dans requirements.yml.

Questions d'entretien à préparer

  • Ansible ou Terraform, quelle différence ?
  • C'est quoi l'idempotence, comment la vérifier ?
  • Comment éviter qu'un shell affiche changed à chaque fois ?
  • C'est quoi un handler et quand s'exécute-t-il ?
  • Comment gères-tu les secrets dans Ansible ?
  • Comment mettre à jour un cluster de 3 brokers sans coupure ? (serial: 1, health check entre chaque)
  • Comment tester un rôle ? (--check, Molecule)
Faire le QCMExercicesGitLab CI →