Aller au contenu
Infrastructure as Code medium

lineinfile vs template Ansible : quel module pour quel usage

70 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)
- name: Ajuster sysctl.conf
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: '^net.ipv4.ip_forward'
line: 'net.ipv4.ip_forward = 1'
# ✅ Inscrire un host dans /etc/hosts
- name: Ajuster 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)
- name: Ajuster admin
ansible.builtin.lineinfile:
path: /etc/sudoers.d/admin
line: 'admin ALL=(ALL) NOPASSWD:ALL'
validate: '/usr/sbin/visudo -cf %s'
create: true
owner: root
group: root
mode: "0440"

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 :

- name: Ajuster sysctl.conf
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
- name: Générer myapp.conf
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)
- name: Générer nginx.conf
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: '/usr/sbin/nginx -t -c %s'
backup: true
mode: "0644"
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
- name: Générer sysctl.conf
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
- name: Ajuster hosts
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
- name: Ajuster hosts
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

Section intitulée « Cas pratique, les 2 approches dans un même playbook »

Voici l'exemple validé sur le lab 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.

L'arbitrage entre les deux modules se tranche sur un fichier réel, avec un propriétaire et un format. Le lab vous fait modifier deux lignes de /etc/hosts avec lineinfile, générer une configuration complète avec template, et combiner les deux dans un même playbook selon le motif base plus overrides. Vous y provoquez aussi le défaut classique du lineinfile sans regexp, qui empile une ligne de plus à chaque exécution.

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