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 tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn