Otomatik jenerasyon ak ranpli eleman konfigirasyon aparèy rezo lè l sèvi avèk Nornir

Otomatik jenerasyon ak ranpli eleman konfigirasyon aparèy rezo lè l sèvi avèk Nornir

Hey Habr!

Dènyèman, yon atik parèt isit la Mikrotik ak Linux. Woutin ak automatisation kote yo te rezoud yon pwoblèm ki sanble ak mwayen fosil. Ak byenke travay la se konplètman tipik, pa gen anyen ki sanble sou li sou Habré. Mwen oze ofri bisiklèt mwen an bay kominote IT respekte a.

Sa a se pa bisiklèt la premye pou yon travay konsa. Premye opsyon a te aplike plizyè ane de sa tounen nan ansible vèsyon 1.x.x. Te bisiklèt la raman itilize ak Se poutèt sa toujou ap rouye. Nan sans ke travay la tèt li pa leve osi souvan ke vèsyon yo mete ajou ansible. Epi chak fwa ou bezwen kondwi, chèn nan tonbe oswa wou a tonbe. Sepandan, premye pati a, génération konfigirasyon, toujou travay trè klè, erezman jinja2 Motè a depi lontan etabli. Men, dezyèm pati a - woule soti konfigirasyon - anjeneral te pote supriz. Epi depi mwen oblije woule konfigirasyon an adistans nan mwatye yon santèn aparèy, kèk nan yo ki sitiye dè milye de kilomèt lwen, lè l sèvi avèk zouti sa a te yon ti kras raz.

Isit la mwen dwe admèt ke ensètitid mwen gen plis chans nan manti mwen nan mank de abitye ak ansiblepase nan enpèfeksyon li yo. Ak sa a, nan chemen an, se yon pwen enpòtan. ansible se yon domèn konplètman separe, pwòp li yo nan konesans ak pwòp li yo DSL (Domain Specific Language), ki dwe kenbe nan yon nivo konfyans. Oke, moman sa a ansible Li ap devlope byen vit, epi san konsiderasyon espesyal pou konpatibilite bak, li pa ajoute konfyans.

Se poutèt sa, pa tèlman lontan de sa yon dezyèm vèsyon bisiklèt la te aplike. Fwa sa a sou python, oswa pito sou yon kad ekri nan python ak pou python rele Nornir

Se konsa - Nornir se yon microframework ekri nan python ak pou python ak fèt pou automatisation. Menm jan ak nan ka a ak ansible, pou rezoud pwoblèm isit la, yo mande pou preparasyon done konpetan, i.e. envantè ki gen tout pouvwa a ak paramèt yo, men scripts yo ekri pa nan yon DSL separe, men nan menm bagay la tou pa trè ansyen, men trè bon p[i|i]ton.

Ann gade nan ki sa li se lè l sèvi avèk egzanp sa a ap viv.

Mwen gen yon rezo filyal ki gen plizyè douzèn biwo atravè peyi a. Chak biwo gen yon routeur WAN ki mete fen nan plizyè chanèl kominikasyon ki soti nan operatè diferan. Pwotokòl routage se BGP. WAN routeurs vini nan de kalite: Cisco ISG oswa Juniper SRX.

Koulye a, travay la: ou bezwen konfigirasyon yon sous-rezo dedye pou siveyans videyo sou yon pò separe sou tout routeurs WAN nan rezo branch lan - fè piblisite sou rezo sa a nan BGP - konfigirasyon limit vitès pò a dedye.

Premyèman, nou bezwen prepare yon koup nan modèl, sou baz ki konfigirasyon yo pral pwodwi separeman pou Cisco ak Juniper. Li nesesè tou pou prepare done pou chak pwen ak paramèt koneksyon, i.e. kolekte menm envantè a

Modèl pare pou Cisco:

$ 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 any

Modèl pou Juniper:

$ 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 limiter

Modèl, nan kou, pa soti nan lè mens. Sa yo se esansyèlman diferans ant konfigirasyon yo k ap travay ki te epi yo te apre rezoud travay la sou de routeurs espesifik nan modèl diferan.

Soti nan modèl nou yo nou wè ke yo rezoud pwoblèm nan, nou sèlman bezwen de paramèt pou Juniper ak 3 paramèt pou Cisco. isit la yo ye:

  • ifname
  • ipsufiks
  • asn

Koulye a, nou bezwen mete paramèt sa yo pou chak aparèy, i.e. fè menm bagay la envantè.

Pou envantè Nou pral entèdi swiv dokiman an Inisyalize Nornir

sa vle di, ann kreye menm eskèlèt dosye a:

.
├── config.yaml
├── inventory
│   ├── defaults.yaml
│   ├── groups.yaml
│   └── hosts.yaml

Fichye config.yaml a se fichye estanda konfigirasyon nornir

$ 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"

Nou pral endike paramèt prensipal yo nan dosye a hosts.yaml, gwoup (nan ka mwen an sa yo se logins/modpas) nan groups.yaml, ak nan defaults.yaml Nou pa pral endike anyen, men ou bezwen antre twa minus la - ki endike ke li se yaml dosye a vid menm si.

Men sa hosts.yaml sanble:

---
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

Ak isit la nan groups.yaml:

---
cisco:
    platform: ios
    username: admin1
    password: cisco1

juniper:
    platform: junos
    username: admin2
    password: juniper2

Sa a se sa ki te pase envantè pou travay nou. Pandan inisyalizasyon, paramèt ki soti nan dosye envantè yo trase nan modèl objè a InventoryElement.

Anba beke a se yon dyagram nan modèl InventoryElement la

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"
                }
            }
        }
    }
}

Modèl sa a ka gade yon ti kras konfizyon, espesyalman nan premye. Yo nan lòd yo kalkile li soti, mòd nan entèaktif nan piton.

 $ 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'

Epi finalman, se pou nou deplase sou script nan tèt li. Mwen pa gen anyen yo dwe patikilyèman fyè de isit la. Mwen jis pran yon egzanp pare-fè soti nan leson patikilye epi itilize li prèske san okenn chanjman. Men ki jan script travay fini an sanble:

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):
    # Transform inventory data to configuration via a template file
    r = task.run(task=text.template_file,
                 name="Base Configuration",
                 template="base.j2",
                 path=f"templates/{task.host.platform}")

    # Save the compiled configuration into a host variable
    task.host["config"] = r.result

    # Save the compiled configuration into a file
    with open(f"configs/{task.host.hostname}", "w") as f:
        f.write(r.result)

    # Deploy that configuration to the device using NAPALM
    task.run(task=networking.napalm_configure,
             name="Loading Configuration on the device",
             replace=False,
             configuration=task.host["config"])

nr = InitNornir(config_file="config.yaml", dry_run=True) # set dry_run=False, cross your fingers and run again

# run tasks
result = nr.run(task=config_and_deploy)
print_result(result)

Peye atansyon sou paramèt la dry_run=Se vre nan inisyalizasyon objè liy nr.
Isit la menm jan ak nan ansible yo te aplike yon tès tès kote yo fè yon koneksyon ak routeur la, yo prepare yon nouvo konfigirasyon modifye, ki Lè sa a, valide pa aparèy la (men sa a se pa sèten; sa depann de sipò nan aparèy ak aplikasyon chofè a nan NAPALM) , men nouvo konfigirasyon an pa aplike dirèkteman. Pou itilize konba, ou dwe retire paramèt la sèk_kouri oswa chanje valè li an Fo.

Lè script la egzekite, Nornir soti mòso detaye nan konsole a.

Anba spoiler la se pwodiksyon yon konba kouri sou de routeur tès:

config_and_deploy***************************************************************
* cisco-test ** changed : True *******************************************
vvvv config_and_deploy ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : 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
---- Loading Configuration on the device ** changed : 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
^^^^ END config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* srx-test ** changed : True *******************************************
vvvv config_and_deploy ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : True ------------------------------------- INFO
set interfaces fe-0/0/2 unit 0 description "Video surveillance"
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
---- Loading Configuration on the device ** changed : True --------------------- INFO
[edit interfaces]
+   fe-0/0/2 {
+       unit 0 {
+           description "Video surveillance";
+           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;
+              }
+          }
+      }
+  }
^^^^ END config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Kache modpas nan ansible_vault

Nan kòmansman atik la mwen kouri yon ti kras sou ansible, men li pa tout sa ki mal. Mwen vrèman renmen yo vout tankou, ki fèt yo kache enfòmasyon sansib soti nan je. Ak pwobableman anpil te remake ke nou gen tout koneksyon yo / modpas pou tout routeurs konba briyan nan fòm louvri nan yon dosye. gorups.yaml. Li pa bèl, nan kou. Ann pwoteje done sa yo ak vout.

Ann transfere paramèt yo soti nan groups.yaml nan creds.yaml, epi ankripte li ak AES256 ak yon modpas 20 chif:

$ 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  
Encryption successful
$ cat creds.yaml 
$ANSIBLE_VAULT;1.1;AES256
39656463353437333337356361633737383464383231366233386636333965306662323534626131
3964396534396333363939373539393662623164373539620a346565373439646436356438653965
39643266333639356564663961303535353364383163633232366138643132313530346661316533
6236306435613132610a656163653065633866626639613537326233653765353661613337393839
62376662303061353963383330323164633162386336643832376263343634356230613562643533
30363436343465306638653932366166306562393061323636636163373164613630643965636361
34343936323066393763323633336366366566393236613737326530346234393735306261363239
35663430623934323632616161636330353134393435396632663530373932383532316161353963
31393434653165613432326636616636383665316465623036376631313162646435

Li senp konsa. Li rete pou anseye nou Nornir-script pou rekipere epi aplike done sa yo.
Pou fè sa, nan script nou an apre liy lan inisyalizasyon nr = InitNornir(config_file=... ajoute kòd sa a:

...
nr = InitNornir(config_file="config.yaml", dry_run=True) # set dry_run=False, cross your fingers and run again

# enrich Inventory with the encrypted vault data
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))

# run tasks
...

Natirèlman, vault.passwd pa ta dwe lokalize akote creds.yaml tankou nan egzanp mwen an. Men, li se oke pou jwe.

Se tout pou kounye a. Gen yon koup plis atik sou Cisco + Zabbix ap vini, men sa a se pa yon ti jan sou automatisation. Ak nan fiti prè mwen planifye yo ekri sou RESTCONF nan Cisco.

Sous: www.habr.com

Add nouvo kòmantè