Cynhyrchu a llenwi elfennau cyfluniad dyfeisiau rhwydwaith yn awtomatig gan ddefnyddio Nornir

Cynhyrchu a llenwi elfennau cyfluniad dyfeisiau rhwydwaith yn awtomatig gan ddefnyddio Nornir

Hei Habr!

Yn ddiweddar ymddangosodd erthygl yma Mikrotik a Linux. Arferol ac awtomeiddio lle cafodd problem debyg ei datrys gan ddefnyddio dulliau ffosil. Ac er bod y dasg yn gwbl nodweddiadol, does dim byd tebyg amdani ar Habré. Rwy'n meiddio cynnig fy meic i'r gymuned TG uchel ei pharch.

Nid dyma'r beic cyntaf ar gyfer tasg o'r fath. Rhoddwyd yr opsiwn cyntaf ar waith sawl blwyddyn yn ôl ansible fersiwn 1.x.x. Anaml y defnyddid y beic ac felly roedd yn rhydu'n gyson. Yn yr ystyr nad yw'r dasg ei hun yn codi mor aml ag y mae fersiynau'n cael eu diweddaru ansible. A phob tro y mae angen i chi yrru, mae'r gadwyn yn disgyn i ffwrdd neu mae'r olwyn yn disgyn i ffwrdd. Fodd bynnag, mae'r rhan gyntaf, cynhyrchu configs, bob amser yn gweithio'n glir iawn, yn ffodus jînja2 Mae'r injan wedi'i hen sefydlu. Ond roedd yr ail ran - cyflwyno configs - fel arfer yn peri syndod. A chan fod yn rhaid i mi gyflwyno'r cyfluniad o bell i hanner cant o ddyfeisiau, y mae rhai ohonynt wedi'u lleoli filoedd o gilometrau i ffwrdd, roedd defnyddio'r offeryn hwn ychydig yn ddiflas.

Yma mae'n rhaid i mi gyfaddef bod fy ansicrwydd yn fwyaf tebygol o fod yn fy niffyg cynefindra ag ef ansiblenag yn ei ddiffygion. Ac mae hwn, gyda llaw, yn bwynt pwysig. ansible yn faes gwybodaeth hollol ar wahân, gyda’i DSL (Iaith Parth Penodol) ei hun, y mae’n rhaid ei gynnal ar lefel hyderus. Wel, y foment honno ansible Mae'n datblygu'n eithaf cyflym, a heb roi sylw arbennig i gydnawsedd yn ôl, nid yw'n ychwanegu hyder.

Felly, nid mor bell yn ôl gweithredwyd ail fersiwn o'r beic. Y tro hwn ymlaen python, neu yn hytrach ar fframwaith a ysgrifennwyd yn python ac ar gyfer python o'r enw Nornir

Felly - Nornir yn ficrofframwaith wedi'i ysgrifennu yn python ac ar gyfer python ac wedi'u cynllunio ar gyfer awtomeiddio. Yr un peth ag yn yr achos gyda ansible, i ddatrys problemau yma, mae angen paratoi data cymwys, h.y. rhestr o westeion a'u paramedrau, ond nid yw sgriptiau'n cael eu hysgrifennu mewn DSL ar wahân, ond yn yr un nid yw'n hen iawn, ond yn dda iawn p[i|i]ton.

Gadewch i ni edrych ar yr hyn ydyw gan ddefnyddio'r enghraifft fyw ganlynol.

Mae gen i rwydwaith o ganghennau gyda sawl dwsin o swyddfeydd ledled y wlad. Mae gan bob swyddfa lwybrydd WAN sy'n terfynu sawl sianel gyfathrebu gan wahanol weithredwyr. Y protocol llwybro yw BGP. Daw llwybryddion WAN mewn dau fath: Cisco ISG neu Juniper SRX.

Nawr y dasg: mae angen i chi ffurfweddu is-rwydwaith pwrpasol ar gyfer Gwyliadwriaeth Fideo ar borthladd ar wahân ar holl lwybryddion WAN y rhwydwaith cangen - hysbysebwch yr is-rwydwaith hwn yn BGP - ffurfweddu terfyn cyflymder y porthladd pwrpasol.

Yn gyntaf, mae angen i ni baratoi cwpl o dempledi, ar y sail y bydd ffurfweddiadau'n cael eu cynhyrchu ar wahân ar gyfer Cisco a Juniper. Mae hefyd angen paratoi data ar gyfer pob pwynt a pharamedrau cysylltiad, h.y. casglu'r un rhestr eiddo

Templed parod ar gyfer 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

Templed ar gyfer 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

Nid yw templedi, wrth gwrs, yn dod allan o awyr denau. Mae'r rhain yn eu hanfod yn wahaniaethau rhwng y ffurfweddiadau gweithio a oedd ac a oedd ar ôl datrys y dasg ar ddau lwybrydd penodol o wahanol fodelau.

O'n templedi gwelwn, i ddatrys y broblem, mai dim ond dau baramedr sydd eu hangen arnom ar gyfer Juniper a 3 pharamedr ar gyfer Cisco. Dyma nhw:

  • ifenw
  • ipswdod
  • asn

Nawr mae angen i ni osod y paramedrau hyn ar gyfer pob dyfais, h.y. gwneud yr un peth rhestr.

I rhestr Byddwn yn dilyn y ddogfennaeth yn llym Cychwyn Nornir

hynny yw, gadewch i ni greu'r un sgerbwd ffeil:

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

Y ffeil config.yaml yw'r ffeil ffurfweddu safonol 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"

Byddwn yn nodi'r prif baramedrau yn y ffeil gwesteiwyr.yaml, grŵp (mewngofnodiadau/cyfrineiriau yw'r rhain yn fy achos i). grwpiau.yaml, ac yn rhagosodiadau.yaml Ni fyddwn yn nodi unrhyw beth, ond mae angen i chi nodi tri minws yno - gan nodi ei fod iaml mae'r ffeil yn wag serch hynny.

Dyma sut olwg sydd ar 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

A dyma grwpiau.yaml:

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

juniper:
    platform: junos
    username: admin2
    password: juniper2

Dyma beth ddigwyddodd rhestr ar gyfer ein tasg. Wrth gychwyn, mae paramedrau o ffeiliau rhestr eiddo yn cael eu mapio i'r model gwrthrych Elfen Stocrestr.

O dan y sbwyliwr mae diagram o'r model InventoryElement

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

Gall y model hwn edrych ychydig yn ddryslyd, yn enwedig ar y dechrau. Er mwyn ei chyfrifo, mae'r modd rhyngweithiol i mewn 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'

Ac yn olaf, gadewch i ni symud ymlaen at y sgript ei hun. Does gen i ddim byd i fod yn arbennig o falch ohono yma. Cymerais enghraifft barod o tiwtorial ac yn ei ddefnyddio bron yn ddigyfnewid. Dyma sut olwg sydd ar y sgript waith orffenedig:

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)

Rhowch sylw i'r paramedr dry_run=Gwir mewn cychwynnol gwrthrych llinell nr.
Yma yr un peth ag yn ansible mae rhediad prawf wedi'i roi ar waith lle mae cysylltiad â'r llwybrydd yn cael ei wneud, mae cyfluniad newydd wedi'i addasu yn cael ei baratoi, sydd wedyn yn cael ei ddilysu gan y ddyfais (ond nid yw hyn yn sicr; mae'n dibynnu ar gefnogaeth y ddyfais a gweithrediad y gyrrwr yn NAPALM) , ond nid yw'r cyfluniad newydd yn cael ei gymhwyso'n uniongyrchol. Ar gyfer defnydd ymladd, rhaid i chi gael gwared ar y paramedr sych_rhedeg neu newid ei werth i Anghywir.

Pan fydd y sgript yn cael ei gweithredu, mae Nornir yn allbynnu logiau manwl i'r consol.

O dan y sbwyliwr mae allbwn rhediad ymladd ar ddau lwybrydd prawf:

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

Cuddio cyfrineiriau yn ansible_vault

Ar ddechrau'r erthygl es i ychydig dros ben llestri ansible, ond nid yw mor ddrwg â hynny. Dwi'n hoff iawn ohonyn nhw claddgell tebyg, sydd wedi'i gynllunio i guddio gwybodaeth sensitif o'r golwg. Ac mae'n debyg bod llawer wedi sylwi bod gennym yr holl fewngofnodi / cyfrineiriau ar gyfer yr holl lwybryddion ymladd yn pefrio ar ffurf agored mewn ffeil gorups.yaml. Nid yw'n bert, wrth gwrs. Gadewch i ni amddiffyn y data hwn gyda claddgell.

Gadewch i ni drosglwyddo'r paramedrau o groups.yaml i creds.yaml, a'i amgryptio gydag AES256 gyda chyfrinair 20 digid:

$ 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

Mae mor syml â hynny. Erys i ddysgu ein Nornir-sgript i adfer a chymhwyso'r data hwn.
I wneud hyn, yn ein sgript ar ôl y llinell ymgychwyn nr = InitNornir(config_file=… ychwanegwch y cod canlynol:

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

Wrth gwrs, ni ddylid lleoli vault.passwd wrth ymyl creds.yaml fel yn fy enghraifft i. Ond mae'n iawn ar gyfer chwarae.

Dyna i gyd am y tro. Mae yna gwpl mwy o erthyglau am Cisco + Zabbix yn dod, ond nid yw hyn yn ymwneud ag awtomeiddio ychydig. Ac yn y dyfodol agos dwi'n bwriadu ysgrifennu am RESTCONF yn Cisco.

Ffynhonnell: hab.com

Ychwanegu sylw