APRESIA Technical Blog

Ansibleでホワイトボックススイッチの設定自動化(Cumulus Linux編)

今回ご紹介するのは、Ansibleを用いたネットワーク設定の管理および自動化です。
前回は、ZTPでホワイトボックススイッチの構築自動化でZTPを使用した初期構築の自動化を紹介しているのでこちらも参考下さい。
Ansibleを使用することで、ネットワークの構成管理をコード化できます。コード化によるメリットとしては、
  • 人的ミスの削減、作業工数の削減
  • テンプレート利用により、ロールで部品化し、簡単に再利用(設定変更/追加)が出来る
  • 属人化の防止
等々ですね。
私のホワイトボックススイッチのテスト環境では、よくNetworkOSを入れ替えて、構成を壊したり、再構築したりを繰り返したりしているのでAnsibleで構成管理しています。
その環境を紹介していきます。

ネットワーク構成

Cumulus Linuxで構築したEVPN-VXLAN構成を例に紹介していきます。以下に物理構成および論理構成を示します。
Configなど設定情報の詳細については、Cumulus LinuxでVXLANルーティング構築を参考下さい。
物理構成
論理構成

Playbookの構成

- hosts # インベントリファイル - deploy.yml # playbookの起点となるファイル。ファイル名は任意。 - ansible.cfg - group_vars all # テンプレート内で使用する変数を記載 - roles # 設定ロールを記載するフォルダ - bgp-peer # BGP Unnumbered設定に関するロール - tasks main.yml - exit # Exit01(外接LeafSW)に限定した設定に関するロール - tasks main.yml - general # I/FやVLAN設定に関するロール - tasks main.yml - leaf # LeafSWのEVPN-VXLAN設定に関するロール - tasks main.yml - spine # SpineSWのEVPN-VXLAN設定に関するロール - tasks main.yml
playbook構成

hosts

インベントリに相当するファイルです。[xxx.chirdren]と記載することで、グルーピング化することもできます。
[leaf] leaf01 ansible_ssh_host=192.168.100.90 leaf02 ansible_ssh_host=192.168.100.91 [spine] spine01 ansible_ssh_host=192.168.100.85 spine02 ansible_ssh_host=192.168.100.86 [exit] exit01 ansible_ssh_host=192.168.100.94 [leafs:children] leaf exit [spines:children] spine [exits:children] exit [all:vars] ansible_ssh_port=22 ansible_ssh_user=cumulus ansible_ssh_pass=CumulusLinux! ansible_sudo_pass=CumulusLinux!

deploy.yml

hostsにて実行対象となる装置グループを指定し、rolesにて実行したいロールを指定します。
今回の例では、タスクを直接記載せずに、全てのタスクをロールとして纏めています。
ロールとして纏めること再利用(設定追加など)が可能な事と、下記のとおりPlabookをシンプル化し、可読性が高くなるのでロールを使用しています。
--- - hosts: - spines user: cumulus become: yes become_method: sudo roles: - general - bgp-peer - spine - hosts: - leafs user: cumulus become: yes become_method: sudo roles: - general - bgp-peer - leaf - hosts: - exits gather_facts: False user: cumulus become: yes become_method: sudo roles: - exit

group_vars/all

実際の設定情報を記載するファイルです。それぞれの設定項目は、後述するテンプレート内で変数として扱うことが可能です。Exit01を例に解説を記載したので参考下さい。
nodes: leaf01: loopback: "10.0.0.11" asn: 65011 vxlans: 10100: vid: "100" 10200: vid: "200" 104001: vid: "4001" vrf: "vrf1" 104002: vid: "4002" vrf: "vrf2" neighbors: swp49: swp50: svis: 100: ipv4: "172.16.100.1/24" vrf: "vrf1" 200: ipv4: "172.16.200.1/24" vrf: "vrf2" 4001: vrf: "vrf1" 4002: vrf: "vrf2" ports: swp1: mode: Tagged vlans: - 100 - 200 swp49: speed: 40000 swp50: speed: 40000 leaf02: loopback: "10.0.0.12" asn: 65012 vxlans: 10100: vid: "100" 10200: vid: "200" 10101: vid: "101" 10201: vid: "201" 104001: vid: "4001" vrf: "vrf1" 104002: vid: "4002" vrf: "vrf2" neighbors: swp49: swp50: svis: 100: ipv4: "172.16.100.1/24" vrf: "vrf1" 200: ipv4: "172.16.200.1/24" vrf: "vrf2" 101: ipv4: "172.16.101.1/24" vrf: "vrf1" 201: ipv4: "172.16.201.1/24" vrf: "vrf2" 4001: vrf: "vrf1" 4002: vrf: "vrf2" ports: swp1: mode: Tagged vlans: - 100 - 101 - 200 - 201 swp49: speed: 40000 swp50: speed: 40000 exit01: # ノードの名前 loopback: "10.0.0.41" # loopbackで設定するIPアドレス asn: 65041 # AS番号 vxlans: # VXLAN <-> VLANのマッピング設定 104001: # VNI vid: "4001" # マッピングするVLANのVID vrf: "vrf1" # L3VNIの場合はVRFを指定 104002: vid: "4002" vrf: "vrf2" neighbors: # 隣接情報 swp1: # BGPピア接続インターフェース(Unnumberedの場合はインターフェース名となる) swp2: svis: # VLANインターフェース設定 2001: # VID ipv4: "10.10.10.0/31" # 付与するIPアドレス vrf: "vrf1" # 所属するVRF 2002: ipv4: "10.10.20.0/31" vrf: "vrf2" 4001: vrf: "vrf1" 4002: vrf: "vrf2" ports: # ポートの個別設定 swp3: # ポート名 mode: Tagged # 設定モード (現状Taggedのみ) vlans: # Taggedの場合に所属するVIDを指定 - 2001 - 2002 external: # 外部ルータと接続する場合の設定 bgp: # BGPで接続する場合は、bgpとする(現状BGPのみ) vrf1: # 関連するVRF neighbors: # 隣接情報 - 10.10.10.1 networks: # 配布するネットワーク - "172.16.100.0/24" - "172.16.101.0/24" evpn: # EVPN Type5で配布するネットワーク default: # 名前 route_map: "DGW" # ルートマップ名を指定 vrf2: neighbors: - 10.10.20.1 networks: - "172.16.200.0/24" - "172.16.201.0/24" evpn: default: route_map: "DGW" route_maps: # ルートマップを作成 DGW: # 名前 prefix: "0.0.0.0/0" # ルートマップで指定する条件 (現状Prefixのみ) spine01: loopback: "10.0.0.1" asn: 65020 neighbors: swp48: swp49: swp50: ports: swp49: speed: 40000 swp50: speed: 40000 spine02: loopback: "10.0.0.2" asn: 65020 neighbors: swp48: swp49: swp50: ports: swp49: speed: 40000 swp50: speed: 40000

roles

ロールは、roles配下に作成したフォルダの名前がロールの名前になります。ロール名のフォルダ配下に、tasks/main.ymlというファイルを作成することで、ロール内のタスクを設定可能です。

bgp-peer/tasks/main.yml

template の部分でコマンドを設定しています。Ansible のテンプレートと同様に、Jinja2 の書式で記載可能なため、プログラマブルで柔軟なテンプレート設計が可能です。先述した通り、テンプレート内では、group_vars で設定した値を{{変数}}として利用可能です。これにより、タスク内容を変更することなく、設定の変更が可能になります。BGP Unnumbered設定に関するロールです。
- name: configure fabric # タスク名 nclu: # モジュール名(Ansible公式のCumulus Linuxのモジュール NCLU を使用) atomic: true # atomic=trueにすると、設定が即時コミットされる description: "configure bgp peer for fabric" template: | # コマンドのテンプレート add loopback lo ip address {{ nodes[inventory_hostname].loopback }}/32 add bgp autonomous-system {{ nodes[inventory_hostname].asn }} add bgp router-id {{ nodes[inventory_hostname].loopback }} add bgp network {{ nodes[inventory_hostname].loopback }}/32 {% for iface in nodes[inventory_hostname].neighbors %} add interface {{ iface }} add interface {{ iface }} ipv6 nd ra-interval 10 del interface {{ iface }} ipv6 nd suppress-ra {% endfor %} add bgp bestpath as-path multipath-relax add bgp neighbor FABRIC peer-group add bgp neighbor FABRIC capability extended-nexthop add bgp neighbor FABRIC remote-as external {% for neighbor in nodes[inventory_hostname].neighbors %} add bgp neighbor {{ neighbor }} peer-group FABRIC {% endfor %}

exit/tasks/main.yml

Exit01(外接LeafSW)に限定した設定に関するロールです。
- name: configure external route nclu: atomic: true description: "configure external route" template: | {% if nodes[inventory_hostname].external is defined %} {% if nodes[inventory_hostname].external.bgp is defined %} {% for vrf in nodes[inventory_hostname].external.bgp %} {% set conf=nodes[inventory_hostname].external.bgp[vrf] %} add bgp vrf {{vrf}} autonomous-system {{nodes[inventory_hostname].asn}} add bgp vrf {{vrf}} router-id {{nodes[inventory_hostname].loopback}} {% for neighbor in nodes[inventory_hostname].external.bgp[vrf].neighbors %} add bgp vrf {{vrf}} neighbor {{neighbor}} remote-as external {% endfor %} {% for network in nodes[inventory_hostname].external.bgp[vrf].networks %} add bgp vrf {{vrf}} ipv4 unicast aggregate-address {{network}} summary-only {% endfor %} {% if nodes[inventory_hostname].external.bgp[vrf].evpn is defined %} {% for evpn in nodes[inventory_hostname].external.bgp[vrf].evpn %} {% if nodes[inventory_hostname].external.bgp[vrf].evpn[evpn].route_map is defined %} {% set route_map = nodes[inventory_hostname].external.bgp[vrf].evpn[evpn].route_map %} add bgp vrf {{vrf}} l2vpn evpn advertise ipv4 unicast route-map {{route_map}} {% endif %} {% endfor %} {% endif %} {% endfor %} {% endif %} {% endif %}

general/tasks/main.yml

IFやVLAN設定に関するロールです。
- name: configure nodes nclu: atomic: true description: "configure general settings" template: | {% if nodes[inventory_hostname].ports is defined %} {% for port in nodes[inventory_hostname].ports %} add interface {{port}} {% if nodes[inventory_hostname].ports[port].mode is defined %} {% if nodes[inventory_hostname].ports[port].mode == 'Tagged' %} {% for vlan in nodes[inventory_hostname].ports[port].vlans %} add interface {{port}} bridge vids {{vlan}} {% endfor %} {% endif %} {% endif %} {% if nodes[inventory_hostname].ports[port].speed is defined %} add interface {{port}} link speed {{nodes[inventory_hostname].ports[port].speed}} {% endif %} {% endfor %} {% endif %} {% if nodes[inventory_hostname].route_maps is defined %} {% for route_map in nodes[inventory_hostname].route_maps %} add routing prefix-list ipv4 {{route_map}} permit {{nodes[inventory_hostname].route_maps[route_map].prefix}} add routing route-map {{route_map}} permit {{loop.index}}0 match ip address prefix-list {{route_map}} {% endfor %} {% endif %}

leaf/tasks/main.yml

LeafSWのEVPN-VXLAN設定に関するロールです。
- name: configure leaf nclu: atomic: true description: "configure leaf" template: | add bgp l2vpn evpn neighbor FABRIC activate add bgp l2vpn evpn advertise-all-vni {% for vni in nodes[inventory_hostname].vxlans %} {% set vid = nodes[inventory_hostname].vxlans[vni].vid %} {% set vnid = 'vni-' + vni|string %} add vxlan {{vnid}} vxlan id {{vni}} add bridge bridge ports {{vnid}} add bridge bridge vids {{vid}} add bridge bridge vlan-aware add vxlan {{vnid}} bridge access {{vid}} add vxlan {{vnid}} bridge learning off add vxlan {{vnid}} bridge arp-nd-suppress on add vxlan {{vnid}} vxlan local-tunnelip {{ nodes[inventory_hostname].loopback }} {% if nodes[inventory_hostname].vxlans[vni].vrf is defined %} {% set vrf = nodes[inventory_hostname].vxlans[vni].vrf %} add vrf {{vrf}} vni {{vni}} {% endif %} {% endfor %} {% if nodes[inventory_hostname].svis is defined %} {% for svi in nodes[inventory_hostname].svis %} {% set vrf = nodes[inventory_hostname].svis[svi].vrf %} {% if nodes[inventory_hostname].svis[svi].ipv4 is defined %} add vlan {{svi}} ip address {{nodes[inventory_hostname].svis[svi].ipv4}} {% endif %} add vlan {{svi}} vrf {{vrf}} {% endfor %} {% endif %}

spine/tasks/main.yml

SpineSWのEVPN-VXLAN設定に関するロールです。
- name: configure spine nclu: atomic: true description: "configure spine" template: | add bgp l2vpn evpn neighbor FABRIC activate

Ansible Playbook実行

それでは、Playbookを実行します。「deploy.yml」の実行ログです。全てのタスクが"changed"となり、論理構成で示した設定が全て投入されました。
# ansible-playbook deploy.yml PLAY [spines] ********************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************ ok: [spine02] ok: [spine01] TASK [general : configure nodes] ************************************************************************************** changed: [spine01] changed: [spine02] TASK [bgp-peer : configure fabric] ************************************************************************************ changed: [spine01] changed: [spine02] TASK [spine : configure spine] **************************************************************************************** changed: [spine01] changed: [spine02] PLAY [leafs] ********************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************ ok: [exit01] ok: [leaf02] ok: [leaf01] TASK [general : configure nodes] ************************************************************************************** changed: [leaf01] changed: [leaf02] changed: [exit01] TASK [bgp-peer : configure fabric] ************************************************************************************ changed: [leaf02] changed: [leaf01] changed: [exit01] TASK [leaf : configure leaf] ****************************************************************************************** changed: [exit01] changed: [leaf02] changed: [leaf01] PLAY [exits] ********************************************************************************************************** TASK [exit : configure external route] ******************************************************************************** changed: [exit01] PLAY RECAP ************************************************************************************************************ exit01 : ok=5 changed=4 unreachable=0 failed=0 leaf01 : ok=4 changed=3 unreachable=0 failed=0 leaf02 : ok=4 changed=3 unreachable=0 failed=0 spine01 : ok=4 changed=3 unreachable=0 failed=0 spine02 : ok=4 changed=3 unreachable=0 failed=0

設定追加

設定追加してみます。Tenan2(VRF2)に新しくネットワーク VLAN202(VNI 10202)を追加します。設定追加するノードは、Leaf01とLeaf02の2台だけです。 設定追加 group_vars/allに以下のとおり、設定情報を追加するだけです。一回テンプレートを利用してロールを作ってしまえば、その後のメンテナンスは非常に楽ですね~

group_vars/all(抜粋)

nodes: leaf01: loopback: "10.0.0.11" asn: 65011 vxlans: 10100: vid: "100" 10200: vid: "200" 10202: # 設定追加 vid: "202" # 設定追加 104001: vid: "4001" vrf: "vrf1" 104002: vid: "4002" vrf: "vrf2" neighbors: swp49: swp50: svis: 100: ipv4: "172.16.100.1/24" vrf: "vrf1" 200: ipv4: "172.16.200.1/24" vrf: "vrf2" 202: # 設定追加 ipv4: "172.16.202.1/24" # 設定追加 vrf: "vrf2" # 設定追加 4001: vrf: "vrf1" 4002: vrf: "vrf2" ports: swp1: mode: Tagged vlans: - 100 - 200 - 202 # 設定追加 swp49: speed: 40000 swp50: speed: 40000 leaf02: loopback: "10.0.0.12" asn: 65012 vxlans: 10100: vid: "100" 10200: vid: "200" 10101: vid: "101" 10201: vid: "201" 10202: # 設定追加 vid: "202" # 設定追加 104001: vid: "4001" vrf: "vrf1" 104002: vid: "4002" vrf: "vrf2" neighbors: swp49: swp50: svis: 100: ipv4: "172.16.100.1/24" vrf: "vrf1" 200: ipv4: "172.16.200.1/24" vrf: "vrf2" 101: ipv4: "172.16.101.1/24" vrf: "vrf1" 201: ipv4: "172.16.201.1/24" vrf: "vrf2" 202: # 設定追加 ipv4: "172.16.202.1/24" # 設定追加 vrf: "vrf2" # 設定追加 4001: vrf: "vrf1" 4002: vrf: "vrf2" ports: swp1: mode: Tagged vlans: - 100 - 101 - 200 - 201 - 202 # 設定追加 swp49: speed: 40000 swp50: speed: 40000

Playbook実行

それでは、Playbook実行します。設定の追加対象であるLeaf01、Leaf02のタスク「general」「leaf」のみ"changed"となっています。Ansibleの特徴である、冪等性がCumulus Linuxでもしっかり担保されていることが確認できました。
# ansible-playbook deploy.yml PLAY [spines] ********************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************ ok: [spine02] ok: [spine01] TASK [general : configure nodes] ************************************************************************************** ok: [spine01] ok: [spine02] TASK [bgp-peer : configure fabric] ************************************************************************************ ok: [spine01] ok: [spine02] TASK [spine : configure spine] **************************************************************************************** ok: [spine01] ok: [spine02] PLAY [leafs] ********************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************ ok: [exit01] ok: [leaf01] ok: [leaf02] TASK [general : configure nodes] ************************************************************************************** changed: [leaf01] changed: [leaf02] ok: [exit01] TASK [bgp-peer : configure fabric] ************************************************************************************ ok: [leaf02] ok: [exit01] ok: [leaf01] TASK [leaf : configure leaf] ****************************************************************************************** ok: [exit01] changed: [leaf01] changed: [leaf02] PLAY [exits] ********************************************************************************************************** TASK [exit : configure external route] ******************************************************************************** ok: [exit01] PLAY RECAP ************************************************************************************************************ exit01 : ok=5 changed=0 unreachable=0 failed=0 leaf01 : ok=4 changed=2 unreachable=0 failed=0 leaf02 : ok=4 changed=2 unreachable=0 failed=0 spine01 : ok=4 changed=0 unreachable=0 failed=0 spine02 : ok=4 changed=0 unreachable=0 failed=0

まとめ

Ansibleを用いた、ネットワーク構成管理のコード化、いかがでしたでしょうか。
テンプレートを用いてロールを作りこんでしまえば、以降の設定追加のメンテナンスは非常に簡単に出来ることが確認出来ました。閲覧頂いた方の参考になっていれば幸いです!

実機で試してみたい方必見!POCキャンペーン開催中!!

ホワイトボックススイッチに興味ある方はこちらへ