.. -*- coding: utf-8 -*- :orphan: ====================================== 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* : .. code-block:: console 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 ----------------- .. code-block:: bash 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: .. code-block:: bash master:~$ salt \* grains.get ip_interfaces:eth0 master:~$ salt \* grains.item ipv4 ipv6 master:~$ salt \* grains.item fqdn_ip4 À partir d'un *minion* : .. code-block:: console 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 : .. code-block:: jinja 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 : .. code-block:: python 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) : .. code-block:: jinja {% 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 : .. code-block:: python 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 -------------- .. image:: media/salt_pillars.png :align: center 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`` .. warning:: 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`` : .. code-block:: yaml base: 'machine2': - zone ``/srv/pillar/zone.sls`` : .. code-block:: yaml zone: intranet location: Paris Interroger les *pillars* : .. code-block:: console 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*) : .. code-block:: jinja {% if pillar['zone'] == 'intranet' %} [snip] {% endif %} ``pillar.get`` permet de définir une valeur par défaut : .. code-block:: jinja {% 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`` .. code-block:: yaml 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`` .. code-block:: yaml dev-users: - alice - bob - paul dev-users-sudo: - bob - paul Peut être utilisé dans un autre *pillar* : ``/srv/pillars/intranet.sls`` .. code-block:: yaml include: - users ``defaults`` ------------ La directive ``defaults`` permet d'introduire des variables : ``/srv/pillars/users.sls`` .. code-block:: jinja dev-users: - alice - bob - paul dev-users-sudo: - defaults : {{ sudo }} Permet : ``/srv/pillars/intranet.sls`` .. code-block:: yaml 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`` : .. code-block:: yaml foo: bar: baz: qux - en utilisant la syntaxe Python : .. code-block:: jinja {{ pillar['foo']['bar']['baz'] }} - ou avec le séparateur ':' : .. code-block:: jinja {{ 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``: .. code-block:: yaml 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`` : .. code-block:: console master:~$ salt '*' mine.send disk.usage master:~$ salt '*' mine.send test.ping - Les données de la mine du ```` sont stockées sur le *master* dans:: /var/cache/salt/master/minions//mine.p Récupérer des informations de la mine ------------------------------------- Depuis un minion : .. code-block:: bash machine1# salt-call mine.get machine2 test.ping Ou depuis le *master* : .. code-block:: bash 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. .. code-block:: jinja {% 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 : .. code-block:: console master:~$ salt 'minion1' mine.delete 'disk.usage' ou vider toutes les informations de la mine pour tous les minions .. code-block:: console 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. .. code-block:: console salt-run cache.mine salt-run cache.clear_mine_func tgt='*' \ clear_mine_func_flag='network.interfaces' salt-run cache.clear_mine