Nornir ашиглан сүлжээний төхөөрөмжийн тохиргооны элементүүдийг автоматаар үүсгэх, дүүргэх

Nornir ашиглан сүлжээний төхөөрөмжийн тохиргооны элементүүдийг автоматаар үүсгэх, дүүргэх

Хөөе Хабр!

Саяхан энд нэг нийтлэл гарч ирэв Микротик ба Линукс. Тогтмол ба автоматжуулалт ижил төстэй асуудлыг чулуужсан аргаар шийдсэн. Хэдийгээр даалгавар нь ердийн зүйл боловч Хабре дээр үүнтэй төстэй зүйл байхгүй. Би нэр хүндтэй мэдээллийн технологийн нийгэмлэгт унадаг дугуйгаа санал болгож зүрхэлдэг.

Энэ бол ийм даалгаварт зориулсан анхны дугуй биш юм. Эхний хувилбар нь хэдэн жилийн өмнө хэрэгжиж эхэлсэн ойлгомжгүй хувилбар 1.x.x. Унадаг дугуй нь ховор хэрэглэгддэг байсан тул байнга зэвэрсэн. Даалгавар нь хувилбарууд шинэчлэгдэж байх тусам үүсдэггүй гэсэн үг юм ойлгомжгүй. Мөн жолоо барих шаардлагатай болгонд гинж унадаг эсвэл дугуй нь унадаг. Гэсэн хэдий ч тохиргоог үүсгэдэг эхний хэсэг нь аз болоход үргэлж маш тодорхой ажилладаг жинжа2 Хөдөлгүүр нь удаан хугацаанд тогтсон. Гэхдээ хоёр дахь хэсэг - тохиргоог хийх нь ихэвчлэн гэнэтийн бэлэг авчирдаг. Зарим нь хэдэн мянган километрийн зайд байрладаг хагас зуун төхөөрөмжид тохиргоог алсаас хийх шаардлагатай болсон тул энэ хэрэгслийг ашиглах нь жаахан уйтгартай байсан.

Энд би тодорхойгүй байдал нь миний сайн мэдэхгүйгээс үүдэлтэй гэдгийг хүлээн зөвшөөрөх ёстой ойлгомжгүйдутагдлаасаа илүү. Дашрамд хэлэхэд энэ бол чухал цэг юм. ойлгомжгүй Энэ нь бүрэн тусдаа, өөрийн DSL (Домэйн тодорхой хэл) бүхий өөрийн мэдлэгийн талбар бөгөөд үүнийг итгэлтэй түвшинд байлгах ёстой. За тэр мөч ойлгомжгүй Энэ нь нэлээд хурдацтай хөгжиж байгаа бөгөөд хоцрогдсон нийцтэй байдлыг онцгой анхаарахгүйгээр өөртөө итгэх итгэлийг нэмдэггүй.

Тиймээс удалгүй дугуйны хоёр дахь хувилбар хэрэгжиж эхэлсэн. Энэ удаад Python, эсвэл илүү дээр бичсэн хүрээн дээр Python болон Python нэрээр нь Норнир

Тэгэхээр - Норнир дотор бичигдсэн микрофрэйворк юм Python болон Python мөн автоматжуулалтад зориулагдсан. -тай адил ойлгомжгүй, энд асуудлыг шийдвэрлэхийн тулд чадварлаг өгөгдөл бэлтгэх шаардлагатай, i.e. хостуудын тооллого, тэдгээрийн параметрүүд, гэхдээ скриптүүд нь тусдаа DSL-д биш, гэхдээ тийм ч хуучин биш, гэхдээ маш сайн p[i|i]тон дээр бичигдсэн байдаг.

Дараах амьд жишээн дээр юу ашиглаж байгааг харцгаая.

Би орон даяар хэдэн арван оффистой салбар сүлжээтэй. Оффис бүр өөр өөр операторуудын хэд хэдэн холбооны сувгийг тасалдаг WAN чиглүүлэгчтэй байдаг. Чиглүүлэлтийн протокол нь BGP юм. WAN чиглүүлэгчид нь Cisco ISG эсвэл Juniper SRX гэсэн хоёр төрөлтэй.

Одоо даалгавар: та салбар сүлжээний бүх WAN чиглүүлэгчид тусдаа порт дээр Видео тандалтад зориулагдсан дэд сүлжээг тохируулах хэрэгтэй - энэ дэд сүлжээг BGP дээр сурталчлах - тусгай портын хурдны хязгаарыг тохируулах.

Эхлээд бид хэд хэдэн загвар бэлтгэх хэрэгтэй бөгөөд үүний үндсэн дээр Cisco болон Juniper-ийн тохиргоог тусад нь үүсгэх болно. Мөн цэг бүрийн өгөгдөл, холболтын параметрүүдийг бэлтгэх шаардлагатай, i.e. ижил бараа материал цуглуулах

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

Арцанд зориулсан загвар:

$ 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

Загварууд нь мэдээжийн хэрэг агаараас гардаггүй. Эдгээр нь үндсэндээ өөр өөр загварын хоёр чиглүүлэгч дээр даалгаврыг шийдэж байсан болон дараа нь байсан ажлын тохиргоонуудын хооронд ялгаатай байдаг.

Бидний загваруудаас бид асуудлыг шийдэхийн тулд Juniper-д хоёр параметр, Cisco-д 3 параметр хэрэгтэй болохыг харж байна. Тэд энд байна:

  • ifname
  • ipsuffix
  • асн

Одоо бид төхөөрөмж бүрийн хувьд эдгээр параметрүүдийг тохируулах хэрэгтэй, i.e. ижил зүйлийг хий бараа материалын.

Хэрэгтэй бараа материалын Бид баримт бичгийг чанд дагаж мөрдөх болно Норнирыг эхлүүлж байна

өөрөөр хэлбэл, ижил файлын араг ясыг үүсгэцгээе:

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

config.yaml файл нь стандарт норнир тохиргооны файл юм

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

Бид файл дахь үндсэн параметрүүдийг зааж өгөх болно hosts.yaml, бүлэг (миний тохиолдолд эдгээр нь нэвтрэх/нууц үг) дотор бүлгүүд.yaml, дотор нь defaults.yaml Бид юу ч зааж өгөхгүй, гэхдээ та тэнд гурван хасах зүйл оруулах хэрэгтэй - энэ нь байгааг илтгэнэ ямл гэхдээ файл хоосон байна.

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

Мөн энд group.yaml байна:

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

juniper:
    platform: junos
    username: admin2
    password: juniper2

Ийм зүйл болсон бараа материалын бидний даалгаврын төлөө. Эхлэх явцад бараа материалын файлуудын параметрүүдийг объектын загварт буулгадаг Бараа материалын элемент.

Спойлерын доор 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"
                }
            }
        }
    }
}

Энэ загвар нь, ялангуяа эхэндээ бага зэрэг ойлгомжгүй харагдаж болно. Үүнийг ойлгохын тулд интерактив горимд орно питон.

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

Эцэст нь скрипт рүү шилжье. Надад энд онцлон бахархах зүйл алга. Би зүгээр л бэлэн жишээ авлаа заавар мөн үүнийг бараг өөрчлөгдөөгүй ашигласан. Дууссан ажлын скрипт дараах байдалтай байна.

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)

Параметрт анхаарлаа хандуулаарай dry_run=Үнэн шугамын объектыг эхлүүлэх nr.
Энд байгаатай адилхан ойлгомжгүй чиглүүлэгчтэй холболт хийж, шинэ өөрчлөгдсөн тохиргоог бэлтгэж, дараа нь төхөөрөмжөөр баталгаажуулсан туршилтыг хэрэгжүүлсэн (гэхдээ энэ нь тодорхойгүй, төхөөрөмжийн дэмжлэг болон NAPALM дахь драйверын хэрэгжилтээс хамаарна) , гэхдээ шинэ тохиргоог шууд ашиглахгүй. Байлдааны зориулалтаар та параметрийг хасах хэрэгтэй хуурай_гүйлт эсвэл утгыг нь өөрчлөх хуурамч.

Скриптийг гүйцэтгэх үед Nornir нарийвчилсан бүртгэлийг консол руу гаргадаг.

Спойлерын доор хоёр туршилтын чиглүүлэгч дээр хийсэн байлдааны гаралт байна.

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

ansible_vault-д нууц үгээ нууж байна

Өгүүллийн эхэнд би арай хэтрэв ойлгомжгүй, гэхдээ энэ бүхэн тийм ч муу биш юм. Би тэдэнд үнэхээр дуртай амралт гэх мэт, энэ нь эмзэг мэдээллийг нүднээс далд нуух зорилготой юм. Бидэнд бүх байлдааны чиглүүлэгчийн бүх нэвтрэлт/нууц үг нээлттэй хэлбэрээр файлд байгаа гэдгийг олон хүн анзаарсан байх. gorups.yaml. Энэ нь мэдээжийн хэрэг хөөрхөн биш юм. Энэ өгөгдлийг ашиглан хамгаалцгаая амралт.

Group.yaml-аас creds.yaml руу параметрүүдийг шилжүүлж, AES256-аар 20 оронтой нууц үгээр шифрлэе:

$ 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

Ийм энгийн. Энэ нь бидэнд заах хэвээр байна Норнир-энэ өгөгдлийг татаж авах, хэрэглэх скрипт.
Үүнийг хийхийн тулд эхлүүлэх мөрийн дараа бидний скрипт дээр nr = InitNornir(config_file=… дараах кодыг нэмнэ:

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

Мэдээжийн хэрэг, vault.passwd нь миний жишээн дээрх шиг creds.yaml-ийн хажууд байх ёсгүй. Гэхдээ тоглоход зүгээр.

Одоохондоо ийм л байна. Cisco + Zabbix-ийн талаар хэд хэдэн нийтлэл гарч байгаа боловч энэ нь автоматжуулалтын тухай биш юм. Ойрын ирээдүйд би Cisco дээр RESTCONF-ийн талаар бичихээр төлөвлөж байна.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх