これを実現します。コードは次を参照してください。
CML2ってノードの初期設定面倒ですよね。以下を自動化します。
- 1 pythonのvirl2_clientを使ってCML上に外部からアクセス可能なNWノードを自動作成する
- 1-1 ノードとExternalコネクタ(Bridge)を自動デプロイする
- 1-2 各ノード間を自動配線する
- 1-3 Mgmt側I/Fに関する設定を起動時に読み込ませる
- 2 デプロイしたノードのmgmtアドレスを取得する
- 3 AnsibleのCisco.iosで簡単なI/F設定とOSPF設定を投入する
この記事ではポイントを絞って説明します。
作りたいネットワーク
- rt1 <-> rt2 でOSPF。PC1, PC2のセグメントの経路を交換。
- 各mgmtはexternal bridge経由で外部(CMLの外)から叩く
- PC1 -> PC2でpingできればOK
Cisco CMLのインストール
いろいろな人が書いているので割愛します。私が過去に書いた記事は以下です。
lib2_clientを使ったCML上へのノードの自動デプロイ
lib2_clientはCiscoCMLのWebUI上で行えるすべての作業が行えるPythonのライブラリです。
基本的な使い方
# CMLに接続しlabnwを開く
client = ClientLibrary(f"https://192.168.0.100/", "admin", "cisco", ssl_verify=False)
lab = client.create_lab("labnw")
# labにnode1, node2を作り、(新しいI/Fを作って) ノードを接続する
node1 = lab.create_node("RT1", "iol-xe")
node2 = lab.create_node("RT2", "ioll2-xe")
pc1 = lab.create_node("PC1", "alpine") # alpine(linux)を作成
node1.config = "...(node1のconfig)"
node2.config = "...(node2のconfig)"
pc1.config = "..."
lab.connect_two_nodes(node1, node2 ) # この場合、eth0/0.node1 <-> eth0/0.node2が接続される(intfは勝手に作成される)
intf1 = pc1.create_interface() # eth0.pc1が作成される
intf2 = node1.create_interface() # eth0/1.node1が作成される
lab.connect_two_nodes(intf1, intf2) # eth0.pc1 <-> eth0/1.node1が接続される
lab.start()
# ノードが起動しきったら終了する
後述のようにnode.configにbootstrapのscriptを設定しておくことでmgmtアドレスを設定可能です。このアドレスはこのように取得可能です。iosxeでも、alpineでも取得できます。
for node in lab.nodes():
print(node)
for interface in node.interfaces():
print(" ", interface, interface.discovered_ipv4)
特殊なノード
# Bridge (external_connectorのconfigにテキストで"System Bridge"と書く)
bridgeNode = lab.create_node("bridge", "external_connector", 0, 0)
bridgeNode.config = "System Bridge"
mgmtsw = lab.create_node("mgmtsw", "unmanaged_switch", 0, 500)
Configのポイント
node.config
に設定する設定のポイントです。
iol-xeあるいはioll2-xeの場合
ここを参考してください。
sshのkey作成はmod 2048
などを与えるとキーボード入力を求められることがありません。
ip domain name cisco
transport input telnet ssh
ip ssh server algorithm authentication password
crypt key gen rsa mod 2048
do write memory
DHCPによるアドレス取得としたい場合は以下の通りとなります。
interface Ethernet 0/0
ip address dhcp
Alpine設定のポイント
ここを参考にしてください。注意書きに書いた通り、staticにアドレスを指定してもdhcpcが起動します。DHCPのみの付与でよい場合はipコマンド群は不要です。
hostname {hostname}
# ここで設定したUSER, PASSでユーザが作成されます。sudo可能です。
USERNAME={loginUsername}
PASSWORD={loginPassword}
# 以下のようにstaticアドレスを付与できますが、この有無にかかわらず"udhcpcが起動する"ことを注意してください
ip address add {mgmtaddr}/{mgmtsubnet} dev eth0
ip route add default via {mgmtgw}
Nexus設定のポイント
今回のスクリプトには含めていませんがNexusの場合、一番最初のcreate_interface()
がmgmtインタフェースです。
インベントリの作成
次の工程でansibleによる自動設定を行いたいのでhostname <-> mgmt ipaddr
のマッピングを作ります。
mgmtアドレスをDHCPで設定した場合、デプロイしたノードのmgmtアドレスを知るのは困難に思えますが上記で見た通りCML2ではnodeのinterface.discovered_ipv4に設定されたIPアドレスが格納されています。これを利用して以下のようなアプローチでmgmtを取得できます。
- アプローチ1: 各nodeのipv4アドレスを見て、あらかじめ設定したmgmtのアドレス空間に含まれていればそれをそのnodeのmgmtアドレスとして扱う
- アプローチ2: 各ノードの最初のインタフェースがmgmtになるように生成を行いそれをそのnodeのmgmtアドレスとして扱う
今回はこのようにアプローチ2を採用しています。(今回の実装ではeth0あるいはfastether0/0かで判定しています)
for node in lab.nodes():
nodeName = node._label
for interface in node.interfaces():
if "Interface: Ethernet0/0" in str(interface):
# この場合、iosxe
elif "Interface: eth0" in str(interface):
# この場合、alpine
ansibleのinventoryのテンプレートをこのように作って起き、loadした後にhosts部分を埋めて再度dumpします。
baseYaml = """
all: ...
children:
iosxe:
hosts:
pc:
hosts:
...
"""
dat = yaml.safe_load(baseYaml)
dat["all"]["children"]["iosxe"]["hosts"] = {}
dat["all"]["children"]["pc"]["hosts"] = {}
...
dat["all"]["children"]["pc"]["hosts"][nodeName] = {
"ansible_host": str(mgmtAddr),
} # など
...
with open('inventory.yaml','w')as fw:
yaml.dump(dat, fw, default_flow_style=False, allow_unicode=True)
これにより、このようなinventoryが生成されます。
all:
children:
iosxe:
hosts:
rt1:
ansible_host: 192.168.101.222
ansible_network_os: ios
rt2:
ansible_host: 192.168.101.223
ansible_network_os: ios
pc:
hosts:
pc1:
ansible_host: 192.168.101.224
設定する
ここまでくればansibleのcisco.iosモジュールで設定できます。このように各種設定をして以下のように実行しましょう。
ansible-playbook -i inventory.yaml 30_setconfig.yaml
- name: router 1
hosts: rt1
gather_facts: no
tasks:
- name: rt1-interface-addr
cisco.ios.ios_l3_interfaces:
config:
- name: Ethernet0/1
ipv4:
- address: 10.0.0.1/24
state: merged
- name: OSPF V2 configuration
cisco.ios.ios_ospfv2:
config:
processes:
- process_id: 1
router_id: 10.255.0.1
log_adjacency_changes:
set: true
network:
- address: 10.0.0.0
wildcard_bits: 0.0.0.255
area: 0
alpine設定のコツ
alpineにはPythonが入っていないのでbuiltin.raw
で流します。しかし、sudo ip
で実行すると戻り値がtrueにならないようなので以下のように|| true
を使って強制的にtrueを戻すようにしています。
- hosts: pc1
gather_facts: no
tasks:
- name: link up ip address
ansible.builtin.raw: sudo ip link set eth1 up || true
- name : set ip address
ansible.builtin.raw: sudo ip addr add 10.101.0.2/24 dev eth1 || true
- name: set route
ansible.builtin.raw: sudo ip route add 10.0.0.0/8 via 10.101.0.1 || true
動作確認
設定変更と変わりません。