Aller au contenu
Infrastructure as Code medium

lineinfile vs template Ansible : quel module pour quel usage

10 min de lecture

Logo Ansible

Vous avez une config à gérer côté managed node, choisissez-vous lineinfile: (modifie une ligne précise) ou template: (génère le fichier complet) ? Ce choix est moins évident qu'il n'y paraît : lineinfile est tentant parce que rapide à écrire, mais il devient un anti-pattern dès qu'on dépasse 2-3 lignes. À l'inverse, template est puissant mais demande de posséder le fichier complet (vous le générez de zéro). Cette page donne les critères de choix précis et désamorce les pièges classiques.

  • Les 2 cas d'usage clairs de lineinfile: (et seulement ceux-là)
  • Les 2 cas d'usage clairs de template:
  • L'usage de blockinfile: (bloc multi-lignes ponctuel)
  • Les anti-patterns : lineinfile × 30, template qui écrase un fichier que vous ne possédez pas
  • Critères de décision sur 3 questions
  • Avoir lu Module template ;
  • Avoir manipulé lineinfile sur le lab bootstrap/prepare-managed-nodes (cf. la tâche /etc/hosts).
Vous voulez...Module
Ajouter / modifier 1 ligne précise dans un fichier que vous ne possédez pas (/etc/sysctl.conf, /etc/sudoers)lineinfile:
Insérer un bloc de N lignes (instructions cloud-init, snippet)blockinfile:
Générer un fichier de config complet (nginx.conf, myapp.conf, sshd_config)template:
Effacer + régénérer un fichier à chaque runtemplate: ou copy:
Modifier 30 lignes d'un fichier de configtemplate: (jamais 30 lineinfile:)
# ✅ Activer IP forwarding (1 ligne)
- ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: '^net.ipv4.ip_forward'
line: 'net.ipv4.ip_forward = 1'
# ✅ Inscrire un host dans /etc/hosts
- ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^192\.168\.99\.99\s'
line: '192.168.99.99 mon-host.lab'
# ✅ Ajouter un alias dans /etc/sudoers (avec validate)
- ansible.builtin.lineinfile:
path: /etc/sudoers.d/admin
line: 'admin ALL=(ALL) NOPASSWD:ALL'
validate: '/usr/sbin/visudo -cf %s'
create: true

Pattern clair : 1 ligne précise, identifiable par une regex, dans un fichier dont vous ne contrôlez pas le reste.

Pour insérer plusieurs lignes sans posséder tout le fichier :

- ansible.builtin.blockinfile:
path: /etc/sysctl.conf
block: |
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv4.conf.all.rp_filter = 1
marker: "# {mark} ANSIBLE MANAGED forwarding"

marker: génère deux marqueurs # BEGIN ANSIBLE MANAGED forwarding et # END ANSIBLE MANAGED forwarding autour du bloc, Ansible les utilise pour le remettre à jour ou le supprimer au prochain run.

À utiliser pour 3-10 lignes consécutives. Au-delà, basculez sur template:.

# ✅ Générer un fichier de config applicatif complet
- ansible.builtin.template:
src: templates/myapp.conf.j2
dest: /etc/myapp/myapp.conf
mode: "0644"
# ✅ Régénérer nginx.conf à chaque run (vous le possédez)
- ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: '/usr/sbin/nginx -t -c %s'
backup: true
notify: Reload nginx

Pattern clair : vous possédez le fichier complet, le contenu est généré à partir de variables Ansible, l'idempotence est gérée par checksum.

Si vous écrivez plus de 3 lineinfile: sur le même fichier, vous avez un problème de design. Exemples vus en code legacy :

# ❌ ANTI-PATTERN
- ansible.builtin.lineinfile: { path: /etc/nginx/nginx.conf, regexp: '^worker_processes', line: 'worker_processes 4;' }
- ansible.builtin.lineinfile: { path: /etc/nginx/nginx.conf, regexp: '^worker_connections', line: 'worker_connections 1024;' }
- ansible.builtin.lineinfile: { path: /etc/nginx/nginx.conf, regexp: '^server_tokens', line: 'server_tokens off;' }
- ansible.builtin.lineinfile: { path: /etc/nginx/nginx.conf, regexp: '^keepalive_timeout', line: 'keepalive_timeout 65;' }
# ... 26 lignes plus loin

→ Migrer vers template: avec un fichier nginx.conf.j2 complet. Plus lisible, plus rapide à exécuter, plus facile à maintenir.

Anti-pattern 2, template: sur un fichier que vous ne possédez pas

Section intitulée « Anti-pattern 2, template: sur un fichier que vous ne possédez pas »
# ❌ ANTI-PATTERN
- ansible.builtin.template:
src: my-sysctl.conf.j2
dest: /etc/sysctl.conf # ← écrase TOUT le contenu existant

/etc/sysctl.conf peut contenir des entrées ajoutées par d'autres paquets (/etc/sysctl.d/*.conf aussi). Les écraser casse des choses.

→ Préférer lineinfile: (ou un nouveau fichier dans /etc/sysctl.d/99-myapp.conf que vous possédez).

# ❌ ANTI-PATTERN
- ansible.builtin.lineinfile:
path: /etc/hosts
line: '192.168.99.99 mon-host.lab' # ← ajoutée à CHAQUE run

Sans regexp:, Ansible vérifie si la ligne exacte existe. Si vous changez la valeur (192.168.99.99192.168.99.100), l'ancienne ligne reste ET la nouvelle est ajoutée → doublon.

Toujours poser un regexp: qui identifie la ligne par son pattern stable :

# ✅ Avec regexp pour idempotence
- ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^192\.168\.99\.99\s' # match la ligne par IP
line: '192.168.99.99 mon-host.lab'

Quand vous hésitez, demandez-vous :

  1. Combien de lignes vous voulez gérer dans ce fichier ?

    • 1-2 → lineinfile:
    • 3-10 → blockinfile: (avec marker)
    • 10+ → template:
  2. Vous possédez le fichier complet ?

    • Oui (vous générez tout) → template:
    • Non (d'autres systèmes y écrivent) → lineinfile: / blockinfile:
  3. Le fichier a un format structuré que lineinfile peut parser proprement ?

    • Format simple key = valuelineinfile: OK
    • Format complexe avec blocs (server { ... }, sections [xxx]) → template:

Cas pratique, les 2 approches dans un même playbook (lab ecrire-code/lineinfile-vs-template)

Section intitulée « Cas pratique, les 2 approches dans un même playbook (lab ecrire-code/lineinfile-vs-template) »

Voici l'exemple validé sur le lab 30-ecrire-code-lineinfile-vs-template :

- name: Challenge lineinfile vs template
hosts: db1.lab
become: true
vars:
server:
host: "0.0.0.0"
port: 8080
workers: 4
database:
url: "postgres://db1.lab/myapp"
pool_size: 10
tasks:
# 1 ligne : lineinfile (on ne possède pas /etc/hosts complet)
- name: Ajouter une entrée DNS via lineinfile
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^192\.168\.99\.99\s'
line: '192.168.99.99 mon-host.lab'
# Fichier complet : template (on possède /etc/myapp.conf)
- name: Générer le fichier de config app via template
ansible.builtin.template:
src: templates/myapp.conf.j2
dest: /etc/myapp.conf
owner: root
group: root
mode: "0644"

/etc/hosts reçoit une seule ligne ajoutée. /etc/myapp.conf est généré complètement depuis le template. Chaque module sur son terrain.

  • lineinfile: = 1-2 lignes ciblées par regex dans un fichier que vous ne possédez pas.
  • blockinfile: = bloc multi-lignes (3-10) avec marker délimitant la zone managée.
  • template: = fichier complet généré depuis un .j2 + variables Ansible.
  • Anti-pattern n°1 : 30 lineinfile: sur le même fichier → migrer vers template:.
  • Anti-pattern n°2 : template: qui écrase un fichier partagé (/etc/sysctl.conf) → revenir à lineinfile: ou créer un nouveau fichier dans le .d/.
  • 3 questions de décision : nombre de lignes, propriété du fichier, complexité du format.

Cette page a un lab d'accompagnement : labs/ecrire-code/lineinfile-vs-template/ dans stephrobert/ansible-training. Le challenge combine les 2 approches dans un même playbook (lineinfile sur /etc/hosts, template pour /etc/myapp.conf).

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/lineinfile-vs-template/
cat README.md
pytest -v challenge/tests/

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn