
Tere, Habr!
Hiljuti ilmus siin artikkel kus sarnast probleemi lahendati vanade vahenditega. Ja kuigi probleem on tÀiesti tavaline, ei leia selle kohta Habras mingit sarnast teavet. Julgen pakkuda austatud IT-kogukonnale oma jalgratast.
See ei ole esimene jalgratas sarnase ĂŒlesande jaoks. Esimene variant realiseeriti mitu aastat tagasi veel versioonil ansible 1.x.x. Jalgratast kasutati harva ja seetĂ”ttu see pidevalt roostetama hakkas. Just selle tĂ”ttu, et probleem ei esine nii tihti kui versioonid uuenevad ansible. Ja iga kord, kui tuleb minna, siis kas kett kukub maha vĂ”i ratta kelk laguneb. Siiski teadmiseks, et esimene osa, konfiguratsioonide genereerimine, töötab alati vĂ€ga tĂ€pselt, Ă”nneks jinja2 mootor on ammu tuntud. Ent teine osa â konfiguratsioonide rakendamine, tĂ”i tavaliselt ĂŒllatusi. Ja kuna pean neid konfiguratsioone rakendama kaugjuhtimisega viiekĂŒmnele seadmele, millest mĂ”ned asuvad tuhandete kilomeetrite kaugusel, siis oli selle tööriista kasutamine pisut hirmutav.
Siin tuleb tunnistada, et minu ebakindlus peitub pigem minu vĂ€heses tutvuses ansible, kui tema puudustes. Ja see on, muide, oluline punkt. ansible â see on tĂ€iesti eraldi, omaette teadmiste valdkond oma DSL-iga (spetsiaalne keel), mida tuleb hoida kindlal tasemel. Ja see, et ansible areneb ĂŒsna kiiresti, lisaks veel ilma eriliste tagasipöördumise arvestamiseta, ei lisa kindlust.
SeetÔttu realiseeriti hiljuti teine jalgratas. Sel korral python, tÀpsemalt raamistik, mis on kirjutatud python ja python nimega
Nii et â Nornir see on mikrorakendus, mis on kirjutatud python ja python ja mĂ”eldud automatiseerimiseks. Samamoodi nagu ka ansible, nĂ”uab siinsete ĂŒlesannete lahendamine korralikku andmete ettevalmistamist, st hostide ja nende parameetrite inventeerimist, samas kui stsenaariumid kirjutatakse mitte eraldi DSL-is, vaid kĂ”ik samas mitte eriti vanas, kuid ĂŒsna heas p[ĂŒ|i]thonis.
Vaatame, mis see on jÀrgmise elava nÀite pÔhjal.
Mul on filiaalide vĂ”rk, kus on mitu tosinat kontorit ĂŒle kogu riigi. Igas kontoris on WAN-ruuter, mis termineerib mitu sidekanalit erinevatelt operaatoritelt. Marsruutimisprotokoll â BGP. WAN-ruuterid on kahte tĂŒĂŒpi: Cisco ISG vĂ”i Juniper SRX.
NĂŒĂŒd on ĂŒlesanne: konfigureerida kĂ”ikides filiaalivĂ”rgu WAN marsruuterites eraldatud alamvĂ”rk videosalvestuse jaoks eraldi pordis â teatada sellest alamvĂ”rgust BGP-s â seadistada eraldi pordi kiiruspiirang.
Esiteks peame valmistama ette paar mallide, mille alusel genereeritakse konfigureerimised eraldi Cisco ja Juniper jaoks. Samuti on vaja ette valmistada andmed iga punkti kohta ja ĂŒhendusparameetrid, st koguda vajalik inventuur.
Valmis mall Cisco jaoks:
$ 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 anyMall Juniper jaoks:
$ cat templates/junos/base.j2
set interfaces {{ host.task_data.ifname }} unit 0 description "Video surveillance"
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 limiterMallid ei tule muidugi iseenesest. Need on tegelikult diffâid töökonfiguratsioonide vahel, mis olid ja said pĂ€rast ĂŒlesande lahendamist kahe konkreetse erineva mudeliga marsruuteri puhul.
Meie mallidest nĂ€eme, et ĂŒlesande lahendamiseks on Juniper jaoks vaja kahte parameetrit ja Cisco jaoks kolme parameetrit. Need on:
- ifname
- ipsuffix
- asn
NĂŒĂŒd peame need parameetrid mÀÀrama iga seadme jaoks, st tegema selle vajalikku. inventory.
Tooge inventory jÀrgime kindlalt dokumentatsiooni
st loome sarnase failistruktuuri:
.
âââ config.yaml
âââ inventory
â âââ defaults.yaml
â âââ groups.yaml
â âââ hosts.yamlFail config.yaml â standardne norniri konfiguratsioonifail
$ 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"Peamised parameetrid mÀÀrame failis hosts.yaml, rĂŒhmad (minu puhul on need sisselogimised/paroolid) sisse groups.yamltoob kaasa selle, et "saadetamise" hetkel server ei ole veel kĂ€ivitatud ja kĂ”ik lĂ€heb katki. defaults.yaml ei hakka midagi tĂ€psustama, kuid sinna tuleb kindlasti kirjutada kolm miinust â mis nĂ€itavad, et see on yaml fail on kĂŒll tĂŒhi.
Nii nÀeb vÀlja 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: 65111Ja nii nÀeb vÀlja groups.yaml:
---
cisco:
platform: ios
username: admin1
password: cisco1
juniper:
platform: junos
username: admin2
password: juniper2Selline asi sai valmis inventory meie ĂŒlesande jaoks. Algatamisel mappitakse parameetrid inventory-failidest objektimudelile InventoryElement.
Peidetud ala all on InventoryElementi mudeli skeem
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"
}
}
}
}
}See mudel vĂ”ib esialgu tunduda natuke segane. Et paremini aru saada, aitab vĂ€ga interaktiivne reĆŸiim ipython.
$ ipython3
Python 3.6.9 (default, Nov 7 2019, 10:44:02)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.1.1 -- An enhanced Interactive Python. Type '?' for help.
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'
LÔpuks liigume skripti juurde. Siin ei ole mul liiga palju uhkust tunda. Ma vÔtsin lihtsalt valmis nÀite ja kasutasin seda peaaegu muutmata. Valmis töötav skript nÀeb vÀlja selline:
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):
# Muuda inventari andmed konfiguratsiooniks malandi faili kaudu
r = task.run(task=text.template_file,
name="PÔhi konfiguratsioon",
template="base.j2",
path=f"templates/{task.host.platform}")
# Salvesta kompileeritud konfiguratsioon hosti muutujasse
task.host["config"] = r.result
# Salvesta kompileeritud konfiguratsioon faili
with open(f"configs/{task.host.hostname}", "w") as f:
f.write(r.result)
# Deploi see konfiguratsioon seadmesse kasutades NAPALM-i
task.run(task=networking.napalm_configure,
name="Laadib konfiguratsiooni seadmesse",
replace=False,
configuration=task.host["config"])
nr = InitNornir(config_file="config.yaml", dry_run=True) # pane dry_run=False, hoia pöialt ja proovi uuesti
# kĂ€i ĂŒlesanded lĂ€bi
result = nr.run(task=config_and_deploy)
print_result(result)Pange tÀhele parameetrit dry_run=True rea vitamise objekti initsialiseerimine nr.
Siin on samuti nagu ansible teostatud testkĂ€ik, mille kĂ€igus toimub ĂŒhendus marsruuteriga, valmistatakse ette uus muudetud konfiguratsioon, mis seejĂ€rel seadme poolt valideeritakse (aga see ei pruugi kehtida; sĂ”ltub seadme toest ja NAPALMi draiveri teostusest), kuid uue konfiguratsiooni rakendamist endiselt ei toimu. Tootmises kasutamiseks tuleb parameeter dry_run kas eemaldada vĂ”i selle vÀÀrtust muuta Vale.
Nornir skripti kĂ€ivitamisel kuvab konsool ĂŒksikasjalikke logisid.
Spoileri all on tootmisproovie teated kahel testmarsruuteril:
config_and_deploy***************************************************************
* cisco-test ** muutunud : True *******************************************
vvvv config_and_deploy ** muutunud : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PÔhikonfiguratsioon ** muutunud : True ------------------------------------- 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
---- Seadmise laadimine seadmesse ** muutunud : True --------------------- 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
^^^^ LĂPP config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* srx-test ** muutunud : True *******************************************
vvvv config_and_deploy ** muutunud : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PÔhikonfiguratsioon ** muutunud : True ------------------------------------- INFO
set interfaces fe-0/0/2 unit 0 description "Videovalve"
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
---- Seadmise laadimine seadmesse ** muutunud : True --------------------- INFO
[edit interfaces]
+ fe-0/0/2 {
+ unit 0 {
+ description "Videovalve";
+ 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;
+ }
+ }
+ }
+ }
^^^^ LĂPP config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^Peidame paroolid ansible_vaults
Artikli alguses ĂŒtlesin natuke kriitiliselt ansible, aga seal ei ole kĂ”ik nii halb. Mulle vĂ€ga meeldib nende vault meeldib, mis on mĂ”eldud tundlike andmete silmadest varjamiseks. Ja ilmselt on paljud mĂ€rganud, et kĂ”ik meie kasutajanimed/paroolid kĂ”igi tootmisruuterite jaoks on avatud kujul failis. gorups.yaml. See pole kindlasti ilus. Kaitseme neid andmeid selle abil. vault.
Kandke parameetrid groups.yaml failist creds.yaml faili ja krĂŒpteerige see AES256-ga 20-mĂ€rgise parooliga:
$ 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
KrĂŒptimine Ă”nnestus
$ cat creds.yaml
$ANSIBLE_VAULT;1.1;AES256
39656463353437333337356361633737383464383231366233386636333965306662323534626131
3964396534396333363939373539393662623164373539620a346565373439646436356438653965
39643266333639356564663961303535353364383163633232366138643132313530346661316533
6236306435613132610a656163653065633866626639613537326233653765353661613337393839
62376662303061353963383330323164633162386336643832376263343634356230613562643533
30363436343465306638653932366166306562393061323636636163373164613630643965636361
34343936323066393763323633336366366566393236613737326530346234393735306261363239
35663430623934323632616161636330353134393435396632663530373932383532316161353963
31393434653165613432326636616636383665316465623036376631313162646435Nii lihtsalt. JÀÀnud on Ôpetada meie Nornir-skripti andmeid tooma ja rakendama.
Selle jaoks lisame oma skripti pÀrast initsialiseerimisrida nr = InitNornir(config_file=⊠jÀrgnev kood:
...
nr = InitNornir(config_file="config.yaml", dry_run=True) # seadke dry_run=False, hoidke pöidlad ristis ja kÀivitage uuesti
# rikastage inventaari krĂŒpteeritud rauaga
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))
# kĂ€ivitage ĂŒlesanded
...Muidugi ei tohiks vault.passwd asuda naastudega koos creds.yaml failiga, nagu minu nÀites. Kuid harjuda vÔib sellega.
Sellega praeguseks kÔik. Tulekul on veel paar artiklit Cisco + Zabbixist, kuid see pole just automatiseerimise kohta. Ja peagi plaanin kirjutada RESTCONF-ist Cisco-s.
Allikas: habr.com
