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でどのように操作できるか調べて、またブログに書いてみたいと思います。
IaC支援サービスのご紹介
SHIFTではTerraformやCDKを使ったクラウドインフラ構築の自動化や、Ansibleを使ったサーバOSの設定自動化や構成管理のご支援も行っております。ご依頼・ご相談は下記リンクからお願いします。
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
SHIFTのサービスについて(サービスサイト)
SHIFTの導入事例
お役立ち資料はこちら
SHIFTの採用情報はこちら
PHOTO:UnsplashのAlex Kulikov