見出し画像

AWSのCisco Catalyst 8000V AMIでAnsibleのCisco.Iosコレクションをちょっとだけ試してみる


はじめに


株式会社SHIFT ITソリューション部の水谷です。

Ansibleに興味がある方の多くはご存じかと思いますが、AnsibleはLinux系OSやWindows Serverの設定変更だけでなく、ネットワーク機器の設定変更も行えます。

私もそれを知ってはいたものの、テスト用に自由に使えるルーターやスイッチなどの機器を持っているわけでもなく、また仕事でネットワーク機器の設定自動化を行う機会もなかったため、これを試してみることすらありませんでした。

自分自身の興味という点からも、ネットワーク機器の設定はそれほど関心がなかったのですが、つい先日、AWSに Cisco Catalyst 8000Vという仮想ルーターのAMIがあることを知り、ちょっと考えが変わりました。調べてみると、Ciscoが30日間は無料でこのAMIを使える(EC2の料金だけで使用できる)ライセンスを発行していることもわかったので、これを使ってAnsibleでCiscoのルーターがどのように設定できるのか見てみよう、と思い立ちました。

しっかり使い込んだ、とか、なにか具体的な構築したいシステムがあってそれを作った、というわけでもなく、ちょっと味見してみた程度なのですが、せっかく(?)なので今回はその様子を書いてみたいと思います。

《参考》Ansible関連のマガジンができました

CiscoのフリートライアルとEC2の起動


まずはEC2の作成までの手順を見ていきます。

※以下は私が2024年5月に行ったものであり、手順が変わっていたり、フリートライアルが終わっている可能性もあるため、試してみようという方は必ず最新情報をご確認ください。

では、Ciscoのトライアルページにアクセスすることから始めます。

https://www.cisco.com/c/ja_jp/solutions/enterprise-networks/promotions-free-trials.html

少しスクロールダウンしたところに「Catalyst 8000V エッジソフトウェアを試す」という項目があるので、これをクリックします。

すると、AWSとAzureが選べるようなので、私はAWSの方を選びました。

ここでAWSのページに遷移しますので、右上の「Continue to Subscribe」をクリックします。

AWSにログインをしていない場合はログイン画面が表示されますので、AWSにログインします。

続いて利用条件のページが出ますので、「Accept Term」をクリックします。

さらに利用規約が表示されますので、右上の「Continue to Configuration」をクリックします。

ここで、Cisco IOSのバージョンが選択できるので、最新のものを選択しました。また、EC2を建てるリージョンが選択できますので、適宜選択してください。

あとはEC2のインスタンスタイプを選択するのですが、ここはデフォルトの「c5n.large」のままにしました。それなりの大きさのインスタンスが必要なようですね。

あとは通常のEC2を建てるのと同じように進めば起動できるはずです。

SSHで接続してみる


まずは、SSHで接続してみます。

※キーペアはEC2インスタンス作成時に作りました。また、下記IPアドレスは私がテスト用に取得したElasticIPで、本ブログが掲載される前に削除しております。

$ ssh -i .ssh/cisco.pem ec2-user@18.178.247.104

AWSコンソールで、EC2の「接続」を見てみると、なぜかrootユーザーで接続するように書かれてますが、上に書いたようにec2-userで接続できました(rootユーザーでは接続できなかった)。

接続ができたら、とりあえず"show version"と打ってみます。

ip-172-31-68-94#show version
Cisco IOS XE Software, Version 17.14.01a
Cisco IOS Software [IOSXE], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.14.1a, RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2024 by Cisco Systems, Inc.
Compiled Thu 25-Apr-24 18:53 by mcpre
...

確かに指定したバージョンのCisco IOS XEが動いているようですね。

ここで、Ansibleが接続に使うユーザー "ansible" を作っておきます。

下のように、"configure terminal"でいろいろな設定変更ができる「グローバルコンフィグレーションモード」に移行し、"username"コマンドを実行します。この際に"previlege 15"とすると、すべての操作ができる権限を持つユーザーが作れるとのことなので、そのようにしてみます。

ip-172-31-68-94#configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
ip-172-31-68-94(config)#username ansible privilege 15 password Password
 WARNING: Command has been added to the configuration using a type 0 password. However, recommended to migrate to strong type-6 encryption

平文のパスワードに対する警告(?)がでていますが、一旦このまま"show running-config"コマンドでユーザーができたか確認します。

ip-172-31-68-94(config)#exit
ip-172-31-68-94#show running-config
Building configuration...

Current configuration : 6579 bytes
...
username ec2-user privilege 15
username ansible privilege 15 password 7 053B071C325B411B1D
...

無事出来ているようですね。

"copy running-config startup-config"コマンドでこの設定を保存できるようなので、しておきます。

ip-172-31-68-94#copy running-config startup-config
Destination filename [startup-config]?
Building configuration...
[OK]

Ansibleから接続してみる


さて、Ansibleからの接続に進みます。

インベントリを作成するのですが、ポイントとしては、 'ansible_connection' を 'ansible.netcommon.network_cli' としている点と、 'ansible_network_os' を 'cisco.ios.ios' としている点です。

それから、権限昇格には'enable'というコマンドを使用するようなので、'ansible_become_method'を'enable'にしています。

このあたりはAnsibleの公式ドキュメント にもわかりやすく書かれていますので、そちらも合わせて参照してください。

[cisco]
cisco8000v ansible_host=18.178.247.104

[cisco:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=cisco.ios.ios
ansible_user=admin
ansible_password=Password
ansible_become_method=enable

pingモジュールを実行してみます。

$ ansible -i inventory -m ping cisco
cisco8000v | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

無事Ansibleから接続できたようで一安心。

ネットワーク機器の情報収集


今回試している(仮想)ネットワーク機器では、Cisco IOS(IOS XE)というOSが動作しているので、Ansibleで操作する際にはcisco.iosコレクションを使用します。

このコレクションはデフォルトでインストールされているケースが多いと思いますが、念のため確認しておきます。

$ ansible-galaxy collection list

# /usr/lib/python3/dist-packages/ansible_collections
Collection                               Version
---------------------------------------- -------
amazon.aws                               7.6.0
ansible.netcommon                        5.3.0
ansible.posix                            1.5.4
ansible.utils                            2.12.0
ansible.windows                          2.3.0
arista.eos                               6.2.2
awx.awx                                  23.9.0
azure.azcollection                       1.19.0
check_point.mgmt                         5.2.3
chocolatey.chocolatey                    1.5.1
cisco.aci                                2.9.0
cisco.asa                                4.0.3
cisco.dnac                               6.13.3
cisco.intersight                         2.0.9
cisco.ios                                5.3.0   <-- これ
cisco.iosxr                              6.1.1
cisco.ise                                2.9.1
cisco.meraki                             2.18.1
cisco.mso                                2.6.0
cisco.nxos                               5.3.0
cisco.ucs                                1.10.0
cloud.common                             2.1.4
...

インストールされているようですね。もし、インストールされていなければ、 ansible-galaxy collection install cisco.iosでインストールします。

さて、このコレクションにはcisco.ios.ios_factsというモジュールがあり、これを実行すると対象機器に設定されている各種リソースの情報を得ることができます。

取得したいリソースの種類を選択できるようですが、まずはallですべて取ってみようと思います。

Playbookはこちら。

---
- hosts: cisco
  become: true
  gather_facts: false
  tasks:
    - name: ios_facts to get returned values
      cisco.ios.ios_facts:
        gather_subset: all
        gather_network_resources: all

    - name: show result
      ansible.builtin.debug:
        msg: "{{ ansible_facts.network_resources }}"

で、実行してみると、このようにエラーが出ました。

$ ansible-playbook -i inventory gather.yml

PLAY [cisco] ************************************************************************************************

TASK [ios_facts to get returned values] *********************************************************************
fatal: [cisco8000v]: FAILED! => {"changed": false, "msg": "'configuration'"}

PLAY RECAP **************************************************************************************************
cisco8000v                 : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

この出力からはエラーの原因がよくわかりませんね。。。

いろいろ試してみると、(理由はわからないのですが)VLANの情報を取得する際にエラーが出ているようだったので、vlan以外のリソースを取得するようにしてみます。

---
- hosts: cisco
  become: true
  gather_facts: false
  tasks:
    - name: ios_facts to get returned values
      cisco.ios.ios_facts:
        gather_subset: all
        gather_network_resources:
          - acl_interfaces
          - acls
          - bgp_address_family
          - bgp_global
          - evpn_evi
          - evpn_global
          - hostname
          - interfaces
          - l2_interfaces
          - l3_interfaces
          - lacp
          - lacp_interfaces
          - lag_interfaces
          - lldp_global
          - lldp_interfaces
          - logging_global
          - ntp_global
          - ospf_interfaces
          - ospfv2
          - ospfv3
          - prefix_lists
          - route_maps
          - service
          - snmp_server
          - static_routes
          # - vlans
          - vxlan_vtep

    - name: show result
      ansible.builtin.debug:
        msg: "{{ ansible_facts.network_resources }}"

結果は以下のようになりました。

$ ansible-playbook -i inventory test.yml

PLAY [cisco] ************************************************************************************************

TASK [ios_facts to get returned values] *********************************************************************
ok: [cisco8000v]

TASK [show result] ******************************************************************************************
ok: [cisco8000v] => {
    "msg": {
        "acl_interfaces": [],
        "acls": [
            {
                "acls": [
                    {
                        "aces": [
                            {
                                "grant": "permit",
                                "sequence": 10,
                                "source": {
                                    "address": "192.168.35.0",
                                    "wildcard_bits": "0.0.0.255"
                                }
                            }
                        ],
                        "acl_type": "standard",
                        "name": "GS_NAT_ACL"
                    },
                    {
                        "acl_type": "extended",
                        "name": "meraki-fqdn-dns"
                    }
                ],
                "afi": "ipv4"
            }
        ],
        "bgp_address_family": {},
        "bgp_global": {},
        "evpn_evi": [],
        "evpn_global": {},
        "hostname": {
            "hostname": "ip-172-31-68-94"
        },
        "interfaces": [
            {
                "enabled": true,
                "name": "GigabitEthernet1"
            },
            {
                "enabled": true,
                "name": "VirtualPortGroup0"
            }
        ],
        "l2_interfaces": [
            {
                "name": "VirtualPortGroup0"
            },
            {
                "name": "GigabitEthernet1"
            }
        ],
        "l3_interfaces": [
            {
                "ipv4": [
                    {
                        "dhcp": {
                            "enable": true
                        }
                    }
                ],
                "ipv6": [
                    {
                        "address": "dhcp"
                    }
                ],
                "name": "GigabitEthernet1"
            },
            {
                "ipv4": [
                    {
                        "address": "192.168.35.101/24"
                    }
                ],
                "name": "VirtualPortGroup0"
            }
        ],

...

        ],
        "vxlan_vtep": {}
    }
}

PLAY RECAP **************************************************************************************************
cisco8000v                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

どうやら、たくさんのリソースがAWSのネットワーク設定に合わせてあらかじめ設定されているようですね。

以下のタスクを追加して、この設定をYAML形式で保存してみたいと思います。

    - name: Save to a file
      ansible.builtin.copy:
        dest: /tmp/cisco8000v.yml
        content: "{{ ansible_facts.network_resources | to_nice_yaml(indent=2) }}"

実行して、生成されたYAMLファイルを見てみると、以下のようになってました。

$ cat /tmp/cisco8000v.yml
acl_interfaces: []
acls:
- acls:
  - aces:
    - grant: permit
      sequence: 10
      source:
        address: 192.168.35.0
        wildcard_bits: 0.0.0.255
    acl_type: standard
    name: GS_NAT_ACL
  - acl_type: extended
    name: meraki-fqdn-dns
  afi: ipv4
bgp_address_family: {}
bgp_global: {}
evpn_evi: []
evpn_global: {}
hostname:
  hostname: ip-172-31-68-94
interfaces:
- enabled: true
  name: GigabitEthernet1
- enabled: true
  name: VirtualPortGroup0
l2_interfaces:
- name: VirtualPortGroup0
- name: GigabitEthernet1
l3_interfaces:
- ipv4:
  - dhcp:
      enable: true
  ipv6:
  - address: dhcp
  name: GigabitEthernet1
- ipv4:
  - address: 192.168.35.101/24
  name: VirtualPortGroup0
lacp:
  system:
    priority: 32768
lacp_interfaces:
- name: GigabitEthernet1
lag_interfaces: []
logging_global:
  persistent:
    filesize: 8192
    immediate: true
    size: 1000000
ntp_global: {}
ospf_interfaces:
- name: VirtualPortGroup0
- name: GigabitEthernet1
prefix_lists: []
service:
  counters: 0
  dhcp: true
  password_encryption: true
  password_recovery: true
  private_config_encryption: true
  prompt: true
  slave_log: true
  timestamps:
  - datetime_options:
      msec: true
    msg: debug
    timestamp: datetime
  - datetime_options:
      msec: true
    msg: log
    timestamp: datetime
snmp_server: {}
static_routes:
- address_families:
  - afi: ipv4
    routes:
    - dest: 0.0.0.0/0
      next_hops:
      - forward_router_address: 172.31.64.1
        interface: GigabitEthernet1
- address_families:
  - afi: ipv4
    routes:
    - dest: 0.0.0.0/0
      next_hops:
      - forward_router_address: 172.31.64.1
        global: true
        interface: GigabitEthernet1
  vrf: GS
vxlan_vtep: {}

こちらの方が見やすいですね(見慣れているだけかもしれませんが)。

構成の変更を試す

cisco.ios コレクションの多くのモジュールは、いわゆる'Network Resource Module'に分類(?)されるモジュールであり、このfactsで得られた既存リソースのパラメーターが、そのままリソース作成のパラメーターとして使えます。

例えば、一番上のACLであれば、

acls:
- acls:
  - aces:
    - grant: permit
      sequence: 10
      source:
        address: 192.168.35.0
        wildcard_bits: 0.0.0.255
    acl_type: standard
    name: GS_NAT_ACL
  - acl_type: extended
    name: meraki-fqdn-dns
  afi: ipv4

ここの部分を変数として定義して、cisco.ios.ios_aclsモジュールに渡せば、その通りにACLリソースを追加してくれます(以下はその例)。

---
- hosts: cisco
  become: true
  gather_facts: false
  vars:
    acls:
    - acls:
      - aces:
        - grant: permit
          sequence: 10
          source:
            address: 192.168.35.0
            wildcard_bits: 0.0.0.255
        acl_type: standard
        name: GS_NAT_ACL
      - acl_type: extended
        name: meraki-fqdn-dns
      afi: ipv4
  tasks:
    - name: merge acl resource(s)
      cisco.ios.ios_acls:
        config: "{{ acls }}"
        state: merged

このモジュール仕様は、現在の機器の設定のバックアップおよびリストアに有効ですね。

では、設定変更の例としてACLを1つ追加してみたいと思います。

Playbookはこのように、追加するACLをaclsという変数に定義して、cisco.ios.ios_aclsモジュールを実行し、cisco.ios.ios_factsモジュールでACLを取得して表示しています。

cisco.ios.ios_aclsモジュールのstateパラメーターは、mergedにしています。これは、既存の設定にパラメーターで与えたリソースを追加する、という意味になります(すでにそのリソースがある場合は与えられた内容で更新します)。

なお、この追加するACLはcisco.ios.ios_aclsモジュールのドキュメントにあったサンプルをそのまま使用していますので、特に何かを意図したものではありません。

---
- hosts: cisco
  become: true
  gather_facts: false
  vars:
    acls:
      - acls:
        - name: std_acl
          acl_type: standard
          aces:
            - grant: deny
              source:
                address: 192.168.1.200
            - grant: deny
              source:
                address: 192.168.2.0
                wildcard_bits: 0.0.0.255
  tasks:
    - name: Merge acl resource(s)
      cisco.ios.ios_acls:
        config: "{{ acls }}"
        state: merged

    - name: Get facts - acls
      cisco.ios.ios_facts:
        gather_subset: all
        gather_network_resources:
          - acls

    - name: Show acls
      ansible.builtin.debug:
        var: ansible_facts.network_resources.acls

実行結果は以下の通りで、期待通りACLが追加されていますね。

$ ansible-playbook -i inventory acls.yml

PLAY [cisco] ************************************************************************************************

TASK [Merge acl resource(s)] ********************************************************************************
changed: [cisco8000v]

TASK [Get facts - acls] *************************************************************************************
ok: [cisco8000v]

TASK [Show acls] ********************************************************************************************
ok: [cisco8000v] => {
    "ansible_facts.network_resources.acls": [
        {
            "acls": [
                {
                    "aces": [
                        {
                            "grant": "permit",
                            "sequence": 10,
                            "source": {
                                "address": "192.168.35.0",
                                "wildcard_bits": "0.0.0.255"
                            }
                        }
                    ],
                    "acl_type": "standard",
                    "name": "GS_NAT_ACL"
                },
                {
                    "acl_type": "extended",
                    "name": "meraki-fqdn-dns"
                },
                {
                    "aces": [
                        {
                            "grant": "deny",
                            "sequence": 10,
                            "source": {
                                "host": "192.168.1.200"
                            }
                        },
                        {
                            "grant": "deny",
                            "sequence": 20,
                            "source": {
                                "address": "192.168.2.0",
                                "wildcard_bits": "0.0.0.255"
                            }
                        }
                    ],
                    "acl_type": "standard",
                    "name": "std_acl"
                }
            ],
            "afi": "ipv4"
        }
    ]
}

PLAY RECAP **************************************************************************************************
cisco8000v                 : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

このcisco.ios.ios_aclsのように、Network Resource Moduleに分類されるモジュールの一部では、stateにoverriddenを指定することができます。

これは、そのリソースを(追加するのではなく)与えられた値ですべてを上書きするもので、宣言的な記述を可能にするものになります。

例えば今回の例では、nameパラメーターがGS_NAT_ACLであるACLと、nameパラメーターがmeraki-fqdn-dnsであるACLが存在していた状態に対して、先ほどnameパラメーターがstd_aclのACLを追加して、現在ACLは合計3つ存在する状態になっていますが、最後に追加した3つ目を削除したい場合を考えます。

方法は2つあって、命令的に3つ目を削除するよう、stateをdeletedとし、与えるパラメーターは3つ目のもののみを書く方法。もう1つは、stateをoverriddenにして、あるべき姿である1つ目と2つ目のACLを列挙したパラメーターを渡す方法です。

後者の方法であれば、達成したい状態をモジュールに渡せばあとはモジュールが良しなに追加、削除、更新を行ってくれるので、構成情報の管理がよりわかりやすいものになり、Playbookも簡単なものになりますね。

ということで、今回のPlaybookはこちらです。

---
- hosts: cisco
  become: true
  gather_facts: false
  vars:
    acls:
    - acls:
      - aces:
        - grant: permit
          sequence: 10
          source:
            address: 192.168.35.0
            wildcard_bits: 0.0.0.255
        acl_type: standard
        name: GS_NAT_ACL
      - acl_type: extended
        name: meraki-fqdn-dns
      afi: ipv4
  tasks:
    - name: Override acl resource(s)
      cisco.ios.ios_acls:
        config: "{{ acls }}"
        state: overridden

    - name: Get facts - acls
      cisco.ios.ios_facts:
        gather_subset: all
        gather_network_resources:
          - acls

    - name: Show acls
      ansible.builtin.debug:
        var: ansible_facts.network_resources.acls

実行してみます。

$ ansible-playbook -i inventory acls2.yml

PLAY [cisco] ************************************************************************************************

TASK [Override acl resource(s)] *****************************************************************************
changed: [cisco8000v]

TASK [Get facts - acls] *************************************************************************************
ok: [cisco8000v]

TASK [Show acls] ********************************************************************************************
ok: [cisco8000v] => {
    "ansible_facts.network_resources.acls": [
        {
            "acls": [
                {
                    "aces": [
                        {
                            "grant": "permit",
                            "sequence": 10,
                            "source": {
                                "address": "192.168.35.0",
                                "wildcard_bits": "0.0.0.255"
                            }
                        }
                    ],
                    "acl_type": "standard",
                    "name": "GS_NAT_ACL"
                },
                {
                    "acl_type": "extended",
                    "name": "meraki-fqdn-dns"
                }
            ],
            "afi": "ipv4"
        }
    ]
}

PLAY RECAP **************************************************************************************************
cisco8000v                 : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

期待通り3つ目のACLが削除され、1つ目と2つ目だけが存在する、aclsパラメーターで記述した通りの状態になりました。

まとめ


まず、AWSやAzureで使用できるCisco Catalyst 8000vは、Ansibleで操作できる仮想ネットワーク機器であることがわかりました。

ただし、例えばinterfaceリソースはAWSのENIと結びついており、AnsibleからもSSHで接続してコマンドから追加したり削除することもできません。また、VLANを追加することもできず、上で書いたようにfactsを取得も失敗しました。

このように、実際のネットワーク機器との違いもたくさんあるため、ネットワークスイッチの設定変更PlaybookのテストをこのCatalyst 8000vで行う、というような使い方はできないようです(ちょっと残念)。

それでも個人的には、Ansibleでネットワーク機器の操作が(ほとんどお金もかからず)少し体験できたことは良い経験になりましたし、Ansibleでのネットワーク機器の構成管理についても(少し)興味がでてきました。

ネットワーク機器と一口に言っても、いろいろなメーカーのいろいろな製品がありますので、それらの機器がAnsibleでどのように操作できるか調べて、またブログに書いてみたいと思います。


執筆者プロフィール:水谷 裕一
大手外資系IT企業で15年間テストエンジニアとして、多数のプロジェクトでテストの自動化作業を経験。その後画像処理系ベンチャーを経てSHIFTに入社。

SHIFTグループ会社「RGA」および「システムアイ」に出向し、インフラ構築の自動化やCI/CD、コンテナ関連の業務に従事した後、2024年3月よりSHIFTのインフラサービスGに配属。

LinuxよりWindowsの方が好き。


IaC支援サービスのご紹介

SHIFTではTerraformやCDKを使ったクラウドインフラ構築の自動化や、Ansibleを使ったサーバOSの設定自動化や構成管理のご支援も行っております。ご依頼・ご相談は下記リンクからお願いします。

お問合せはお気軽に

SHIFTについて(コーポレートサイト)

SHIFTのサービスについて(サービスサイト)

SHIFTの導入事例

お役立ち資料はこちら

SHIFTの採用情報はこちら

PHOTO:UnsplashAlex Kulikov