
Halo, Habr!
Baru-baru ini, sebuah artikel oleh Mikrotik dan Linux tergelincir di sini . Rutin dan otomatisasi di mana masalah serupa diselesaikan dengan cara fosil. Dan meskipun tugasnya sangat khas, tidak ada yang serupa ditemukan di Habré tentang hal itu. Saya berani menawarkan sepeda saya ke komunitas IT terkemuka.
Ini bukan motor pertama untuk tugas ini. Opsi pertama diimplementasikan beberapa tahun yang lalu pada versi yang dimungkinkan 1.x.x. Sepeda itu jarang digunakan dan karena itu terus berkarat. Dalam arti bahwa tugas itu sendiri tidak terjadi sesering versi yang diperbarui mungkin . Dan setiap kali Anda harus pergi, rantai akan jatuh, roda akan jatuh. Namun, bagian pertama, generasi konfigurasi - selalu berhasil dengan sangat jelas, karena mesin telah lama didirikan untuk jinja2 . Tetapi bagian kedua - meluncurkan konfigurasi, sebagai aturan membawa kejutan. Dan karena saya harus meluncurkan konfigurasi jarak jauh ke lantai ratusan perangkat, beberapa di antaranya ribuan kilometer jauhnya, itu agak menjengkelkan untuk menggunakan alat ini.
Di sini saya harus mengakui bahwa rasa tidak aman saya, lebih tepatnya, terletak pada kurangnya keakraban dengan memungkinkan saya daripada kekurangannya. Dan ini, omong-omong, adalah poin penting. ansible adalah domain pengetahuan yang benar-benar terpisah, dengan DSL (Domain Specific Language) sendiri, yang harus dipertahankan pada tingkat yang dapat diandalkan. Nah, momen yang memungkinkan berkembang cukup cepat, dan tanpa memperhatikan kompatibilitas ke belakang, tidak menambah kepercayaan diri.
Oleh karena itu, belum lama ini, versi kedua sepeda diimplementasikan. Kali ini dalam python , atau lebih tepatnya, dalam kerangka kerja yang ditulis dalam python dan untuk python disebut Nornir
Jadi - Nornir adalah mikroframework yang ditulis dengan python dan untuk python dan dimaksudkan untuk otomatisasi. Seperti dalam kasus yang memungkinkan , untuk menyelesaikan masalah, persiapan data yang kompeten diperlukan di sini yaitu. inventarisasi host dan parameternya, tetapi skrip tidak ditulis pada DSL yang terpisah, tetapi semuanya pada nada yang tidak terlalu lama, tetapi sangat bagus [dan | ah].
Mari kita lihat apa itu dalam contoh hidup berikut ini.
Saya memiliki jaringan cabang dengan beberapa lusin kantor di seluruh negeri. Di setiap kantor, ada router WAN yang mengakhiri beberapa saluran komunikasi dari operator yang berbeda. Protokol perutean adalah BGP. Ada dua jenis router WAN: Cisco ISG atau Juniper SRX.
Sekarang tugas: perlu mengkonfigurasi subnet khusus untuk pengawasan video di port terpisah pada semua router WAN dari jaringan cabang - umumkan subnet ini di BGP - konfigurasikan batas kecepatan untuk port khusus.
Pertama, kita perlu menyiapkan beberapa templat, berdasarkan konfigurasi mana yang akan dibuat secara terpisah untuk Cisco dan Juniper. Dan juga perlu menyiapkan data untuk setiap titik dan parameter koneksi yaitu untuk mengumpulkan inventaris itu
Template siap untuk 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
Templat untuk 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
Template, tentu saja, tidak diambil dari langit-langit. Ini pada dasarnya adalah perbedaan antara konfigurasi kerja-itu menjadi setelah menyelesaikan tugas pada dua router spesifik dari model yang berbeda.
Dari template kami, kami melihat bahwa bagi kami untuk menyelesaikan masalah, dua parameter untuk Juniper dan 3 parameter untuk Cisco sudah cukup. ini mereka:
Sekarang kita perlu mengatur parameter ini untuk setiap perangkat, mis. lakukan inventaris yang sama.
Untuk inventaris, kami akan dengan jelas mengikuti dokumentasi Inisialisasi Nornir
yaitu membuat kerangka file yang sama:
. ├── config.yaml ├── inventory │ ├── defaults.yaml │ ├── groups.yaml │ └── hosts.yaml
File config.yaml - file konfigurasi standar 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"
Kami akan menentukan parameter utama di file hosts.yaml , grup (dalam kasus saya, nama pengguna / kata sandi) di groups.yaml , dan kami tidak akan menentukan apa pun di defaults.yaml , tetapi harus ada tiga minus untuk menunjukkan bahwa ini adalah file yaml walaupun kosong.
Inilah yang tampak seperti 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
Dan ini adalah groups.yaml:
--- cisco: platform: ios username: admin1 password: cisco1 juniper: platform: junos username: admin2 password: juniper2
Ini adalah inventaris untuk tugas kita. Selama inisialisasi, parameter dari file inventaris dipetakan ke model objek InventoryElement .
Di bawah spoiler, diagram 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" } } } } }
Model ini mungkin terlihat sedikit membingungkan, terutama pada awalnya. Mode interaktif di ipython banyak membantu mengetahuinya.
$ 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'
Akhirnya, mari kita beralih ke skrip itu sendiri. Tidak ada yang istimewa yang bisa saya banggakan. Saya baru saja mengambil contoh yang sudah selesai dari tutorial dan menggunakannya hampir tanpa perubahan. Seperti inilah tampilan skrip yang berfungsi:
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):
Perhatikan dry_run = True parameter pada baris inisialisasi objek nr .
Di sini, seperti halnya dalam suatu kemungkinan , suatu uji coba dilaksanakan di mana koneksi ke router dibuat, konfigurasi baru yang berubah disiapkan, yang kemudian divalidasi oleh perangkat (tetapi ini tidak tepat; itu tergantung pada dukungan perangkat dan implementasi driver di NAPALM), tetapi konfigurasi baru tidak secara langsung diterapkan . Untuk penggunaan tempur, Anda harus menghapus parameter dry_run atau mengubah nilainya menjadi Salah .
Saat skrip berjalan, Nornir menampilkan log detail ke konsol.
Di bawah spoiler, kesimpulan pertempuran berjalan pada dua router uji: 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Menyembunyikan kata sandi di ansible_vault
Pada awal artikel, saya menemukan beberapa kemungkinan , tetapi tidak semuanya buruk di sana. Saya sangat suka brankas mereka, yang dirancang untuk menyembunyikan informasi sensitif dari pandangan. Dan mungkin banyak yang memperhatikan bahwa kita memiliki semua nama pengguna / kata sandi untuk semua router perang yang bersinar dalam bentuk terbuka di file groups.yaml . Itu jelek, tentu saja. Mari lindungi data ini dengan brankas .
Kami mentransfer parameter dari groups.yaml ke creds.yaml, dan mengenkripsinya dengan AES256 dengan kata sandi 20 digit:
$ 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
Sangat sederhana. Tetap mengajarkan skrip Nornir kami untuk mendapatkan dan menggunakan data ini.
Untuk melakukan ini, dalam skrip kami, setelah baris inisialisasi nr = InitNornir (config_file = ... tambahkan kode berikut:
... nr = InitNornir(config_file="config.yaml", dry_run=True)
Tentu saja, vault.passwd tidak boleh terletak di sebelah creds.yaml seperti dalam contoh saya. Tetapi untuk bermain itu akan dilakukan.
Itu saja untuk saat ini. Beberapa artikel tentang Cisco + Zabbix akan datang, tetapi ini tidak sedikit tentang otomatisasi. Dan dalam waktu dekat saya berencana untuk menulis tentang RESTCONF di Cisco.