Damien Perrin

Ansible pour l’administrateur des réseaux

Copyright © 2022 by Damien Perrin

Le Code de la propriété intellectuelle interdit les copies ou reproductions destinées à une utilisation collective. Toute représentation ou reproduction intégrale ou partielle faite par quelque procédé que ce soit, sans le consentement de l’auteur ou de ses ayant droit ou ayant cause, est illicite et constitue une contrefaçon, aux termes des articles L.335-2 et suivants du Code de la propriété intellectuelle. Toutes les données techniques exposées dans cet ouvrage ne représentent rien d’autre que des exemples.

First edition

This book was professionally typeset on Reedsy
Find out more at reedsy.com

Contents

1. Introduction

A qui s’adresse ce guide ?

Faut-il des connaissances préalables ?

2. Ansible pour les réseaux

Présentation de l’outil

3. Utilisation d’un Lab

GNS3

4. Installation d’Ansible

Python

Ansible

5. Fichier de configuration

Exemple de fichier minimal

6. YAML

Syntaxe

YAML et Python

7. Modules

Network modules

Plugin network_cli

Module cli_parse

8. Inventaire

Création du fichier

Les Groupes

Les Alias

Les variables

9. Playbook

Qu’est ce qu’un Playbook ?

Play

Tasks

Quelques exemples

10. Variables

Playbook

Inventaire

group_vars

host_vars

Utilisation

11. Ansible Vault

12. Rôles

13. Jinja2

Templates

Filtres

14. Version Control

Installer Git

Utiliser Git

Les branches

15. Cas pratiques

Automatiser les sauvegardes

Déployer une configuration avec template Jinja2

Effectuer un inventaire ou un audit

Créer des vlans sur des switchs

Synthèse

16. Guide de survie

Arborescence

Troubleshooting

Inventaire

Playbook

Variables

Filtres

Modules

17. Conclusion

1

Introduction

L’automatisation est plus que jamais sous le feu des projecteurs. C’est ce que met en avant l’approche Devops, automatiser toutes les étapes qui peuvent l’être dans la chaîne de développement et de mise en production d’une application. Les entreprises y trouvent bien entendu leur compte, elle peuvent ainsi accroître leur efficacité et leur agilité.

Quel lien peut-on faire avec les couches basses et notamment l’infrastructure réseau des entreprises ? Et bien il est possible d’utiliser certains outils d’Infrastructure As Code et certaines pratiques Devops pour s’aligner sur cette philosophie et rendre l’administration d’équipements et le provisionnement de ressources réseaux plus agile et automatique. Ce guide est dédié à un outil en particulier, il est open-source, et se nomme Ansible. Si vous êtes un professionnel de l’informatique, comme c’est probablement le cas si vous lisez ceci, vous en aurez certainement entendu parler et vous aurez peut-être noté sa polyvalence. Ici nous l’exploiterons pour les besoins spécifiques des intervenants réseaux.

Comme chacun sait, il est laborieux et chronophage d’effectuer de manière unitaire des opérations sur des équipements réseaux, sans parler du coût d’exploitation et des erreurs potentielles liées au facteur humain. Des outils constructeurs existent, il sont là pour pour simplifier la vie de l’administrateur à travers une console web mais la plupart d’entre eux sont coûteux et ne sont pas des solutions adaptées à toutes les infrastructures ni toutes les bourses. D’ailleurs, vous êtes peut-être devenu un spécialiste du scripting afin de répondre à vos problématiques ? Avec Ansible, vous allez découvrir une approche de l’automatisation accessible et structurée, même si vous n’êtes pas un expert en programmation.

A qui s’adresse ce guide ?

Ce guide s’adresse à ceux qui souhaitent découvrir Ansible. D’une manière générale, il est recommandé d’être un professionnel de l’informatique pour pleinement profiter de ce guide technique. Le cas échéant, vous risquez de très rapidement vous ennuyer.

Si vous avez un profil de sysadmin, de spécialiste cloud ou sécurité et que vous souhaitez simplement vous initier à Ansible tout en devenant opérationnel en un minimum de temps, ce guide pourra tout à fait vous convenir. Vous y trouverez les notions de bases et de multiples exemples de mise en pratique.

La véritable cible cependant, c’est le spécialiste des réseaux. Le contenu est orienté spécifiquement pour les besoins des administrateurs et ingénieurs souhaitant mettre en place des solutions d’automatisation.

Faut-il des connaissances préalables ?

Ansible est un outil relativement simple d’utilisation et d’apprentissage par rapport à son potentiel, cependant, il est préférable d’être à l’aise avec les systèmes d’exploitation Linux.

Par ailleurs, si vous êtes familier du langage Python c’est encore mieux ! Ce dernier point n’est absolument pas bloquant pour l’apprentissage d’Ansible, mais je vous recommande tout de même de vous y mettre, vous ne perdrez pas votre temps.

En effet, la connaissance du langage Python est désormais une compétence incontournable pour le spécialiste des réseaux, particulièrement pour l’automatisation et l’exploitation de solutions centralisées (Software Defined). Pour vous en convaincre, Cisco intègre depuis quelque temps à ses cursus réseaux des notions de base concernant Ansible et propose surtout un cursus nettement orienté développement et automatisation (DevNet) comprenant en autres du scripting avec Python.

2

Ansible pour les réseaux

Présentation de l’outil

Ansible est un outil libre, écrit en Python, et dédié à l’automatisation et plus généralement à l’Infrastructure As Code. L’idée de cette pratique est de pouvoir administrer son infrastructure via des fichiers de configuration et des scripts.

Ansible permet donc de gérer et de déployer des configurations ou des paquets logiciels sur des groupes d’hôtes. Ces hôtes sont définis dans un fichier dédié à cet usage, l’inventaire, nous y reviendrons. Ansible est beaucoup utilisé pour automatiser la configuration de serveurs et de services notamment dans un contexte de déploiement cloud, mais il est également possible d’en faire un précieux allié de l’administrateur réseaux.

L’avantage d’Ansible, contrairement à d’autres solutions concurrentes comme puppet, est qu’il est agent-less, c’est-à-dire qu’il ne nécessite pas l’installation d’un agent logiciel sur les hôtes ciblés. Vous en conviendrez, c’est plutôt pratique dans le cas qui nous intéresse, la gestion d’équipements réseaux. Ansible utilise, entre autres protocoles, ssh pour se connecter et configurer les équipements cibles.

Ansible est capable de s’adapter à de multiples constructeurs et utilise un système de playbook pour déployer les configurations (comme pour la notion d’inventaire, nous y reviendrons plus en détails). Cet aspect est intéressant pour l’administration réseau puisqu’il permet de faire abstraction de la syntaxe des commandes des équipements cibles. Autrement dit, il n’est pas nécessaire d’être familier du cli Juniper ou Cisco pour créer un vlan ou configurer une route par exemple, dans les deux cas on utilisera un module qui s’en chargera. Ansible dispose d’un nombre conséquent de ces modules développés pour exécuter des tâches spécifiques sur certains hôtes, nous verrons que plusieurs d’entre eux sont dédiés aux équipements réseaux.

Enfin, Ansible est idempotent. Pour résumer, cela signifie que pour une configuration donnée, Ansible ne lance pas les actions demandées sur les hôtes qui sont déjà en configuration cible. C’est une particularité intéressante par rapport au scripting conventionnel.

Vous trouverez ci-dessous un schéma simplifié du fonctionnement d’Ansible. La machine sur laquelle nous allons installer Ansible sera le control node ou contrôleur. C’est également sur cette machine que nous stockerons l’inventaire et les playbook.

schéma simplifié du fonctionnement d’Ansible

3

Utilisation d’un Lab

Afin de mieux comprendre les concepts exposés et progresser rapidement, il faut mettre en pratique, raison pour laquelle je vous recommande vivement de mettre en place un lab pour reproduire les différents exemples proposés en fin de guide.

Disposer d’une vm linux afin d’en faire votre contrôleur Ansible ne devrait pas vous poser de problème. En revanche, il n’est pas toujours simple de simuler des équipements réseaux.

Pour que vous puissiez facilement mettre en pratique en environnement bac à sable les différentes approches qui vont être présentées, vous avez plusieurs possibilités :

Vous pouvez pratiquer dans le cadre professionnel car vous disposez de matériel de test et dans ce cas, c’est parfait.

Deuxième solution, vous pouvez vous procurez du matériel d’occasion, ce qui n’est pas forcément très pertinent sauf si vous projetez de vous en servir pour d’autres besoins. En effet, même si le prix d’achat n’est pas obligatoirement un problème car les équipements d’entreprises sont renouvelés régulièrement et qu’il est possible de profiter d’opportunités intéressantes, il faut être conscient que les équipements réseaux sont bruyants et relativement gourmands en électricité.

La dernière solution, la plus simple à mettre en place et celle que je recommande : Installer un lab constitué d’équipements virtuels sur son ordinateur.

GNS3

Pour les exemples de ce guide, j’utiliserais GNS3 (Graphical Network Simulator) qui est un logiciel libre et multi-plateforme de simulation de réseaux informatiques.

Il existe bien entendu des alternatives avec chacune leurs avantages et inconvénients. Afin de simuler un équipement réseau dans GNS3, vous devrez au préalable vous procurer l’image logicielle de l’équipement. A titre informatif, je vais principalement utiliser dans ce guide des topologies composées de routeurs et switchs Cisco dans le genre de celle-ci :

Le nuage LAN représente mon réseau local sur lequel se trouve mon contrôleur Ansible (machine virtuelle Linux). Ma topologie virtuelle GNS3 est donc bridged sur mon réseau local. Sachez qu’il est possible d’utiliser Ansible directement depuis GNS3 qui propose d’émuler des machines Linux.

L’installation et la configuration de GNS3 peut être un peu laborieuse et dépendre de votre matériel et infrastructure, par conséquent, je ne vais pas la détailler pour rester focalisé sur Ansible. La documentation est complète, vous devriez y trouver votre compte :

https://docs.gns3.com/docs

Si vous n’avez pas d’environnement maquette, n’hésitez pas à passer du temps sur sa création, le jeu en vaut la chandelle et cela pourra certainement vous être utile plus tard.

4

Installation d’Ansible

Premièrement, même si pour certains j’imagine que ça coule de source, procurez-vous une machine Linux ! Il est possible d’installer Ansible sur une machine Windows, mais je n’en ferais pas la démonstration, nous ne sommes pas des sauvages.

Il y a plusieurs façon d’installer Ansible, soit via le gestionnaire de paquets de votre distribution (je pars du principe que vous êtes à l’aise avec Linux), soit via le gestionnaire de paquets Python. Dans ce guide, nous utiliserons la méthode Python avec environnement virtuel.

Python

Nous aurons donc besoin de Python sur notre machine, vérifiez donc qu’il est bien installé. Préférez bien entendu Python 3 dans une version récente. Afin de s’aligner sur les bonnes pratiques, vous pouvez créer un environnement virtuel ou virtual env (venv). Si vous ne connaissez pas le principe, il s’agit de segmenter votre interpréteur Python grâce à une instance virtuelle. Votre venv vous permettra d’installer ou de mettre à jour vos modules et bibliothèques sans risquer de provoquer de conflits de version dans votre environnement et entre vos scripts Python existants. C’est aussi pratique si vous n’êtes pas le seul utilisateur de la machine.

Créer un virtual env

damien@Debian-SRV:~# mkdir $HOME/ansible
damien@Debian-SRV:~# python3 -m venv $HOME/ansible

Activer le virtual env

damien@Debian-SRV:~# source $HOME/ansible/bin/activate
(ansible) damien@Debian-SRV:~#

Sortir de l’environnement virtuel

(ansible) damien@Debian-SRV:~# deactivate
damien@Debian-SRV:~#

Pour terminer avec les pré-requis Python, mettez à jour votre gestionnaire de paquet pip :

pip install pip --upgrade

Ansible

Pour lancer l’installation de l’outil et de ses dépendances, rien de plus simple :

$ pip3 install ansible

Notez que j’ai utilisé la commande pip3 et non pip. En effet, si vous avez Python 2 sur votre machine et que vous utilisez pip pour effectuer l’installation, Ansible risque de prendre en compte Python 2 et non pas Python 3 comme interpréteur lors de l’installation.

Lorsque les quelques minutes d’installation sont terminées, vous pouvez très simplement contrôler que tout s’est bien déroulé en exécutant une commande comme celle-ci :

$ ansible --version

Plusieurs informations s’affichent alors, vous pourrez y retrouver entre autres la version d’Ansible installée et la version de Python utilisée :

ansible [core 2.11.12]
config file = None
configured module search path = ['/home/damien/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /home/damien/ansible/lib/python3.7/site-packages/ansible
ansible collection location = /home/damien/.ansible/collections:/usr/share/ansible/collections
executable location = /home/damien/ansible/bin/ansible
python version = 3.7.3 (default, Jan 22 2021, 20:04:44) [GCC 8.3.0]
jinja version = 3.1.2
libyaml = True

A ce stade, l’installation d’Ansible est achevée.

5

Fichier de configuration

De multiples paramètres d’Ansible sont modifiables grâce à un fichier de configuration :

ansible.cfg

Où le positionner ?

Le fichier peut être positionné à plusieurs endroits et être créé par l’utilisateur s’il n’existe pas. Par défaut, Ansible cherchera le fichier de configuration aux emplacements suivants, dans l’ordre :

variable d’environnement ANSIBLE_CONFIG

./ansible.cfg (le répertoire courant)

~/ansible.cfg (votre répertoire home)

/etc/ansible/ansible.cfg (ce n’est pas l’idéal si vous avez fait une installation via Python puisque le répertoire n’existe pas)

Je vous conseille de stocker le fichier dans le répertoire de votre projet avec l’inventaire et les playbook.

Exemple de fichier minimal

Si vous ne voulez pas créer le fichier from scratch ou bien si vous souhaitez avoir une vue d’ensemble des paramètres disponibles, depuis la version 2.12 d’Ansible, il est possible de générer un exemple de fichier de configuration contenant les différents paramètres, mais désactivés (commentés). Pour ce faire, utilisez la commande suivante :

$ ansible-config init --disabled > ansible.cfg

D’autre part, vous pouvez consulter le contenu du fichier ansible.cfg via cette commande :

$ ansible-config view

Le fichier de configuration est divisé en blocs, ci-dessous le début d’un fichier de configuration de base avec quelques paramètres du bloc [defaults], qui sont désactivés :

# config file for ansible -- https://ansible.com/
# ===============================================

# nearly all parameters can be overridden in ansible-playbook
# or with command line flags. ansible will read ANSIBLE_CONFIG,
# ansible.cfg in the current working directory, .ansible.cfg in
# the home directory or /etc/ansible/ansible.cfg, whichever it
# finds first

[defaults]

# some basic default values...

#inventory = /etc/ansible/hosts
#library = /usr/share/my_modules/
#module_utils = /usr/share/my_module_utils/
#remote_tmp = ~/.ansible/tmp
#local_tmp = ~/.ansible/tmp
#plugin_filters_cfg = /etc/ansible/plugin_filters.yml
#forks = 5
#poll_interval = 15
#sudo_user = root
#ask_sudo_pass = True
#ask_pass = True
#transport = smart
#remote_port = 22
#module_lang = C
#module_set_locale = False

Quelques paramètres peuvent être intéressant comme inventory qui permet de préciser le chemin vers le fichier d’inventaire ou bien remote_port pour redéfinir le port de connexion ssh si celui-ci n’est pas le port standard.

Dans la majorité des cas pour démarrer, nous pourrons laisser les paramètres par défaut. Malgré tout, pour votre environnement lab, vous pouvez ajouter les deux paramètres suivants :

[defaults]
deprecation_warnings = False
host_key_checking = False

Le premier désactivera les messages d’avertissement informant de suppression de fonctionnalités. Vous pouvez le commenter dans un premier temps afin de prendre connaissance de ces avertissements mais vous allez vite préférer vous en passer pour plus de lisibilité. Le second paramètre désactivera la vérification des clés ssh lors de la connexion du contrôleur Ansible vers les équipements réseaux distants et évitera les erreurs si ces clés ne sont pas connues.

6

YAML

Avant d’entrer dans le vif du sujet, arrêtons nous pour discuter de YAML. C’est un standard de sérialisation utilisé par Ansible pour interpréter l’inventaire de nos équipements ou encore les playbook qui nous servirons à agir sur ceux-ci, c’est pourquoi il est très important de s’y frotter. Cela étant dit, ne vous inquiétez pas, YAML est plutôt simple à assimiler et à utiliser.

D’ailleurs, si vous êtes à l’aise avec le format JSON, vous ne serez pas dépaysé avec YAML puisque les deux formats se ressemblent beaucoup et partagent la même finalité : représenter des informations sous une forme structurée.

Voyons dans ce chapitre quelques notions de base, le reste viendra naturellement au fur et à mesure de la découverte d’Ansible, des exemples et de la pratique. Si vous souhaitez avoir une vue d’ensemble bien construite et synthétique, je vous recommande de visiter la page suivante :

https://learnxinyminutes.com/docs/fr-fr/yaml-fr/

Syntaxe

Première chose à préciser, et pas des moindres, YAML ne prend pas en charge les tabulations pour sa structure.

Par convention, 3 tirets indiquent le commencement d’un fichier .yml :

---

Vous pouvez insérer des commentaires dans un fichier YAML en les précédant du caractère # comme ceci :

---
# je suis un commentaire

YAML prend en charge les habituels types de données, notez que les chaînes de caractères ne nécessitent pas de guillemets :

---
100   # valeur numérique
7,11  # float
true  # booléen
hello # string
null  # null

YAML propose des maps (combinaisons clé : valeur) qui sont l’équivalent des dictionnaires Python :

---
Type: Switch
Constructeur: Cisco
Modele: Catalyst 9500
En service: True

Vous retrouverez également des listes :

---
- Cisco
- Catalyst
- 9500

L’indentation dans un fichier YAML se fait uniquement avec des espaces :

---
switchs:
 sw1:
  ip: 192.168.1.1
  vendor: Cisco
 sw2:
  ip: 192.168.1.1
  vendor: Cisco
servers:
 - 192.168.1.100
 - 192.168.2.100

Pour bien comprendre et faire le rapprochement, si vous êtes plus à l’aise avec la notation Python ou JSON, voici l’équivalent :

{
 "switchs":
  {
   "sw1": {"ip":"192.168.1.1", "vendor":"Cisco"},
   "sw2": {"ip":"192.168.1.2", "vendor":"Cisco"}
  },
 "servers": ["192.168.1.100","192.168.1.100"]
}

Sachez enfin que YAML prend justement en charge la syntaxe JSON :

---
# dictionnaire
{"Type": "Switch", "Constructeur": "Cisco",
"Modele": "Catalyst 9500", "En service": true}

# liste
["Cisco", "Catalyst", 9500]

Arrêtons-nous ici pour l’introduction, je pense que vous avez saisi la logique. Dans les chapitres suivants, vous aurez l’occasion de vous familiariser un peu plus avec le format YAML à travers les différents fichiers présentés comme exemples. Pour terminer ce chapitre, voyons une bibliothèque intéressante, qui plaira notamment à ceux qui travaillent sur Python.

YAML et Python

PyYAML est une bibliothèque qui permet la conversion de dictionnaires et autres données Python en YAML. Cela pourra se révéler très pratique pour construire des inventaires via des scripts Python à partir de données structurées en JSON par exemple.

Pour installer la bibliothèque :

pip install pyyaml

Pour l’utiliser dans un script :

import yaml

Deux fonctions proposées par cette bibliothèque sont particulièrement intéressantes :

yaml.dump()        # Traduit le format Python en YAML
yaml.safe_load()   # Traduit le format YAML en Python

Afin d’illustrer le fonctionnement de ces deux fonctions, vous trouverez ci-dessous un petit script Python. Nous faisons tout d’abord la déclaration d’une variable python_inventory qui n’est autre qu’un dictionnaire Python. Nous allons dans un premier temps le convertir au format YAML grâce à la fonction dump() puis le ramener au format Python avec la fonction safe_load().

import yaml

python_inventory = {
 "switchs":
  {
   "sw1": {"ip":"192.168.1.1", "vendor":"Cisco"},
   "sw2": {"ip":"192.168.1.2", "vendor":"Cisco"}
  }
}
print("## Conversion Python -> YAML")
python_to_yaml = yaml.dump(python_inventory)
print(python_to_yaml)

print("## Conversion YAML -> Python")
yaml_to_python = yaml.safe_load(python_to_yaml)
print(yaml_to_python)

Résultat

Cette bibliothèque propose des fonctions supplémentaires pour d’autres usages, je vous invite à consulter la documentation pour les découvrir :

https://pyyaml.org/wiki/PyYAMLDocumentation

7

Modules

Ansible ne doit pas se comporter de la même façon si la cible est un équipement réseau ou un serveur, c’est la raison pour laquelle nous allons lui préciser et nous allons voir comment dans le chapitre suivant. Pour préparer cela, nous allons installer quelques modules et plugins dédiés.

Les modules sont des programmes externes, généralement développés en Python, qui vont proposer des fonctions dédiées à des plateformes spécifiques. Il en existe pour tous les goûts et de nouveau sont régulièrement ajoutés. Voici quelques plateformes qui disposent de modules :

vmware

grafana

aws

azure

Pour notre contexte d’administration réseau, nous allons faire un petit tour d’horizon des modules qui pourront nous intéresser.

Network modules

Les modules qui vont particulièrement intéresser l’administrateur, ce sont les network modules. Voici une liste non exhaustive des constructeurs d’équipements réseaux qui disposent de modules Ansible :

Cisco (ios, nxos, aci, meraki, asa)

Juniper

Aruba

Dell

Arista

BigIP F5

Checkpoint

Fortinet

Pour découvrir plus en détails tous ces modules, rendez-vous sur la documentation officielle, rubrique network modules :

https://docs.ansible.com/ansible/2.9/modules/list_of_network_modules.html

Détaillons cependant les modules dont je vais principalement me servir lors des exemples et cas pratiques. Sans surprise, ce sont ceux développés pour interagir avec le système ios de Cisco. Ces modules sont compris dans la collection cisco.ios que je vous propose d’installer :

$ ansible-galaxy collection install cisco.ios

Une fois que c’est fait, vous pouvez lister les modules disponibles grâce à l’option -l de la commande ansible-doc ainsi qu’en filtrant le résultat :

$ ansible-doc -l | grep cisco

Une partie des modules disponibles pour ios

De la même façon, vous pouvez afficher les informations relatives à l’un de ces modules, par exemple cisco.ios.ios_hostname permettant de gérer le hostname d’un équipement Cisco avec la même commande que précédemment, sans option :

$ ansible-doc cisco.ios.ios_hostname

Vous pouvez constater qu’il y a un nombre assez conséquent de modules. Le point important à noter ici, c’est qu’il y a globalement deux types de modules pour cette collection. Il y a tout d’abord les modules génériques, qui vont nous permettre d’envoyer des commandes un peu de la même façon qu’on le ferait en cli, voici les deux principaux :

ios_command (arbitrary commands)

ios_config (configuration mode commands)

Puis il y a les modules spécialisés, qui vont se charger d’effectuer des tâches bien précises et de façon beaucoup plus contrôlées, comme ceux-ci :

ios_vlans (gérer les vlans)

ios_static_routes (gérer les routes)

ios_interfaces (gérer les interfaces)

ios_ospfv2 (configurer ospf)

ios_acls (configurer des acls)

Ces modules, nous verrons comment en utiliser certains à travers les playbook un peu plus tard dans ce guide.

Plugin network_cli

Pour notre contexte d’administration réseau, nous allons devoir ajuster Ansible lors de nos exécutions de tâches. Pour interagir avec la console cli des équipements réseaux utilisant ssh, nous aurons besoin du plugin network_cli qui est compris dans la collection ansible.netcommon. Une collection est une sorte de package contenant divers éléments comme des plugins, des playbook et des modules.

Les plugins sont un peu différents des modules, ils permettent de modifier des fonctionnalités de base d’Ansible ou d’en ajouter. Le plugin network_cli influe sur le comportement d’Ansible lors de connexions ssh aux hôtes, il est développé spécifiquement pour les équipements réseaux.

Pour installer la collection contenant le plugin :

$ ansible-galaxy collection install ansible.netcommon

Nous verrons dès le chapitre suivant où et comment nous servir de ce plugin.

Module cli_parse

Tous les équipements réseaux n’ont malheureusement pas la possibilité de formater les retours cli et ceux-ci sont affichés en texte brut ce qui les rendent difficile à exploiter dans un contexte d’automatisation.

Cependant, Ansible propose d’utiliser les modules Python TextFSM et ntc-templates afin de résoudre ce problème. Installez-les, ils nous serviront par la suite.

$ pip install textfsm
$ pip install ntc-templates

Ces modules fournissent entre autres des templates à base de regex qui permettent de structurer les retours commandes. Il y a un template par commande en fonction de l’OS de l’équipement. Les constructeurs les plus répandus, comme Cisco, sont pris en charge et des ajouts sont faits régulièrement. Pour vérifier si vos équipements sont pris en charge, je vous propose d’aller visiter le github de network to code :

https://github.com/networktocode/ntc-templates

Il y a plusieurs façon d’utiliser ces templates mais l’un des plus simple est d’utiliser le module cli_parse de la collection ansible.utils. Si celle-ci n’est pas disponible sur votre contrôleur, installez-là :

$ ansible-galaxy collection install ansible.utils

8

Inventaire

L’inventaire ou inventory file est le fichier dans lequel nous allons lister les équipements cibles (hosts), ceux pour lesquels nous allons exécuter des tâches. Avec les playbook, c’est l’un des éléments les plus importants au fonctionnement d’Ansible.

Nous allons pouvoir y décrire l’adresse ip d’administration, les identifiants de connexion ou bien encore le type d’équipement. Il peut se présenter sous différents formats et différents niveaux de complexité, un exemple très minimaliste de fichier d’inventaire pourrait ressembler à cela :

192.168.1.77
192.168.1.78
rt2
sw3

Ce fichier décrit 4 hosts , 2 représentés par leur IP et 2 représentés par leur nom. Bien entendu, si vous utilisez des noms, il faut que votre contrôleur Ansible soit en mesure de les résoudre.

Création du fichier

De manière générale, n’utilisez que le format INI ou YAML pour créer le fichier d’inventaire. Par défaut, Ansible ira le chercher ici :

/etc/ansible/hosts

En fonction de votre installation, le répertoire et le fichier n’existent peut-être pas. Je vous propose d’en créer un nouveau dans votre répertoire home et d’en préciser le chemin à Ansible via le fichier de configuration ansible.cfg, ce sera notre inventaire par défaut :

[defaults]
deprecation_warnings = False
host_key_checking = False
inventory = ~/home

Notez l’ajout du paramètre inventory

Sachez qu’il est tout à fait possible d’avoir plusieurs fichiers d’inventaire en plus du fichier d’inventaire par défaut. En effet, lorsque nous exécuterons nos playbook, nous pourrons utiliser un fichier alternatif en utilisant l’option -i, nous y reviendrons.

Les Groupes

Nous avons vu un exemple d’inventaire simple au début du chapitre composé d’hosts listés les uns après les autres. Heureusement, il est possible de hiérarchiser cette liste afin qu’elle reflète au mieux votre infrastructure et vos besoins. Pour ce faire, nous allons utiliser des groupes.

Par défaut, il existe deux groupes :

all (comprend tous les hosts)

ungrouped (comprend tous les hosts qui ne sont pas dans d’autres groupes que all)

En complément, vous pouvez créer de nouveaux groupes pour classer vos équipements. Par exemple, je vais créer un groupe routers pour identifier mes routeurs et un groupe switches pour identifier mes switchs.

INI

[routers]
192.168.1.77
[switches]
192.168.1.78

YAML

---
routers:
 hosts:
  192.168.1.77:
switches:
 hosts:
  192.168.1.78:

Vous pouvez également créer des sous-groupes, où groupes enfants, grâce à l’instruction children. Ainsi, pour l’exemple, je vais ajouter un groupe parent production et transformer les groupes routers et switchs en groupes enfants du groupe production.

INI

[production]

[production:children]
routers
switches

[routers]
192.168.1.77
[switches]
192.168.1.78

YAML

---
production:
 children:
  routers:
   hosts:
    192.168.1.77:
  switches:
   hosts:
    192.168.1.78:

Vous pouvez de cette façon imbriquer autant de groupes que vous le souhaitez. Dans un contexte d’administration réseau, il serait intéressant de distinguer les différents constructeurs.

Si vous souhaitez vérifier la cohérence de votre inventaire et avoir une vue d’ensemble et un peu plus graphique, vous pouvez saisir cette commande :

ansible-inventory --graph

Vous retrouverez ainsi les hosts et leurs appartenances aux différents groupes.

@all:
|--@production:
| |--@routers:
| | |--192.168.1.77
| |--@switches:
| | |--192.168.1.78
|--@ungrouped:

Comme précisé précédemment, vous pouvez très bien construire plusieurs inventaires pour plusieurs besoins. Pour interagir avec un inventaire qui n’est pas celui par défaut, vous devez utiliser l’option -i dans vos commandes :

ansible-inventory --graph -i test_hosts.ini

N’oubliez pas que l’inventaire par défaut est défini dans le fichier de configuration

Les Alias

Vous pouvez également utiliser des alias dans l’inventaire ce qui peut être pratique si vos devices ne font pas l’objet d’enregistrements DNS. Vous pourrez ainsi dissocier l’adresse ip d’administration d’un nom plus explicite :

---
routers:
 hosts:
  Router-branch-nantes:
   ansible_host: 192.168.1.77
  Router-branch-marseille:
   ansible_host: 192.168.2.88
  Router-branch-paris:
   ansible_host: 192.168.3.99

Les variables

Depuis l’inventaire, nous allons également pouvoir directement spécifier des variables pour décrire certaines propriétés de nos hosts grâce à l’instruction vars. Ces variables peuvent être attribuées directement à un host :

INI

[routers]
192.168.1.77 ansible_user=cisco ansible_ssh_pass=C1sc0

YAML

---
routers:
 hosts:
  192.168.1.77:
 vars:
  ansible_user: cisco
  ansible_ssh_pass=C1sc0

Ici, nous avons défini les identifiants de connexion pour le routeur.

Ces variables peuvent également être attribuées à des groupes ou des sous-groupes afin de correspondre à tous les hosts sous-jacents :

INI

[production]

[production:children]
routers
switches

[routers]
192.168.1.77
[switches]
192.168.1.78

[production:vars]
ansible_connection = network_cli
ansible_network_os = ios
ansible_user = cisco
ansible_ssh_pass = C1sc0

YAML

---
production:
 children:
  routers:
   hosts:
    192.168.1.77:
  switches:
   hosts:
    192.168.1.78:

 vars:
  ansible_connection: network_cli
  ansible_network_os: ios
  ansible_user: cisco
  ansible_ssh_pass: C1sc0

Les variables souvent stockées dans l’inventaire sont celles relatives à la connexion SSH. Hormis celles relatives aux identifiants et au constructeur, il y a celle relative à la connexion et l’exécution des commandes, ansible_connection. Afin qu’Ansible prenne bien en compte le fait que nos équipements cibles sont de type réseaux et que par conséquent, ils ne sont (pour la plupart) pas en mesure d’exécuter de scripts, nous devons positionner l’une des valeurs suivantes :

network_cli (plugin dédié, installation préalable nécessaire)

local (précise à Ansible d’exécuter le code sur le contrôleur et non sur l’host)

Nous aurons l’occasion de revenir sur les bonnes pratiques liées aux variables dans un chapitre dédié un peu plus loin.

9

Playbook

En préambule du chapitre sur l’utilisation des playbooks, évoquons rapidement les commandes Ad-Hoc. Ces commandes sont le moyen d’agir sur les hosts sans passer par un playbook. Elles sont généralement peu exploitées mais pourront vous être utile dans certains cas, pour effectuer des tests notamment.

la syntaxe est composée de la commande ansible suivi d’un nom de groupe (all si vous voulez toucher tous les hosts) et accompagnée d’une ou plusieurs des options disponibles. Voici quelques-unes de ces options :

-i (spécifie le chemin de l’inventaire à utiliser)

-m (spécifie le module à utiliser)

-u (spécifie l’utilisateur à utiliser)

-k (spécifie le password à utiliser)

-v (mode verbose)

-C (décrit les changements sans les faire)

Ainsi, si vous souhaitez vérifier la disponibilité des routeurs de votre inventaire, vous pourrez utiliser la commande suivante :

$ ansible routers -m ping

ansible inventory_group -m module

Notez que le module ping n’envoie pas une requête icmp comme on pourrait le croire, mais contrôle simplement que tous les éléments permettant une connexion à l’host sont présents (trivial test pour citer la documentation). Si tout est OK, vous aurez le retour ci-dessous :

192.168.1.77 | SUCCESS => {
"changed": false,
"ping": "pong"
}

Si vous souhaitez interroger vos routeurs Cisco ios afin de contrôler le type d’informations retournées, vous pouvez utiliser le module ios_facts. Voici un exemple utilisant en plus l’option -i signalant que je souhaite toucher les routeurs d’un autre fichier d’inventaire que celui par défaut :

$ ansible routers -m ios_facts -i inventaire.ini

ansible inventory_group -m module -i inventory_file

Qu’est ce qu’un Playbook ?

Avec l’inventaire, c’est l’autre élément fondamental au fonctionnement d’Ansible. Un playbook est un fichier YAML qui va décrire les actions à opérer sur tout ou partie des machines de l’inventaire. Un playbook est divisé en sous parties appelées plays qui sont elles-même divisées en tasks qui pourront faire appel à des modules pour exécuter des actions.

Structure d’un playbook

Voici un exemple de playbook ci-dessous composé d’un play et d’une task :

---
- name: first play
  hosts: routers
  connection: local
  gather_facts: no
  tasks:
   - name: first task - disable telnet
     ios_config:
      lines:
       - transport input ssh
      parents:
       - line vty 0 4
      save_when: changed

Il y a plusieurs choses à noter, je vous propose d’effectuer une analyse des différentes briques formants ce playbook :

Play

C’est l’élément qui regroupe les actions à effectuer (tasks) et qui précise les hosts concernés par celles-ci.

- name: first play

Le nom donné au play.

hosts: routers

Le nom du groupe d’hosts de l’inventaire ciblé par le play.

connection: local

Spécifie de ne pas exécuter de script sur les hosts (systématique pour les network devices mais inutile si vous avez renseigné la variable ansible_connection dans l’inventaire comme vu précédemment).

gather_facts: no

Spécifie de ne pas récupérer d’informations sur les hosts (systématique pour les network devices).

Tasks

Les tasks sont les actions à effectuer.

- name: first task - disable telnet

Le nom donné à la task.

ios_config:

Utilisation du module ios_config de la collection cisco.ios.

lines:
       - transport input ssh

Commandes de modification de la configuration du host. Vous pouvez en saisir plusieurs sous formes de liste.

parents:
       - line vty 0 4

Modes de configuration relatives aux commandes de modifications (lines). Vous pouvez en saisir plusieurs sous forme de liste.

save_when: changed

Spécifie de sauvegarder la configuration courante du host si un changement a été opéré. Les autres valeurs possibles sont :

always : sauvegarde systèmatique de la configuration courante.

never (défaut) : pas de sauvegarde.

modified : sauvegarde s’il y a eu un changement depuis la dernière sauvegarde.

Point d’attention sur l’idempotence

Notez que pour en bénéficier, vous devez saisir les commandes telles qu’elles apparaissent dans la running-config. Ansible regarde en effet dans la configuration courante avant d’effectuer un changement. Dans notre exemple, il va vérifier la présence de la ligne transport input ssh. Si elle est déjà présente, Ansible ne déclarera aucun changement et n’exécutera pas la tâche. En revanche, si vous vous saisissez une commande abrégée comme trans in ssh dans le playbook, Ansible ne sera pas en mesure de voir que la commande est déjà appliquée car il ne la reconnaîtra pas, et déclarera donc un changement. Mettez donc un point d’honneur à utiliser la syntaxe complète des commandes d’écriture.

Point d’attention sur les changements en production

Faites attention aux revers de l’automatisation et testez vos playbook avant de les déployer en production, vous pourriez occasionner de multiples incidents en un clin d’oeil ! Pour vous aider à déterminer si un changement va avoir lieu, vous pouvez utiliser l’option -C de la commande ansible-playbook. Dans ce cas, un check des actions va être opéré et aucun changement de configuration ne sera effectué. Attention la fiabilité du résultat dépend du contexte.

Quelques exemples

Voyons un autres playbook permettant d’afficher l’uptime des routeurs de notre inventaire, sh_uptime.yml.

---
- name: show uptime
  hosts: routers
  gather_facts: no
  tasks:
   - name: Task 1 - send command
     ios_command:
      commands: "show version | include uptime"
     register: result

   - name: Task 2 - display result
     debug:
      msg: "{{ result }}"

Vous aurez noté quelques différences par rapport à l’exemple précédent. Premièrement, nous avons 2 tasks successives et non plus une seule. L’une des tasks s’occupe d’envoyer la commande et de stocker le résultat et la seconde permet de l’afficher.

Ensuite, nous avons ici utilisé un module différent, ios_command. Ce module est dédié pour les commandes hors mode de configuration. L’autre élément important est la clé register permettant de stocker le retour cli dans une variable, result en l’occurrence.

Enfin, la variable result est appelée dans la seconde task afin d’afficher son contenu. Pour appeler une variable, nous utiliserons la syntaxe suivante :

"{{ variable }}"

Exécutons le playbook sur notre maquette de deux routeurs :

$ ansible-playbook sh_uptime.yml

Tout s’est bien déroulé, les informations demandées sont visibles, 13 minutes d’uptime pour les deux équipements. Nous pouvons encore améliorer la lisibilité en modifiant l’appel à la variable dans le playbook :

msg: "{{ result.stdout[0] }}"

Nous aurons l’occasion de développer d’autres playbooks dans le chapitre dédié aux cas pratiques.

10

Variables

Avec Ansible, les variables seront créées avec la syntaxe YAML, comme nous l’avons vu dans le chapitre dédié. Les différents types décrits (string, dictionnaires, …) sont donc accessibles. Les variables sont utilisables à divers endroits et nous allons voir lesquels.

Playbook

Pour pouvoir utiliser des variables dans nos playbooks, nous devons au préalable les déclarer et nous pouvons le faire directement à l’intérieur à l’aide de vars :

---
- name: my play
  hosts: all
  vars:
   vendor: huawei
   model: s5735

  tasks:
   - name: my task
     ...

Vous pouvez également les déporter au sein d’un fichier et simplement spécifier le chemin vers celui-ci :

---
- name: my play
  hosts: all
  vars_files:
   - /vars/my_vars.yml

Inventaire

Nous avons également vu précédemment qu’elles peuvent être définies dans l’inventaire., toujours grâce à vars :

---
switches:
 hosts:
  192.168.1.78:

 vars:
  ansible_connection: network_cli
  ansible_network_os: ios
  model: s5735

Les deux possibilités précédentes sont plutôt pratiques et il est important de savoir que c’est possible, mais ce n’est pas l’idéal, mieux vaut utiliser des fichiers dédiés à cet usage ce qui permettra d’aérer votre inventaire et vos playbooks.

Premièrement, il convient de distinguer les variables qui s’appliquent aux groupes et celles qui s’appliquent aux hosts. Nous ferons la déclaration des variables de groupes dans des fichiers sous le répertoire group_vars et nous ferons la même chose sous le répertoire host_vars pour les variables d’hosts. Nous privilégierons le format YAML pour ces fichiers.

Ainsi, l’arborescence de notre projet Ansible ressemblera à celle-ci:

├── ansible.cfg

├── group_vars

│ ├── routers.yml

│ └── swtiches.yml

├── host_vars

│ ├── R1.yml

│ └── SW1.yml

├── inventory.yml

└── playbook.yml

group_vars

Étant donné que notre plugin et nos identifiants de connexions seront les mêmes pour tous les routeurs, nous pouvons sortir les variables correspondantes de l’inventaire pour les déclarer dans un fichier que nous nommerons à l’identique du groupe concerné, en l’occurance routers.yml :

---
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: cisco
ansible_ssh_pass: C1sc0

Dans un contexte multi constructeurs, nous aurions pu dissocier les routeurs Cisco des routeurs Juniper en créant deux sous-groupes dans l’inventaire :

---
production:
 children:
  routers:
   children:
    cisco:
     hosts:
      192.168.1.77:
    juniper:
     host:
      172.16.7.254:

De cette façon, nous pouvons décrire les paramètres communs dans le fichier routers.yml et déclarer les variables relatives aux constructeurs comme le connecteur (ios pour Cisco et junos pour Juniper) dans les fichiers cisco.yml et juniper.yml.

Enfin, pour toucher tous les groupes et tous les hosts, vous pouvez créer un fichier all.yml.

host_vars

Pour déclarer des variables relatives uniquement à certains hosts, la méthode est exactement la même que pour les groupes. Il vous suffit de créer un fichier nom_du_host.yml sous le répertoire host_vars.

Utilisation

Pour rappel, si vous devez appeler le contenu du variable, il vous faut utiliser la syntaxe suivante :

"{{ variable }}"

Si le contenu de la variable est un dictionnaire, vous pourrez accéder à la valeur d’une clé sous-jacente de cette façon :

"{{ variable.item }}"

Si le contenu est une liste, vous pouvez accéder aux différents éléments via l’index :

"{{ variable[0] }}"    # 1er élément de la liste
"{{ variable[2:4] }}"  # éléments 3 & 4 de la liste

Encore une fois, si vous êtes à l’aise en Python, vous retrouverez des automatismes. Quoi qu’il en soit, vous aurez l’occasion de revoir l’utilisation des variables lors des exemples en fin de guide.

11

Ansible Vault

Vault est une fonctionnalité d’Ansible permettant de chiffrer des données sensibles, comme des mots de passe ou des tokens, dans le but d’éviter qu’elles soient stockées en clair dans les playbooks, inventaires ou fichiers de variables.

Création d’un vault

Nous pourrons donc utiliser Vault pour dissimuler les identifiants de connexion à nos équipements réseaux. L’idée dans un premier temps va être de créer un fichier YAML dans lequel placer ces informations, password.yml pour l’exemple (vous pouvez le créer au même point d’arborescence que les playbooks) :

$ vi password.yml

Notre fichier va contenir l’utilisateur et le mot de passe de connexion aux routeurs. Vous pouvez créer de nouvelles variables, ce sont elles que nous renseignerons en lieu et place des informations de connexion dans les autres fichiers. Voici le contenu du fichier password.yml :

---
router_user: cisco
router_password: C1sc0

L’étape suivante est le chiffrement du fichier. Par défaut Ansible Vault propose un chiffrement symétrique AES256 et vous demandera un mot de passe qui fera office de clé. Ce mot de passe sera à utiliser à chaque lancement de playbook afin de débloquer l’accès aux identifiants. Voici la commande de de chiffrement :

$ ansible-vault encrypt password.yml

New Vault password:
Confirm New Vault password:
Encryption successful

Si vous tentez de lire votre fichier password.yml, vous constaterez que le contenu est bien chiffré :

$ cat password.yml

Malgré tout, sachez que vous pouvez toujours consulter le contenu du fichier en clair. Pour cela, vous pouvez utiliser la commande ci-dessous ainsi que le mot de passe utilisé pour le chiffrement :

$ ansible-vault view password.yml

De la même façon, vous pouvez utiliser cette commande pour modifier le fichier :

$ ansible-vault edit password.yml

Ou bien encore celle-ci pour déchiffrer le fichier :

$ ansible-vault decrypt password.yml

Enfin, si vous souhaitez changer la clé de chiffrement (mot de passe) pour votre fichier, vous pouvez utiliser cette commande :

$ ansible-vault rekey password.yml

Utiliser le vault

Commencez par remplacer les informations de connexion présentes dans vos fichiers par les variables déclarées dans le fichier vault, password.yml dans notre cas :

---
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: "{{ router_user }}"
ansible_ssh_pass: "{{ router_password }}"

Ensuite vous avez plusieurs possibilités. Vous pouvez appeler votre fichier vault au lancement du playbook. Dans ce cas, vous devrez utiliser l’option -e permettant d’ajouter un fichier de variables supplémentaire et l’option —ask-vault-password qui vous demandera votre mot de passe pour le déchiffrement du vault :

$ ansible-playbook playbook.yml -e @password.yml --ask-vault-password
Vault password:

Sinon, vous pouvez intégrer le vault directement au playbook en ajoutant la clé vars_file qui contient le chemin vers le fichier vault :

---
- name: play A
  hosts: routers
  connection: local
  gather_facts: no
  vars_files:
   - password.yml

Dans ce cas de figure, inutile d’utiliser l’option -e :

$ ansible-playbook playbook.yml --ask-vault-password
Vault password:

Une fois votre mot de passe saisi, votre playbook s’exécutera normalement.

L’utilisation d’Ansible Vault est donc particulièrement utile pour éviter de laisser traîner vos identifiants de connexion dans les différents fichiers utilisés par Ansible et ainsi respecter les bonnes pratiques de sécurité.

12

Rôles

Un rôle pour Ansible est un ensemble d’actions qui va prendre la forme d’un répertoire structuré. Les rôles ont vocation à être réutilisés et assurent donc souvent des fonctions assez génériques. Les rôles peuvent ensuite être appelés très simplement au sein d’un playbook, ce qui améliorera grandement la lisibilité de celui-ci en le rendant moins complexe.

Nous pourrions avoir des rôles dédiés à des fonctions comme celles-ci :

backup de configuration

paramètrage d’ACLs

configuration d’accès utilisateurs

Ainsi, ces rôles pourraient être appelés par des playbooks dans le cadre de déploiements d’équipements par exemple.

Voyons maintenant comment créer des rôles et nous en servir. Voici la commande de création d’un rôle que nous nommerons basic_settings :

$ ansible-galaxy init basic_settings

Un répertoire basic_settings a été créé et une arborescence de fichier a été générée :

Il y a principalement deux fichiers qui vont nous intéresser. Premièrement, le fichier main.yml situé dans le répertoire tasks va nous permettre de décrire les opérations à effectuer. C’est en quelque sorte l’équivalent d’un playbook. Ensuite, le fichier main.yml du répertoire vars va nous permettre de définir nos variables.

Notre exemple de rôle basic_settings aura pour but de configurer des paramètres communs sur nos équipements réseaux :

Configuration d’un serveur ntp préféré

Désactivation du serveur http

Ajout d’une bannière de connexion

Etant donné que nous n’allons pas utiliser de variables, nous n’avons besoin que du fichier main.yml du répertoire tasks.

tasks/main.yml

---
- name: configure ntp
  cisco.ios.ios_ntp_global:
   config:
    peers:
     - peer: 0.fr.pool.ntp.org
       prefer: true
       use_ipv4: true

- name: disable http server
  cisco.ios.ios_config:
   lines:
    - no ip http server

- name: Configure the login banner
  cisco.ios.ios_banner:
    banner: login
    text: |
      --------------------------------------
                This is my banner
      --------------------------------------
    state: present

Comme vous pouvez le voir, différents modules de la collection Cisco ont été utilisés afin mener à bien les actions souhaitées.

Maintenant que les tasks de notre rôle sont complètes, il ne nous reste plus qu’à appeler celui-ci au sein d’un playbook. Idéalement, vous aurez créé votre rôle à la racine de votre projet dans le même répertoire que votre playbook.

config.yml

---
- name: configure cisco routers
  hosts: routers
  gather_facts: no

  roles:
    - basic-settings

Nous aurions pu créer d’autres rôles pour d’autres tâches et les cumuler au sein du playbook. En utilisant cette méthode, vous améliorez grandement l’organisation et la segmentation pour des opérations complexes.

Sachez enfin que vous pouvez partager vos rôles ou télécharger des rôles existants sur la plate-forme Ansible Galaxy.

https://galaxy.ansible.com/

13

Jinja2

Jinja est un système de template, notamment utilisé en Python et que nous pouvons sans surprise exploiter avec Ansible. Ces templates sont particulièrement utiles pour appliquer des configurations génériques en adaptant, grâce à des variables, certains paramètres en fonction de l’équipement.

Templates

Les templates Jinja2 sont des fichiers avec extension .j2 et nous les placerons de préférence dans un répertoire dédié, en l’occurrence templates. Voir l’exemple d’un projet ci-dessous avec un template dédié aux routeurs :

├── ansible.cfg

├── playbook.yml

├── group_vars

│ └── Routers.yml

├── host_vars

│ ├── R1.yml

│ ├── R2.yml

│ └── R3.yml

├── inventory.yml

└── templates

└── routers.j2

Dans le cadre de l’automatisation des réseaux, nous utiliserons typiquement ces templates pour décrire la configuration de nos équipements. Pour un parc de switchs, un template pourrait être une running-config sur laquelle nous remplacerions les paramètres de configuration non génériques par des variables, par exemple la configuration des ports.

Exemple d’un extrait de template de configuration switch :

interface Ethernet0/3
 description {{ name }}
 switchport access vlan {{ vlan }}
 switchport mode access
 spanning-tree bpduguard enable

Les variables sont cernés de {{ }} lorsqu’elles sont appelées et nous pourrons définir leurs valeurs dans des fichiers de variables de groupe ou bien d’host.

Il est possible d’insérer des conditions logiques dans les templates :

{% if vlan == 100 %}

{% else %}

{% endif %}

Ainsi que des boucles :

{% for x in y %}

{% endfor %}

Tout comme en Python vous pouvez utiliser des opérateurs de comparaison :

>   #supérieur
<   #inférieur
>=  #supérieur ou égal
<=  #inférieur ou égal
==  #égal à
!=  #différent de
in  #présent dans

Enfin, il est également possible de déclarer des fonctions :

{% macro myfunct(param1, param2) %}
 ...
 {% endmacro %}

Et bien entendu de les appeler :

{% myfunc(cmd1, cmd2) %}

Ces templates Jinja2 se rapprochent finalement beaucoup des possibilités offertes par les scripts.

Filtres

En complément, il est possible d’utiliser Jinja2 directement dans les playbooks grâce aux filtres. Il y a une multitude de possibilités et certaines sont spécifiquement orientées pour les réseaux. Vous pourrez retrouver la documentation ici :

https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html

Par exemple, voici un filtre permettant de vérifier si le contenu de la variable output est une adresse IP valide (nécessite d’avoir le module Python netaddr installé) :

{{ output | ansible.netcommon.ipaddr }}

Vous connaissez peut-être le module TextFSM permettant de parser les données à l’aide de templates ? Et bien il est possible de l’associer à un filtre Jinja2 (là encore, assurez-vous d’avoir le module Python TextFSM installé) :

{{ output | ansible.netcommon.parse_cli_textfsm('template') }}

Enfin si vous êtes un aficionados des regex, vous pourrez vous en donner à coeur joie, voici quelques exemples en vrac :

# Vérifier si le pattern est présent
{{ output | regex_search('vlan[0-9]', ignorecase=True) }}

# Remplacer un pattern par une chaîne de caractères
{{ output | regex_replace('GigabitEthernet', 'GE') }}

# Trouver toutes les occurences du pattern
{{ output | regex_findall('GigabitEthernet0\/0\/[0-9]{1,2}'}}

Pour terminer, je vous propose un cas d’usage des filtres en affichant les adresses mac dans les tables arp de nos routeurs. Pour cela, je vais réutiliser un playbook vu tout à l’heure et l’adapter afin de lancer la commande show arp et de filtrer le retour cli en n’affichant que les adresses mac. Vous pouvez utiliser ce pattern regex pour capter le format mac des routeurs Cisco :

[\d\w]{4}\.[\d\D]{4}\.[\d\D]{4}

Ci-dessous, le playbook :

---
- name: search mac addresses
  hosts: routers
  connection: local
  gather_facts: no
  tasks:
   - name: Task 1 - send command
     ios_command:
      commands: "show arp"
     register: result
   - name: Task 2 - filter with regex
     debug:
      msg: '{{ result.stdout[0] | regex_findall("[\d\w]{4}\.[\d\D]{4}
            \.[\d\D]{4}") }}'

Vous l’aurez compris, les possibilités offertes par Jinja2 sont nombreuses et vous y serez immanquablement confronté lors de votre apprentissage d’Ansible.

14

Version Control

Le versionnage de code est traditionnellement utilisé par les développeurs, mais c’est une bonne pratique qui peut et devrait également servir aux administrateurs pour leurs scripts et leurs fichiers de configuration, notamment pour améliorer la traçabilité.

Dans le cadre de l’utilisation d’Ansible, nous exploiterons différents fichiers nécessaires à son bon fonctionnement (inventaires, playbooks, …) mais qui serons construits différemment en fonction des besoins et des projets. C’est la raison pour laquelle il est intéressant de cloisonner et versionner ces différents fichiers.

Ce chapitre va porter sur Git, le plus populaire des outils de versionnage disponibles et qui par ailleurs présente l’avantage d’être open-source. Les intérêts de Git sont multiples car au-delà du contrôle de version de vos fichiers, il facilite la collaboration de plusieurs intervenants sur un même projet. De plus, vous pouvez créer un dépôt public ou privé contenant votre code et vos fichiers pour le partager. La plateforme la plus connu proposant ce service est GitHub.

Dans le cadre de ce guide dédié à Ansible, je vais simplement faire une brève introduction des fonctionnalités de Git pour contrôler vos fichiers de configuration mais je ne vais pas m’attarder sur le travail collaboratif.

Installer Git

Git est disponible sur Windows, Mac et Linux. Étant donné que vous pratiquez très probablement Ansible sur une machine Linux, il vous suffit de passer par votre gestionnaire de paquet pour l’installer :

$ apt-get install git

Utilisez la commande suivante afin de vérifier que l’installation s’est bien déroulée. Si c’est bien le cas, le version de Git installée s’affichera :

$ git --version
git version 2.20.1

Utiliser Git

Afin de versionner nos fichiers, nous allons commencer par créer un nouveau dépôt. Placez-vous dans le répertoire de votre projet puis utilisez la commande suivante :

$ git init

Pour indexer des fichiers au dépôt, vous devrez utiliser la commande git add :

$ git add inventory.yml

Si vous souhaitez ajouter tous les fichiers du répertoire, utilisez cette commande :

$ git add *

Vous pouvez vérifier les fichiers qui sont actuellement indexés :

$ git status

A ce stade vos fichiers sont en attente d’un commit, c’est-à-dire d’une déclaration de changement. L’idée est d’effectuer un commit à chaque changement important dans vos fichiers (inutile de le faire en permanence). Par exemple, pour notre projet backup, je pourrais effectuer un commit après l’ajout de nouveaux équipements dans le fichier d’inventaire, comme des switchs.

Pour valider un changement via commit, utilisez la commande git commit. L’option -m permet d’ajouter un label à votre commit afin d’identifier plus facilement vos modifications (Choisissez un nom parlant en lien avec vos modifications) :

$ git commit -m "add switches in inventory"

Vous pouvez ensuite afficher les commits déjà effectués à l’aide de la commande git log :

$ git log

Notez que chaque commit est identifié par un ID :

6ba162c58fccf096d8c057de9eece448f84fed6c

Enfin, si vous souhaitez rappeler une version antérieure de vos fichiers de configuration, donc un commit précédent, vous devrez utiliser la commande git checkout et spécifier l’ID du commit qui vous intéresse.

$ git checkout 6ba162c58fccf096d8c057de9eece448f84fed6c

Notez que vous pouvez ajouter un peu de couleur aux retours d’information Git ce qui pourra améliorer la lisibilité :

$ git config color.ui true

Les branches

Créer une branche va vous permettre de dériver votre projet pour travailler sur une nouvelle fonctionnalité par exemple. Vous pourrez ainsi faire votre développement sans impacter votre projet qui restera dans la branche Master, la branche par défaut.

Lorsque votre fonctionnalité sera terminée, vous pourrez la fusionner à votre branche principale (si vous le souhaitez).

Dans notre contexte Ansible, la feature en question pourrait être le test de nouvelles tasks au sein d’un playbook. Si les tests sont concluants, le merge permettrait à Git d’ajouter les modifications de fichiers, notamment les tasks, aux fichiers de la branche Master.

Pour créer une nouvelle branche, par exemple dédiée à l’ajout de fonctionnalités pour la sauvegarde des switchs (backup_switch), utilisez la commande suivante :

$ git branch backup_switch

Pour vous positionner sur une branche, par exemple sur backup_switch, utilisez la commande git checkout :

$ git checkout backup_switch

Sachez que vous pouvez mutualiser les deux commandes précédentes avec celle ci-dessous qui permet de créer une nouvelle branche et de vous y positionner (option -b) :

$ git checkout -b backup_switch

Avec la commande git branch, vous pouvez consulter les branches existantes (rappelez-vous que Master est la branche par défaut) :

$ git branch

Si votre branche devient inutile, vous pouvez la supprimer :

$ git branch -d nom_branch

Enfin, si vous souhaitez fusionner deux branches, par exemple votre fonctionnalité et votre branche Master, vous pouvez utiliser la commande git merge. Cette commande permet de fusionner la branche active avec une autre. Par exemple, si nous sommes sur Master, nous pourrons fusionner avec backup_switch comme ceci :

$ git merge backup_switch

Si la fusion de vos fichiers de configuration provoque des conflits, ceux-ci vous seront remontés et vous devrez alors les corriger à la main.

Pour illustrer le concept de branche et de fusion, admettons le cas d’usage suivant. Je me suis positionné sur la branche backup_switch puis j’ai indexé mes fichiers inventory.yml et backup.yml. Ensuite, je les ai modifié avec quelques ajouts et j’ai effectué un commit de ces deux fichiers au sein de ma branche backup_switch. Mes commandes ont donc été les suivantes :

$ git checkout backup_switch
$ git add inventory.yml
$ git add backup.yml

# Modification de mes fichiers

$ git commit -m "backup switch feature V1"

Pour terminer, étant donné que je suis satisfait de mes ajouts, je vais fusionner (merge) la branche backup_switch avec la branche master afin d’appliquer la fonctionnalité à ma branche principale.

$ git checkout master
$ git merge backup_switch

Malgré la brièveté de cette introduction, vous en savez maintenant suffisamment pour vous lancer avec Git et commencer à versionner vos scripts ou vos fichiers de configuration.

15

Cas pratiques

Maintenant, c’est à vous de jouer !

L’objectif de ce chapitre est de mettre bout à bout les différentes briques survolées jusqu’à présent afin d’en faire des ensembles fonctionnels répondants à des problématiques courantes et que vous pourrez implémenter en entreprise.

Dans ce premier cas d’usage et pour les suivants, je vais présenter et décrire les éléments importants. Vous pourrez noter cependant que je ne respecte pas toujours les bonnes pratiques conseillées précédemment (vault, fichiers de variables, …) afin de minimiser le nombre de fichiers et me concentrer sur le fond plutôt que sur la forme.

Automatiser les sauvegardes

Pour se mettre en jambe, commençons par un playbook très simple mais néanmoins très utile pour sauvegarder les configurations !

Topologie GNS3

Nous allons créer ici une tâche de sauvegarde de la configuration courante sur une topologie fictive de 3 routeurs Cisco présentée ci-dessous :

Voici les adresses IP d’administration et masques des équipements :

R1 -> 192.168.1.77 / 24
R2 -> 192.168.2.88 / 24
R3 -> 192.168.3.99 / 24

Inventaire

---
lab:
 children:
  routers:
   hosts:
    192.168.1.77:
    192.168.2.88:
    192.168.3.99:

   vars:
    ansible_connection: network_cli
    ansible_network_cli_ssh_type: paramiko
    ansible_network_os: ios
    ansible_user: cisco
    ansible_ssh_pass: C1sc0

L’inventaire inventory.yml de cet exemple est constitué d’un groupe racine lab et d’un seul sous groupe routers. Celui-ci contient 3 hosts qui sont nos 3 routeurs R1, R2 & R3 décrits par leur adresse IP. Les variables décrivant les informations de connexion sont directement intégrées à l’inventaire. Nous y retrouvons la sélection du plugin network_cli, du connecteur paramiko (facultatif), le type d’OS client (ansible_network_os) et les identifiants de connexion (ansible_user & ansible_ssh_pass).

Playbook

---
- name: backup the router running-config
  hosts: routers
  gather_facts: no

  tasks:
    - name: backup
      ios_config:
        backup: yes
        backup_options:
         dir_path: "/network/backup/"
         filename: "{{inventory_hostname}}.txt"

Le playbook backup.yml ci-dessus ne contient qu’un play et qu’une task. Celle-ci utilise l’option backup du module ios_config. Ce paramètre est à no par défaut et permet de générer une sauvegarde de la running-config lorsqu’il est à yes. Il convient bien entendu de préciser les équipements de l’inventaire ciblés par notre playbook, ici ce sont les membres du groupe routers, donc nous spécifions routers comme valeur de hosts.

Plusieurs possibilités s’offrent à nous. Nous pouvons nous contenter de simplement activer l’option backup et dans ce cas les fichiers de configuration seront automatiquement stockés dans un répertoire backup lui même automatiquement créé dans le répertoire courant, celui du playbook. Le nom du fichier sera également automatiquement généré en concaténant le nom de l’host et la date, exemple :

192.168.3.99_config.2022-09-24@20:52:18

Deuxième solution possible, nous pouvons utiliser les backup_options, comme c’est le cas dans notre playbook. Il sera alors possible de choisir le répertoire de destination des sauvegardes grâce à l’option dir_path et le nom des fichiers de configuration avec l’option filename.

Dans cet exemple, afin d’avoir un nom cohérent et différent pour chaque équipement à chaque exécution de la task, j’ai utilisé la variable inventory_hostname qui contient le nom de l’host comme indiqué dans l’inventaire.

Exécution et Résultat

$ ansible-playbook backup.yml -i inventory.yml

résultat de l’exécution du playbook backup.yml

Comme prévu, un dossier /network/backup a été créé et nous y retrouvons nos 3 fichiers de configuration.

├── backup

├── 192.168.1.77.txt

├── 192.168.2.88.txt

└── 192.168.3.99.txt

Déployer une configuration avec template Jinja2

Précédemment dans ce guide, nous avons évoqué le système de template Jinja2, c’est le moment d’étudier un cas pratique. Nous allons travailler avec notre maquette de 3 routeurs et le projet suivant :

├── ansible.cfg

├── configs

├── routers_config.yml

├── group_vars

├── host_vars

│ ├── R1.yml

│ ├── R2.yml

│ └── R3.yml

├── inventory.yml

└── templates

└── routers.j2

Il y a quelques particularités :

Un répertoire config, vide pour le moment.

Un répertoire templates contenant notre template Jinja2.

Un fichier de variable par équipements, R1, R2 & R3.yml

L’idée ici va être de créer un fichier de configuration par routeur et de charger ladite configuration sur l’équipement correspondant. Les fichiers de configuration seront générés à partir d’un template générique s’adaptant à tous les routeurs en fonction des paramètres définis dans chaque fichier de variable.

inventory.yml

---
lab:
 children:
  routers:
   hosts:
    R1:
     ansible_host: 192.168.1.77
    R2:
     ansible_host: 192.168.2.88
    R3:
     ansible_host: 192.168.3.99

   vars:
    ansible_connection: network_cli
    ansible_network_cli_ssh_type: paramiko
    ansible_network_os: ios
    ansible_user: cisco
    ansible_ssh_pass: C1sc0

R1.yml

---
settings:
- loopback: "172.16.25.254 255.255.255.254"
  hostname: "R1"
  scp: true
  ntp: "0.fr.pool.ntp.org"

R2.yml

---
settings:
- loopback: "172.16.26.254 255.255.255.254"
  hostname: "R2"
  scp: false
  ntp: "1.fr.pool.ntp.org"

R3.yml

---
settings:
- loopback: "172.16.27.254 255.255.255.254"
  hostname: "R3"
  scp: false
  ntp: "2.fr.pool.ntp.org"

routers.j2

{% for each in settings %}

hostname {{ each.hostname }}
interface Loopback0
 ip address {{ each.loopback }}
ntp server {{ each.ntp }}

{% if each.scp is true %}
ip scp server enable
{% endif %}

{% endfor %}

Si l’on se penche rapidement sur le template Jinja2 ci-dessus, on remarque qu’il débute par une boucle qui itère tous les éléments contenus dans le fichier de variable du routeur (tous les éléments contenus dans settings). Ensuite, chaque variable est remplacée par la valeur associée à chaque élément (loopback, hostname, scp, ntp). La commande ip scp server enable est soumis à contrôle et ne sera ajoutée au fichier de configuration que si la valeur de scp est à true.

routers_config.yml

---
- name: Routers config
  hosts: routers
  gather_facts: no
  tasks:
   - name: Generate config
     template: src=templates/routers.j2
               dest=configs/{{inventory_hostname}}.cfg

   - name: Apply config
     ios_config:
      src: configs/{{inventory_hostname}}.cfg
      save_when: changed

Notre playbook ici se nomme routers_config.yml et va contenir deux tâches. La première va générer un fichier de configuration basé sur le template Jinja2. La deuxième tâche va passer les commandes contenues dans le fichier. Vous pouvez très bien dissocier ces actions dans des playbook différents si vous le souhaitez.

Le playbook va balayer les hosts du groupe routers de l’inventaire. A chaque passage, grâce à la variable inventory_hostname, un fichier portant le nom de l’host est créé dans le répertoire configs. Ensuite, toujours à l’aide de cette même variable, le fichier correspondant à l’host concerné est envoyé au module ios_config pour appliquer la configuration.

Exécution du playbook

$ ansible-playbook routers_config.yml -i inventory.yml

Les 3 fichiers de configuration correspondants à nos routeurs ont bien été créés (TASK Generate config) et les configurations ont bien été appliquées (TASK Apply config). Notez que si les commandes du template respectent la syntaxe de la running-config alors Ansible ne devrait pas déclarer de changement si vous repassez le playbook :

Dans le répertoire config, vous retrouverez les fichiers de conf qui ont été générés et appliqués :

├── configs

│ ├── R1.cfg

│ ├── R2.cfg

│ └── R3.cfg

Exemple avec R1.cfg :

hostname R1
interface Loopback0
 ip address 172.16.25.254 255.255.255.254
ntp server 0.fr.pool.ntp.org

Avec cette manière de faire, vous pouvez décrire l’état souhaité de votre infrastructure directement dans vos fichiers de variable et déployer les configurations en un tour de main. Les paramètres communs peuvent être décrits dans des fichiers de groupe.

/!\ Rappelez-vous de toujours tester vos playbooks avant de les déployer en production.

Effectuer un inventaire ou un audit

L’idée dans cet exercice va être de récolter un certain nombre d’informations relatives à notre parc d’équipements et de les lister dans un rapport au format CSV. C’est très utile pour des besoins d’inventaire ou d’audit de conformité. Nous allons voir deux playbooks utilisant des modules différents.

Dans les deux cas, les rapports seront stockés dans le répertoire reports et nous utiliserons le même lab de 3 routeurs que le premier cas pratique, donc le même inventaire.

Inventaire

---
lab:
 children:
  routers:
   hosts:
    192.168.1.77:
    192.168.2.88:
    192.168.3.99:

   vars:
    ansible_connection: network_cli
    ansible_network_os: ios
    ansible_user: cisco
    ansible_ssh_pass: C1sc0

#1 -Inventorier les adresses IP de l’interface Loopback0

Playbook

---
- name: Get Loopback0
  hosts: routers
  gather_facts: yes
  vars:
    report_path: "./reports/loopback_report.csv"

  tasks:
  - name: get Loopback0 address
    ios_command:
     commands: "show ip interface brief | include Loopback0"
    register: loopback

  - name: Write File
    lineinfile:
     line: '{{ ansible_net_hostname + "," + loopback |
            regex_search("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") }}'
     path: "{{ report_path }}"
     state: present
     insertafter: "EOF"
     create: yes

  - name: Adding headers to report
    lineinfile:
     path: "{{ report_path }}"
     insertbefore: "BOF"
     line: "HOSTNAME,LOOPBACK0"
     state: present
    run_once: true

Contrairement à d’habitude, nous allons passer l’option gather_facts à yes dans le playbook afin que le module récupère une partie des informations dont nous aurons besoin.

Nous avons ici 3 tasks :

Requête du routeur pour déterminer l’adresse IP

Écriture des informations dans un fichier csv

Ajout de la ligne d’en-tête au fichier

La première task est une simple utilisation du module ios_command nous permettant de passer la commande souhaitée et d’enregistrer le résultat dans la variable loopback.

La seconde task fait appel au module lineinfile pour l’écriture du fichier. Nous allons spécifier plusieurs éléments dont le chemin du fichier et le contenu de la ligne à écrire. Notez ici que la variable ansible_net_hostname nous est fourni par les ios_facts activés par le gather_facts: yes. La variable loopback nous est fournie par la task précédente et celle-ci fait l’objet d’un filtre. Ce filtre utilisant une regex a pour objectif d’extraire l’adresse IP du résultat de la commande du routeur. En effet, le contenu de la variable avant filtrage ressemble à ceci :

Loopback0 172.16.25.254 YES NVRAM up up

Gardez bien à l’esprit que chaque task est exécutée pour chaque host de l’inventaire concerné par le playbook.

Enfin, la troisième et dernière task utilise également le module lineinline pour insérer en début de fichier le nom des colonnes (HOSTNAME et LOOPBACK0). Notez la présence du paramètre run_once à true indiquant de n’exécuter la task qu’une seule fois.

Exécution du playbook

loopback_report.csv

#2 - Auditer la version d’OS et l’uptime des routeurs

Ce cas pratique est relativement similaire au premier mais il utilise un module différent pour interroger nos routeurs ce qui le rend intéressant. Découvrons tout d’abord le playbook.

Playbook

---
- name: Get router facts
  hosts: routers
  gather_facts: no
  vars:
    report_path: "./reports/routers_facts.csv"

  tasks:
   - name: Run command and parse with ntc_templates
     ansible.utils.cli_parse:
      command: "show version"
      parser:
       name: ansible.netcommon.ntc_templates
     register: parser_output

   - name: Generate Report
     lineinfile:
      line: "{{ item.hostname +','+ item.version +','+ item.uptime }}"
      path: "{{ report_path }}"
      state: present
      insertafter: "EOF"
      create: yes
     with_items:
      - "{{ parser_output.parsed }}"

   - name: Adding headers to report
     lineinfile:
      path: "{{ report_path }}"
      insertbefore: "BOF"
      line: "HOSTNAME,VERSION,UPTIME"
      state: present
     run_once: true

Tout comme le playbook précédent, celui-ci contient 3 tasks. Je ne vais m’attarder sur les deux dernières qui assurent globalement les même fonctions.

La première tâche permettant d’interroger les routeurs n’utilise pas le module ios_command comme précédemment mais le module cli_parse dans le chapitre dédié aux modules. L’avantage ici est que nous pouvons lancer notre commande et appeler à la volée un parser pour structurer notre output. Le parser ici sera fourni par le module ntc-templates que vous avez également dû installer. Celui-ci fournit des templates pour un nombre considérable de commandes et si la syntaxe est complète (non abrégée), le module est capable d’aligner le template correspondant.

Le résultat est ensuite stocké dans la variable parser_output, voici une partie de celui-ci :

ok: [192.168.1.77] => {
    "msg": {
        "changed": false,
        "failed": false,
        "parsed": [
            {
                "config_register": "0x0",
                "hardware": [
                    "IOSv"
                ],
                "hostname": "R1",
                "mac": [],
                "reload_reason": "Unknown reason",
                "restarted": "18:02:00 UTC Fri Oct 21 2022",
                "rommon": "Bootstrap",
                "running_image": "/vios-adventerprisek9-m",
                "serial": [
                    "9FUSUQTEP7D5KUXVM6WED"
                ],
                "uptime": "1 hour, 55 minutes",
                "uptime_days": "",
                "uptime_hours": "1",
                "uptime_minutes": "55",
                "uptime_weeks": "",
                "uptime_years": "",
                "version": "15.6(2)T"
            }
        ],

],

Comme vous pouvez le constater, c’est un dictionnaire et ce qui nous intéresse est la liste sous la clé parsed. Dans ce cas précis nous aurions pu renseigner l’index [0] directement pour interroger le seul élément de la liste qui est le dictionnaire contenant les informations. Mais ici, nous avons plutôt utilisé une boucle itérant les différents éléments du dictionnaire parsed :

with_items:
      - "{{ parser_output.parsed }}"

Ce type de boucle sera très utile dans d’autres circonstances, par exemple pour parcourir la liste des interfaces retournées par une commande du type show ip interface brief.

De manière générale, pour vous aider à déterminer ce que vous cherchez et construire vos requêtes en conséquence, n’oubliez pas que vous pouvez intégrer des tasks d’affichage :

- name: display result
  debug:
   msg: "{{ parser_output }}"

routers_facts.csv

Créer des vlans sur des switchs

Afin de compléter le panel de possibilités, terminons les cas pratiques par quelques actions sur 3 switchs que j’ai ajouté à la topologie :

Voici les adresses IP d’administration et masques des équipements supplémentaires :

SW1 -> 192.168.4.1 / 24
SW2 -> 192.168.5.1 / 24
SW3 -> 192.168.6.1 / 24

Ceux-ci on bien entendu été ajouté à l’inventaire :

---
lab:
 children:
  routers:
   hosts:
    192.168.1.77:
    192.168.2.88:
    192.168.3.99:
  switches:
   hosts:
    SW1:
     ansible_host: 192.168.4.1
    SW2:
     ansible_host: 192.168.5.1
    SW3:
     ansible_host: 192.168.6.1

   vars:
    ansible_connection: network_cli
    ansible_network_os: ios
    ansible_user: cisco
    ansible_ssh_pass: C1sc0

L’objectif va être de créer deux nouveaux vlans sur notre parc de switchs. Le premier sera dédié à la data et aura l’ID 100 tandis que le deuxième servira à la téléphonie et aura pour ID 200. Nous souhaitons que ce dernier soit désactivé pour le moment.

Tant que nous y sommes, profitons-en pour configurer deux interfaces en mode trunk sur chaque switch pour utiliser ces vlans.

Playbook

---
- name: Switch vlan configuration
  hosts: switches
  gather_facts: no
  tasks:
   - name: Create vlans
     ios_vlans:
      config:
       - name: Data
         vlan_id: 100
         state: active
         shutdown: disabled
       - name: Toip
         vlan_id: 200
         state: active
         shutdown: enabled

   - name: Configure vlans on interfaces
     ios_l2_interfaces:
      config:
       - name: FastEthernet2/0
         mode: trunk
         trunk:
          allowed_vlans: 200
          native_vlan: 100
          encapsulation: dot1q
       - name: FastEthernet2/1
         mode: trunk
         trunk:
          allowed_vlans: 200
          native_vlan: 100
          encapsulation: dot1q

Le premier élément à noter dans ce playbook est la valeur du paramètre hosts à changer pour switches afin de ne cibler que ces derniers dans l’inventaire.

Ensuite, pour la tâche de création de vlans c’est le module ios_vlan de la collection cisco.ios qui intervient. Nous avons la possibilité d’en créer plusieurs et configurer, en plus de l’ID, le nom et le statut pour chaque vlan. Ce module peut également servir à modifier des vlans déjà existants. Notez que pour le vlan 200, le paramètre shutdown est à enabled.

Enfin, pour assigner des interfaces à ces vlans, c’est le module ios_l2_interfaces qui est utilisé. Nous allons configurer deux interfaces (FastEthernet 2/0-1) de la même façon avec ces paramètres :

switchport mode trunk
switchport trunk native vlan 100
switchport trunk allowed vlan 200
switchport trunk encapsulation dot1q

Exécution

Synthèse

Grâce à ces différents exemples, vous avez maintenant un bon aperçu des tâches d’administration automatisables avec Ansible sur une infrastructure composée d’équipements réseaux. J’espère que ces exercices pourront vous aider dans la mise en place de vos projets et à progresser avec l’outil.

Dans le chapitre suivant, vous retrouverez quelques fondamentaux et éléments importants qui pourront vous servir d’aide mémoire.

16

Guide de survie

Arborescence

ansible.cfg     # fichier de configuration général
playbook.yml    # fichier playbook (YAML)
group_vars      # répertoire des fichiers YAML de variables groupes
host_vars       # répertoire des fichiers YAML de variables hosts
inventory.yml   # fichier d'inventaire des hosts (YAML)
templates       # répertoire des templates jinja2

Troubleshooting

Test de connexion aux hôtes du groupe de l’inventaire :

$ ansible routers -m ping -i inventory.yml

Lister les modules d’une collection :

$ ansible-doc -l | grep cisco.ios

Afficher la documentation d’un module :

$ ansible-doc ios_bgp

Inventaire

---
group_A:
 children:
  subgroup_A:
   hosts:
    host_1:
     ansible_host: 192.168.1.2
    host_2:
     ansible_host: 192.168.1.3
    host_3:
     ansible_host: 192.168.1.4

Vue graphique de l’inventaire :

$ ansible-inventory --graph -i inventory.yml

Liste complète des hosts de l’inventaire avec leurs variables :

$ ansible-inventory --list -i inventory.yml

Voir les détails d’un host :

$ ansible-inventory --host R1

Playbook

$ ansible-playbook playbook.yml

-i                     # choisir l'inventaire
-C                     # faire un check (pas de changement)
-M                     # Préciser le module
-c                     # Préciser la connexion
-u                     # Préciser l'utilisateur
-k                     # Préciser le password
-e                     # ajouter un fichier de variables
--list-hosts           # voir les hôtes ciblés (pas de changement)
---ask-vault-password  # demander le password vault

Lancer un playbook en utilisant Ansible Vault :

$ ansible-playbook playbook.yml -e @password.yml --ask-vault-password

Variables

Connexion

ansible_connection:            # local ou network_cli
ansible_network_os:            # plateforme (Cisco -> ios)
ansible_user:                  # username ssh
ansible_ssh_pass:              # password ssh
ansible_ssh_private_key_file:  # clé ssh

Exécution

ansible_command_timeout:       # timeout exécution en seconde
ansible_become:                # élévation de privilège (yes ou no)
ansible_become_method:         # enable

Filtres

Transformer des données compatibles en JSON ou en YAML :

{{ output | to_json }}
{{ output | to_yaml }}

Parser un retour de commande avec un template textfsm :

{{ output | ansible.netcommon.parse_cli_textfsm('template') }}

Trouver une correspondance au pattern regex :

{{ output | regex_search('[\d\w]{4}\.[\d\D]{4}\.[\d\D]{4}') }}

Lister toutes les occurrences du pattern regex :

{{ output | regex_findall('GigabitEthernet0\/0\/[0-9]{1,2}'}}

regex communes

.    # match n'importe quel caractère
^    # match le début d'une chaîne de caractères
$    # match la fin d'une chaîne de caractères
|    # équivaut à OR
[]   # ensemble de caractères, match ceux contenus dans l'ensemble
[^ ] # comme [] mais match ceux NON contenus dans l'ensemble
()   # match l'expression contenue entre parenthèses (exactement)
{n}  # match n fois le caractère ou l'expression précédente
\s   # match un espace
\d   # match un chiffre
\w   # match une lettre

Modules

ios_config

- name: top level configuration
  ios_config:
    lines: hostname my_device

- name: configure interface
  ios_config:
    lines:
    - description LAN
    - ip address 192.168.1.254 255.255.255.0
    parents: interface GigabitEthernet0/0

- name: save running to startup when modified
  ios_config:
    save_when: modified

ios_command

- name: run commands
  ios_command:
    commands:
    - show version
    - show vlans

cli_parse

   - name: run command and parse with ntc_templates
     ansible.utils.cli_parse:
      command: "show version"
      parser:
       name: ansible.netcommon.ntc_templates
     register: output

debug

- name: display result
  debug:
   msg: "{{ output }}"

17

Conclusion

Nous voici arrivé au terme de ce guide dédié à Ansible. Comme vous l’aurez remarqué tout au long de votre lecture, c’est un outil à la fois simple d’utilisation et très complet grâce à ses modules qui vous demanderont de la pratique pour être maîtrisés.

Pour aller plus loin et répondre à des besoins plus complexes, vous devrez envisager de développer vos scripts. Pour cela, je vous propose de jeter un œil à mon autre guide disponible sur Amazon, “Automatiser les réseaux avec Python”. Celui-ci vous permettra de découvrir le langage et de l’exploiter spécifiquement pour interagir avec votre parc d’équipements réseaux. Python est un langage très accessible même pour un débutant et c’est désormais une compétence incontournable de l’administrateur.

Installer son environnement Python sur Windows ou Linux

Découverte des fondamentaux du langage

Pratique du traitement de données (pandas, regex, api, …)

Préparation d’un lab avec GNS3

Découverte des bibliothèques spécialisées (Paramiko, Netmiko, Napalm)

Mise en pratique à travers plusieurs scripts (Routeurs & switchs Cisco, F5)

Enfin, je vous remercie de m’avoir lu jusqu’au bout et j’espère que ce guide vous aura été utile pour découvrir et vous familiariser avec Ansible. Si c’est le cas, je vous invite à laisser un avis ! :) Quoi qu’il en soit, je vous souhaite beaucoup de réussite dans vos futurs projets.