Aller au contenu
Infrastructure as Code medium

Modules Salt : pkg, service, file, user, cron et git

11 min de lecture

Un state assemble des fonctions d'état issues de modules. Ce module fait le tour des briques indispensables pour déployer une application réelle : pkg, service, file, user, group, cron, git et, en dernier recours, cmd. L'objectif : toujours préférer un module idempotent au shell brut. Vous terminerez par un TP complet qui déploie une application en combinant ces modules, puis prouve son idempotence au second passage. Public visé : débutants et intermédiaires ayant écrit leur premier state.

  • Reconnaître les fonctions d'état les plus utilisées.
  • Choisir le bon module au lieu d'un cmd.run fragile.
  • Assembler ces modules pour un déploiement applicatif complet.

Salt expose deux familles de fonctions au nom trompeusement proche, et c'est la confusion la plus fréquente au démarrage. Les modules d'exécution agissent tout de suite en ligne de commande : pkg.install, service.restart, cmd.run. Les fonctions d'état décrivent un résultat voulu dans un fichier SLS : pkg.installed, service.running, file.managed. Le participe passé (installed, running) est l'indice : il désigne un état à atteindre, pas une action à lancer.

Module d'exécutionFonction d'état
Exemplepkg.install nginxpkg.installed
Ligne de commandeFichier SLS
QuandAgit immédiatementConverge vers l'état voulu
IdempotentNonOui

C'est pourquoi vous tapez salt web1 pkg.install nginx pour un dépannage rapide, mais écrivez pkg.installed dans un state destiné à être rejoué. Ce module ne traite que les fonctions d'état, celles qui composent vos SLS.

Chaque module gère une facette du système. Le tableau ci-dessous résume les plus courants ; la règle d'or est de décrire un état, jamais une commande.

ModuleFonction d'état typiqueRôle
pkgpkg.installedInstaller un paquet, multi-distribution
serviceservice.runningDémarrer et activer un service
filefile.managed, file.directoryDéployer fichiers et répertoires
useruser.presentCréer et gérer des comptes
groupgroup.presentGérer les groupes
croncron.presentPlanifier des tâches
gitgit.latestCloner ou mettre à jour un dépôt
cmdcmd.runDernier recours, non idempotent par défaut

Chaque ligne mérite un mot d'explication en contexte : service.running avec enable: true garantit le démarrage au boot, file.managed sait poser le propriétaire, le mode et vérifier le contenu, git.latest maintient un dépôt à jour de façon idempotente. Un point clé souvent oublié : la plupart de ces modules ont besoin de leur outil sous-jacent installé sur le minion. Le module cron n'est disponible que si le paquet cron est présent, et git.latest échoue sans le binaire git. On les installe donc d'abord avec pkg.installed, puis on chaîne le reste avec un requisite require.

cmd.run exécute n'importe quelle commande, mais Salt ne sait pas si l'état est déjà atteint : il relance à chaque highstate. Pour le rendre supportable, on l'encadre avec unless ou onlyif, qui court-circuitent l'exécution quand une condition est déjà vraie. Ici, un marqueur .built évite de rejouer une étape de build une fois qu'elle a réussi :

marqueur-build:
cmd.run:
- name: touch /opt/appsvc/.built
- unless: test -f /opt/appsvc/.built

Au premier passage, la commande s'exécute (Command "touch /opt/appsvc/.built" run). Au second, unless trouve le fichier et Salt saute la commande sans la relancer : c'est ce test qui rend un cmd.run idempotent.

Assemblons ces modules pour déployer une application complète sur un minion : un utilisateur de service, ses dépendances installées, un fichier de configuration, le code récupéré avec git.latest, une tâche cron de maintenance, un service activé et une étape shell gardée. Le state suivant est enregistré côté master dans /srv/salt/appdeploy.sls.

# groupe + utilisateur de service
appsvc-group:
group.present:
- name: appsvc
appsvc-user:
user.present:
- name: appsvc
- gid: appsvc
- home: /opt/appsvc
- shell: /bin/bash
- require:
- group: appsvc-group
# dépendances applicatives (git servira au clone, cron à la planification)
app-deps:
pkg.installed:
- pkgs:
- git
- tree
- cron
# répertoire + fichier de configuration
/etc/appsvc:
file.directory:
- user: appsvc
- group: appsvc
- mode: "0750"
- require:
- user: appsvc-user
/etc/appsvc/config.ini:
file.managed:
- user: appsvc
- group: appsvc
- mode: "0640"
- contents: |
[app]
env = production
workers = 4
- require:
- file: /etc/appsvc
# script de maintenance appelé par la tâche cron
/opt/appsvc/maintenance.sh:
file.managed:
- user: appsvc
- group: appsvc
- mode: "0755"
- contents: |
#!/bin/sh
# purge des journaux applicatifs de plus de 7 jours
find /opt/appsvc/logs -type f -mtime +7 -delete 2>/dev/null || true
- require:
- user: appsvc-user
# code récupéré depuis un dépôt Git
app-code:
git.latest:
- name: https://github.com/octocat/Hello-World.git
- target: /opt/appsvc/src
- user: appsvc
- require:
- pkg: app-deps
- user: appsvc-user
# tâche planifiée de maintenance
appsvc-maintenance:
cron.present:
- name: /opt/appsvc/maintenance.sh
- user: appsvc
- minute: 0
- hour: 3
- require:
- file: /opt/appsvc/maintenance.sh
- pkg: app-deps
# service activé au démarrage
cron-service:
service.running:
- name: cron
- enable: true
- require:
- pkg: app-deps
# étape shell rendue idempotente par unless
marqueur-build:
cmd.run:
- name: touch /opt/appsvc/.built
- unless: test -f /opt/appsvc/.built
- require:
- git: app-code

Les requisites require ordonnent le tout : l'utilisateur attend son groupe, le clone attend git, la tâche cron attend son script et le paquet cron. On applique ce state sur un minion en le nommant :

Fenêtre de terminal
sudo salt proxy1 state.apply appdeploy

state.apply appdeploy applique un state précis, sans passer par le top file, pratique pour tester un SLS isolément. C'est différent du highstate (state.apply sans argument), qui applique tout ce que le top file assigne au minion.

Au premier passage, chaque module effectue son changement :

ID: appsvc-group
Function: group.present
Comment: New group appsvc created
ID: appsvc-user
Function: user.present
Comment: New user appsvc created
ID: app-deps
Function: pkg.installed
Comment: The following packages were installed/updated: git, tree, cron
ID: /etc/appsvc
Function: file.directory
Comment:
ID: /etc/appsvc/config.ini
Function: file.managed
Comment: File /etc/appsvc/config.ini updated
ID: /opt/appsvc/maintenance.sh
Function: file.managed
Comment: File /opt/appsvc/maintenance.sh updated
ID: app-code
Function: git.latest
Comment: https://github.com/octocat/Hello-World.git cloned to /opt/appsvc/src
ID: appsvc-maintenance
Function: cron.present
Comment: Cron /opt/appsvc/maintenance.sh added to appsvc's crontab
ID: cron-service
Function: service.running
Comment: The service cron is already running
ID: marqueur-build
Function: cmd.run
Comment: Command "touch /opt/appsvc/.built" run
Summary for proxy1
Succeeded: 10 (changed=9)
Failed: 0

Un seul état ne change rien : cron-service, car l'installation du paquet cron a déjà démarré son service. C'est cohérent : Salt ne redémarre pas un service déjà dans l'état voulu.

Un state correct ne change rien quand on le rejoue sur un système déjà conforme. Le second passage le vérifie :

Fenêtre de terminal
sudo salt proxy1 state.apply appdeploy
Summary for proxy1
Succeeded: 10
Failed: 0

Dix états réussis, aucun changement : chaque module a constaté que l'état voulu était déjà atteint. Le groupe et l'utilisateur existent, les paquets sont installés, le dépôt est à jour, la tâche cron est en place, et unless a fait sauter le cmd.run. La preuve côté machine confirme le résultat :

Fenêtre de terminal
sudo salt proxy1 cmd.run 'id appsvc; ls /opt/appsvc/src; crontab -l -u appsvc | tail -1; systemctl is-active cron; stat -c "%a %U:%G" /etc/appsvc/config.ini'
proxy1:
uid=1001(appsvc) gid=1001(appsvc) groups=1001(appsvc)
README
0 3 * * * /opt/appsvc/maintenance.sh
active
640 appsvc:appsvc

L'utilisateur existe, le code est cloné, la tâche cron pointe vers un script réel exécuté à 3h, le service cron est actif et le fichier de configuration porte le bon propriétaire et les bonnes permissions. Dix modules, une seule déclaration d'état voulu, et un résultat reproductible.

  1. Un state assemble des fonctions d'état issues de modules dédiés.
  2. pkg, service, file, user, git et cron couvrent la plupart des besoins de déploiement.
  3. file.managed gère propriétaire, mode et contenu en une déclaration.
  4. Un module dépend souvent de son outil installé : cron et git.latest exigent leur paquet, chaîné par un requisite require.
  5. cmd.run est le dernier recours ; le garder avec unless ou onlyif pour préserver l'idempotence.

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