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
| Terraform | Ansible | |
|---|---|---|
| Rôle | Créer l'infrastructure (VM, réseau, buckets) | Configurer ce qui tourne dessus |
| Style | Déclaratif | Déclaratif dans les modules, mais exécuté dans l'ordre des tâches |
| Mémoire | State | Aucune, il interroge la machine à chaque exécution |
| Connexion | API du cloud | SSH (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, puisgroup_vars/<groupe>, puishost_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-linten 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
shellaffichechangedà 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)