Salt: à la découverte des States

Attention : cet article a été rédigé en 2014 et n’a pas été mis à jour depuis. Les concepts sont restés les même mais la syntaxe a pu évoluer depuis.

Après notre introduction à Salt, nous allons maintenant aborder les States.

Qu’est-ce qu’un state ?

Les States sont des fichiers textes décrivant l’état dans lequel doit se situer un ordinateur : on y définit les packages à installer, les fichiers à contrôler, les permissions à attribuer, etc. Ces fichiers .sls (Salt State File) utilisent la syntaxe YAML couplée au moteur de template Python Jinja.

Il est possible d’appliquer un state sur l’ensemble du parc ou sur certains serveurs. Dans ce dernier cas, il existe deux possibilités pour sélectionner nos minions : en se basant sur l’ID (généralement le nom d’hôte) ou sur les pillars et grains. J’aborderais ces deux derniers en fin de billet.

Un module est un dossier composé d’un ou plusieurs fichiers .sls. Il est recommandé de grouper ses states par modules (un dossier étant un module).

Structure d’un fichier .sls

Voici un exemple concret:

httpd:
  pkg:
    - installed
  service:
    - running
    - require:
      - pkg: httpd

Décryptons-le:

  • Le state s’intitule httpd (c’est son ID).
  • Lignes 2 et 4: pkg et service indiquent le début d’une déclaration.
  • Ligne 3: on utilise la fonction installed du module pkg.
  • Ligne 5: on utilise la fonction running du module service.
  • Ligne 6: on utilise la fonction require du module service avec, comme argument, le nom du package.

Lorsqu’une fonction requière un argument sans que celui-ci ne lui soit pas passé explicitement, c’est l’ID du state qui est utilisé. pkg.installed et service.running auront donc comme seul argument apache.

Déployer des fichiers de configuration

Il est souvent intéressant de déployer des fichiers de configuration (ntp.conf, vhosts apache, my.cnf, etc.) sur l’ensemble de nos serveurs et Salt va pouvoir nous y aider avec la fonction file.managed.

Créons un second state pour la gestion de /etc/ntp.conf:

/etc/ntp.conf:
  file.managed:
    - source: salt://ntp/ntp.conf
    - user: root
    - group: root
    - mode: 600

L’argument source pour la fonction file.managed définit l’emplacement où aller récupérer le fichier de configuration. Plusieurs sources sont supportées: HTTP, HTTPS et le serveur de fichier embarqué à Salt.

En utilisant salt://, le minion va interroger le master Salt pour récupérer un fichier ntp.conf situé dans un répertoire ntp. Pour activer le serveur de fichier, il est nécessaire de dé-commenter les trois lignes suivantes dans /etc/salt/master:

file_roots:
  base:
    - /srv/salt

Plaçons notre fichier de configuration dans /srv/salt/ntp/ntp.conf:

restrict 127.0.0.1
restrict -6 ::1

server 0.fr.pool.ntp.org iburst
server 1.fr.pool.ntp.org iburst
server 2.fr.pool.ntp.org iburst
server 3.fr.pool.ntp.org iburst

Les states sont recherchés par défaut dans dans /srv/salt/. Il va donc créer ce dossier puis un fichier /srv/salt/ntp.sls avec notre state (voir le code ci-dessus).

Testons notre fichier avec la fonction state.sls:

salt '*' state.sls ntp

Résultat attendu:

[root@salt-master ~]# salt '*' state.sls ntp
salt-minion:
----------
          ID: /etc/ntp.conf
    Function: file.managed
      Result: True
     Comment: File /etc/ntp.conf updated
     Changes:
              (...)
Summary
------------
Succeeded: 1
Failed:    0
------------
Total:     1

Petit HS concernant NTP: Salt propose un module pour gérer les serveurs NTP, notre code ci-dessus n’a donc aucun intérêt si ce n’est découvrir Salt 😉

Utiliser les conditions

Imaginons que nous souhaitons pouvoir gérer plusieurs distributions, nous allons vite être confronté à un problème de taille: le nom des packages et l’emplacement des fichiers voir même la syntaxe de la configuration varie d’une distribution à l’autre.

Nous avons deux solutions: créer autant de fichiers de configuration différents qu’il y a de distributions ou utiliser des conditions dans nos states. La seconde méthode est bien évidemment à préférer.

Exemple avec l’installation du client NTP:

ntp:
  pkg:
    {% if grains['os_family'] == 'RedHat' %}
    - name: ntpd
    {% elif grains['os_family'] == 'Debian' %}
    - name: ntp
    {% endif %}
    - installed

Quelques explications:

Les conditions se présentent sous la forme:

 {% if la condition %}
   Si c’est vrai
{% elif autre condition %}
   Si l’autre condition est vrai
{% else %}
   Si les conditions précédentes sont fausses
{% endif %}

On peut également les écrire sur une ligne:
{% if la condition %}Valeur{% endif %}

Pour une liste exhaustive des possibilités du moteur de template, consultez la documentation de Jinja: http://jinja.pocoo.org/docs/templates/.

Grains est un tableau contenant des informations sur le serveur exécutant le minion. J’y reviendrais plus tard, vous pouvez obtenir les valeurs disponibles à l’aide de la commande:

salt '*' grains.items

Pour les manipuler, la syntaxe est la suivante: grains['nom de la variable'].

Les pillars

Il peut aussi être nécessaire de partager des données entre les différents states ou d’isoler les valeurs des states eux-même (pour faciliter la ré-utilisation). Salt propose un second tableau accessible de n’importe quel minion: les pillars.

Ces fichiers seront à placer dans un répertoire séparé (par défaut: /srv/pillar) avec l’extension .sls.

Tout comme pour les states, il est nécessaire de les activer en indiquant leurs emplacements dans le fichier de configuration du master:

pillar_roots:
  base:
    - /srv/pillar

Exemple complet et concret: Nous souhaitons gérer le fichier /etc/hosts de façon centralisé en ajoutant un entête en haut du fichier. Un entête que nous aimerions bien pouvoir ré-utiliser dans d’autres states sans se répéter.

Nous allons donc commencer par créer un fichier headers.sls dans /srv/pillar:

headers:
  salt:
    file: |
        ################################
        # THIS FILE IS MANAGED BY SALT #
        ################################

Nous créons ici une variable file dans un tableau salt, lui-même dans un tableau headers. Elle sera accessible dans nos states via la variable pillar[‘headers’][‘salt’][‘file’].

Pour l’insérer dans une variable dans un state, il suffit d’entourer la variable par {{ et }}. Le code ci-dessous est à placer dans /srv/salt/hosts/hosts:

{{pillar['headers']['salt']['file']}}
127.0.0.1 localhost {{grains['host']}}
::1 		localhost {{grains['host']}}

Créons le state associé (gestion du fichier /etc/hosts) dans /srv/salt/hosts.sls:

/etc/hosts:
  file.managed:
    - source: salt://hosts/hosts
    - user: root
    - group: root
    - mode: 644
    - template: jinja

La dernière ligne (template: jinja) est nécessaire pour pouvoir utiliser les conditions dans les fichiers managés.

top.sls

Inutile de tester le code ci-dessus, il ne fonctionnera pas tel quel ! Vous aurez en effet l’erreur suivante:

salt-minion:
----------
          ID: /etc/hosts
    Function: file.managed
      Result: False
     Comment: Unable to manage file: Jinja variable 'dict' object has no attribute 'headers'; line 1

Le problème est simple: Salt n’a pas inclut notre pillar fraîchement créé. Plutôt que de l’inclure explicitement, mieux vaut lui dire de le charger systématiquement.

Pour ce faire, Salt a prévu le fichier top.sls qui est systématiquement inclus pour les states et les pillars soit les fichiers /srv/salt/top.sls et /srv/pillar/top.sls.

Côté pillar, il va falloir dire à Salt d’inclure notre headers.sls à partir de /srv/pillar/top.sls:

base:
  '*':
    - headers

Explication rapide:

  • La première ligne indique le début d’une déclaration. Cette valeur n’a pas d’importance ici, assurez-vous seulement qu’elle est unique.
  • La seconde indique la condition à utiliser pour identifier les minions concernés. Pour rappel, * signifie l’ensemble des serveurs.
  • La troisième indique que nous souhaitons inclure /srv/pillar/headers.sls.

Relançons notre state:

[root@salt-master ~]# salt '*' state.sls hosts</pre>
Résultat:
<pre class="lang:default decode:true">salt-minion:
----------
          ID: /etc/hosts
    Function: file.managed
      Result: True
     Comment: File /etc/hosts updated

Et si nous affichons le contenu de /etc/hosts sur notre minion:

################################
# THIS FILE IS MANAGED BY SALT #
################################

127.0.0.1 localhost salt-minion
::1       localhost salt-minion

Côté state, faisons la même chose que hosts.sls et ntp.sls soient systématiquement exécutés sur l’ensemble des minions. Créez le fichier /srv/salt/top.sls avec le contenu suivant:

base:
  '*':
    - hosts
    - ntp

Pour mettre à jour la configuration de nos minions, ne reste qu’à utiliser la fonction state.highstate qui va aller lire les states à appliquer dans top.sls:

[root@salt-master ~]# salt '*' state.highstate

Pour donner un exemple plus complet, imaginons que nous souhaitons en plus appliquer un state httpd uniquement pour les serveurs dont l’ID débute par web. Le top.sls devient:

base:
  '*':
    - hosts
    - ntp
  'web*':
    - httpd

1 réflexion sur « Salt: à la découverte des States »

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s