https://github.com/goffinet/ansible-ccna-lab/blob/master/README.md
On trouvera ici des livres de jeu Ansible inspirés des topologies et des sujets du Cisco CCNA (et plus) pour GNS3 (Cisco IOSv).
Leur but est uniquement pédagogique visant à lier les compétences de gestion du réseau du CCNA avec un outil IaC (“Infrastructure as Code”) de gestion des configurations (“Configuration Management”) comme Ansible et un gestionnaire de source (“Source Control Management”) comme Git/Github. Il s’agit aussi d’avoir sous la main un outil souple pour créer et gérer des scénarios de labs qui demandent une préconfiguration ou des changements de configuration (afin de créer des erreurs à corriger manuellement par exemple).
Le projet est basé sur trois éléments : des livres de jeu qui peuvent en appeler d’autres nommés selon la topologie ; ces livres de jeu configurent des hôtes d’inventaire avec des tâches organisées en rôles ; les paramètres de la topologie sont configurés en tant que variables d’inventaire selon un certain modèle de données.
Les topologies sont organisées de la manière suivante :
ccna:
tripod:
gateway:
bipod:
router_on_a_stick:
switchblock:
Une topologie intitulée “ccna” est composée de deux topologies distinctes “tripod” et “switchblock”. La topologie “tripod” trouve trois variantes amoindries : “gateway”, “bipod”, et “router_on_a_stick”.
Expliqué rapidement :
ccna.yml
utilise l’inventaire par défaut ccna
(tripod
+ switchblock
). On trouve d’autres inventaires adaptés aux livres de jeu du même nom dans le dossier inventories/
.ansible-playbook -i inventories/tripod/hosts tripod.yml
.ansible-playbook ccna.yml --list-tags
.ansible_network_os == 'ios'
;ipv4.routing
et ipv6.routing
. Il est conseillé d’en activer un seul pour une topologie. Des cas de “route redistribution” devraient être envisagés.Note : Pour les utilisateurs de la topologie GNS3 fournie en classe, sur certains voire sur tous les périphériques Cisco, il sera peut-être nécessaire de regénérer les clés RSA :
enable
configure terminal
crypto key generate rsa modulus 2048
exit
wr
La mise place de la solution demande quelques étapes décrites plus bas.
Les livres de jeu sont testés avec GNS3 Server et Qemu/KVM sous Linux.
Il y a trois types de périphériques utilisés dans les topologies.
Périphériques | Images Qemu/KVM | Commentaire |
---|---|---|
Routeur Cisco IOSv 15.6(2)T | vios-adventerprisek9-m.vmdk.SPA.156-2.T avec IOSv_startup_config.img | VIRL 1.3.296 (Aug. 2017 Release) |
Commutateur Cisco IOSv L2/L3 | vios_l2-adventerprisek9-m.03.2017.qcow2 | VIRL 1.3.296 (Aug. 2017 Release) |
Poste de travail L2 à L7, Station de contrôle | centos7.qcow2 | Le fichier d’appliance GNS3 |
Les livres de jeu peuvent vérifier la nature du périphérique utilisé de type Cisco et de type routeur ou commutateur à partir de variables d’inventaire.
On utilise des images IOSv vios-adventerprisek9-m.vmdk.SPA.156-2.T
pour les routeurs L3 avec 8 interfaces GigabitEthernet.
L’interface GigabitEthernet0/7
sert de console de contrôle TCP/IP et ne participe pas au routage.
SSH est activé de la manière suivante, sur R1 par exemple :
hostname R1
int GigabitEthernet0/7
ip address dhcp
no shutdown
no cdp enable
ip domain-name lan
username root privilege 15 password testtest
crypto key generate rsa modulus 2048
ip ssh version 2
ip scp server enable
line vty 0 4
login local
transport input ssh
end
wr
On utilise des images IOSv-L2 vios_l2-adventerprisek9-m.03.2017.qcow2
pour les commutateurs multicouches.
L’interface GigabitEthernet3/3
sert de console de contrôle TCP/IP et ne participe pas au routage.
SSH est activé de la manière suivante, sur AS1 par exemple :
hostname AS1
int GigabitEthernet3/3
no switchport
ip address dhcp
no shutdown
no cdp enable
ip domain-name lan
username root privilege 15 password testtest
crypto key generate rsa modulus 2048
ip ssh version 2
ip scp server enable
line vty 0 4
login local
transport input ssh
end
wr
La station de contrôle connecte tous les périphériques en SSH.
Le logiciel Ansible y est fraîchement installé (avec la libraire python netaddr) avec pip
ou à partir de repos.
La station de contrôle offre un service DHCP avec enregistrement dynamique des noms d’hôte dans un serveur DNS (dnsmasq).
On trouve deux scripts de préparation d’une station de contrôle dans le dossier tests/.
En Centos 7 :
hostnamectl set-hostname controller
yum -y remove ansible
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
pip install --upgrade pip
pip install ansible
pip install ansible-lint
pip install netaddr
yum -y install git dnsmasq
cat << EOF > /etc/dnsmasq.conf
interface=lo0
interface=eth0
dhcp-range=11.12.13.100,11.12.13.150,255.255.255.0,512h
dhcp-option=3
EOF
cat << EOF > /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=none
ONBOOT=yes
TYPE=Ethernet
IPADDR=11.12.13.1
PREFIX=24
IPV4_FAILURE_FATAL=no
DNS1=127.0.0.1
EOF
systemctl disable systemd-resolved
systemctl stop systemd-resolved
rm -f /etc/resolv.conf
echo "nameserver 127.0.0.1" > /etc/resolv.conf
echo "nameserver 1.1.1.1" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
systemctl enable dnsmasq
shutdown -r now
En Ubuntu 18.04 :
hostnamectl set-hostname controller
apt-get update && apt-get -y install python3-pip
pip3 install --upgrade pip
pip3 install ansible
pip3 install ansible-lint
pip3 install netaddr
systemctl disable systemd-resolved
systemctl stop systemd-resolved
rm -f /etc/resolv.conf
echo "nameserver 127.0.0.1" > /etc/resolv.conf
echo "nameserver 1.1.1.1" >> /etc/resolv.conf
apt -y install git dnsmasq
cat << EOF > /etc/dnsmasq.conf
interface=lo0
interface=eth0
dhcp-range=11.12.13.100,11.12.13.150,255.255.255.0,512h
dhcp-option=3
EOF
cat << EOF > /etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: networkd
ethernets:
eth0:
addresses:
- 11.12.13.1/24
nameservers:
addresses: [127.0.0.1, 1.1.1.1]
eth1:
dhcp4: yes
EOF
netplan apply
systemctl restart dnsmasq
systemctl enable dnsmasq
Il est nécessaire de cloner le dépot sur la machine de contrôle.
git clone https://github.com/goffinet/ansible-ccna-lab
cd ansible-ccna-lab/playbooks
Les livres de jeu sont disponibles dans le dossier ansible-ccna-lab/playbooks
et se lancent à partir de ce dossier. On peut aussi les utiliser comme “collection” Ansible : voir Using collections in a Playbook.
On y trouve l’arborescence suivante :
ansible-ccna-lab/playbooks/
├── ansible.cfg --> fichier de configuration par défaut
├── ccna.yml --> livre de jeu de la topologie ccna
├── configs/ --> dossier par défaut des fichiers de configuration
├── demos/ --> livres de jeu de démo / test
├── files/ --> fichiers statiques spécifiques à utiliser avec les livres de jeu
├── gateway.yml --> livre de jeu de la topologie gateway
├── inventories/ --> dossier d'inventaires
├── roles/ -> ../roles --> dossier des rôles utilisés par les livres de jeu
├── router_on_a_stick.yml --> livre de jeu de la topologie router_on_a_stick
├── bipod.yml --> livre de jeu de la topologie bipod
├── switchblock.yml --> livre de jeu de la topologie switchblock
├── tasks/ --> tâches spécifiques à utiliser avec les livres de jeu
├── templates/ --> modèles spécifiques à utiliser avec les livres de jeu
├── tripod.yml --> livre de jeu de la topologie tripod
└── vars --> variables spécifiques à utiliser dans le livre de jeu
Modèle de collection basé sur https://github.com/bcoca/collection.
Le fichier de configuration ansible.cfg
dans le dossier ansible-ccna-lab/playbooks
configure par défaut le comportement de Ansible :
[defaults]
inventory = ./inventories/ccna/hosts
roles_path = ~/.ansible/roles:./roles
host_key_checking = False
retry_files_enabled = False
log_path = ./ansible.log
#forks = 20
strategy = linear
#gathering = explicit
callback_whitelist = profile_tasks
#display_ok_hosts = no
#display_skipped_hosts = no
#[callback_profile_tasks]
#task_output_limit = 100
La section [defaults]
définit différentes variables comportementales du logiciel Ansible utiles à nos exécutions en comparaison aux paramètres par défaut :
inventory
: désigne l’emplacement de l’inventaire par défaut ici ./inventories/ccna/hosts
.roles_path
: désigne les emplacements par défaut des rôles.host_key_checking
: active ou non la vérification des clés SSH, ici désactivée.retry_files_enabled
active ou non la génération de fichier “retry”.log_path
: désigne l’emplacement et le nom du fichier de log.forks
: désigne le nombre d’hôtes à controller en paralèlle (5 par défaut).strategy
: désigne la stratégie “linear” lance chaque tâche sur tous les hôtes concernés par un jeu avant de commencer la tâche suivante alors que la stratégie “free” permet à chaque hôte d’exécuter le jeu jusqu’à la fin aussi vite que possible.gathering
: collecte (“implicit”, par défaut) ou non (“explicit”) les facts. Ici désactivé par défaut.callback_whitelist
: affiche ou non des paramètres de temps (voir la section [callback_profile_tasks]
).display_ok_hosts
: active ou non l’affichage des tâches dont le statut est “OK” (utile pour vérifier l’idempotence).display_skipped_hosts
: active ou non l’affichage des tâches dont le statut est “Skipped” (utile pour vérifier l’idempotence).Les topologies réseau développées sont décrites dans différents inventaires et se configurent avec un livre de jeu du même nom :
Un seul routeur Cisco qui connecte l’Internet et qui offre des services au LAN comme DHCP et RDNSS.
Références :
Diagramme : Topologie CCNA Gateway
Connexion point-à-point entre R1 et R2.
Références :
Diagramme : Topologie CCNA Bipod
Cette topologie maillée à trois routeurs peut être désignée par “tripod”. Elle est la couche “Core” de la topologie CCNA complète.
Trois périphériques IOSv interconnectés entre eux :
Routeur | Interface | Adresse IPv4 | Adresses IPv6 | Description |
---|---|---|---|---|
R1 | G0/0 | 192.168.1.1/24 | FE80::1 , FD00:FD00:FD00:1::1/64 | LAN de R1 |
R1 | G0/2 | 192.168.225.1/24 | FE80::1 | Connexion vers R2 |
R1 | G0/3 | 192.168.226.1/24 | FE80::1 | Connexion vers R3 |
R2 | G0/0 | 192.168.33.1/24 | FE80::2 , FD00:FD00:FD00:2::1/64 | LAN de R2 |
R2 | G0/1 | 192.168.225.2/24 | FE80::2 | Connexion vers R1 |
R2 | G0/3 | 192.168.227.1/24 | FE80::2 | Connexion vers R3 |
R3 | G0/0 | 192.168.65.1/24 | FE80::3 , FD00:FD00:FD00:3::1/64 | LAN de R3 |
R3 | G0/1 | 192.168.226.2/24 | FE80::3 | Connexion vers R1 |
R3 | G0/2 | 192.168.227.2/24 | FE80::3 | Connexion vers R2 |
GigabitEthernet0/0
).On activera les protocoles de routage IPv4 et IPv6 :
Références :
Variante de la topologie Tripod en utilisant un Trunk Vlan entre R1 et SW0 ainsi qu’entre SW0 et SW1.
Diagramme : Topologie variante Router on a Stick
Références :
Cette seconde topologie “switchblock” met en oeuvre des commutateurs. Cette topologie est plus complexe et se connecte à la topologie “tripod”. Elle met en oeuvre les couches “distribution” et “access”.
Références :
VLAN | Ports Access (AS1 et AS2) | plage d’adresse | Passerelle par défaut |
---|---|---|---|
VLAN 10 | g2/0 | 172.16.10.0/24 | 172.16.10.254 |
VLAN 20 | g2/1 | 172.16.20.0/24 | 172.16.10.254 |
VLAN 30 | g2/2 | 172.16.30.0/24 | 172.16.10.254 |
VLAN 40 | g2/3 | 172.16.40.0/24 | 172.16.10.254 |
VLAN 99 | VLAN natif | Management |
PortChannel | ports physiques | Commutateurs |
---|---|---|
po1 | g0/0 ,g1/0 | AS1 - DS1 |
po2 | g0/1 ,g1/1 | AS1 - DS2 |
po3 | g0/2 ,g1/2 | DS1 - DS2 |
po4 | g0/0 ,g1/0 | AS2 - DS2 |
po5 | g0/1 ,g1/1 | AS2 - DS1 |
VLANs | DS1 | DS2 |
---|---|---|
VLANs 1,10,30,99 | root primary | root secondary |
VLANs 20,40 | root secondary | root primary |
Commutateur | Interface | Adresse IPv4 | Adresse(s) IPv6 |
---|---|---|---|
DS1 | VLAN10 | 172.16.10.252/24 | FD00:1AB:10::1/64 |
DS1 | VLAN20 | 172.16.20.252/24 | FD00:1AB:20::1/64 |
DS1 | VLAN30 | 172.16.30.252/24 | FD00:1AB:30::1/64 |
DS1 | VLAN40 | 172.16.40.252/24 | FD00:1AB:40::1/64 |
DS2 | VLAN10 | 172.16.10.253/24 | FD00:1AB:10::2/64 |
DS2 | VLAN20 | 172.16.20.253/24 | FD00:1AB:20::2/64 |
DS2 | VLAN30 | 172.16.30.253/24 | FD00:1AB:30::2/64 |
DS2 | VLAN40 | 172.16.40.253/24 | FD00:1AB:40::2/64 |
Commutateur | Interface | Adresse IPv4 virtuelle | Adresse IPv6 virtuelle | Group | Priorité |
---|---|---|---|---|---|
DS1 | VLAN10 | 172.16.10.254/24 | FE80::d:1/64 | 10⁄16 | 150, prempt |
DS1 | VLAN20 | 172.16.20.254/24 | FE80::d:1/64 | 20⁄26 | default |
DS1 | VLAN30 | 172.16.30.254/24 | FE80::d:1/64 | 30⁄36 | 150, prempt |
DS1 | VLAN40 | 172.16.40.254/24 | FE80::d:1/64 | 40⁄46 | default |
DS2 | VLAN10 | 172.16.10.254/24 | FE80::d:2/64 | 10⁄16 | default |
DS2 | VLAN20 | 172.16.20.254/24 | FE80::d:2/64 | 20⁄26 | 150, prempt |
DS2 | VLAN30 | 172.16.30.254/24 | FE80::d:2/64 | 30⁄36 | default |
DS2 | VLAN40 | 172.16.40.254/24 | FE80::d:2/64 | 40⁄46 | 150, prempt |
Dans l’exercice de laboratoire “Lab répartition de charge avec Rapid Spanning-Tree”, nous avons appris à déployer Rapid Spanning-Tree entre la couche Distribution et la couche Access. Il manque manifestement une sûreté au niveau de la passerelle par défaut que constitue le commutateur de Distribution. Afin d’éviter ce point unique de rupture, on apprendra à configurer et vérifier HSRP. Dans cette topologie une passerelle devient routeur “Active” pour certains VLANs et reste en HSRP “Standby” pour d’autres VLANs et inversément.
On trouvera plus bas les fichiers de configuration qui déploient la solution VLANs, Trunking, Etherchannel, Rapid Spanning-Tree, SVI IPv4 et IPv6 et DHCP. Par rapport à l’exercice de laboratoire “Lab répartition de charge avec Rapid Spanning-Tree”, tout reste identique sauf le paramètre de passerelle.
Cette topologie interconnecte les topologies “tripod” et “switchblock”.
Cette topologie s’utilise dans le cadre de l’exercice ANSIBLE RÉSEAU avec le projet Ansible Networking Workshop Files.
Se rendre dans le dossier des livres de jeu ansible-ccna-lab/playbooks/
:
cd
git clone https://github.com/goffinet/ansible-ccna-lab
cd ansible-ccna-lab/playbooks
Tester la connectivité vers les périphériques :
ansible all -c network_cli -m ping
L’inventaire par défaut est défini comme suit (fichier inventories/ccna/hosts
) et correspond à la topologie ccna (tripod + switchblock) :
[all:vars]
#method=modules # modules or templating not yet implemented
[core]
R1
R2
R3
[distribution]
DS1
DS2
[access]
AS1
AS2
[blocks:children]
distribution
access
[routers:children]
core
[switches:children]
blocks
[cisco:children]
core
blocks
[cisco:vars]
ansible_user=root
ansible_ssh_pass=testtest
ansible_port=22
ansible_connection=network_cli
ansible_network_os=ios
Les configurations sont définies en YAML dans les fichiers de variables d’inventaire (fichier au nom du groupe dans le dossier inventories/ccna/group_vars
et fichier au nom de l’hôte dans le dossier inventories/ccna/host_vars
).
inventories/ccna
├── group_vars
│ ├── all --> protocoles de routage ipv4/ipv6
│ ├── blocks --> variables vlans, switchports et stp mode
│ └── core --> variables routage, rdnss
├── hosts --> fichier d'inventaire, avec des variables génériques
└── host_vars --> variables propres à chaque périphérique
├── AS1
├── AS2
├── DS1
├── DS2
├── R1
├── R2
└── R3
Les livres de jeu (playbooks/
) font appel à des rôles qui trouvent la valeur des variables dans l’inventaire.
Le playbook tripod.yml
configure la topologie tripod :
ansible-playbook tripod.yml -v
Le playbook blocks.yml
configure la topologie switchblock :
ansible-playbook switchblock.yml -v
Le playbook ccna.yml
configure l’ensemble :
ansible-playbook ccna.yml -v
à améliorer
Diagnostic du routage sur R1 :
ansible R1 -m ios_command -a "commands='show ip route'"
Diagnostic à partir des routeurs Core :
ansible core -m ios_command -a "commands='traceroute 192.168.1.1 source GigabitEthernet0/0 probe 1 numeric'"
ansible core -m ios_command -a "commands='traceroute 172.16.10.1 source GigabitEthernet0/0 probe 1 numeric'"
“Être idempotent permet à une tâche définie d’être exécutée une seule fois ou des centaines de fois sans créer un effet contraire sur le système cible, ne provoquant un changement à une seule reprise. En d’autres mots, si un changement est nécessaire pour obtenir le système dans un état désiré, alors le changement est réalisé ; par contre si le périphérique est déjà dans l’état désiré, aucun changement n’intervient. Ce comportement est différent des pratiques de scripts personnalisés et de copier/coller de lignes de commandes. Quand on exécute les mêmes commandes ou scripts sur un même système de manière répétée, le taux d’erreur est souvent élevé.”
Extrait de: Jason Edelman. « Network Automation with Ansible. », O’Reilly Media, 2016.
Attention, Ansible autorise l’idempotence, mais selon le module utilisé, il faudra le manipuler pour atteindre cette exigence de conception.
1/ La section “Why do the config modules always return true” de la “Ansible Network FAQ” explique ceci :
Les modules *_config
d’Ansible Network comparent le texte des commandes que vous spécifiez dans les lignes au texte de la configuration. Si vous utilisez shut
dans la section lines
de la tâche, et que la configuration indique shutdown
, le module retourne changed=true
même si la configuration est déjà correcte. La tâche mettra à jour la configuration à chaque fois qu’elle s’exécutera.
Les commande utilisées avec Ansible pourraient ne pas êtres les mêmes commandes que celles trouvées dans la running_config
: alors, les contrôles entre les lignes ne correspondent pas exactement, même s’ils produisent la même sortie.
2/ Il y a aussi la façon dont le module compare les lignes mises à jour avec la running_config
. Par défaut, le module vérifie chaque ligne, mais il y a d’autres options. La documentation dit ceci à propos de l’argument match
du module :
Instruit le module sur la façon d’effectuer la correspondance du jeu de commandes avec la configuration actuelle du périphérique. Si l’argument match
est valorisé par line
, les commandes sont mises en correspondance ligne par ligne (défaut). Si l’argument match
est valorisé par strict
, les lignes de commande sont mises en correspondance par rapport à la position. Si l’argument match
est valorisé par exact
, les lignes de commande doivent être de même nature. Enfin, si l’argument match
est valorisé par none
, le module ne tentera pas de comparer la configuration source avec la configuration en cours d’exécution sur le périphérique distant.
3/ L’option after
contrôle l’application des changements aux interfaces :
L’ensemble des commandes ordonnées à ajouter à la fin de la pile de commandes si un changement doit être fait. Comme avec l’option before
, cela permet au concepteur du livre de lecture d’ajouter un ensemble de commandes à exécuter après l’ensemble de commandes.
Combinée avec l’option before
, on applique des commandes avant et après que les changements soient faits. Par exemple, on peut définir une réinitialisation en cinq minutes pour éviter une déconnexion à cause d’un problème de configuration, ou écrire les changements dans la ROM (bien que l’on puisse le faire avec l’option save_when
).1
1 Texte original de guzmonne en réponse à la question stackoverflow How can I make my ios_config task idempotent?.
Aussi, l’argument defaults
qu’il sera nécessaire d’activer avec la valeur yes
spécifie s’il faut ou non collecter toutes les valeurs par défaut lors de l’exécution de la configuration du périphérique distant. Lorsqu’il est activé, le module obtient la configuration actuelle en lançant la commande show running-config all
. En effet, des commandes comme no shutdown
ou encore ipv6 enable
ou encore ipv4 routing
et beaucoup n’apparaissent pas avec la commande show running-config
.
Tendre vers des rôles idempotents avec des modules standards.
Usage du filtre jinja2 ipaddr, voir playbooks/ipaddr.yml.
Structure en “collection” Ansible. Using collections in a Playbook.
Rôles à améliorer :
Rôles à créer :
tasks by jinja2 templating
Rôles “immutables” qui agissent sur un modèle de fichier de configuration basé sur des choix d’infrastructure (des variables) et qui sera poussé sur les périphériques par la procédure config replace flash:XXX force
.
“Immutable” roles by templating one config file based on infrastructure choices (variables) and pushed by config replace flash:XXX force
procedure to the devices.
Reporting (role ansible-network.cisco_ios) :