Aller au contenu
Infrastructure as Code medium

Les states Salt : SLS, highstate, top file et requisites

11 min de lecture

Les commandes distantes sont pratiques mais éphémères. Les states font le saut décisif : décrire une fois l'état voulu d'un serveur, puis laisser Salt le faire converger de façon idempotente. Ce guide construit un serveur web déclaratif de bout en bout, du premier fichier SLS au highstate rejoué sans dérive, avec les requisites qui rechargent le service quand sa configuration change. Public visé : administrateurs à l'aise avec les commandes distantes de Salt.

  • Écrire un fichier SLS déclaratif (paquet, service, fichiers).
  • Associer des states aux minions avec le top file.
  • Appliquer un highstate et prouver son idempotence.
  • Ordonner les états avec les requisites (require, watch).
  • Simuler un changement avec test=True avant de l'appliquer.

Un state décrit un résultat, pas une suite d'étapes. Au lieu d'ordonner « lance apt install nginx », vous déclarez : « le paquet nginx est présent, le service est démarré et activé, ce fichier a ce contenu ». À chaque application, Salt compare l'état réel à l'état voulu et n'agit que sur les écarts.

C'est le principe d'idempotence : rejouer le même state deux fois ne change rien la seconde fois. Cette propriété est ce qui distingue la gestion de configuration d'un simple script shell, qui réexécute tout à chaque passage. La même logique déclarative existe chez Ansible, Chef ou Puppet ; Salt l'exprime en YAML rendu par Jinja.

Les states vivent dans des fichiers SLS (SaLt State), rangés sous le répertoire /srv/salt du master. Voici l'arborescence que nous allons construire pour un serveur web :

  • Répertoiresrv/salt/
    • top.sls
    • Répertoirenginx/
      • init.sls
      • tuning.conf
      • index.html

Le cœur du serveur web tient dans nginx/init.sls. Chaque bloc porte un identifiant, appelle une fonction d'état (pkg.installed, service.running, file.managed) et liste ses arguments. Le fichier ci-dessous installe nginx, garde son service démarré, déploie un fichier de réglage et une page d'accueil.

/srv/salt/nginx/init.sls
nginx:
pkg.installed: []
service.running:
- enable: true
- require:
- pkg: nginx
/etc/nginx/conf.d/tuning.conf:
file.managed:
- source: salt://nginx/tuning.conf
- user: root
- group: root
- mode: "0644"
- require:
- pkg: nginx
- watch_in:
- service: nginx
/var/www/html/index.html:
file.managed:
- source: salt://nginx/index.html
- user: root
- group: root
- mode: "0644"
- require:
- pkg: nginx

Trois détails méritent l'attention. Le mode est toujours quoté ("0644") : sans les guillemets, YAML l'interpréterait comme un nombre décimal. Le propriétaire et le groupe sont explicites plutôt qu'hérités du compte d'exécution. Enfin, la source salt://nginx/tuning.conf désigne un fichier servi par le master, depuis /srv/salt/nginx/.

Le fichier de réglage et la page sont volontairement simples :

/srv/salt/nginx/tuning.conf
client_max_body_size 32m;

Ici, tout tient dans un seul init.sls, ce qui reste lisible pour un service simple. Dès qu'un service se complexifie, la bonne pratique est de découper : l'installation dans init.sls, la configuration dans un conf.sls dédié, et les valeurs spécifiques à l'OS (nom de paquet, chemins) dans un map.jinja. On garde le cas simple pour l'instant.

Un state ne s'applique pas tout seul : le top file (top.sls) fait le lien entre les minions et les states à leur appliquer. Il réutilise le même ciblage que les commandes distantes, ici par grain role.

/srv/salt/top.sls
base:
'role:web':
- match: grain
- nginx

La ligne base désigne l'environnement par défaut. Tout minion dont le grain role vaut web recevra le state nginx. C'est la carte d'assignation de votre parc : un seul endroit décrit qui doit être quoi.

Le highstate applique à chaque minion l'ensemble des states que le top file lui assigne. C'est la commande centrale de Salt en configuration.

Fenêtre de terminal
sudo salt 'web1' state.apply
web1:
----------
ID: nginx
Function: pkg.installed
Result: True
Comment: All specified packages are already installed
Changes:
----------
ID: /etc/nginx/conf.d/tuning.conf
Function: file.managed
Result: True
Comment: File /etc/nginx/conf.d/tuning.conf updated
Changes:
----------
diff:
New file
mode:
0644
----------
ID: nginx
Function: service.running
Result: True
Comment: Service nginx is already enabled, and is running
Changes:
----------
nginx:
True
----------
ID: /var/www/html/index.html
Function: file.managed
Result: True
Comment: File /var/www/html/index.html updated
Summary for web1
------------
Succeeded: 4 (changed=2)
Failed: 0
------------

Lisez le résumé de bas en haut : 4 états exécutés, 2 modifiés, aucun échec. Salt n'a rien fait sur le paquet (déjà présent), a créé le fichier de réglage et la page, et a rechargé le service. Chaque bloc indique son résultat, un commentaire lisible et les changements appliqués.

Relançons exactement la même commande. C'est le test qui valide un state correct.

Fenêtre de terminal
sudo salt 'web1' state.apply
Summary for web1
------------
Succeeded: 4
Failed: 0
------------

Le résumé ne mentionne plus changed : zéro modification. Chaque état est déjà « in the correct state ». Un state qui reviendrait avec des changements à chaque passage aurait une dérive à corriger : c'est le signal d'alarme numéro un en gestion de configuration.

Par défaut, Salt exécute les états dans l'ordre du fichier, mais les requisites rendent les dépendances explicites et robustes. Vous en avez déjà utilisé deux dans le state nginx.

require garantit qu'un état ne s'exécute qu'après un autre : le service et les fichiers dépendent tous du paquet (require: pkg: nginx), donc nginx est installé avant qu'on tente de le démarrer ou d'écrire sa configuration.

watch (et son miroir watch_in) déclenche une réaction sur changement. Ici, tuning.conf porte watch_in: service: nginx : dès que le fichier change, Salt recharge nginx, et seulement dans ce cas. Vérifions-le en modifiant la valeur du réglage puis en réappliquant :

Fenêtre de terminal
sudo salt 'web1' state.apply
ID: /etc/nginx/conf.d/tuning.conf
Function: file.managed
Result: True
Comment: File /etc/nginx/conf.d/tuning.conf updated
----------
ID: nginx
Function: service.running
Result: True
Comment: Service restarted

Le Service restarted prouve que la modification du fichier a entraîné le rechargement, sans que vous ayez à l'ordonner manuellement. D'autres requisites complètent la palette : onchanges agit seulement si un autre état a changé, unless et onlyif conditionnent l'exécution à une commande. Pour réutiliser des states entre eux, include importe un autre fichier SLS et extend en ajuste un bloc.

# Réutiliser un state existant
include:
- nginx
extend:
nginx:
service.running:
- reload: true

Avant tout changement sensible, Salt sait simuler un highstate : il montre ce qui changerait sans rien modifier. C'est le filet de sécurité à adopter systématiquement.

Fenêtre de terminal
sudo salt 'web1' state.apply test=True
ID: /etc/nginx/conf.d/tuning.conf
Function: file.managed
Result: None
Comment: The file /etc/nginx/conf.d/tuning.conf is set to be changed

Le Result: None et le commentaire « set to be changed » signalent une modification prévue. Le fichier réel sur le minion reste intact : test=True n'écrit rien. Vous validez ainsi l'impact d'un changement avant de l'appliquer, exactement comme un plan avec Terraform.

Un state n'a de valeur que si le résultat est observable. Interrogeons le serveur web depuis le minion :

Fenêtre de terminal
sudo salt 'web1' cmd.run 'curl -s http://localhost/ | grep -o "<title>.*</title>"'
web1:
<title>Serveur web géré par Salt</title>

Le serveur répond et sert bien notre page : le paquet, le service, la configuration et le contenu sont tous conformes à ce que le state décrit.

Trois réflexes rendent vos states fiables en production.

Simuler systématiquement. Un state.apply test=True avant chaque application sensible vous montre l'impact sans risque. C'est gratuit et cela évite les mauvaises surprises.

Valider les configurations. Un fichier qui casse un service bloque son rechargement. Faites précéder les configs critiques d'une validation (nginx -t, sshd -t) via un état de vérification, et gardez les modes stricts et les propriétaires explicites, comme dans l'exemple.

Aucun secret dans les states. Mots de passe, clés et jetons n'ont rien à faire dans un fichier SLS versionné. Ils vivent dans les pillars, sujet d'un prochain module.

SymptômeCause probableSolution
No Top file foundtop.sls absent ou minion non cibléVérifier /srv/salt/top.sls et le grain de ciblage
Le state revient « changed » à chaque passageDérive ou state non idempotentIdentifier le bloc fautif, souvent un cmd.run non gardé
Job for <service> failedConfig déployée invalideCorriger le fichier, valider avec test=True puis <service> -t
Rendering SLS ... failedErreur de syntaxe YAML ou JinjaVérifier l'indentation et le mode quoté
Le watch ne recharge pasRequisite mal orientéUtiliser watch_in côté fichier ou watch côté service
  1. Un state décrit un résultat ; Salt le fait converger de façon idempotente.
  2. Les states vivent dans des fichiers SLS (YAML + Jinja) sous /srv/salt.
  3. Le top file assigne les states aux minions par ciblage (ici un grain).
  4. Le highstate applique tout ; le second passage sans changement prouve l'idempotence.
  5. Les requisites (require, watch) ordonnent les états et déclenchent les rechargements ; test=True simule avant d'appliquer.

Le prochain module, Les modules Salt, détaille les briques d'état (pkg, service, file, user, cron, git) pour déployer une application complète. En attendant sa publication, ces ressources prolongent ce guide.

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