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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Reconnaître les fonctions d'état les plus utilisées.
- Choisir le bon module au lieu d'un
cmd.runfragile. - Assembler ces modules pour un déploiement applicatif complet.
Prérequis
Section intitulée « Prérequis »- Savoir écrire et appliquer un state (module Les states).
Deux familles à ne pas confondre
Section intitulée « Deux familles à ne pas confondre »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écution | Fonction d'état | |
|---|---|---|
| Exemple | pkg.install nginx | pkg.installed |
| Où | Ligne de commande | Fichier SLS |
| Quand | Agit immédiatement | Converge vers l'état voulu |
| Idempotent | Non | Oui |
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.
Les modules du quotidien
Section intitulée « Les modules du quotidien »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.
| Module | Fonction d'état typique | Rôle |
|---|---|---|
pkg | pkg.installed | Installer un paquet, multi-distribution |
service | service.running | Démarrer et activer un service |
file | file.managed, file.directory | Déployer fichiers et répertoires |
user | user.present | Créer et gérer des comptes |
group | group.present | Gérer les groupes |
cron | cron.present | Planifier des tâches |
git | git.latest | Cloner ou mettre à jour un dépôt |
cmd | cmd.run | Dernier 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.
Le piège du cmd.run
Section intitulée « Le piège du cmd.run »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/.builtAu 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.
Travaux pratiques : déployer une application
Section intitulée « Travaux pratiques : déployer une application »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 serviceappsvc-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 Gitapp-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 maintenanceappsvc-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émarragecron-service: service.running: - name: cron - enable: true - require: - pkg: app-deps
# étape shell rendue idempotente par unlessmarqueur-build: cmd.run: - name: touch /opt/appsvc/.built - unless: test -f /opt/appsvc/.built - require: - git: app-codeLes 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 :
sudo salt proxy1 state.apply appdeploystate.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 proxy1Succeeded: 10 (changed=9)Failed: 0Un 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.
Prouver l'idempotence
Section intitulée « Prouver l'idempotence »Un state correct ne change rien quand on le rejoue sur un système déjà conforme. Le second passage le vérifie :
sudo salt proxy1 state.apply appdeploySummary for proxy1Succeeded: 10Failed: 0Dix é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 :
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:appsvcL'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.
À retenir
Section intitulée « À retenir »- Un state assemble des fonctions d'état issues de modules dédiés.
pkg,service,file,user,gitetcroncouvrent la plupart des besoins de déploiement.file.managedgère propriétaire, mode et contenu en une déclaration.- Un module dépend souvent de son outil installé :
cronetgit.latestexigent leur paquet, chaîné par un requisiterequire. cmd.runest le dernier recours ; le garder avecunlessouonlyifpour préserver l'idempotence.