Này Habr!
Gần đây có một bài viết xuất hiện ở đây
Đây không phải là chiếc xe đạp đầu tiên thực hiện nhiệm vụ như vậy. Tùy chọn đầu tiên đã được triển khai cách đây vài năm vào năm ansible phiên bản 1.x.x. Xe đạp ít được sử dụng nên thường xuyên bị rỉ sét. Theo nghĩa là bản thân nhiệm vụ không phát sinh thường xuyên khi các phiên bản được cập nhật ansible. Và mỗi khi bạn cần lái xe, xích lại rơi ra hoặc bánh xe bị rơi ra. Tuy nhiên, phần đầu tiên tạo config luôn hoạt động rất rõ ràng, thật may mắn jinja2 Động cơ đã được thiết lập từ lâu. Nhưng phần thứ hai - tung ra cấu hình - thường mang đến những điều bất ngờ. Và vì tôi phải triển khai cấu hình từ xa cho nửa trăm thiết bị, một số thiết bị ở cách xa hàng nghìn km nên việc sử dụng công cụ này hơi nhàm chán.
Ở đây tôi phải thừa nhận rằng sự không chắc chắn của tôi rất có thể nằm ở việc tôi chưa quen với ansiblehơn những thiếu sót của nó. Và nhân tiện, đây là một điểm quan trọng. ansible là một lĩnh vực kiến thức hoàn toàn riêng biệt, có DSL (Ngôn ngữ cụ thể miền) riêng, phải được duy trì ở mức độ tin cậy. Chà, khoảnh khắc đó ansible Nó đang phát triển khá nhanh và không có sự quan tâm đặc biệt đến khả năng tương thích ngược nên nó không tạo thêm sự tự tin.
Vì vậy, cách đây không lâu, phiên bản thứ hai của xe đạp đã được triển khai. Lần này trên mãng xà, hay đúng hơn là trên một khung được viết bằng mãng xà và vì mãng xà được gọi là
Vì thế - Nornir là một microframework được viết bằng mãng xà và vì mãng xà và được thiết kế để tự động hóa. Tương tự như trường hợp với ansible, để giải quyết vấn đề ở đây, cần phải chuẩn bị dữ liệu có thẩm quyền, tức là. kiểm kê các máy chủ và các tham số của chúng, nhưng các tập lệnh được viết không phải bằng một DSL riêng biệt mà bằng p[i|i]ton không cũ lắm nhưng rất tốt.
Hãy xem nó đang làm gì bằng ví dụ trực tiếp sau đây.
Tôi có mạng lưới chi nhánh với vài chục văn phòng khắp cả nước. Mỗi văn phòng có một bộ định tuyến WAN kết cuối một số kênh liên lạc từ các nhà khai thác khác nhau. Giao thức định tuyến là BGP. Bộ định tuyến WAN có hai loại: Cisco ISG hoặc Juniper SRX.
Bây giờ, nhiệm vụ: bạn cần định cấu hình mạng con chuyên dụng cho Giám sát video trên một cổng riêng trên tất cả các bộ định tuyến WAN của mạng nhánh - quảng cáo mạng con này trong BGP - định cấu hình giới hạn tốc độ của cổng chuyên dụng.
Đầu tiên, chúng ta cần chuẩn bị một số mẫu, trên cơ sở đó sẽ tạo cấu hình riêng cho Cisco và Juniper. Cũng cần chuẩn bị dữ liệu cho từng điểm và thông số kết nối, tức là. thu thập cùng một khoảng không quảng cáo
Mẫu sẵn sàng cho 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
Mẫu cho 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
Tất nhiên, các mẫu không tự nhiên xuất hiện. Về cơ bản, đây là những khác biệt giữa các cấu hình hoạt động trước đây và sau khi giải quyết tác vụ trên hai bộ định tuyến cụ thể của các kiểu máy khác nhau.
Từ các mẫu của chúng tôi, chúng tôi thấy rằng để giải quyết vấn đề, chúng tôi chỉ cần hai tham số cho Juniper và 3 tham số cho Cisco. họ đây rồi:
- nếu tên
- hậu tố ip
- mông
Bây giờ chúng ta cần đặt các tham số này cho từng thiết bị, tức là. Làm điều tương tự hàng tồn kho.
vì hàng tồn kho Chúng tôi sẽ tuân thủ nghiêm ngặt các tài liệu
nghĩa là, hãy tạo bộ khung tập tin tương tự:
.
├── config.yaml
├── inventory
│ ├── defaults.yaml
│ ├── groups.yaml
│ └── hosts.yaml
File config.yaml là file cấu hình nornir tiêu chuẩn
$ 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"
Chúng tôi sẽ chỉ ra các tham số chính trong tập tin máy chủ.yaml, nhóm (trong trường hợp của tôi đây là thông tin đăng nhập/mật khẩu) trong nhóm.yamlvà trong mặc định.yaml Chúng tôi sẽ không chỉ ra bất cứ điều gì, nhưng bạn cần nhập ba điểm trừ vào đó - cho biết rằng đó là khoai mỡ mặc dù tập tin trống.
Đây là giao diện của 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
Và đây là nhóm.yaml:
---
cisco:
platform: ios
username: admin1
password: cisco1
juniper:
platform: junos
username: admin2
password: juniper2
Đây là những gì đã xảy ra hàng tồn kho cho nhiệm vụ của chúng tôi. Trong quá trình khởi tạo, các tham số từ tệp kiểm kê được ánh xạ tới mô hình đối tượng Phần tử tồn kho.
Bên dưới spoiler là sơ đồ của mô hình 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"
}
}
}
}
}
Mô hình này có thể trông hơi khó hiểu, đặc biệt là lúc đầu. Để tìm ra điều đó, chế độ tương tác trong con trăn.
$ 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'
Và cuối cùng, hãy chuyển sang phần kịch bản. Tôi không có gì đặc biệt để tự hào ở đây. Tôi vừa lấy một ví dụ làm sẵn từ
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)
Chú ý đến tham số dry_run=Đúng khởi tạo đối tượng trong dòng nr.
Ở đây giống như ở ansible một lần chạy thử đã được triển khai trong đó kết nối với bộ định tuyến được thực hiện, một cấu hình sửa đổi mới được chuẩn bị, sau đó được thiết bị xác thực (nhưng điều này không chắc chắn; nó phụ thuộc vào sự hỗ trợ của thiết bị và việc triển khai trình điều khiển trong NAPALM) , nhưng cấu hình mới không được áp dụng trực tiếp. Để sử dụng trong chiến đấu, bạn phải loại bỏ tham số Dry_run hoặc thay đổi giá trị của nó thành Sai.
Khi tập lệnh được thực thi, Nornir xuất nhật ký chi tiết ra bảng điều khiển.
Bên dưới spoiler là kết quả của quá trình chạy chiến đấu trên hai bộ định tuyến thử nghiệm:
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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ẩn mật khẩu trong ansible_vault
Ở đầu bài viết tôi đã hơi quá nhiệt tình ansible, nhưng nó không đến nỗi tệ đến thế. Tôi thực sự thích họ kho như, được thiết kế để che giấu thông tin nhạy cảm khỏi tầm mắt. Và có lẽ nhiều người đã nhận thấy rằng chúng tôi có tất cả thông tin đăng nhập/mật khẩu cho tất cả các bộ định tuyến chiến đấu ở dạng mở trong một tệp nhóm.yaml. Tất nhiên là nó không đẹp. Hãy bảo vệ dữ liệu này với kho.
Hãy chuyển các tham số từgroup.yaml sang creds.yaml và mã hóa nó bằng AES256 bằng mật khẩu 20 chữ số:
$ 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
Nó đơn giản mà. Việc còn lại là dạy chúng ta Nornir-script để truy xuất và áp dụng dữ liệu này.
Để làm điều này, trong tập lệnh của chúng tôi sau dòng khởi tạo nr = InitNornir(config_file=… thêm đoạn mã sau:
...
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
...
Tất nhiên, vault.passwd không nên đặt cạnh creds.yaml như trong ví dụ của tôi. Nhưng chơi thì ok.
Đó là tất cả cho bây giờ. Sẽ có thêm một vài bài viết về Cisco + Zabbix, nhưng đây không phải là một chút về tự động hóa. Và sắp tới tôi dự định viết về RESTCONF trên Cisco.
Nguồn: www.habr.com