これを実現します。コードは次を参照してください。
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
動作確認
設定変更と変わりません。
