Laboratorio didattico di network automation che realizza un ciclo NetDevOps completo:
- raccolta dell'intento tramite una piccola applicazione web Flask;
- generazione dei dati YAML tramite template Jinja2;
- configurazione della rete con Ansible e Cisco NSO;
- verifica automatica della connettività con Cisco pyATS;
- esecuzione sequenziale di configurazione e test tramite una pipeline CI/CD.
Il progetto deriva dal laboratorio Cisco Develop a NetDevOps System ed è
stato replicato da un'istanza GitLab locale nel repository GitHub
Demone54/network-automation-lab.
Warning
Questo repository applica configurazioni reali ai dispositivi registrati in Cisco NSO. Usarlo soltanto in un laboratorio isolato o su apparati per i quali si dispone di autorizzazione.
Important
Lo stato attuale riproduce volutamente lo scenario di errore finale del lab:
vars/R1.yaml contiene shutdown: null, quindi l'esecuzione del playbook mette
in shutdown GigabitEthernet2 di R1. La pipeline può quindi fallire nella fase
pyATS. Prima del primo avvio leggere la sezione
Controlli obbligatori.
Il repository dimostra come trattare una modifica di rete come una modifica software: i dati vengono versionati, la configurazione è applicata in modo automatico e il risultato viene verificato da test ripetibili.
flowchart LR
U["Utente"] --> F["Flask<br/>app.py"]
F --> J["Template Jinja2"]
J --> V["File YAML<br/>vars/ e tests/"]
V --> A["Ansible<br/>config_routers.yaml"]
A --> N["Cisco NSO"]
N --> R["Router R1, R2 e R3"]
R --> P["pyATS<br/>test di ping"]
P --> C["Esito CI/CD<br/>PASSED o FAILED"]
Il valore didattico non è il singolo ping, ma il closed loop:
modifica dichiarativa -> configurazione -> verifica -> feedback
| Componente | Ruolo |
|---|---|
| Cisco NSO | Orchestrazione, CDB, modello di servizio e applicazione delle configurazioni |
| YANG | Definisce i dati accettati dal servizio intfs |
| Template XML NSO | Converte i dati del servizio in configurazione Cisco IOS |
| Flask | Espone il form web del laboratorio |
| Jinja2 | Genera i file YAML a partire dai dati inseriti nel form |
| Ansible | Invia a NSO l'istanza di servizio e lo stato dell'interfaccia di R1 |
| pyATS/AEtest | Si collega a R1, R2 e R3 ed esegue i test di raggiungibilità |
| GitLab CI | Esegue prima la configurazione e poi i test |
| GitHub | Rende il codice consultabile, clonabile e adatto a collaborazione/portfolio |
.
├── .gitlab-ci.yml
├── ansible.cfg
├── app.py
├── config_routers.yaml
├── inventory.ini
├── group_vars/
│ └── all.yaml
├── nso-packages/
│ └── intfs/
│ ├── package-meta-data.xml
│ ├── load-dir/
│ ├── src/
│ │ └── yang/intfs.yang
│ ├── templates/
│ │ └── intfs-template.xml
│ └── test/
├── templates/
│ ├── R1_interface.j2
│ ├── service.html
│ ├── service.j2
│ └── targets.j2
├── tests/
│ ├── ping_targets.yaml
│ ├── pyats_job_runner.py
│ ├── pyats_pingtest.py
│ └── testbed.yaml
└── vars/
├── R1.yaml
└── service_instance.yaml
app.py: riceve i valori del form e sovrascrive tre file:vars/service_instance.yaml,tests/ping_targets.yamlevars/R1.yaml.templates/service.j2: genera i dati dell'istanza NSOintfs.templates/targets.j2: genera gli IP che pyATS deve raggiungere.templates/R1_interface.j2: genera lo stato amministrativo diGigabitEthernet2di R1.config_routers.yaml: playbook Ansible principale.tests/testbed.yaml: dispositivi, protocollo e credenziali usati da pyATS.tests/pyats_pingtest.py: test AEtest personalizzato per i ping..gitlab-ci.yml: pipeline con stageconfetest.nso-packages/intfs: package NSO del servizio che crea una Loopback/32e la inserisce in OSPF processo 1, area 0.
Note
app.py genera i file YAML, ma non esegue direttamente il playbook
Ansible o pyATS. Questi passaggi devono essere avviati manualmente oppure da
una pipeline.
Per eseguire il progetto è necessario fornire separatamente:
- installazione e licenza Cisco NSO;
- NED Cisco IOS/IOS XE compatibile con i router;
- registrazione in NSO dei dispositivi denominati esattamente
R1,R2eR3; - immagini e topologia dei tre router;
- accesso di rete dal control node a NSO e ai router;
- un GitLab Runner, se si vuole usare la pipeline originale;
- una pipeline GitHub Actions, che al momento non è presente;
- gestione sicura delle credenziali per un ambiente diverso dal lab.
Il repository non crea quindi da solo la topologia Cisco del corso.
I file attuali fanno riferimento a questa rete privata:
| Sistema | Indirizzo/porta | Utilizzo |
|---|---|---|
| Cisco NSO | 172.21.1.10:8083 |
JSON-RPC usato da Ansible |
| R1 | 172.21.1.21 |
Telnet usato da pyATS |
| R2 | 172.21.1.22 |
Telnet usato da pyATS |
| R3 | 172.21.1.23 |
Telnet usato da pyATS |
| Flask | 0.0.0.0:5000 |
Form web |
Questi indirizzi sono validi soltanto nella rete del laboratorio originale.
Ambiente di riferimento:
- Linux o una VM Linux con accesso alla topologia;
- Python 3.9.7, versione usata nel laboratorio;
- Cisco NSO 5.7 o successivo;
- NED Cisco IOS/IOS XE;
- Ansible e collection
cisco.nso; - Cisco pyATS/Genie e plugin Unicon per IOS XE;
- Flask, Jinja2 e PyYAML;
- Git;
- GitLab e GitLab Runner soltanto per la pipeline originale.
Versioni diverse possono funzionare, ma devono essere verificate. Il package
intfs dichiara NSO 5.7 come versione minima.
Clonare il progetto:
git clone https://github.com/Demone54/network-automation-lab.git
cd network-automation-labCreare un ambiente virtuale:
python3.9 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pipInstallare le dipendenze:
python -m pip install ansible Flask Jinja2 PyYAML "pyats[full]"
ansible-galaxy collection install cisco.nsoLa collection cisco.nso non è più inclusa automaticamente nelle versioni
recenti del pacchetto Ansible e deve essere installata esplicitamente.
Verificare:
python --version
ansible --version
pyats version check
ansible-galaxy collection list | grep cisco.nsoSe Ansible non riconosce il nome breve nso_config, usare il Fully Qualified
Collection Name nel playbook:
cisco.nso.nso_config:al posto di:
nso_config:Il package contiene:
intfs.yang: modello del servizio;intfs-template.xml: template di configurazione;src/Makefile: compilazione del modello YANG;load-dir/intfs.fxs: artefatto compilato presente nel lab.
Il file .fxs incluso potrebbe non essere compatibile con una versione diversa
di NSO. È preferibile ricompilarlo nell'ambiente di destinazione.
Esempio per un'istanza NSO locale:
export NCS_RUN_DIR="$HOME/nso-instance"
cp -R nso-packages/intfs "$NCS_RUN_DIR/packages/"
make -C "$NCS_RUN_DIR/packages/intfs/src" clean all
cd "$NCS_RUN_DIR"
ncs --with-package-reload
ncs --statusSe NSO è già in esecuzione, aprire la CLI e ricaricare i package con il comando previsto dalla propria versione:
ncs_cli -C -u adminpackages reload
Il servizio intfs richiede che R1, R2 e R3 siano già presenti nel CDB,
perché il campo device nel modello YANG è un leafref verso i dispositivi
registrati in NSO.
Adattare almeno questi file prima dell'esecuzione:
| File | Dato da modificare |
|---|---|
inventory.ini |
indirizzo e porta del server NSO |
config_routers.yaml |
URL JSON-RPC di NSO, presente in due task |
group_vars/all.yaml |
credenziali NSO e parametri HTTP API |
tests/testbed.yaml |
IP, protocollo e credenziali dei router |
vars/service_instance.yaml |
servizio, device, Loopback e IP da creare |
vars/R1.yaml |
stato di GigabitEthernet2 di R1 |
tests/ping_targets.yaml |
IP da verificare da ciascun router |
Le chiavi di vars/service_instance.yaml devono corrispondere al modello YANG:
tailf-ncs:services:
intfs:intfs:
- name: srv-demo
device: R2
intf_number: 100
ip_add: <IP_LOOPBACK_LIBERO>L'indirizzo in tests/ping_targets.yaml deve essere lo stesso creato dal
servizio, altrimenti il test di raggiungibilità fallirà.
Nel modello usato dal lab:
shutdown:
- nullequivale al comando IOS:
interface GigabitEthernet2
shutdown
mentre:
shutdown:
- noequivale a:
interface GigabitEthernet2
no shutdown
Per una prima esecuzione non distruttiva, verificare che R1 non venga isolato involontariamente.
group_vars/all.yaml e tests/testbed.yaml contengono credenziali dimostrative
in chiaro. Sostituirle prima di usare una rete diversa dal laboratorio.
Verificare che:
- l'IP della nuova Loopback non sia già utilizzato;
- la Loopback sia annunciata da OSPF;
- il control node raggiunga NSO e tutti e tre i router;
- l'IP in
ping_targets.yamlcorrisponda alla Loopback creata.
Nel laboratorio preconfigurato viene usato sync-to, che invia ai router la
configurazione presente nel CDB. In un ambiente appena creato potrebbe invece
essere necessario sync-from, che importa in NSO la configurazione dei router.
Non eseguire sync-to senza aver verificato il diff, perché può sovrascrivere
la configurazione dei dispositivi.
cd "$HOME/nso-instance"
ncs
ncs --status
ncs_cli -C -u adminSe ncs --status restituisce connection refused, il processo NSO locale non è
avviato oppure il comando viene eseguito dalla directory errata.
Nel lab preconfigurato, la sincronizzazione mostrata dal corso è:
devices device R1 sync-to
devices device R2 sync-to
devices device R3 sync-to
Esempio equivalente al primo esercizio:
config
services intfs srv1 device R1 intf_number 10 ip_add 10.1.1.1
top
commit
Il template genera indicativamente:
interface Loopback10
ip address 10.1.1.1 255.255.255.255
ip ospf 1 area 0
Per eliminare l'istanza:
config
no services intfs srv1
commit
Avviare l'applicazione dalla radice del repository:
python app.pyAprire:
http://127.0.0.1:5000
oppure l'indirizzo della VM sulla porta 5000.
Il form richiede:
- nome dell'istanza;
- device
R1,R2oR3; - numero della Loopback;
- indirizzo IP;
- eventuale shutdown di
GigabitEthernet2su R1.
Dopo aver premuto Connect, controllare le modifiche:
git diff -- vars/service_instance.yaml tests/ping_targets.yaml vars/R1.yamlCaution
L'applicazione usa il server di sviluppo Flask con debug=True e ascolta su
0.0.0.0. Non esporla su Internet e non usarla in produzione.
Controllare prima sintassi e file interessati:
ansible-playbook --syntax-check config_routers.yaml
git diff -- config_routers.yaml group_vars/all.yaml vars/Eseguire:
ansible-playbook config_routers.yamlIl playbook:
- carica
vars/service_instance.yamlnella variabileserv; - carica
vars/R1.yamlnella variabileR1_intf; - crea o aggiorna l'istanza
intfstramite NSO; - applica a R1 lo stato di
GigabitEthernet2.
Validare il testbed:
pyats validate testbed tests/testbed.yamlEseguire il job:
cd tests
pyats run job pyats_job_runner.py \
--testbed testbed.yaml \
--targets ping_targets.yaml
cd ..Il test:
- carica
testbed.yaml; - si collega a R1, R2 e R3;
- legge per ogni router gli IP presenti in
ping_targets.yaml; - esegue i ping;
- interpreta l'output IOS;
- disconnette i dispositivi;
- restituisce un esito a pyATS e alla pipeline.
Il test è personalizzato per output Cisco IOS contenente una riga simile a:
Success rate is 100 percent
Comandi utili:
show ip interface brief
show ip route ospf
show ip ospf neighbor
ping <IP_LOOPBACK>
Verificare che:
- la Loopback esista sul router scelto;
- l'indirizzo sia corretto;
- OSPF annunci la rotta agli altri router;
GigabitEthernet2di R1 sia nello stato previsto;- tutti i ping attesi abbiano successo.
- Esplorare il CDB e il servizio
intfsin NSO. - Creare e rimuovere una Loopback dalla CLI NSO.
- Creare un'istanza
intfscon Ansible. - Spostare i dati del servizio in
vars/service_instance.yaml. - Eseguire pyATS con un IP inesistente e osservare il fallimento.
- Creare la Loopback corrispondente e osservare il test superato.
- Portare
GigabitEthernet2di R1 in shutdown e osservare la perdita di adiacenza/connettività. - Ripristinare
no shutdowne verificare il recupero. - Generare i tre file YAML con Flask e Jinja2.
- Versionare le modifiche ed eseguire configurazione e test in CI/CD.
Il file .gitlab-ci.yml contiene due stage:
conf -> test
topology_confesegueansible-playbook config_routers.yaml;test_connectentra nella cartellatestsed esegue il job pyATS.
Un fallimento nello stage conf impedisce normalmente l'avvio del test. Un
fallimento nello stage test indica che la configurazione è stata applicata,
ma la connettività attesa non è stata verificata.
Per usare la pipeline originale è necessario un GitLab Runner che:
- abbia Ansible, la collection
cisco.nsoe pyATS installati; - possa raggiungere la rete privata del laboratorio;
- disponga delle credenziali tramite variabili CI/CD protette;
- sia autorizzato ad applicare configurazioni ai dispositivi.
La presenza di .gitlab-ci.yml in GitHub non avvia GitHub Actions.
GitHub riconosce i workflow soltanto sotto .github/workflows/.
Per migrare la pipeline su GitHub Actions è consigliato un runner self-hosted
all'interno del laboratorio. Un runner GitHub pubblico non può raggiungere
direttamente gli indirizzi privati 172.21.1.0/24 e non deve contenere
credenziali hardcoded.
Prima di condividere o riutilizzare il progetto:
- rimuovere password, token e indirizzi sensibili dalla cronologia Git;
- usare Ansible Vault, un secret manager o i secret della piattaforma CI;
- sostituire Telnet con SSH quando l'ambiente lo permette;
- disattivare il debug Flask;
- limitare l'ascolto web a un'interfaccia affidabile;
- proteggere e isolare i runner CI;
- eseguire backup e diff NSO prima delle modifiche;
- non usare
sync-toalla cieca; - non pubblicare package, immagini o materiali per i quali non si possiedono i diritti di redistribuzione.
Le credenziali admin/admin, cisco/cisco e root/cisco123 visibili nel
materiale originale sono valori dimostrativi del laboratorio e non devono
essere riutilizzati.
Entrare nella directory dell'istanza NSO e avviare ncs:
cd "$HOME/nso-instance"
ncs
ncs --statusControllare compilazione, percorso del package e reload:
make -C "$NCS_RUN_DIR/packages/intfs/src" clean all
ncs_cli -C -u adminQuindi eseguire packages reload o il comando equivalente della versione NSO
installata.
ansible-galaxy collection install cisco.nsoUsare quindi cisco.nso.nso_config nel playbook.
Verificare:
- URL
http://<NSO>:<PORTA>/jsonrpc; - porta HTTP API configurata in
ncs.conf; - credenziali;
- firewall e routing;
- coerenza tra
inventory.ini,group_vars/all.yamleconfig_routers.yaml.
Verificare tests/testbed.yaml, protocollo, IP, credenziali e raggiungibilità:
pyats validate testbed tests/testbed.yamlControllare prima:
vars/R1.yaml:shutdown: nullpuò isolare R1;tests/ping_targets.yaml: gli IP devono coincidere con quelli creati;- stato OSPF e route;
- presenza della Loopback sul router;
- output dettagliato del job
test_connect.
Nel commit finale del laboratorio il fallimento è intenzionale.
- Creare un fork del repository.
- Creare un branch descrittivo.
- Non inserire credenziali reali.
- Verificare YAML, playbook e test.
- Aprire una pull request spiegando ambiente, topologia e risultati.
Miglioramenti utili:
- aggiungere
requirements.txtopyproject.toml; - aggiungere
.gitignoreper__pycache__, ambienti virtuali e log pyATS; - spostare credenziali e indirizzi in variabili sicure;
- usare il FQCN
cisco.nso.nso_config; - aggiungere validazione del form Flask;
- aggiungere test unitari;
- creare un workflow GitHub Actions per controlli statici;
- usare un runner self-hosted soltanto per i test che richiedono il lab.
Questo repository non contiene attualmente un file LICENSE. Un repository
pubblico è consultabile, ma non diventa automaticamente open source.
Il progetto è pubblicato a scopo didattico e fa riferimento a tecnologie e materiali Cisco. Cisco, Cisco NSO, IOS XE, pyATS e i relativi marchi appartengono ai rispettivi titolari. Prima di aggiungere una licenza o redistribuire materiale derivato dal corso, verificare di possederne i diritti.
