てくなべ (tekunabe)

ansible / network / automation

[Ansible] 「ポート管理表+パラメータ表+Jinja2テンプレートから、L2SWのConfigを自動生成してみた」を Ansible で

■ はじめに

以下の記事(以下、元記事)を拝見し、Ansible でもできるかなと思って試してみました。

qiita.com

開発中の Ansible 2.8 で導入予定の read_csv モジュールを利用しています。現在(2019/04/02)安定版の Ansible 2.7 系では利用できませんのでご注意ください。read_csv モジュール を利用しない方法もできると思いますが、少し複雑になるでしょう。

  • Ansible devel バージョン (2019/04/02時点)


■ 用意するもの

ポート管理表

元記事と同じものです。

  • port_list_hqaccess1.csv
port_no,speed,duplex,mode,vlan,portfast,status,description
FastEthernet0/1,auto,auto,access,100,o,x,To PC1
FastEthernet0/2,auto,auto,access,100,o,x,To PC2
FastEthernet0/3,auto,auto,access,100,o,x,
FastEthernet0/4,auto,auto,access,100,o,x,
FastEthernet0/5,auto,auto,access,100,o,x,To PC3
FastEthernet0/6,auto,auto,access,100,o,x,To PC4
FastEthernet0/7,auto,auto,access,100,o,x,
FastEthernet0/8,auto,auto,access,100,o,x,
GigabitEthernet0/1,1000,full,access,100,o,x,To hqdist1 Gi0/0/1
GigabitEthernet0/1,1000,full,access,100,o,x,To hqdist2 Gi0/0/1

パラメータ表

元記事と同じものです。

  • parameter_list_hqaccess1.csv
hostname,hardware,secret,username,password,vlan_num,vlan_desc,ip_address,subnet,default_gw,ntp_server
hqdist1,catalyst2960,test,test,cisco,100,<< Server Segment >>,192.168.100.47,255.255.255.0,192.168.100.150,192.168.100.44

Jinja2

ほぼ、元記事と同じものです。

Ansibleで試すと改行が入ってほしいところに入らなかったので、少し調整しました。

  • catalyst2960_template.j2

クリックして展開する

!
no service pad
service timestamps debug datetime localtime
service timestamps log datetime localtime
service password-encryption
!
hostname {{ hostname }}
!
no logging console
enable secret {{ secret }}
!
username {{ username }} privilege 15 password {{ password }}
clock timezone JST 9
ip subnet-zero
no ip domain-lookup
ip domain-name {{ hostname }}
ip ssh version 2
!
spanning-tree mode pvst
no spanning-tree optimize bpdu transmission
spanning-tree extend system-id
!
!
{% for item in interfaces %}
interface {{ item.port_no }}
{% if item.description != '' %}
 description << {{ item.description }} >>
{% endif %}
{% if item.mode == 'access' %}
 switchport access {{ item.vlan }}
 switchport mode access
{% elif item.mode == 'trunk' %}
 switchport mode trunk
{% endif %}
{% if item.duplex != 'auto' %}
 duplex {{ item.duplex }}
{% endif %}
{% if item.speed != 'auto' %}
 speed {{ item.speed }}
{% endif %}
{% if item.status == 'x' %}
 shutdown
{% endif %}
{% if item.portfast == 'o' %}
 spanning-tree portfast
{% endif %}
!
{% endfor %}
!
interface Vlan1
 no ip address
 no ip route-cache
 shutdown
!
interface Vlan{{ vlan_num }}
 description {{ vlan_desc }}
 ip address {{ ip_address }} {{ subnet }}
 no ip route-cache
!
ip default-gateway {{ default_gw }}
no ip http server
no ip http secure-server
!
logging 192.168.100.107
snmp-server community C1sc0 RO
snmp-server host 192.168.100.107 C1sc0 
banner login ^C
============NOTICE==============
| This is test device for demo |
================================
^C
!
line con 0
line vty 0 4
 login local
line vty 5 15
 login local
!
ntp server {{ ntp_server }}
!
crypto key generate rsa modulus 2048
!
end

Playbook

元記事の config_generation.py に相当する Playbook です。

name: adjust..(略)... の2つのタスクは、元記事の Jinja2 テンプレートの変数構造に合わせるための調整です。テンプレート側を変更すれば、これらのタスクは不要になります。特に、adjust parameter variables のほうは、やや乱暴な調整をしているので、テンプレート側で調整したほうが良いかもしれません。

また、今回の要件では、Ansible を実行しているホストのローカルで動けばよいので、対象ホストは localhost、コネクション方式は local を指定しています。

  • build_template.yml
- hosts: localhost
  gather_facts: no
  connection: local
  
  vars:
    TEMPLATE: ./catalyst2960_template.j2
    PARAMETER_LIST: ./parameter_list_hqaccess1.csv
    PORT_LIST: ./port_list_hqaccess1.csv
    CONFIG_FILENAME: ./config_hqaccess1.txt
  
  tasks:
    - name: read interfaces
      read_csv:
        path: "{{ PORT_LIST }}"
      register: interfaces_temp
    
    - name: read params
      read_csv:
        path: "{{ PARAMETER_LIST }}"
      register: params_temp

    - name: adjust interface variable
      set_fact:
        interfaces: "{{ interfaces_temp.list }}"

    - name: adjust parameter variables
      set_fact:
        "{{ item.key }}": "{{ item.value }}"
      loop: "{{ lookup('dict', params_temp.list[0]) }}"

    - name: build template
      template:
        src: "{{ TEMPLATE }}"
        dest: "{{ CONFIG_FILENAME }}"


■ 実行

Playbookを実行します。インベントリファイルは用意せず、-i オプションでは -i localhost, のよううに host_list の形式で指定しています。

$ ansible-playbook -i localhost, build_template.yml

PLAY [localhost] *******************************************************************************************************

TASK [read  interfaces] ***********************************************************************************************************
ok: [localhost]

TASK [read params] ***********************************************************************************************************
ok: [localhost]

TASK [adjust interface variable] ***************************************************************************************
ok: [localhost]

TASK [adjust parameter variables] **************************************************************************************
ok: [localhost] => (item={'key': u'username', 'value': u'test'})
ok: [localhost] => (item={'key': u'subnet', 'value': u'255.255.255.0'})
ok: [localhost] => (item={'key': u'hostname', 'value': u'hqdist1'})
ok: [localhost] => (item={'key': u'secret', 'value': u'test'})
ok: [localhost] => (item={'key': u'default_gw', 'value': u'192.168.100.150'})
ok: [localhost] => (item={'key': u'hardware', 'value': u'catalyst2960'})
ok: [localhost] => (item={'key': u'vlan_num', 'value': u'100'})
ok: [localhost] => (item={'key': u'vlan_desc', 'value': u'<< Server Segment >>'})
ok: [localhost] => (item={'key': u'ntp_server', 'value': u'192.168.100.44'})
ok: [localhost] => (item={'key': u'password', 'value': u'cisco'})
ok: [localhost] => (item={'key': u'ip_address', 'value': u'192.168.100.47'})

TASK [build template] **************************************************************************************************
changed: [localhost]

PLAY RECAP *************************************************************************************************************
localhost                  : ok=5    changed=1    unreachable=0    failed=0    skipped=0

できあがったコンフィグファイルです。

クリックして展開する

!
no service pad
service timestamps debug datetime localtime
service timestamps log datetime localtime
service password-encryption
!
hostname hqdist1
!
no logging console
enable secret test
!
username test privilege 15 password cisco
clock timezone JST 9
ip subnet-zero
no ip domain-lookup
ip domain-name hqdist1
ip ssh version 2
!
spanning-tree mode pvst
no spanning-tree optimize bpdu transmission
spanning-tree extend system-id
!
!
interface FastEthernet0/1
 description << To PC1 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/2
 description << To PC2 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/3
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/4
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/5
 description << To PC3 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/6
 description << To PC4 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/7
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/8
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface GigabitEthernet0/1
 description << To hqdist1 Gi0/0/1 >>
 switchport access 100
 switchport mode access
 duplex full
 speed 1000
 shutdown
 spanning-tree portfast
!
interface GigabitEthernet0/1
 description << To hqdist2 Gi0/0/1 >>
 switchport access 100
 switchport mode access
 duplex full
 speed 1000
 shutdown
 spanning-tree portfast
!
!
interface Vlan1
 no ip address
 no ip route-cache
 shutdown
!
interface Vlan100
 description << Server Segment >>
 ip address 192.168.100.47 255.255.255.0
 no ip route-cache
!
ip default-gateway 192.168.100.150
no ip http server
no ip http secure-server
!
logging 192.168.100.107
snmp-server community C1sc0 RO
snmp-server host 192.168.100.107 C1sc0 
banner login ^C
============NOTICE==============
| This is test device for demo |
================================
^C
!
line con 0
line vty 0 4
 login local
line vty 5 15
 login local
!
ntp server 192.168.100.44
!
crypto key generate rsa modulus 2048
!
end


■ まとめ

Ansible と Jinja2 を利用して、パラメータとなる CSV からコンフィグを生成させることができました。 Ansible が備える変数管理の仕組みを活用すれば、もっと応用ができるようになると思います。

今回のように、Jinja2 内の制御文があまり複雑でない場合や、他の処理を組み合わせなくて良い場合は Ansible でもよさそうです。一方で、もっと準備に複雑な処理を必要とする場合は、元記事のように Python などで書くのが良いと思います。