KokeLab

Cisco/Linux/AWS/Ansible/Kubernetes

Ansibleを使ってCatalystのVLAN管理をしてみた

お久しぶりです。コケラです。

最近Twitterで、TeraTermに関する有益な情報を発見しました。


どうやら、TeraTermの背景を任意の画像に変更できるようです。ということで、さっそく私もやってみました。

f:id:kokelabo:20190913131146p:plain
妻です……ウソです
モチベーションが上がり、よりいっそう日々の学習が捗りそうですね。皆さんもぜひ試してみてはいかがでしょうか?

閑話休題

CCNPLPICといった資格の勉強に時間を割いていたこともあり、約半年ぶりのブログ更新となってしまいました。
そんな半年ぶりの本記事で、今回、皆さんにシェアしたいのは、構成管理ツールAnsibleを利用したVLAN設定の自動化です。
Ansibleについては、1月頃に本ブログでいちばん最初に公開した記事でも少し言及しましたが、サーバやNW機器の設定を自動で実行してくれる、オープンソースPython製のツールです。
Python製といっても、ごく一般的な使い方をするだけであれば、Pythonの知識は全く必要ありません。詳しくは後述します。

AnsibleでVLAN管理をしてみたいと思ったきっかけ

そもそも、なぜこういったことを試してみたいと思ったのか。それはやはり、VLANの設定を自動化できれば、間違いなく業務のKAIZENに繋がるだろうなと、実業務を通じて感じたからです。自動化してみたいなと感じたからです。
私はいま、NWの運用業務に携わっていることもあり、顧客の方から様々なシステム更改の依頼を受けることがあります。システムの追加や廃止、拠点間通信やDHCPの固定割り当てなど、依頼は多岐にわたります。私見となりますが、このなかでも特に「システムの追加や廃止」は、膨大な量の設定作業が伴うことが多いです。
新たなシステムを追加するとき、そのシステムで使うVLANを新規で機器にアサインしないといけない場合があり、NWの構成や規模にもよりますが、その設定作業に膨大な手間と時間を要する可能性があります
例えば、以下のようなNW構成があったとします。
f:id:kokelabo:20190914120704j:plain
コア層、ディストリビューション層、アクセス層と3階層に分かれた、よくあるNW構成ですね。
このNW構成において、例えば、とある拠点に設置されているSWAのFa0/1ポートに新システム用のVLANをアサインすることになれば、どれだけの設定作業が必要になってくるでしょうか。
もったいぶらずに答えだけを言うと、Coreを含めONUに繋がっている全スイッチの(WAN用)ポートに、TrunkでVLANを透過させる設定が必要になってきます。要するに、上図でポート番号が記されている全ての箇所です。ポートに新たなVLANをアサインするからには、VLAN定義もしなければなりません
数拠点ならまだしも、これが数十拠点、数百拠点となると……想像しただけでも鳥肌が立ちますね。それに、実際の現場で運用されているようなNW構成では、コスト面や更なる冗長化の確保のために、より複雑な構成である場合が多いです。複雑であればあるほど、更に同じような設定作業が必要になってきます。
「いちいち1拠点ずつ手順書を作成して、1拠点ずつ機器にログインして設定を投入するの大変だなぁ」と、実際に作業していて感じました。もちろん、私ひとりで全部やったわけではなく、数人で手分けしてやったのですが、それでも大変な作業でした。
ある日、この作業をもっと効率よく行える方法があるのではないかと疑問に思い、ふと頭に浮かんだのがAnsibleです。
Googleで「Ansible VLAN」と検索してみると、こちらのQiita記事を見つけました。
「やっぱりできるのか!」と、興奮せざるを得ませんでした。
ただ、こちらの記事はある程度、Ansibleの使い方を分かっているエンジニア向けだったので、完全にAnsible初心者なエンジニアがこちらの記事だけを参考にして試しても、おそらく上手くいかないかと思われます。
ということで本記事では、Ansible初心者なエンジニアの方々のために、もう少し色々な要素を噛み砕きながら、VLAN設定の自動化を解説していきたいと思います。

検証環境

  • ノートPC(Windows10/i7-8550U/RAM16G)
  • Ansible 2.8.4

Ansibleのインストール

まずは、CentOSにAnsibleをインストールします。本記事執筆時点(2019/9/14)では、CentOSの標準リポジトリにAnsibleが登録されていないので、Ansibleをインストールする前に、EPEL(Extra Package for Enterprise Linux)パッケージをインストールして、EPELリポジトリを利用します。

[root@localhost ~]# yum install -y epel-release
[root@localhost ~]# yum install -y ansible

インストールが完了したら、Ansibleのバージョンを確認します。

[root@localhost ~]# ansible --version
ansible 2.8.4
   config file = /etc/ansible/ansible.cfg

最新のバージョンであることを確認してください。余談ですが、EPELパッケージをインストールせずにAnsibleをインストールすると、以下のように最新のバージョンが正しくインストールされていませんでした。

[root@localhost ~]# ansible --version
ansible 2.4.2.0
  config file = /etc/ansible/ansible.cfg

InventoryとPlaybookの編集

InventoryとPlaybookを編集する前に、CatalystSSHの設定を済ませておきましょう。コマンドプロンプト等からPingが通り、VMからSSH接続できるかどうか確認しておきます。

[v-kento@localhost ~]$ ssh spira@192.168.0.254
Password:

Tidus>

SSHの設定が完了したら、いよいよInventoryとPlaybookを編集していきます。
Inventoryとは、接続先のホスト(サーバ)を定義したり、その接続方式や認証情報を変数定義するファイルです。基本的には、どのホストに対して設定を行うのかを決めるためのファイルとなります。
説明だけでは分かりづらいと思うので、実際にInventoryを編集してみましょう。
Inventory用に新たなファイルを用意するのも良いですが、今回は/etc/ansible/配下にデフォルトで存在するhostsファイルにInventoryを置きます。というのも、後述するansible-playbookコマンドの-iオプションでInventoryを指定しなかった場合、デフォルトでhostsファイルをInventoryとして読み込んでくれるからです。わざわざInventoryを新たに用意して、オプションで指定する必要もなくなります。

[root@localhost ansible]# vi hosts
[spira] 
192.168.0.254 

[spira:vars]
ansible_connection=network_cli
ansible_network_os=ios
ansible_ssh_user=spira
ansible_ssh_pass=nagi
ansible_become=yes
ansible_become_method=enable
ansible_become_pass=yuna

このように、InventoryはINI形式で記述するのが主流ですが、Playbookと同様にYAML形式でも記述することができます。見やすさ、編集のしやすさという意味では、INI形式で記述したほうがベターだと思われます。複雑な変数を定義したい場合は、YAML形式で記述されることもあります。

  • [任意のグループ名]で、複数の対象ホストをグループ化することができます。その下に対象ホストのIPアドレスやホスト名を列記していきます。複数台でやったほうが良い検証になりますがメンドクサイので、今回は一台だけで検証します。
  • [任意のグループ名:vars]で、グループ化した対象ホストへ接続する際の接続方式や認証情報を共通の変数として定義し列記していきます。

ansible_connection変数
⇒ネットワーク機器へのプラグイン情報を定義。network_cli以外だと、JUNOS対応のnetconfも指定可。

ansible_network_os変数
⇒プラットホームを定義。例えば、Cisco IOSならiosと指定し、JUNOS OSならjunosと指定する。
プラグイン情報としてnetwork_clinetconfを指定した場合は必須の定義となる。

ansible_ssh_user変数
SSH先のSSHユーザー名を定義。

ansible_ssh_pass変数
SSH先のSSHパスワードを定義。

ansible_become変数
⇒特権モード(enable)への移行が必要かどうかを定義。グローバルコンフィギュレーションモード(config)へ移行しないとVLAN等の設定は不可能なので、必ずyesと指定する。デフォルトではnoとなっている。

ansible_become_method変数
⇒特権モードへ移行するためのコマンドを定義。プラグイン情報としてnetwork_cliを指定していた場合はenableのみ指定可。

ansible_become_pass変数
⇒特権モードへ移行するためのパスワードを定義。

これらの変数情報は、ansible-playbookコマンドのオプションでも指定することができたり、Playbookにも記述することができますが、管理上Inventoryでまとめて定義しておいたほうが無難でしょう。
Inventoryを編集しましたので、続けてPlaybookを作成しましょう。
Playbookとは、対象ホストにどのような設定を行うのかを定義するファイルです。ChefでいうところのRecipeですね。実行したい設定情報のレシピというわけです。
先述しましたが、PlaybookはYAML形式で記述できるファイルで、いわゆるコードを書くようなプログラミングの未経験者であっても理解しやすいものとなっています。Pythonなど高度なプログラミング言語が書けなくても、サンプルを参考にすれば簡単に書くことができます。
それではさっそく、Playbookを作成していきましょう。ファイルの場所はhostsファイルと同じく、
/etc/ansible/配下とし、ファイル名はvlan.ymlとします。

[root@localhost ansible]# vi vlan.yml
---
 - hosts: spira
   gather_facts: no
   tasks:
    - name: vlan10
      ios_vlan:
        vlan_id: 10
        name: Rikku
        state: present
    - name: vlan20
      ios_vlan:
        vlan_id: 20
        name: Auron
        state: present
    - name: no vlan50
      ios_vlan:
        vlan_id: 50
        state: absent
    - name: Fa0/3
      ios_l2_interface:
        name: FastEthernet0/3
        mode: access
        access_vlan: 10
    - name: Fa0/5
      ios_l2_interface:
        name: FastEthernet0/5
        mode: access
        access_vlan: 20
    - name: Fa0/15 remove
      ios_l2_interface:
        name: FastEthernet0/15
        mode: trunk
        trunk_vlans: 50
        state: absent
    - name: Fa0/15 add
      ios_l2_interface:
        name: FastEthernet0/15
        mode: trunk
        trunk_vlans: 10,20
        state: present

今回はVLANの設定をしたいので、ios_vlanios_l2_interfaceという2つのモジュールを使います。Playbookにおけるモジュールとは、例えるなら料理の材料でしょうか。

  • - hosts: ではInventoryで定義したホストグループ名を記述します。Inventoryで[spira]と定義しましたので、hosts: spiraとします。
  • gather_facts:では対象ホストのメタデータを取得するかどうかを記述します。有効にすると、そのぶんPlaybookの実行に時間がかかりますし、今回はあくまでも検証ということでnoにしておきます。
  • - name: では対象ホストに行うタスク名を記述します。このタスク名は任意で付けるものなので、どのような名前でも構いません。

NW機器の設定に詳しい方なら、上記のPlaybookがどのような設定情報を定義しているのか、なんとなく分かるかと思います。タスクごとに、上から順番に説明していきます。

★VLAN10(Rikku)を定義
★VLAN20(Auron)を定義
★定義されていたVLAN50を削除
★定義したVLAN10をFa0/3ポートにアサイン(Access)
★定義したVLAN20をFa0/5ポートにアサイン(Access)
★Fa0/15ポートにアサイン(Trunk)されていたVLAN50をリムーブ
★定義したVLAN10とVLAN20をFa0/15ポートにアサイン(Trunk)

以上、7つのタスクを行うPlaybookとなります。
本当は1つのモジュールだけを使って、もう少しシンプルな形にしたかったのですが、ios_vlanモジュールはtrunkの設定をサポートしておらず、ios_l2_interfaceモジュールはVLAN定義の設定をサポートしていないようなので、これら2つのモジュールを組み合わせました。
ios_vlanモジュールとios_l2_interfaceモジュールは、Ansible 2.9より非推奨のモジュールになる予定とのことです。それぞれ幾つかの機能が追加されてios_vlansモジュール、ios_l2_interfacesモジュールとして生まれ変わります。複数形になります。なぜ複数形になるのかは不明です。

Playbookの実行

Playbookを実行する前に、Ansibleのpingモジュールを使って簡易的な疎通確認をしておきましょう。pingといっても、よく知られているICMPを利用したpingとは少し異なります。単に対象ホストにモジュールでSSH接続できるのかどうかを試すものです。

[v-kento@localhost ansible]$ ansible 192.168.0.254 -m ping
192.168.0.254 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

Ansibleが正常に動作していれば、このように"pong"が返ってきます。
ちゃんとSSHの設定ができていなかったり、先述しましたansible_connection変数などの変数情報を、Inventoryやオプションで指定し定義されていなければ、SUCCESSとならずPlaybookも実行できません。
もうひとつ確認しておきたいのがPlaybookに文法的な誤りがないかどうかです。--syntax-checkオプションをつけてansible-playbookコマンドを実行してみましょう。

[v-kento@localhost ansible]$ ansible-playbook vlan.yml --syntax-check

playbook: vlan.yml

このように何もエラーメッセージが表示されなければ、Playbookに文法的な誤りはありません。
対象ホストへの疎通確認とPlaybookの文法チェックができましたので、いよいよPlaybookを実行してみます。

[v-kento@localhost ansible]$ ansible-playbook vlan.yml

PLAY [spira] *******************************************************************

TASK [vlan10] ******************************************************************
changed: [192.168.0.254]

TASK [vlan20] ******************************************************************
changed: [192.168.0.254]

TASK [no vlan50] ***************************************************************
changed: [192.168.0.254]

TASK [Fa0/3] *******************************************************************
 [WARNING]: The value 10 (type int) in a string field was converted to u'10'
(type string). If this does not look like what you expect, quote the entire
value to ensure it does not change.

changed: [192.168.0.254]

TASK [Fa0/5] *******************************************************************
 [WARNING]: The value 20 (type int) in a string field was converted to u'20'
(type string). If this does not look like what you expect, quote the entire
value to ensure it does not change.

changed: [192.168.0.254]

TASK [Fa0/15 remove] ***********************************************************
 [WARNING]: The value 50 (type int) in a string field was converted to u'50'
(type string). If this does not look like what you expect, quote the entire
value to ensure it does not change.

changed: [192.168.0.254]

TASK [Fa0/15 add] **************************************************************
changed: [192.168.0.254]

PLAY RECAP *********************************************************************
192.168.0.254              : ok=7    changed=7    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

PLAY RECAPを見ると、: ok=7 changed=7となっています。無事に7つのタスクが正常に実行されました。
ios_l2_interfaceモジュールを使ったタスクでWARNINGが出ていますが、これはPythonの文法上の仕様によるものです。要するに「文字のデータ型がint型からstr型に変換されたけど大丈夫?数値ではなくて文字列として扱うけど大丈夫?」という警告でして、文字列として扱われても特に問題ありません。Playbookは正常に実行されていますので、気にする必要はありません。
対象ホスト(Catalyst2960)のPlaybook実行前と実行後の状態はこちらです。

★実行前★

Tidus#sh vlan bri

VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Fa0/1, Fa0/2, Fa0/3, Fa0/4
                                                Fa0/5, Fa0/6, Fa0/7, Fa0/8
                                                Fa0/9, Fa0/10, Fa0/11, Fa0/12
                                                Fa0/13, Fa0/14, Fa0/16, Fa0/17
                                                Fa0/18, Fa0/19, Fa0/20, Fa0/21
                                                Fa0/22, Fa0/23, Fa0/24, Gi0/1
                                                Gi0/2
50   VLAN0050                         active
1002 fddi-default                     act/unsup
1003 token-ring-default               act/unsup
1004 fddinet-default                  act/unsup
1005 trnet-default                    act/unsup

Tidus#sh int tru

Port        Mode             Encapsulation  Status        Native vlan
Fa0/15      on               802.1q         trunking      1

Port        Vlans allowed on trunk
Fa0/15      50

Port        Vlans allowed and active in management domain
Fa0/15      50

Port        Vlans in spanning tree forwarding state and not pruned
Fa0/15      none

★実行後★

Tidus#sh vlan bri

VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Fa0/1, Fa0/2, Fa0/4, Fa0/6
                                                Fa0/7, Fa0/8, Fa0/9, Fa0/10
                                                Fa0/11, Fa0/12, Fa0/13, Fa0/14
                                                Fa0/16, Fa0/17, Fa0/18, Fa0/19
                                                Fa0/20, Fa0/21, Fa0/22, Fa0/23
                                                Fa0/24, Gi0/1, Gi0/2
10   Rikku                            active    Fa0/3
20   Auron                            active    Fa0/5
1002 fddi-default                     act/unsup
1003 token-ring-default               act/unsup
1004 fddinet-default                  act/unsup
1005 trnet-default                    act/unsup

Tidus#sh int tru

Port        Mode             Encapsulation  Status        Native vlan
Fa0/15      on               802.1q         trunking      1

Port        Vlans allowed on trunk
Fa0/15      10,20

Port        Vlans allowed and active in management domain
Fa0/15      10,20

Port        Vlans in spanning tree forwarding state and not pruned
Fa0/15      10,20

意図通りConfig投入されていることが分かります。

おまけ

おまけというか、Ansibleを使う上でやっておくと便利な小技を紹介します。まぁ、小技というほどのものでもないですが。
/etc/ansible/配下にある、Ansibleの各ファイルのデフォルトのパーミッションを確認してみると、以下のようになっています。

[root@localhost ansible]# ll
合計 28
-rw-r--r--. 1 root root 19980  8月 17 05:54 ansible.cfg
-rw-r--r--. 1 root root  1016  8月 17 05:54 hosts
drwxr-xr-x. 2 root root     6  8月 17 05:54 roles
-rw-r--r--. 1 root root     5  9月  4 22:26 vlan.yml

hostsファイルとPlaybook(vlan.yml)のファイルが所有者(root)ユーザーのみ書き込み可能となっています。いちいちrootユーザーになってから編集するのは非効率的で面倒なので、ファイルのパーミッションを変えましょう。sudoでも良いですが、それはそれでまた手間がかかるので、ファイルのパーミッションを変えてしまったほうがベターかと思います。

[root@localhost ansible]# chmod 666 vlan.yml
[root@localhost ansible]# chmod 666 hosts
[root@localhost ansible]# ll
合計 28
-rw-r--r--. 1 root root 19980  8月 17 05:54 ansible.cfg
-rw-rw-rw-. 1 root root  1016  8月 17 05:54 hosts
drwxr-xr-x. 2 root root     6  8月 17 05:54 roles
-rw-rw-rw-. 1 root root     5  9月  4 22:41 vlan.yml

おわりに

Ansibleを利用したVLAN設定の自動化を紹介させて頂きましたが、皆さんいかがだったでしょうか。
私自身もまだAnsibleを始めて1カ月半ほどで、細かいディティールまで詰め切れてないこともあり、まだまだ分からないことがたくさんあります。ですので、本記事を鵜呑みにして信用し過ぎるのはリスキーだと思います。しがないエンジニアによる、ただの自己満記事だと捉えて頂ければ幸いです。
Ansibleはシンプルさも奥深さも兼ね備えた良いツールだなと感じました。無限の可能性を感じますし、これからも自動化ツールとして、より普及していくことを切に願っています。
ここまで長々とお付き合い下さいまして有難うございました!(次はroleとかjinja2使って遊んでみたいな)