Nornir аркылуу тармактык түзүлүштүн конфигурациясынын элементтерин автоматтык түрдө түзүү жана толтуруу

Nornir аркылуу тармактык түзүлүштүн конфигурациясынын элементтерин автоматтык түрдө түзүү жана толтуруу

Эй Хабр!

Жакында бул жерде бир макала пайда болду Mikrotik жана Linux. Режим жана автоматташтыруу ушуга окшош маселе фоссилдик каражаттардын жардамы менен чечилген. Ал эми тапшырма толугу менен мүнөздүү болсо да, Habréде ага окшош эч нерсе жок. Мен урматтуу IT коомчулугуна велосипедимди сунуштоого батындым.

Бул мындай тапшырма үчүн биринчи велосипед эмес. Биринчи параметр кайра бир нече жыл мурун ишке ашырылган түшүнүктүү версия 1.x.x. Велосипед сейрек колдонулгандыктан, дайыма дат басып калган. Бул тапшырманын өзү версиялар жаңыртылган сайын пайда болбойт деген мааниде түшүнүктүү. Ал эми айдоо керек болгон сайын чынжыр же дөңгөлөк түшүп калат. Бирок, конфигурацияларды жаратуучу биринчи бөлүк, бактыга жараша, ар дайым абдан так иштейт jinja2 Мотор узак убакыттан бери орнотулган. Бирок экинчи бөлүк - конфигурацияларды жайылтуу - адатта күтүлбөгөн нерселерди алып келди. Мен конфигурацияны алыстан жарым жүздөй түзмөккө жайылтышым керек болгондуктан, алардын айрымдары миңдеген километр алыстыкта ​​жайгашкан, бул куралды колдонуу бир аз кызыксыз болду.

Бул жерде менин белгисиздигим, ​​балким, тааныштыгымдын жоктугунан экенин моюнга алышым керек түшүнүктүүкемчиликтерине караганда. Жана бул, демек, маанилүү жагдай. түшүнүктүү толугу менен өзүнчө, өзүнүн DSL (домендик тил) менен өз билим чөйрөсү болуп саналат, ал ишенимдүү деңгээлде сакталышы керек. Ооба, ошол учур түшүнүктүү Ал тездик менен өнүгүп жатат жана артта калган шайкештикке өзгөчө маани бербестен, ишенимди арттырбайт.

Ошондуктан, жакында эле велосипеддин экинчи версиясы ишке киргизилген. Бул жолу код, же тагыраак айтканда, жазылган алкак боюнча код жана код Атындагы Nornir

Ошентип - Nornir ичинде жазылган микрофремворк болуп саналат код жана код жана автоматташтыруу учун арналган. менен болгон учурда эле түшүнүктүү, бул жерде көйгөйлөрдү чечүү үчүн, компетенттүү маалыматтарды даярдоо талап кылынат, б.а. хосттордун жана алардын параметрлеринин инвентаризациясы, бирок сценарийлер өзүнчө DSLде эмес, ошол эле өтө эски эмес, бирок абдан жакшы p[i|i]ton менен жазылган.

Келгиле, бул төмөнкү жандуу мисалды колдонуп жатканын карап көрөлү.

Менин өлкө боюнча бир нече ондогон кеңселери бар филиал тармагым бар. Ар бир кеңседе ар кандай операторлордун бир нече байланыш каналдарын токтоткон WAN роутери бар. Багыттоо протоколу BGP болуп саналат. WAN роутерлери эки түрдүү болот: Cisco ISG же Juniper SRX.

Эми тапшырма: Сиз Филиалдык тармактын бардык WAN роутерлеринде өзүнчө портто Видеокөзөмөл үчүн бөлүнгөн субтармакты конфигурациялашыңыз керек - бул субсетти BGPде жарнамалаңыз - бөлүнгөн порттун ылдамдык чегин конфигурациялаңыз.

Биринчиден, биз бир нече шаблонду даярдашыбыз керек, алардын негизинде конфигурациялар Cisco жана Juniper үчүн өзүнчө түзүлөт. Ошондой эле ар бир чекит жана байланыш параметрлери үчүн маалыматтарды даярдоо керек, б.а. ошол эле запастарды чогултуу

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

Эми биз ар бир түзмөк үчүн бул параметрлерди орнотуу керек, б.а. ошол эле нерсени кыл жолу.

үчүн жолу Документти так сактайбыз Nornir инициализациялоо

башкача айтканда, ошол эле файлдын скелетин түзөлү:

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

config.yaml файлы стандарттуу 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"

Биз файлдагы негизги параметрлерди көрсөтөбүз 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

Жана бул жерде groups.yaml:

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

juniper:
    platform: junos
    username: admin2
    password: juniper2

Бул эмне болду жолу биздин милдетибиз үчүн. Инвентаризациялоо учурунда инвентарлык файлдардын параметрлери объект моделине түшүрүлөт InventoryElement.

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

Бул модель, өзгөчө, башында бир аз баш аламан көрүнүшү мүмкүн. Аны түшүнүү үчүн, интерактивдүү режим 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'

Акыр-аягы, сценарийдин өзүнө өтөбүз. Мен бул жерде өзгөчө сыймыктана турган эч нерсем жок. Мен жөн гана даяр мисалды алдым окуу куралы жана дээрлик өзгөрүүсүз колдонулат. Бул даяр жумушчу скрипт окшойт:

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де драйвердин ишке ашырылышына көз каранды) , бирок жаңы конфигурация түз колдонулбайт. Күжүрмөн колдонуу үчүн параметрди алып салуу керек dry_run же анын маанисин өзгөртүү False.

Скрипт аткарылганда, 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. Бул, албетте, сулуу эмес. Бул маалыматтарды коргойлу жыйнак.

Параметрлерди groups.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

Бул жөнөкөй. Бизге үйрөтүү калды Nornir-скрипт бул маалыматтарды алуу жана колдонуу үчүн.
Бул үчүн, инициализация сызыгынан кийин биздин скриптте 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 жөнүндө жазууну пландап жатам.

Source: www.habr.com

Комментарий кошуу