💾

Laboratorijska vežba – Modul 24 – Automatizacija dva VyOS rutera Python-om, Netmiko-om, REST API-jem i Ansible-om

Cilj vežbe

Pripremiti laboratoriju sa dva VyOS rutera i Ubuntu Server kontrolnim računarom, pa sa kontrolnog računara:

  • automatski proveriti dostupnost uređaja,
  • prikupiti izlaze show komandi,
  • napraviti backup konfiguracija sa poređenjem verzija,
  • primeniti standardnu konfiguraciju Netmiko-om,
  • pročitati konfiguraciju preko REST API-ja,
  • uraditi isto Ansible-om uz proveru idempotentnosti.

Kredencijali se nikada ne upisuju u kod.

Napomena o legalnosti i ovlašćenju: Vežba koristi isključivo besplatan softver otvorenog koda (VyOS, Ubuntu Server, Python, Netmiko, Ansible) i uređaje koje ste sami napravili. Iste skripte pokrećite nad drugim uređajima samo ako ih administrirate ili imate pisano ovlašćenje.

Potrebno predznanje

  • Kompletna teorija ovog modula (Teme 1–15).
  • Rad u Linux terminalu, SSH i UFW (Modul 18); VirtualBox i Host-only mreža (Modul 21); VyOS configure/set/commit/save (Modul 22).

Potrebni programi

  • VirtualBox (Modul 21).
  • Ubuntu Server VM iz Modula 18 (24.04 LTS ili novija), sa pristupom internetu preko NAT adaptera radi instalacije paketa.
  • VyOS ISO image — besplatni rolling (ili Stream) image sa zvaničnog sajta vyos.io, isti kao u Modulu 22.

Potrebni resursi

VMvCPURAMDisk
Ubuntu Server (kontrolni računar)1–22 GBpostojeći
VYOS111 GB4 GB
VYOS211 GB4 GB

Preporuka: najmanje 8 GB RAM-a na host računaru.

Topologija

            VirtualBox Host-only mreža 192.168.56.0/24
   ------------------------------------------------------------
        |                      |                      |
   UBUNTU-SRV             VYOS1 (eth0)           VYOS2 (eth0)
   192.168.56.20          192.168.56.11          192.168.56.12
   (Python, Netmiko,      SSH, HTTPS API         SSH
    Ansible)
        |
     NAT adapter → internet (apt, pip)

Plan adresiranja

VMInterfejsAdresaNamena
UBUNTU-SRVHost-only192.168.56.20/24Kontrolni računar (Modul 18)
VYOS1eth0 (Host-only)192.168.56.11/24Upravljanje
VYOS1dum010.255.255.11/32Loopback (dodaje se skriptom)
VYOS2eth0 (Host-only)192.168.56.12/24Upravljanje
VYOS2dum010.255.255.12/32Loopback (dodaje se skriptom)

Ako ste u Modulu 21 Ubuntu Server VM prebacili u Internal Network segmente, vratite snapshot iz Modula 18 ili ponovo uključite Host-only adapter sa adresom 192.168.56.20.

Struktura projekta na kontrolnom računaru

~/netauto-lab/
├── venv/                      (virtuelno Python okruženje)
├── .gitignore
├── uredjaji.yaml              (spisak uređaja — bez lozinki)
├── zajednicko.py              (učitavanje uređaja i kredencijala)
├── provera_dostupnosti.py
├── prikupi_show.py
├── backup.py
├── konfigurisi.py
├── api_citanje.py
└── ansible/
    ├── ansible.cfg
    ├── inventory.ini
    ├── group_vars/vyos/vars.yml
    ├── group_vars/vyos/vault.yml   (šifrovano)
    ├── prikupi.yml
    └── standard.yml

Koraci vežbe

Korak 1 – Instalacija VyOS VM-ova

Za svaki ruter (VYOS1, pa VYOS2):

  1. U VirtualBox-u napravite novu VM: tip Linux, verzija Debian (64-bit), resursi iz tabele. Adapter 1: Host-only (ista mreža kao Ubuntu Server VM).
  2. Pokrenite VM sa VyOS ISO image-om i prijavite se kao vyos / vyos (live režim).
  3. Instalirajte sistem na disk komandom install image i prihvatite ponuđene odgovore; kada se traži, postavite svoju lozinku za korisnika vyos.
  4. Posle završetka isključite VM, uklonite ISO iz virtuelnog optičkog uređaja i ponovo je pokrenite.

Korak 2 – Osnovna konfiguracija i nalog za automatizaciju

Na VYOS1 (konzola u VirtualBox-u):

configure
set system host-name VYOS1
set interfaces ethernet eth0 address 192.168.56.11/24
set service ssh
set system login user netauto authentication plaintext-password '<vaša lozinka za automatizaciju>'
commit
save
exit

Na VYOS2 isto, sa host-name VYOS2 i adresom 192.168.56.12/24, i istom lozinkom za nalog netauto.

Sa Ubuntu Server VM-a proverite i prihvatite SSH ključeve oba rutera (odgovorite yes na pitanje o ključu):

ssh netauto@192.168.56.11
exit
ssh netauto@192.168.56.12
exit

Napravite snapshot oba VyOS VM-a sa nazivom Pre-automatizacije (Modul 21).

VyOS nalozi imaju administratorska prava. U produkciji bi nalog za automatizaciju imao samo potrebna prava, preko centralne autentifikacije (TACACS+/RADIUS, Modul 16).

Korak 3 – Python okruženje

Na Ubuntu Server VM-u:

sudo apt update
sudo apt install -y python3-venv python3-pip git
mkdir -p ~/netauto-lab && cd ~/netauto-lab
python3 -m venv venv
source venv/bin/activate
pip install netmiko pyyaml requests ansible

Provera:

python -c "import netmiko, yaml, requests; print('Netmiko', netmiko.__version__)"
ansible --version
ansible-galaxy collection list | grep -E "vyos|netcommon"

Treba da se vide kolekcije vyos.vyos i ansible.netcommon. Prompt terminala počinje sa (venv) — kad god otvorite novi terminal, pre rada izvršite cd ~/netauto-lab && source venv/bin/activate.

Korak 4 – Kredencijali i .gitignore

Kredencijali se postavljaju kao promenljive okruženja samo u tekućoj sesiji terminala:

export NET_USER=netauto
read -s -p "Lozinka za netauto: " NET_PASS; echo; export NET_PASS

Napravite .gitignore i inicijalizujte Git repozitorijum:

cat > .gitignore << 'EOF'
venv/
backup/
izlazi/
*.json
.env
EOF
git init

Backup konfiguracija ne ide u javni repozitorijum (sadrži hash-eve lozinki), pa je isključen; u firmi bi se čuvao u posebnom, zaštićenom repozitorijumu.

Korak 5 – Spisak uređaja i zajednički modul

uredjaji.yaml:

---
uredjaji:
  - ime: VYOS1
    host: 192.168.56.11
    device_type: vyos
    loopback: 10.255.255.11/32
  - ime: VYOS2
    host: 192.168.56.12
    device_type: vyos
    loopback: 10.255.255.12/32

zajednicko.py:

import os

import yaml


def ucitaj_uredjaje(putanja="uredjaji.yaml"):
    with open(putanja, encoding="utf-8") as f:
        return yaml.safe_load(f)["uredjaji"]


def parametri_veze(uredjaj):
    return {
        "device_type": uredjaj["device_type"],
        "host": uredjaj["host"],
        "username": os.environ["NET_USER"],
        "password": os.environ["NET_PASS"],
    }

Korak 6 – Provera dostupnosti

provera_dostupnosti.py:

import json
import socket
import subprocess
from datetime import datetime

from zajednicko import ucitaj_uredjaje


def ping(ip):
    rezultat = subprocess.run(["ping", "-c", "2", "-W", "1", ip], capture_output=True)
    return rezultat.returncode == 0


def port_otvoren(ip, port=22, timeout=2):
    try:
        with socket.create_connection((ip, port), timeout=timeout):
            return True
    except OSError:
        return False


izvestaj = {"vreme": datetime.now().isoformat(timespec="seconds"), "uredjaji": []}
for u in ucitaj_uredjaje():
    stanje = {"ime": u["ime"], "host": u["host"], "ping": ping(u["host"]), "ssh": port_otvoren(u["host"])}
    izvestaj["uredjaji"].append(stanje)
    print(f"{u['ime']:6} ping={'OK' if stanje['ping'] else 'NE'}  ssh={'OK' if stanje['ssh'] else 'NE'}")

with open("dostupnost.json", "w", encoding="utf-8") as f:
    json.dump(izvestaj, f, indent=2, ensure_ascii=False)

Pokrenite python provera_dostupnosti.py i otvorite dostupnost.json.

Korak 7 – Prikupljanje show komandi

prikupi_show.py:

from datetime import datetime
from pathlib import Path

from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutException

from zajednicko import parametri_veze, ucitaj_uredjaje

KOMANDE = ["show version", "show interfaces", "show ip route"]

folder = Path("izlazi") / datetime.now().strftime("%Y-%m-%d_%H%M")
folder.mkdir(parents=True, exist_ok=True)

for u in ucitaj_uredjaje():
    try:
        with ConnectHandler(**parametri_veze(u)) as veza:
            for komanda in KOMANDE:
                izlaz = veza.send_command(komanda)
                fajl = folder / f"{u['ime']}_{komanda.replace(' ', '_')}.txt"
                fajl.write_text(izlaz, encoding="utf-8")
                if komanda == "show version":
                    for red in izlaz.splitlines():
                        if red.startswith("Version:"):
                            print(f"{u['ime']}: {red.split(':', 1)[1].strip()}")
        print(f"{u['ime']}: prikupljeno {len(KOMANDE)} komandi u {folder}")
    except (NetmikoTimeoutException, NetmikoAuthenticationException) as greska:
        print(f"{u['ime']}: GREŠKA — {type(greska).__name__}")

Pokrenite skriptu i pregledajte fajlove u folderu izlazi/.

Korak 8 – Backup konfiguracija sa poređenjem verzija

backup.py:

import difflib
from datetime import datetime
from pathlib import Path

from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutException

from zajednicko import parametri_veze, ucitaj_uredjaje

folder = Path("backup")
folder.mkdir(mode=0o700, exist_ok=True)
oznaka = datetime.now().strftime("%Y-%m-%d_%H%M%S")
neuspesni = []

for u in ucitaj_uredjaje():
    prethodni = sorted(folder.glob(f"{u['ime']}_*.cfg"))
    try:
        with ConnectHandler(**parametri_veze(u)) as veza:
            konfiguracija = veza.send_command("show configuration commands")
    except (NetmikoTimeoutException, NetmikoAuthenticationException) as greska:
        neuspesni.append(u["ime"])
        print(f"{u['ime']}: BACKUP NIJE USPEO — {type(greska).__name__}")
        continue

    novi = folder / f"{u['ime']}_{oznaka}.cfg"
    novi.write_text(konfiguracija + "\n", encoding="utf-8")
    print(f"{u['ime']}: sačuvano u {novi}")

    if prethodni:
        stari_redovi = prethodni[-1].read_text(encoding="utf-8").splitlines()
        razlike = list(difflib.unified_diff(stari_redovi, konfiguracija.splitlines(),
                                            prethodni[-1].name, novi.name, lineterm=""))
        print("\n".join(razlike) if razlike else "  (nema izmena u odnosu na prethodni backup)")

if neuspesni:
    raise SystemExit(f"Neuspešan backup: {', '.join(neuspesni)}")
  1. Pokrenite python backup.py — nastaju prvi backup-i.
  2. Ručno se prijavite na VYOS2 (ssh netauto@192.168.56.12) i dodajte opis: configure, set interfaces ethernet eth0 description 'RUCNA-IZMENA', commit, save, exit, exit.
  3. Ponovo pokrenite python backup.py — za VYOS2 skripta prikazuje razliku (red sa +), a za VYOS1 „nema izmena".

Korak 9 – Primena standardne konfiguracije Netmiko-om

konfigurisi.py:

from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutException

from zajednicko import parametri_veze, ucitaj_uredjaje

for u in ucitaj_uredjaje():
    komande = [
        "set system login banner pre-login 'Samo za ovlascene korisnike'",
        "set interfaces ethernet eth0 description 'MGMT-HOSTONLY'",
        f"set interfaces dummy dum0 address {u['loopback']}",
        "set interfaces dummy dum0 description 'LOOPBACK-UPRAVLJANJE'",
    ]
    try:
        with ConnectHandler(**parametri_veze(u)) as veza:
            veza.send_config_set(komande)
            veza.commit(comment="Standard iz skripte konfigurisi.py")
            veza.save_config()
            provera = veza.send_command("show interfaces")
        print(f"=== {u['ime']} ===\n{provera}")
    except (NetmikoTimeoutException, NetmikoAuthenticationException, ValueError) as greska:
        print(f"{u['ime']}: IZMENA NIJE PRIMENJENA — {greska}")

Pokrenite skriptu. U izlazu show interfaces proverite adresu i opis dum0 i opis eth0 na oba rutera. Zatim pokrenite python backup.py i pogledajte razlike — opis RUCNA-IZMENA na VYOS2 zamenjen je standardnim.

Korak 10 – Čitanje konfiguracije preko REST API-ja

Na VYOS1 (SSH ili konzola) uključite HTTP API sa ključem koji sami izmislite:

configure
set service https api keys id automatizacija key '<vaš API ključ>'
commit
save
exit

Ako commit prođe, ali API na zahteve ne odgovara ili vraća grešku 404, u novijim rolling verzijama VyOS-a REST pristup se uključuje i posebnom komandom set service https api rest (pa ponovo commit i save).

Na Ubuntu Server VM-u postavite ključ kao promenljivu okruženja (read -s -p "API ključ: " VYOS_API_KEY; echo; export VYOS_API_KEY) i napravite api_citanje.py:

import json
import os

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

odgovor = requests.post(
    "https://192.168.56.11/retrieve",
    data={
        "data": json.dumps({"op": "showConfig", "path": ["interfaces"]}),
        "key": os.environ["VYOS_API_KEY"],
    },
    verify=False,  # samo u izolovanoj laboratoriji: samopotpisan sertifikat
    timeout=10,
)
odgovor.raise_for_status()
rezultat = odgovor.json()
if not rezultat["success"]:
    raise SystemExit(f"API greška: {rezultat['error']}")

for tip, interfejsi in rezultat["data"].items():
    for ime, podesavanja in interfejsi.items():
        adrese = podesavanja.get("address", [])
        if isinstance(adrese, str):
            adrese = [adrese]
        print(f"{tip:9} {ime:5} {', '.join(adrese):20} {podesavanja.get('description', '')}")

Pokrenite skriptu. Uporedite ovaj rezultat (strukturisani podaci) sa tekstualnim izlazom show interfaces iz Koraka 9. Probajte i pogrešan ključ — rezultat je HTTP greška 401 ili 403.

Korak 11 – Ansible: inventory, Vault i prvi playbook

mkdir -p ~/netauto-lab/ansible/group_vars/vyos && cd ~/netauto-lab/ansible

ansible.cfg:

[defaults]
inventory = inventory.ini

[persistent_connection]
command_timeout = 30

inventory.ini:

[vyos]
vyos1 ansible_host=192.168.56.11
vyos2 ansible_host=192.168.56.12

[vyos:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=vyos.vyos.vyos
ansible_user=netauto

group_vars/vyos/vars.yml:

---
ansible_password: "{{ vault_lozinka }}"

Šifrovani fajl sa lozinkom:

ansible-vault create group_vars/vyos/vault.yml

Postavite lozinku za Vault, a u editoru koji se otvori upišite jedan red vault_lozinka: <lozinka naloga netauto> i sačuvajte. Proverite da je fajl šifrovan: cat group_vars/vyos/vault.yml prikazuje samo $ANSIBLE_VAULT;1.1;AES256 i niz brojeva.

prikupi.yml:

---
- name: Prikupljanje podataka sa VyOS rutera
  hosts: vyos
  gather_facts: false
  tasks:
    - name: Verzija softvera
      vyos.vyos.vyos_command:
        commands:
          - show version
      register: verzija

    - name: Prikaz prva tri reda
      ansible.builtin.debug:
        msg: "{{ verzija.stdout_lines[0][:3] }}"
ansible-inventory --graph
ansible-playbook prikupi.yml --ask-vault-pass

Korak 12 – Ansible: standardna konfiguracija i idempotentnost

standard.yml:

---
- name: Standardna konfiguracija VyOS rutera
  hosts: vyos
  gather_facts: false
  tasks:
    - name: Vremenska zona i poruka posle prijave
      vyos.vyos.vyos_config:
        lines:
          - set system time-zone 'Europe/Belgrade'
          - set system login banner post-login 'Pristup se evidentira'
        save: true
  1. Suvi rad: ansible-playbook standard.yml --ask-vault-pass --check --diff — prikazuje šta bi se promenilo, bez izmena.
  2. Primena: ansible-playbook standard.yml --ask-vault-pass — u PLAY RECAP je changed=1 za oba rutera.
  3. Ponovo ista komanda — sada changed=0: uređaji su već u željenom stanju.
  4. Na VYOS1 ručno obrišite poruku (configure, delete system login banner post-login, commit, save) i pokrenite playbook samo za taj ruter: ansible-playbook standard.yml --ask-vault-pass --limit vyos1 — odstupanje je otkriveno i ispravljeno (changed=1 samo za vyos1).

Korak 13 – Dijagnostička vežba: ruter odgovara na ping, a skripte ne rade

  1. Na konzoli VYOS2 (u VirtualBox-u, ne preko SSH-a) isključite SSH servis:
configure
delete service ssh
commit
save
exit
  1. Na kontrolnom računaru pokrenite python backup.py. VYOS1 uspeva, a za VYOS2 skripta prijavljuje grešku i završava se porukom „Neuspešan backup: VYOS2".
  2. Utvrdite uzrok sistematski, bez nagađanja: koju proveru prvo pokrećete, šta ona pokazuje, i šta iz toga zaključujete?
  3. Ispravite problem na konzoli VYOS2 i potvrdite da i provera_dostupnosti.py i backup.py ponovo uspevaju za oba rutera.

Komande za proveru

KomandaŠta proverava
python provera_dostupnosti.pyPing i SSH dostupnost svih uređaja iz uredjaji.yaml
ls -l izlazi/*/ backup/Da li su izlazi i backup-i sačuvani
show interfaces (VyOS)Adrese i opisi interfejsa posle izmena
show configuration commands | match banner (VyOS)Da li je banner primenjen
ansible-inventory --graphStruktura inventory-ja
ansible-playbook ... --check --diffŠta bi playbook promenio
grep -rn "<deo lozinke>" ~/netauto-lab --exclude-dir=venvDa lozinka nije nigde upisana kao tekst

Najčešće greške

  • KeyError: 'NET_PASS' — promenljive okruženja nisu postavljene u ovom terminalu (Korak 4).
  • ModuleNotFoundError: No module named 'netmiko' — virtuelno okruženje nije aktivirano (source venv/bin/activate).
  • NetmikoAuthenticationException — pogrešna lozinka ili nalog netauto nije napravljen na tom ruteru.
  • Ansible greška o nepoznatom ključu hosta — ruter nije bar jednom otvoren ručnim ssh pristupom (Korak 2), ili je VM ponovo instaliran pa ima novi ključ (ssh-keygen -R 192.168.56.11).
  • Ansible svaki put prijavljuje changed — linija u lines se ne poklapa sa zapisom u show configuration commands.
  • YAML greška — tab umesto razmaka ili pomereno uvlačenje.

Postupak rešavanja problema

  1. Mrežna dostupnost: ping sa kontrolnog računara.
  2. Servis: TCP port 22 (provera_dostupnosti.py ili nc -zv 192.168.56.12 22).
  3. Autentifikacija: ručni ssh netauto@<adresa> sa istom lozinkom.
  4. Okruženje skripte: aktivirano virtuelno okruženje, postavljene promenljive okruženja.
  5. Logika skripte: poruka o grešci i red u kodu na koji ukazuje.

Završna provera

  • [ ] Oba VyOS rutera su instalirana, dostupna preko SSH-a i imaju nalog netauto.
  • [ ] dostupnost.json sadrži oba rutera sa ping i ssh = true.
  • [ ] U folderu izlazi/ postoje izlazi tri komande za oba rutera.
  • [ ] Postoje najmanje dva backup-a po ruteru, a razlika posle ručne izmene je prikazana.
  • [ ] Standardna konfiguracija (banner, opisi, dum0) je primenjena i sačuvana na oba rutera.
  • [ ] Konfiguracija interfejsa je pročitana preko REST API-ja.
  • [ ] Ansible playbook drugi put prijavljuje changed=0.
  • [ ] Nijedan fajl u projektu ne sadrži lozinku ili API ključ kao običan tekst.
  • [ ] Greška iz Koraka 13 je pronađena i ispravljena.

Dodatni izazov

  1. Automatsko noćno pokretanje: podesite cron da backup.py pokreće svake noći u 2:00. Kredencijale čitajte iz fajla ~/.netauto.env sa pravima 600 (samo vlasnik), koji skripta za pokretanje učita pre Python-a. Obrazložite zašto je ovo prihvatljivo u laboratoriji, a šta bi se koristilo u produkciji.
  2. Bezbedan povratak: na VYOS1 (preko konzole) primenite izmenu komandom commit-confirm 2 (umesto commit), ne izvršavajte save i nemojte je potvrditi komandom confirm. Posmatrajte šta se dešava posle dva minuta i objasnite kako ovo štiti od izmene koja preseca upravljački pristup.
  3. Paralelno izvršavanje: prepravite prikupi_show.py tako da uređaje obrađuje paralelno pomoću concurrent.futures.ThreadPoolExecutor.

Rešenje

Kompletno rešenje dostupno je u /resenja/modul-24/04-laboratorijska-vezba-resenje.md.

📖 Prikaži rešenje

Rešenje laboratorijske vežbe – Modul 24

Tačne verzije softvera, MAC adrese i vremena u izlazima razlikuju se od instalacije do instalacije. Bitni su struktura izlaza i zaključci.

Korak 3 – Očekivana provera okruženja

(venv) admin@ubuntu-srv:~/netauto-lab$ python -c "import netmiko, yaml, requests; print('Netmiko', netmiko.__version__)"
Netmiko 4.x.x
(venv) admin@ubuntu-srv:~/netauto-lab$ ansible-galaxy collection list | grep -E "vyos|netcommon"
ansible.netcommon                        x.y.z
vyos.vyos                                x.y.z

Korak 6 – Provera dostupnosti

VYOS1  ping=OK  ssh=OK
VYOS2  ping=OK  ssh=OK

dostupnost.json:

{
  "vreme": "2026-09-25T10:15:02",
  "uredjaji": [
    {"ime": "VYOS1", "host": "192.168.56.11", "ping": true, "ssh": true},
    {"ime": "VYOS2", "host": "192.168.56.12", "ping": true, "ssh": true}
  ]
}

Korak 7 – Prikupljanje show komandi

VYOS1: VyOS 1.5-rolling-...
VYOS1: prikupljeno 3 komandi u izlazi/2026-09-25_1016
VYOS2: VyOS 1.5-rolling-...
VYOS2: prikupljeno 3 komandi u izlazi/2026-09-25_1016

U folderu je šest fajlova: VYOS1_show_version.txt, VYOS1_show_interfaces.txt, VYOS1_show_ip_route.txt i isti za VYOS2. Tabela ruta sadrži samo direktno povezanu mrežu C>* 192.168.56.0/24 is directly connected, eth0.

Korak 8 – Backup i razlike

Prvo pokretanje:

VYOS1: sačuvano u backup/VYOS1_2026-09-25_101700.cfg
VYOS2: sačuvano u backup/VYOS2_2026-09-25_101700.cfg

Drugo pokretanje, posle ručne izmene na VYOS2:

VYOS1: sačuvano u backup/VYOS1_2026-09-25_102030.cfg
  (nema izmena u odnosu na prethodni backup)
VYOS2: sačuvano u backup/VYOS2_2026-09-25_102030.cfg
--- VYOS2_2026-09-25_101700.cfg
+++ VYOS2_2026-09-25_102030.cfg
@@ -1,4 +1,5 @@
 set interfaces ethernet eth0 address '192.168.56.12/24'
+set interfaces ethernet eth0 description 'RUCNA-IZMENA'
 set interfaces ethernet eth0 hw-id '08:00:27:xx:xx:xx'
 ...

Zašto je folder backup/ napravljen sa pravima 0o700: show configuration commands sadrži i encrypted-password hash-eve naloga; backup je poverljiv dokument (Modul 23).

Korak 9 – Standardna konfiguracija

Skraćen izlaz za VYOS1:

=== VYOS1 ===
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface    IP Address         MAC                VRF      MTU  S/L    Description
-----------  -----------------  -----------------  -------  ---  -----  --------------------
dum0         10.255.255.11/32   xx:xx:xx:xx:xx:xx  default  1500 u/u    LOOPBACK-UPRAVLJANJE
eth0         192.168.56.11/24   08:00:27:xx:xx:xx  default  1500 u/u    MGMT-HOSTONLY
lo           127.0.0.1/8        00:00:00:00:00:00  default  65536 u/u

Naredni backup.py za VYOS2 prikazuje zamenu 'RUCNA-IZMENA' sa 'MGMT-HOSTONLY' i nove redove za dum0 i banner. Za VYOS1 prikazuje samo nove redove. Na VYOS-u je i commit komentar vidljiv u istoriji izmena: show system commit.

Korak 10 – REST API

dummy     dum0  10.255.255.11/32     LOOPBACK-UPRAVLJANJE
ethernet  eth0  192.168.56.11/24     MGMT-HOSTONLY
loopback  lo

Sa pogrešnim ključem raise_for_status() prekida skriptu HTTP greškom (401 ili 403, zavisno od verzije VyOS-a). Upit se ne izvršava, a ključ nije otkriven. Razlika u odnosu na Korak 9: API vraća rečnike po tipu i nazivu interfejsa, pa skripta ne mora da parsira tekstualnu tabelu.

Korak 11 – Ansible prikupljanje

TASK [Prikaz prva tri reda] ****************************************
ok: [vyos1] => {
    "msg": [
        "Version:          VyOS 1.5-rolling-...",
        "Release train:    current",
        "Release flavor:   generic"
    ]
}
ok: [vyos2] => { ... }

PLAY RECAP *********************************************************
vyos1  : ok=2  changed=0  unreachable=0  failed=0  skipped=0
vyos2  : ok=2  changed=0  unreachable=0  failed=0  skipped=0

(Tačan sadržaj prva tri reda zavisi od verzije VyOS-a.)

Korak 12 – Idempotentnost

PokretanjeRezultat
--check --diffchanged za oba rutera; prikazane linije koje bi bile poslate; na uređajima ništa nije promenjeno
Prvo stvarno pokretanjechanged=1 za vyos1 i vyos2
Drugo pokretanjechanged=0 za oba — stanje je već ispravno
Posle ručnog brisanja poruke, --limit vyos1changed=1 samo za vyos1; vyos2 nije ni kontaktiran

Modul vyos_config poredi zahtevane linije sa show configuration commands (bez obzira na navodnike) i šalje samo linije koje nedostaju. Zato je drugo pokretanje bez izmena.

Korak 13 – Dijagnostička vežba

Simptom: backup.py uspeva za VYOS1, a za VYOS2 prijavljuje BACKUP NIJE USPEO — NetmikoTimeoutException i završava se sa „Neuspešan backup: VYOS2".

Dijagnostika: prvo python provera_dostupnosti.py:

VYOS1  ping=OK  ssh=OK
VYOS2  ping=OK  ssh=NE

Ping radi, pa su mrežna veza i adresa ispravne. TCP port 22 nije dostupan, pa problem nije u lozinki ni u skripti, već u SSH servisu na ruteru. Na konzoli VYOS2 show configuration commands | match ssh ne vraća ništa — SSH servis nije podešen.

Uzrok: isključen SSH servis (delete service ssh). Netmiko ne može da uspostavi TCP vezu i prijavljuje NetmikoTimeoutException.

Ispravka (konzola VYOS2):

configure
set service ssh
commit
save
exit

Posle ispravke obe skripte uspevaju za oba rutera. Pouka: provera ping + TCP port za nekoliko sekundi razdvaja mrežni problem, problem servisa i problem autentifikacije. Zato se pokreće pre svake automatizacije.

Rešenje dodatnog izazova

1. Noćni backup iz cron-a. Fajl ~/.netauto.env (prava 600, vlasnik je jedini koji ga čita):

NET_USER=netauto
NET_PASS=<lozinka>
chmod 600 ~/.netauto.env

Skripta ~/netauto-lab/pokreni_backup.sh (prava 700):

#!/bin/bash
set -a
source "$HOME/.netauto.env"
set +a
cd "$HOME/netauto-lab" || exit 1
"$HOME/netauto-lab/venv/bin/python" backup.py >> "$HOME/netauto-lab/backup.log" 2>&1

crontab -e:

0 2 * * * /home/<korisnik>/netauto-lab/pokreni_backup.sh

U laboratoriji je fajl sa pravima 600 prihvatljiv, jer je lozinka van koda i van Git-a, a čitljiva je samo vlasniku naloga. U produkciji bi se koristio poseban nalog samo za čitanje (dovoljan za backup) sa SSH ključem, ili kredencijali iz sistema za tajne (HashiCorp Vault) koje skripta preuzima pri pokretanju. Neuspeh backup-a (izlazni kod različit od 0) prijavljivao bi se monitoring sistemu.

2. commit-confirm. Posle commit-confirm 2 VyOS ispisuje da izmenu treba potvrditi komandom confirm. Kako potvrde nema, posle dva minuta ruter se sam vraća na prethodnu, sačuvanu konfiguraciju. Na starijim verzijama to radi restartom sistema, a na novijim može i ponovnim učitavanjem konfiguracije (podešavanje system config-management commit-confirm action). Zato se pre potvrde ne sme izvršiti save. Kada izmena greškom preseče upravljački pristup (npr. firewall pravilo ili pogrešna adresa na eth0), administrator ne može da pošalje confirm, pa se ruter sam vraća u stanje u kome je bio dostupan.

3. Paralelno prikupljanje:

from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from pathlib import Path

from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutException

from zajednicko import parametri_veze, ucitaj_uredjaje

KOMANDE = ["show version", "show interfaces", "show ip route"]
folder = Path("izlazi") / datetime.now().strftime("%Y-%m-%d_%H%M")
folder.mkdir(parents=True, exist_ok=True)


def prikupi(u):
    try:
        with ConnectHandler(**parametri_veze(u)) as veza:
            for komanda in KOMANDE:
                izlaz = veza.send_command(komanda)
                (folder / f"{u['ime']}_{komanda.replace(' ', '_')}.txt").write_text(izlaz, encoding="utf-8")
        return f"{u['ime']}: prikupljeno {len(KOMANDE)} komandi"
    except (NetmikoTimeoutException, NetmikoAuthenticationException) as greska:
        return f"{u['ime']}: GREŠKA — {type(greska).__name__}"


with ThreadPoolExecutor(max_workers=10) as izvrsilac:
    for poruka in izvrsilac.map(prikupi, ucitaj_uredjaje()):
        print(poruka)

Svaki uređaj se obrađuje u posebnoj niti, pa ukupno trajanje približno odgovara trajanju najsporijeg uređaja, a ne zbiru svih. max_workers ograničava broj istovremenih SSH sesija, da se ne preopterete uređaji ni kontrolni računar.