Nornir๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์žฅ์น˜ ๊ตฌ์„ฑ ์š”์†Œ ์ž๋™ ์ƒ์„ฑ ๋ฐ ์ฑ„์šฐ๊ธฐ

Nornir๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์žฅ์น˜ ๊ตฌ์„ฑ ์š”์†Œ ์ž๋™ ์ƒ์„ฑ ๋ฐ ์ฑ„์šฐ๊ธฐ

ํ—ค์ด ํ•˜๋ธŒ๋ฅด!

์ตœ๊ทผ์— ์—ฌ๊ธฐ์— ๊ธฐ์‚ฌ๊ฐ€ ๋–ด์–ด์š” ๋ฏธํฌ๋กœํ‹ฑ๊ณผ ๋ฆฌ๋ˆ…์Šค. ๋ฃจํ‹ด ๋ฐ ์ž๋™ํ™” ํ™”์„ ์ˆ˜๋‹จ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„์Šทํ•œ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ์™„์ „ํžˆ ์ผ๋ฐ˜์ ์ด์ง€๋งŒ Habrรฉ์—์„œ๋Š” ๋น„์Šทํ•œ ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ฐํžˆ ๋‚ด ์ž์ „๊ฑฐ๋ฅผ ์กด๊ฒฝ๋ฐ›๋Š” IT ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์ œ๊ณตํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ž์ „๊ฑฐ๋Š” ์ด๋ฒˆ์ด ์ฒ˜์Œ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์˜ต์…˜์€ ๋ช‡ ๋…„ ์ „์— ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋จน์„ ์ˆ˜์žˆ๋Š” ๋ฒ„์ „ 1.x.x. ์ž์ „๊ฑฐ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ง€์†์ ์œผ๋กœ ๋…น์Šฌ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฒ„์ „์ด ์—…๋ฐ์ดํŠธ๋  ๋•Œ๋งˆ๋‹ค ์ž‘์—… ์ž์ฒด๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์—์„œ ๋จน์„ ์ˆ˜์žˆ๋Š”. ๊ทธ๋ฆฌ๊ณ  ์šด์ „ํ•ด์•ผ ํ•  ๋•Œ๋งˆ๋‹ค ์ฒด์ธ์ด ๋–จ์–ด์ง€๊ฑฐ๋‚˜ ๋ฐ”ํ€ด๊ฐ€ ๋–จ์–ด์ ธ ๋‚˜๊ฐ‘๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ตฌ์„ฑ์„ ์ƒ์„ฑํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ถ€๋ถ„์€ ๋‹คํ–‰ํžˆ ํ•ญ์ƒ ๋งค์šฐ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ง„์ž 2 ์—”์ง„์€ ์˜ค๋žซ๋™์•ˆ ํ™•๋ฆฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‘ ๋ฒˆ์งธ ๋ถ€๋ถ„์ธ ๊ตฌ์„ฑ ์ถœ์‹œ๋Š” ๋Œ€๊ฐœ ๋†€๋ผ์›€์„ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ตฌ์„ฑ์„ XNUMX๊ฐœ์˜ ์žฅ์น˜์— ์›๊ฒฉ์œผ๋กœ ๋กค์•„์›ƒํ•ด์•ผ ํ•˜๋ฉฐ ๊ทธ ์ค‘ ์ผ๋ถ€๋Š” ์ˆ˜์ฒœ ํ‚ฌ๋กœ๋ฏธํ„ฐ ๋–จ์–ด์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์•ฝ๊ฐ„ ์ง€๋ฃจํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋‚˜๋Š” ๋‚˜์˜ ๋ถˆํ™•์‹ค์„ฑ์ด ๋‚ด๊ฐ€ ๊ฐ€์ง„ ์ง€์‹์˜ ๋ถ€์กฑ์— ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๊ฐ€์žฅ ๋†’๋‹ค๋Š” ์ ์„ ์ธ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋จน์„ ์ˆ˜์žˆ๋Š”๋‹จ์ ๋ณด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๊ฒƒ์€ ์ค‘์š”ํ•œ ํฌ์ธํŠธ์ž…๋‹ˆ๋‹ค. ๋จน์„ ์ˆ˜์žˆ๋Š” ์™„์ „ํžˆ ๋ถ„๋ฆฌ๋œ ์ž์ฒด ์ง€์‹ ์˜์—ญ์œผ๋กœ, ์ž์ฒด DSL(๋„๋ฉ”์ธ ํŠน์ • ์–ธ์–ด)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž์‹ ๊ฐ ์žˆ๋Š” ์ˆ˜์ค€์œผ๋กœ ์œ ์ง€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ญ, ๊ทธ ์ˆœ๊ฐ„ ๊ทธ ๋จน์„ ์ˆ˜์žˆ๋Š” ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ํŠน๋ณ„ํžˆ ๊ณ ๋ คํ•˜์ง€ ์•Š์œผ๋ฉด ์ž์‹ ๊ฐ์ด ์ถ”๊ฐ€๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์–ผ๋งˆ ์ „ ์ž์ „๊ฑฐ์˜ ๋‘ ๋ฒˆ์งธ ๋ฒ„์ „์ด ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ํŒŒ์ด์ฌ, ๋˜๋Š” ์˜คํžˆ๋ ค ๋‹ค์Œ์œผ๋กœ ์ž‘์„ฑ๋œ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ํŒŒ์ด์ฌ ~์„ ์œ„ํ•ด ํŒŒ์ด์ฌ ์ž๊ฒฉ ๋…ธ๋ฅด๋‹ˆ๋ฅด

๊ทธ๋ž˜์„œ - ๋…ธ๋ฅด๋‹ˆ๋ฅด ๋‹ค์Œ์œผ๋กœ ์ž‘์„ฑ๋œ ๋งˆ์ดํฌ๋กœํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ ~์„ ์œ„ํ•ด ํŒŒ์ด์ฌ ์ž๋™ํ™”๋ฅผ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์˜ ๊ฒฝ์šฐ์™€ ๋™์ผ ๋จน์„ ์ˆ˜์žˆ๋Š”, ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์œ ๋Šฅํ•œ ๋ฐ์ดํ„ฐ ์ค€๋น„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ˜ธ์ŠคํŠธ ๋ฐ ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์ธ๋ฒคํ† ๋ฆฌ์ด์ง€๋งŒ ์Šคํฌ๋ฆฝํŠธ๋Š” ๋ณ„๋„์˜ 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

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

๋ฌผ๋ก  ํ…œํ”Œ๋ฆฟ์€ ๊ฐ‘์ž๊ธฐ ๋‚˜์˜ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ณธ์งˆ์ ์œผ๋กœ ์„œ๋กœ ๋‹ค๋ฅธ ๋ชจ๋ธ์˜ ๋‘ ํŠน์ • ๋ผ์šฐํ„ฐ์—์„œ ์ž‘์—…์„ ํ•ด๊ฒฐํ•œ ์ž‘์—… ๊ตฌ์„ฑ๊ณผ ํ•ด๊ฒฐํ•œ ์ดํ›„์˜ ์ž‘์—… ๊ตฌ์„ฑ ๊ฐ„์˜ ์ฐจ์ด์ ์ž…๋‹ˆ๋‹ค.

ํ…œํ”Œ๋ฆฟ์—์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด Juniper์˜ ๊ฒฝ์šฐ ๋งค๊ฐœ๋ณ€์ˆ˜ 3๊ฐœ, Cisco์˜ ๊ฒฝ์šฐ ๋งค๊ฐœ๋ณ€์ˆ˜ XNUMX๊ฐœ๋งŒ ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ifname
  • IP์ ‘๋ฏธ์‚ฌ
  • asn

์ด์ œ ๊ฐ ์žฅ์น˜์— ๋Œ€ํ•ด ์ด๋Ÿฌํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ ์ผ์„ ๋ชฉ๋ก.

์— ๋ชฉ๋ก ์šฐ๋ฆฌ๋Š” ๋ฌธ์„œ๋ฅผ ์—„๊ฒฉํžˆ ๋”ฐ๋ฅผ ๊ฒƒ์ž…๋‹ˆ๋‹ค ๋…ธ๋ฅด๋‹ˆ๋ฅด ์ดˆ๊ธฐํ™”

์ฆ‰, ๋™์ผํ•œ ํŒŒ์ผ ๋ผˆ๋Œ€๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

.
โ”œโ”€โ”€ 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"

ํŒŒ์ผ์— ์ฃผ์š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํ˜ธ์ŠคํŠธ.yaml, ๊ทธ๋ฃน(์ œ ๊ฒฝ์šฐ์—๋Š” ๋กœ๊ทธ์ธ/๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค) ๊ทธ๋ฃน.yaml, ๋ฐ c defaults.yaml ์šฐ๋ฆฌ๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ‘œ์‹œํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด์ง€๋งŒ ๊ฑฐ๊ธฐ์— ์„ธ ๊ฐœ์˜ ๋งˆ์ด๋„ˆ์Šค๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์–Œ ํ•˜์ง€๋งŒ ํŒŒ์ผ์€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

Host.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 ๋ชจ๋ธ์˜ ๋‹ค์ด์–ด๊ทธ๋žจ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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. ๋ฌผ๋ก  ์˜ˆ์˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณดํ˜ธํ•ฉ์‹œ๋‹ค. ๋†’์ด๋›ฐ๊ธฐ.

groups.yaml์—์„œ creds.yaml๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „์†กํ•˜๊ณ  256์ž๋ฆฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ AES20์œผ๋กœ ์•”ํ˜ธํ™”ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ 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์— ๊ด€ํ•ด ๊ธ€์„ ์“ธ ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€