Gestion de configuration - Variables#

Introduction#

Salt propose trois types de variables utilisables pour paramétrer la configuration d’une infrastructure, qui peuvent être classées selon leur origine :

  • Grains : données essentiellement statiques collectées au démarrage du minion

  • Pillars : données de configuration globale, déclarées sur le master

  • Mine : données poussées par les minions sur le master pour les rendre lisibles par les autres minions (cf. Gestion avancée d’infrastructures avec Salt)

Grains#

Généralités#

  • Les grains sont des données quasi-statiques collectées sur le minion lors de son démarrage.

  • On y trouve en particulier les informations sur les caractéristiques matérielles et logicielles du minion

  • Ils permettent d’adapter des states en fonction de caractéristiques du minion.

  • Il est possible d’écrire ses propres grains sous la forme de modules Python placés dans /path/to/salt/states/_grains

  • Les informations peuvent être rechargées en utilisant saltutil.sync_grains.

Exemples de grains#

Ils peuvent être des informations sur la plate-forme tels que :

  • id

  • fqdn

  • os

  • os_family

  • oscodename (par ex. bullseye)

  • osrelease (par ex. 11)

Exemples de grains sur le matériel#

Ou des informations matérielles (obtenues via dmidecode) comme :

  • cpu_model

  • cpu_flag

  • cpuarch (par ex. x86_64)

  • manufacturer (par ex. Dell Inc.)

  • productname (par ex. PowerEdge 2950)

  • serialnumber

Récolte des données#

  • Les grains sont collectés lors du démarrage de salt-minion

  • Il faut donc considérer ces informations comme statiques

  • Certaines de ces données peuvent varier dans le temps

    • par exemple la quantité de RAM disponible sur un système est variable dans les environnements virtualisés

    • cette variabilité n’est pas détectée et n’est pas prise en charge automatiquement par salt : les données des grains sont collectées au démarrage uniquement

  • On peut demander explicitement à un minion de mettre à jour ses grains :

master:~$ salt '*' saltutil.refresh_grains

Manipuler les grains#

  • append

    ajoute un élément à une valeur de grains de type liste

  • delval

    supprime la valeur d’un grain

  • filter_by

    recherche la valeur associée à l’OS du minion dans le grain

  • get

    essaye de trouver la valeur associée à la clef (éventuellement composite) passée en argument

  • item

    retourne la valeur d’un grain

  • items

    retourne la valeur pour tous les grains

  • ls

    liste les grains (clefs)

  • remove

    supprime un élément d’une valeur de grain de type liste

  • setval

    configure la valeur d’un grain

Des informations ad hoc peuvent être ajoutées à la configuration des minions. Ces valeurs de grains sont stockées dans les fichiers /etc/salt/minion ou /etc/salt/grains.

Toutes les fonctions qui modifient les valeurs de grains affectent en pratique le fichier de configuration des grains du minion.

Grains - exemples#

root@salt:~# salt 'machine1' grains.append mygrain 12
machine1:
    ----------
    mygrain:
        - 12
root@salt:~# salt 'machine1' grains.append mygrain 13
machine1:
    ----------
    mygrain:
        - 12
        - 13
root@salt:~# salt 'machine1' grains.remove mygrain 12
machine1:
    ----------
    mygrain:
        - 13
root@salt:~# salt 'machine1' grains.delkey mygrain force=True
machine1:
    ----------
    changes:
        ----------
        mygrain:
            None
    comment:
    result:
        True
root@salt:~# salt 'machine1' grains.item mygrain
machine1:
    ----------
    mygrain:
master:~$ salt \* grains.get ip_interfaces:eth0
master:~$ salt \* grains.item ipv4 ipv6
master:~$ salt \* grains.item fqdn_ip4

À partir d’un minion :

root@machine1:~# salt-call grains.get ip_interfaces:eth0
local:
    - 10.1.0.3

Utilisation des grains#

Les valeurs des grains sont souvent utilisées

  • dans des templates Jinja2 pour :

    • paramétrer les states,

    • pour générer des fichiers de configuration,

    • adapter les fichiers de configuration en fonction de valeurs de grains

  • au sein des execution modules salt

Utilisation des grains dans un fichier .sls#

Dans un fichier SLS cela donne :

installation-apache-sur-redhat-et-ubuntu:
  pkg.installed:
    {% if grains['os'] == 'RedHat' %}
    - name: httpd
    {% elif grains['os'] == 'Ubuntu' %}
    - name: apache2
    {% endif %}

Utilisation des grains dans un module#

Pour utiliser un grains dans un execution module ou dans un state module, on peut utiliser la variable __grains__ qui se présente comme un dictionnaire :

def _get_cron_info():
    """
    Returns the proper group owner and path
    to the cron directory
    """
    owner = "root"
    if __grains__["os"] == "FreeBSD":
        group = "wheel"
        crontab_dir = "/var/cron/tabs"
    elif __grains__["os"] == "OpenBSD":
        group = "crontab"
        crontab_dir = "/var/cron/tabs"
    #elif ...

Grains - filter_by#

La fonction grains.filter_by permet d’utiliser un dictionnaire de correspondances pour éviter d’utiliser des if/elif/else (comme dans l’exemple précédent) :

{% set apache = salt['grains.filter_by']({
   'RedHat': {
      'pkgname': 'httpd',
      },
   'Ubuntu': {
      'pkgname': 'apache2',
      },
   },
   merge=salt['pillar.get']('apache:lookup'),
   default='Ubuntu',
)%}

state to install apache:
   pkg:
      - name: {{ apache.pkgname }}

Remarques:

  • filter_by utilise le grain os_family par défaut

  • l’argument merge indique éventuellement une clef de pillar dont la valeur est un dictionnaire dont les valeurs pourront surcharger celles du dictionnaire de correspondances

  • l’argument default spécifie la clef par défaut qui sera utilisée si la valeur du grain n’est pas dans la table de correspondance

Fournir la valeur d’un grain directement#

La valeur d’un grain est évaluée dans l’ordre suivant :

  • core grains générés dynamiquement au lancement de salt-minion à partir d’une définition standard

  • /etc/salt/grains fichier sur le minion mis à jour par grains.{set,del}val

  • /etc/salt/minion fichier sur le minion qui contient une section grains:

  • _grains générés dynamiquement au lancement de salt-minion à partir de la définition synchronisée par le master

La valeur lue dans /etc/salt/grains peut donc être remplacée par celle issue de l’exécution des fonctions définies dans _grains.

Définir ses propres grains#

Il est facile d’écrire ses propres modules de récolement de grains sous forme de modules Python:

  • Les fichiers Python doivent être disponibles sur le master dans le dossier /srv/salt/_grains

  • Chacune des fonctions définies dans ces modules Python définissent un nouveau grain

Exemple :

def shell():
    """
    Return the default shell to use on this system
    """
    # Provides:
    #   shell
    return {"shell": os.environ.get("SHELL", "/bin/sh")}

Éléments importants :

  • la fonction doit renvoyer un dictionnaire de grains

  • la clef du dictionnaire correspond au nom du grain, le nom de la fonction ne doit pas commencer par underscore _.

  • une fonction de grain peut renvoyer plusieurs grains (plusieurs clefs dans le dictionnaire)

  • le dictionnaire renvoyé ne doit contenir que des valeurs sérialisables en YAML (mais il est possible d’imbriquer des dictionnaires et des listes)

  • s’il n’y a pas de grain à fournir, renvoyer un dictionnaire vide.

Pillars#

Présentation#

  • Ce sont des variables globales rendues disponibles sur un ou plusieurs minions

    • définies sur le master

    • dans un répertoire différent des states

  • Ils permettent de stocker :

    • des données sensibles (par ex. clefs privées, mots de passe, etc.)

    • des méta-données de l’infrastructure (par ex. emplacement géographique, rôles dans l’infrastructure, etc.)

    • données arbitraires (par ex. tokens d’accès à des API, secrets partagés, clefs publiques des serveurs, etc.)

Vue d’ensemble#

../../_images/salt_pillars.png

Déclaration des pillars#

  • Les pillars sont déclarés dans des fichiers .sls placés dans un répertoire /srv/pillar (configurable)

  • Ce sont des structures de données libres au format YAML

  • Ce répertoire est paramétrable dans le fichier de configuration de salt-master

  • Les pillars sont attribués à des minions dans un fichier top.sls selon le même mécanisme que les states

Manipulation des pillars#

Module de manipulation du pillar system de salt

  • data

    demande les valeurs de pillar au master et les retourne

  • ext

    genère le pillar et applique un pillar externe explicite

  • get

    retourne la valeur de pillar associée à une clef donnée (éventuellement composite)

  • item

    retourne la valeur de pillar associée à une clef donnée

  • items

    retourne le dictionnaire de tous les pillars d’un minion

  • raw

    retourne les valeurs des pillars tels qu’elles sont stockées dans le fichier de cache (dictionnaire __pillar__)

Assigner des pillars à un minion#

Pour envoyer des données de pillar à un minion :

  • il faut qu’il soit sélectionné dans le top.sls

  • utiliser la commande salt saltutil.refresh_pillar

Avertissement

Les erreurs de compilations des pillars sont visibles dans les logs du salt-master et visibles dans _errors des pillars.

Exemple#

/srv/pillar/top.sls :

base:
   'machine2':
      - zone

/srv/pillar/zone.sls :

zone: intranet
location: Paris

Interroger les pillars :

master:~$ salt '*' saltutil.refresh_pillar
master:~$ salt '*' pillar.items
machine1:
  ----------
machine2:
  ----------
  location:
      paris
  zone:
      intranet

Pillars - utilisation dans les states#

Les pillars sont utilisables dans les templates Jinja2 ou pour écrire des execution modules ou des state modules (comme les grains) :

{% if pillar['zone'] == 'intranet' %}
[snip]
{% endif %}

pillar.get permet de définir une valeur par défaut :

{% if pillar.get('zone', 'not-defined') == 'intranet' %}
[snip]
{% endif %}

Espace de nom#

  • Il n’y a pas d’espace de nom induit par le nom d’un fichier .sls dans lequel se trouve une déclaration de pillar

    /srv/pillars/users.sls

    dev-users:
      alice: 42
      bob: 53
    

    la valeur sera obtenue avec salt '*' pillar.get dev-users:alice.

  • Si la même variable est définie dans deux fichiers .sls, la deuxième définition sera masquée. Pour merger les variables, il faut configurer le master.

include#

On peut utiliser la directive include comme pour les states, par exemple :

/srv/pillars/users.sls

dev-users:
  - alice
  - bob
  - paul
dev-users-sudo:
  - bob
  - paul

Peut être utilisé dans un autre pillar :

/srv/pillars/intranet.sls

include:
  - users

defaults#

La directive defaults permet d’introduire des variables :

/srv/pillars/users.sls

dev-users:
  - alice
  - bob
  - paul
dev-users-sudo:
  - defaults : {{ sudo }}

Permet :

/srv/pillars/intranet.sls

include:
  - users:
      defaults:
          sudo: ['bob', 'paul']
      key: dev-users-sudo

Dictionnaires imbriqués#

On peut accéder à des valeurs au fond de dictionnaire ou de listes imbriquées à l’aide de la commande pillar.get :

foo:
  bar:
    baz: qux
  • en utilisant la syntaxe Python :

{{ pillar['foo']['bar']['baz'] }}
  • ou avec le séparateur “:” :

{{ pillar.get('foo:bar:baz') }}

Mine#

Généralités#

Chaque minion peut pousser sur le master des informations dans sa mine où tous les autres minions pourront les lire.

  • ces informations sont nécessairement produites par des commandes salt

  • la déclaration des éléments poussés par un minion se fait dans le fichier de configuration de son salt-minion

  • pas de sécurité : tous les minions peuvent lire toutes les informations publiées

  • seul le minion peut modifier les informations de sa mine

Intérêt :

  • cache de données

  • faciliter l’écriture de configurations maitre-esclave

Ajouter des données dans la mine#

Dans la configuration du minion /etc/salt/minion définir les paramètres mine_functions et mine_interval:

mine_functions:
  test.ping: []
  disk.usage: []

mine_interval: 60
  • dans cet exemple, les données sont envoyées toutes les heures (intervalle de 60 minutes) et aucun argument n’est passé aux commandes test.ping et disk.usage (listes vides)

  • On peut aussi envoyer une requête pour forcer le rafraîchissement de la mine avec le module de commande mine :

    master:~$ salt '*' mine.send disk.usage
    master:~$ salt '*' mine.send test.ping
    
  • Les données de la mine du <minion_id> sont stockées sur le master dans:

    /var/cache/salt/master/minions/<minion_id>/mine.p
    

Récupérer des informations de la mine#

Depuis un minion :

machine1# salt-call mine.get machine2 test.ping

Ou depuis le master :

master# salt machine1 mine.get machine2 test.ping
machine1:
    ----------
    machine2:
        True

Dans les 2 cas, machine1 exécute la commande salt mine.get qui demande au master la valeur stockée dans la mine résultant de l’exécution de test.ping par machine2

  • si on remplace machine1 par \*, tous les minions vont demander cette même information au master

  • si on remplace machine2 par \*, machine1 demande au master l’usage disque stocké dans la mine par tous les minions

Mine - utilisation dans les states#

Pour parcourir la mine depuis un état ou un template jinja, il faut utiliser l’appel à mine.get.

Penser à utiliser .items() pour parcourir à la fois les clefs et les valeurs d’un dictionnaire.

{% for machineid in salt['mine.get']('*', 'test.ping') %}
file configuration for {{ machineid }}:
  file.managed:
    - source: salt://file.conf
    - name: /etc/service/hosts/{{ machineid }}.conf
{% endfor %}

Vider la mine#

On peut sélectivement supprimer un type d’information de la mine depuis le minion qui les a envoyées :

master:~$ salt 'minion1' mine.delete 'disk.usage'

ou vider toutes les informations de la mine pour tous les minions

master:~$ salt '*' mine.flush

Attention : si un minion qui a déjà poussé des données dans la mine est inaccessible ou ne répond au moment l’exécution de cette commande, elles ne seront pas supprimées par le flush : seul le minion peut ajouter ou enlever des informations à sa mine sur le master.

Mine - runners#

Avec les runners coté master, on peut obtenir des informations sur la mine, mais aussi supprimer des informations.

salt-run cache.mine
salt-run cache.clear_mine_func tgt='*' \
         clear_mine_func_flag='network.interfaces'
salt-run cache.clear_mine