
Helló, Habr!
Nemrégiben megjelent egy cikk itt ahol hasonló problémákat fosszilis eszközökkel oldottak meg. És bár a feladat teljesen tipikus, a Habr-on nem található róla semmi hasonló. Engedjétek meg, hogy javasoljak a tisztelt IT közösségnek egy saját megoldást.
Ez nem az első megoldás hasonló problémára. Az első verziót néhány éve valósították meg ansible 1.x.x verzióban. A megoldás ritkán használták, ezért folyamatosan rozsdásodott. Abban az értelemben, hogy a feladat nem merül fel olyan gyakran, mint ahogy a verziók frissülnek ansible. És minden alkalommal, amikor használnom kellett, vagy a lánc leesett, vagy a kerék eltört. Az első rész, a konfigurációk generálása azonban mindig nagyon pontosan működött, hála jinja2 a jól bevált motorának. A második rész - a konfigurációk terjesztése - azonban rendszerint meglepetéseket hozott. Mivel a konfigurációkat távolról kell terjesztenem körülbelül ötven eszközön, amelyek közül néhány ezer kilométerre található, ezért abból az eszközből kissé félelmetes volt használni.
Itt el kell ismernem, hogy a bizonytalanságom valójában inkább a ansiblekellemetlen ismeretlenségemből fakad, mint a hibáiból. Ez egy fontos pont. ansible Ez egy teljesen különálló, saját tudás terület, a saját DSL (Domain Specific Language) nyelvével, amelyet biztosan kezelni kell. És az a tény, hogy ansible meglehetősen gyorsan fejlődik, ráadásul nem nagyon figyel a visszafelé való kompatibilitásra, nem növeli a bizalmat.
Ezért nem olyan régen készült el a második verzió. Ezúttal a pythonkeretrendszeren, amelyet a python és a python Nornir néven hívtak
egy mikrof framework, amelyet a Ez tehát - nyelven írtak és automatizálásra készült. Ahogyan a python és a python -ban is, itt is szükséges a megfelelő adat előkészítés, vagyis a hosztok és jellemzőik nyilvántartása, míg a forgatókönyvek nem külön DSL nyelven készülnek, hanem ugyanazon nem túl régi, de nagyon barátságos P[í|y]tónon. ansibleNézzük meg, miről is van szó egy következő élő példán.
Nézzük meg, miről is van szó a következő élő példán.
Van egy fiókhálózatom, amely több tucat irodát foglal magában az ország egész területén. Minden irodában található egy WAN-router, amely több kapcsolati csatornát terminál a különböző szolgáltatóktól. A forgalomirányítási protokoll BGP. A WAN-routerek két típusúak lehetnek: Cisco ISG vagy Juniper SRX.
A feladat most az, hogy minden WAN-routeren be kell állítani egy dedikált alhálózatot a videomegfigyelés céljára egy külön porton — ezt az alhálózatot be kell hirdetni a BGP-be — be kell állítani a dedikált port sebességkorlátozását.
Először is fel kell készülnünk egy pár sablonra, amelyek alapján külön konfigurációkat fogunk generálni a Cisco és Juniper esetében. Továbbá szükséges adatokat gyűjteni minden pontból és a csatlakozási paramétereket, azaz össze kell gyűjteni a szükséges leltárt.
Kész sablon Cisco számára:
$ cat templates/ios/base.j2
class-map match-all VIDEO_SURV
match access-group 111
policy-map VIDEO_SURV
class VIDEO_SURV
police 1500000 conform-action transmit exceed-action drop
interface {{ host.task_data.ifname }}
description VIDEOSURV
ip address 10.10.{{ host.task_data.ipsuffix }}.254 255.255.255.0
service-policy input VIDEO_SURV
router bgp {{ host.task_data.asn }}
network 10.40.{{ host.task_data.ipsuffix }}.0 mask 255.255.255.0
access-list 11 permit 10.10.{{ host.task_data.ipsuffix }}.0 0.0.0.255
access-list 111 permit ip 10.10.{{ host.task_data.ipsuffix }}.0 0.0.0.255 anySablon Juniper számára:
$ cat templates/junos/base.j2
set interfaces {{ host.task_data.ifname }} unit 0 description "Videomegfigyelés"
set interfaces {{ host.task_data.ifname }} unit 0 family inet filter input limit-in
set interfaces {{ host.task_data.ifname }} unit 0 family inet address 10.10.{{ host.task_data.ipsuffix }}.254/24
set policy-options policy-statement export2bgp term 1 from route-filter 10.10.{{ host.task_data.ipsuffix }}.0/24 exact
set security zones security-zone WAN interfaces {{ host.task_data.ifname }}
set firewall policer policer-1m if-exceeding bandwidth-limit 1m
set firewall policer policer-1m if-exceeding burst-size-limit 187k
set firewall policer policer-1m then discard
set firewall policer policer-1.5m if-exceeding bandwidth-limit 1500000
set firewall policer policer-1.5m if-exceeding burst-size-limit 280k
set firewall policer policer-1.5m then discard
set firewall filter limit-in term 1 then policer policer-1.5m
set firewall filter limit-in term 1 then count limiterA sablonok természetesen nem a levegőből jönnek. Ezek alapvetően a két különböző modellű router esetében a megoldás után létrejött munkaképes konfigurációk közötti eltérések.
A sablonjainkból látható, hogy a feladat megoldásához elegendő két paraméter Juniperhez és három paraméter Ciscohoz. Ezek a következők:
- ifname
- ipsuffix
- asn
Most be kell állítanunk ezeket a paramétereket minden eszközhöz, azaz el kell végezni a szükséges lépéseket. inventory.
A inventory Pontosan követjük a dokumentációt.
Tehát létrehozunk egy hasonló fájlszerkezetet:
.
├── config.yaml
├── inventory
│ ├── defaults.yaml
│ ├── groups.yaml
│ └── hosts.yamlA config.yaml fájl - a nornir alapértelmezett konfigurációs fájlja
$ cat config.yaml
---
core:
num_workers: 10
inventory:
plugin: nornir.plugins.inventory.simple.SimpleInventory
options:
host_file: "inventory/hosts.yaml"
group_file: "inventory/groups.yaml"
defaults_file: "inventory/defaults.yaml"A főbb paramétereket a fájlban fogjuk megadni hosts.yaml, a csoporti (az én esetemben felhasználónév/jelszó) pedig a groups.yaml, míg a defaults.yaml nem tartalmaz semmit, de három kötőjelet kell beírni, amelyek jelzik, hogy ez egy yaml fájl, bármennyire üres is.
Ez így néz ki hosts.yaml:
---
srx-test:
hostname: srx-test
groups:
- juniper
data:
task_data:
ifname: fe-0/0/2
ipsuffix: 111
cisco-test:
hostname: cisco-test
groups:
- cisco
data:
task_data:
ifname: GigabitEthernet0/1/1
ipsuffix: 222
asn: 65111Így néz ki a groups.yaml:
---
cisco:
platform: ios
username: admin1
password: cisco1
juniper:
platform: junos
username: admin2
password: juniper2Ez így alakult inventory a feladatunkhoz. Az inicializáláskor a paraméterek az inventory fájlokból leképeződnek az objektum modellre InventoryElement.
A spoiler alatt található az InventoryElement modell sémája
print(json.dumps(InventoryElement.schema(), indent=4))
{
"title": "InventoryElement",
"type": "object",
"properties": {
"hostname": {
"title": "Hostname",
"type": "string"
},
"port": {
"title": "Port",
"type": "integer"
},
"username": {
"title": "Username",
"type": "string"
},
"password": {
"title": "Password",
"type": "string"
},
"platform": {
"title": "Platform",
"type": "string"
},
"groups": {
"title": "Groups",
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"data": {
"title": "Data",
"default": {},
"type": "object"
},
"connection_options": {
"title": "Connection_Options",
"default": {},
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ConnectionOptions"
}
}
},
"definitions": {
"ConnectionOptions": {
"title": "ConnectionOptions",
"type": "object",
"properties": {
"hostname": {
"title": "Hostname",
"type": "string"
},
"port": {
"title": "Port",
"type": "integer"
},
"username": {
"title": "Username",
"type": "string"
},
"password": {
"title": "Password",
"type": "string"
},
"platform": {
"title": "Platform",
"type": "string"
},
"extras": {
"title": "Extras",
"type": "object"
}
}
}
}
}Ez a modell kicsit zavarosnak tűnhet, különösen az elején. A megértéshez nagyon hasznos az interaktív üzemmód a ipython.
$ ipython3
Python 3.6.9 (default, Nov 7 2019, 10:44:02)
Írja be a 'copyright', 'credits' vagy 'license' parancsokat további információkért
IPython 7.1.1 -- Egy továbbfejlesztett interaktív Python. Írja be a '?' a súgóhoz.
In [1]: from nornir import InitNornir
In [2]: nr = InitNornir(config_file="config.yaml", dry_run=True)
In [3]: nr.inventory.hosts
Out[3]:
{'srx-test': Host: srx-test, 'cisco-test': Host: cisco-test}
In [4]: nr.inventory.hosts['srx-test'].data
Out[4]: {'task_data': {'ifname': 'fe-0/0/2', 'ipsuffix': 111}}
In [5]: nr.inventory.hosts['srx-test']['task_data']
Out[5]: {'ifname': 'fe-0/0/2', 'ipsuffix': 111}
In [6]: nr.inventory.hosts['srx-test'].platform
Out[6]: 'junos'
Most végre rátérünk magára a szkriptre. Itt igazából nincs mivel dicsekednem. Csak vettem egy kész példát a és szinte változtatás nélkül használtam fel. Itt van, hogyan néz ki a működő szkript:
from nornir import InitNornir
from nornir.plugins.tasks import networking, text
from nornir.plugins.functions.text import print_title, print_result
def config_and_deploy(task):
# Az inventory adatokat konfigurációvá alakítjuk egy sablonfájl segítségével
r = task.run(task=text.template_file,
name="Alap konfiguráció",
template="base.j2",
path=f"templates/{task.host.platform}")
# Mentjük a generált konfigurációt egy host változóba
task.host["config"] = r.result
# Mentjük a generált konfigurációt egy fájlba
with open(f"configs/{task.host.hostname}", "w") as f:
f.write(r.result)
# Alkalmazzuk azt a konfigurációt az eszközön a NAPALM használatával
task.run(task=networking.napalm_configure,
name="Konfiguráció betöltése az eszközre",
replace=False,
configuration=task.host["config"])
nr = InitNornir(config_file="config.yaml", dry_run=True) # állítsuk be a dry_run értékét False-ra, keresztbe feszítjük az ujjainkat és újra futtatjuk
# feladatok futtatása
result = nr.run(task=config_and_deploy)
print_result(result)Felhívjuk a figyelmet a dry_run=True paraméterre az objektum inicializálásánál nr.
Itt is, mint a ansible tesztfutásra került sor, ahol kapcsolat létesül az útválasztóval, előkészítjük az új, módosított konfigurációt, amelyet az eszköz érvényesít (de ez nem biztos; függ az eszköz támogatásától és a NAPALM-hoz tartozó meghajtó implementálásáról), de a módosított konfiguráció tényleges alkalmazása nem történik meg. Éles használathoz el kell távolítani a dry_run paramétert, vagy meg kell változtatni az értékét False.
A Nornir szkript futtatásakor részletes naplókat ad a konzolra.
A spoiler alatt két teszt útválasztón végrehajtott élő futás kimenete található:
config_and_deploy***************************************************************
* cisco-test ** megváltoztatva: Igen *******************************************
vvvv config_and_deploy ** megváltoztatva: Igen vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Alap konfiguráció ** megváltoztatva: Igen ------------------------------------- INFO
class-map match-all VIDEO_SURV
match access-group 111
policy-map VIDEO_SURV
class VIDEO_SURV
police 1500000 conform-action transmit exceed-action drop
interface GigabitEthernet0/1/1
description VIDEOSURV
ip address 10.10.222.254 255.255.255.0
service-policy input VIDEO_SURV
router bgp 65001
network 10.10.222.0 mask 255.255.255.0
access-list 11 permit 10.10.222.0 0.0.0.255
access-list 111 permit ip 10.10.222.0 0.0.0.255 any
---- Konfiguráció betöltése az eszközre ** megváltoztatva: Igen --------------------- INFO
+class-map match-all VIDEO_SURV
+ match access-group 111
+policy-map VIDEO_SURV
+ class VIDEO_SURV
+interface GigabitEthernet0/1/1
+ description VIDEOSURV
+ ip address 10.10.222.254 255.255.255.0
+ service-policy input VIDEO_SURV
+router bgp 65001
+ network 10.10.222.0 mask 255.255.255.0
+access-list 11 permit 10.10.222.0 0.0.0.255
+access-list 111 permit ip 10.10.222.0 0.0.0.255 any
^^^^ VÉGE config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* srx-test ** megváltoztatva: Igen *******************************************
vvvv config_and_deploy ** megváltoztatva: Igen vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Alap konfiguráció ** megváltoztatva: Igen ------------------------------------- INFO
set interfaces fe-0/0/2 unit 0 description "Videó megfigyelés"
set interfaces fe-0/0/2 unit 0 family inet filter input limit-in
set interfaces fe-0/0/2 unit 0 family inet address 10.10.111.254/24
set policy-options policy-statement export2bgp term 1 from route-filter 10.10.111.0/24 exact
set security zones security-zone WAN interfaces fe-0/0/2
set firewall policer policer-1m if-exceeding bandwidth-limit 1m
set firewall policer policer-1m if-exceeding burst-size-limit 187k
set firewall policer policer-1m then discard
set firewall policer policer-1.5m if-exceeding bandwidth-limit 1500000
set firewall policer policer-1.5m if-exceeding burst-size-limit 280k
set firewall policer policer-1.5m then discard
set firewall filter limit-in term 1 then policer policer-1.5m
set firewall filter limit-in term 1 then count limiter
---- Konfiguráció betöltése az eszközre ** megváltoztatva: Igen --------------------- INFO
[edit interfaces]
+ fe-0/0/2 {
+ unit 0 {
+ description "Videó megfigyelés";
+ family inet {
+ filter {
+ input limit-in;
+ }
+ address 10.10.111.254/24;
+ }
+ }
+ }
[edit]
+ policy-options {
+ policy-statement export2bgp {
+ term 1 {
+ from {
+ route-filter 10.10.111.0/24 exact;
+ }
+ }
+ }
+ }
[edit security zones]
security-zone test-vpn { ... }
+ security-zone WAN {
+ interfaces {
+ fe-0/0/2.0;
+ }
+ }
[edit]
+ firewall {
+ policer policer-1m {
+ if-exceeding {
+ bandwidth-limit 1m;
+ burst-size-limit 187k;
+ }
+ then discard;
+ }
+ policer policer-1.5m {
+ if-exceeding {
+ bandwidth-limit 1500000;
+ burst-size-limit 280k;
+ }
+ then discard;
+ }
+ filter limit-in {
+ term 1 {
+ then {
+ policer policer-1.5m;
+ count limiter;
+ }
+ }
+ }
+ }
^^^^ VÉGE config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^Tartsuk titokban a jelszavakat az ansible_vault-ban
A cikk elején kicsit kritizáltam ansible, de ott nem minden olyan rossz. Nagyon tetszett náluk vault Ami titokban tart érzékeny információkat a nyilvánosság elől. Valószínűleg sokan észrevették, hogy a bejelentkezési adatok/jelszavak minden üzleti routerünknél nyíltan villognak egy fájlban. gorups.yaml. Ez persze nem szép. Védjük meg ezeket az adatokat a vault.
Az adatokat átviszsky a groups.yaml-ból a creds.yaml-ba, és AES256-tal titkosítjuk 20 karakteres jelszóval:
$ cd inventory
$ cat creds.yaml
---
cisco:
username: admin1
password: cisco1
juniper:
username: admin2
password: juniper2
$ pwgen 20 -N 1 > vault.passwd
ansible-vault encrypt creds.yaml --vault-password-file vault.passwd
Titkosítás sikeres
$ cat creds.yaml
$ANSIBLE_VAULT;1.1;AES256
39656463353437333337356361633737383464383231366233386636333965306662323534626131
3964396534396333363939373539393662623164373539620a346565373439646436356438653965
39643266333639356564663961303535353364383163633232366138643132313530346661316533
6236306435613132610a656163653065633866626639613537326233653765353661613337393839
62376662303061353963383330323164633162386336643832376263343634356230613562643533
30363436343465306638653932366166306562393061323636636163373164613630643965636361
34343936323066393763323633336366366566393236613737326530346234393735306261363239
35663430623934323632616161636330353134393435396632663530373932383532316161353963
31393434653165613432326636616636383665316465623036376631313162646435Így egyszerű. Most tanítsuk meg a Ez tehát --szkriptnek kinyerni és alkalmazni ezeket az adatokat.
Ehhez a szkriptünkben az inicializáló sor után nr = InitNornir(config_file=… hozzáadjuk a következő kódot:
...
nr = InitNornir(config_file="config.yaml", dry_run=True) # állítsd dry_run=False-ra, bízzunk benne és futtassuk újra
# gazdagítjuk az Inventory-t a titkosított vault adatokkal
from ansible_vault import Vault
vault_password_file="inventory/vault.passwd"
vault_file="inventory/creds.yaml"
with open(vault_password_file, "r") as fp:
password = fp.readline().strip()
vault = Vault(password)
vaultdata = vault.load(open(vault_file).read())
for a in nr.inventory.hosts.keys():
item = nr.inventory.hosts[a]
item.username = vaultdata[item.groups[0]]['username']
item.password = vaultdata[item.groups[0]]['password']
#print("hostname={}, username={}, password={}n".format(item.hostname, item.username, item.password))
# futtassuk a feladatokat
...Természetesen a vault.passwd nem állhat közvetlenül a creds.yaml mellett, mint az én példámban. De a kipróbáláshoz megfelel.
Ennyit mára. Két új cikk közeleg a Cisco + Zabbix-ról, de az nem teljesen az automatizálásról szól. A közeljövőben szeretnék írni a RESTCONF-ról a Cisco-nál.
Forrás: habr.com
