Cisco CML(Cisco Modeling Labs)上に実機ネットワークを自動構築し、CCNP 相当の課題を出題 → 受験者が実コマンドで解答 → 自動採点する、Ansible + pyATS/Genie ベースの学習システムです。
対象は Cisco 認定 CCNP ENCOR (350-401) / ENARSI (300-410) の技術領域。「問題を作る・投入する・採点する」を一気通貫で自動化しているので、同じ技術を何度でも・少しずつ値を変えて練習できます。
個人の学習・作問用に育てたリポジトリです。特定のCML環境で実機検証しながら作っていますが、環境依存の値(CML接続情報・管理IP)は差し替え可能にしてあります(後述のセットアップ参照)。
問題生成/作問 ──→ 初期投入 ──→ 受験者が CML で実コマンド解答 ──→ 自動採点
(生成器 gen_*.py) (Ansible) (CML コンソール / SSH) (pyATS Genie で構造化して判定)
- 実機で学べる — シミュレータの近似ではなく、本物の Cisco IOS-XE / IOSv / IOS-L2 を CML 上で動かして解く。
- 完全自動の採点 —
show出力を pyATS Genie で構造化し、フィールド単位でアサート。「隣接が FULL か」「この経路だけ消えているか」「ping が通るか」まで機械判定。合否は収束するまでリトライ。 - 問題の量産 — seed を渡すと生成器がトポロジ・故障・パラメータをランダム化して問題を丸ごと生成。同じ seed なら同じ問題(再現可能)。
- 自己完結の問題パック — 1問 = 1ディレクトリ。問題文・初期config・採点基準・模範解答が揃っている。
- トラブルシュート対応 — 正常構成に故障を注入して「直す」問題や、複数レイヤにまたがる連鎖故障も生成できる。
- Ansible 自動化ラボ — Cisco 機器を題材に Ansible そのものを学ぶ「道場」コースも同梱。
4つの Ansible playbook が「描画 → 起動 → 解答 → 採点」を担います。
build_topology.yml ──→ lab_up.yml ──────→ (受験者が解答) ──→ grade.yml
day0 config を描画 CML へ import 実機 CLI で show 出力を Genie で
+ gen_cml_lab.py で して起動 設定を投入 構造化し、基準と照合
ラボ YAML を生成 して点数を返す
- build_topology.yml — 問題定義(
problem.yml)から day0 の「穴あき」config を描画し、gen_cml_lab.pyが CML 用ラボ YAML(ノード・配線・管理IP)を生成する。 - lab_up.yml — そのラボを CML に import して起動する。管理IPは共有プールから動的に貸与(
mgmt_alloc.pyの台帳が管理)。 - 受験者の解答 — CML のコンソール、または管理IP経由の SSH で機器にログインし、実コマンドで設定する。自動化ラボの場合は付属の作業ディレクトリで Ansible を書く。
- grade.yml — 各ノードへ
showを送って出力を Genie で構造化し、採点基準(grading.yml)のチェックを1つずつ判定。全PASS になるまで一定間隔でリトライ(設定の収束待ち)。
採点の詳細は 採点の仕組み を参照。
現在 problems/ に 165 問(ENCOR 系 46・ENARSI 系 35・生成問題 GEN 系 67・Ansible 自動化 5・FortiGate 4・大型シナリオほか 8)。難易度は 1〜6 で、3〜4 が中心。加えて 生成器から無限にバリエーションを作れます。出題可能な問題の正準台帳は problems/CATALOG.md。
| 分野 | 主な問題 |
|---|---|
| OSPF | 単一エリア / マルチエリア / stub / NSSA / インターフェイスモード / 認証 / OSPFv3 |
| EIGRP | 基本構築 / variance による不等コストロードバランス / SIA トラブルシュート |
| 再配送 | OSPF ⇄ EIGRP など IGP 相互再配送 |
| First-Hop 冗長 | HSRP(FHRP) |
| トンネル / VPN | GRE、DMVPN(Phase2 / Phase3)、IPsec sVTI(IKEv1 / IKEv2) |
| 経路制御 | PBR、IP SLA + Object Tracking、WAN 冗長・フェイルオーバ |
| セグメンテーション | VRF-Lite、VRF ルートリーク、VRF + NAT |
| セキュリティ / L2 | 標準/拡張/名前付き ACL、VACL、SPAN / RSPAN、CoPP、エッジ機器ハードニング |
| 可視化 / 運用 | Flexible NetFlow、EEM |
| QoS | 分類・マーキング / ポリシング / 階層シェーピング + LLQ(効果を iperf3/ping で実測採点する体感シリーズ3問) |
| DHCP | DHCPv4 一気通貫(プール配布 / MAC 固定割当 / リレー / DHCP-only ACL。採点は release/renew の実効確認込み) |
| その他 | LAG(EtherChannel)、IPv6 静的 / SLAAC、NAT/PAT 複合 |
| 分野 | 主な問題 |
|---|---|
| BGP 属性 / 経路制御 | weight / local-preference / MED / community / AS-path / origin / next-hop-self |
| BGP フィルタ / 集約 | prefix-list、route-map、AS-path 正規表現、経路集約、ルートリフレクタ |
| BGP 複合 / 特殊 | 複数属性の総合ポリシー、IPv6 AF、レガシー synchronization 残骸×非BGP中継ブラックホールの2段TS |
| 再配送 | 相互再配送、再配送ループの発生と抑止、3ドメインリング一周ループ(distance bgp 解法)、仕様書駆動の選択的再配送(遮断 / E1 / seed metric / 出自タグ / 監査) |
| MPLS | MPLS L3VPN 6問(基礎構築 / マルチカスタマー / PE-CE eBGP + as-override / フルメッシュ×Hub&Spoke 組み分け / Extranet 共有サービス VRF) |
| オーバーレイ複合 | DMVPN + BGP 再配送、DMVPN + IPsec 完全版、GRE over IPsec(sVTI IKEv1/IKEv2・crypto map 版) |
| アドレッシング / セキュリティ | DHCPv6(stateful/リレー)、uRPF anti-spoofing、VRF-Lite DN ビット |
| ID | 内容 |
|---|---|
CAMPUS-TS-01 |
12台・3層キャンパス(ASA 含む)の複数レイヤ連鎖トラブルシュート超大作 |
SDA-LISP-01 |
SD-Access の土台を素の LISP/VXLAN で組むガイド付き教育ラボ(9台・LISP編→VXLAN編) |
EVPN-VXLAN-01 |
EVPN-VXLAN ファブリック(IOL RR + NX-OS leaf×3・SDA-LISP-01 の続編) |
UM2-BUILD-01/02 |
中規模企業網の総合構築。02 はワンアーム LB 変種(IP SLA トラッキング対比) |
DMVPN-PHASE3-01 ほか |
DMVPN 単体の構築問(Phase2 / Phase3) |
NETAUTO-03-RESTCONF |
RESTCONF によるネットワーク自動化道場(cat8000v) |
FortiGate-VM(eval ライセンス)を Cisco 網に混ぜた4問: FGT-SDWAN-01(SD-WAN)/ FGT-FW-BASIC-01(FWポリシー基礎)/ FGT-IPSEC-01(Cisco との IPsec 相互接続・管理IF自己設定の Phase 0 付き)/ FGT-REPLACE-01(ASA 設定読替の卒業試験)。eval 制約(IF 3個・ポリシー3本・route 3本・DES 限定)を前提に設計。
| 種別 | 生成器 | 内容 |
|---|---|---|
| 到達性 | gen_topology.py |
ランダムなツリー型 OSPF。BFS で一意な next-hop を計算し採点 |
| ひねり | gen_twist.py / gen_aggregate.py / gen_pathctrl.py |
ルートフィルタ / 経路集約・マルチエリア / 経路制御・冗長 |
| トラブルシュート | gen_troubleshoot.py |
OSPF に故障を注入(多重故障・おとり・段差故障に対応) |
| BGP TS | gen_bgp_troubleshoot.py / gen_bgp_pathts.py / gen_bgp_rrts.py / gen_bgp_complex_ts.py |
到達性 / 経路選択 / RR 伝播 / 複合故障 |
| IGP 複合 TS | gen_eigrp_complex_ts.py / gen_ospf_complex_ts.py / gen_ospfv3_complex_ts.py / gen_eigrpv6_complex_ts.py |
EIGRP / OSPF / OSPFv3 / EIGRPv6 の複合故障 |
| 再配送 TS | gen_redist_mutual_ts.py / gen_redist_ripospf_ts.py |
相互再配送 / RIP⇄OSPF 再配送ループ |
| 再配送ループ TS | gen_redist_loop_ts.py / gen_redist_mp_ts.py |
BGP→EIGRP→OSPF リングを出自が一周する定常ループ(3変種) / 多点相互再配送ループ(解法4モードを seed 抽選し監査ポリシーで強制) |
| 連鎖故障 | gen_chain_ts.py |
12台規模でレイヤをまたぐ連鎖故障を生成 |
| MPLS L3VPN TS | gen_mpls_ts.py |
12台 (3PE×Pリング×2顧客) の L3VPN に L1〜L5 の故障を注入。--pece ebgp 軸で PE-CE eBGP 化 |
| L2 TS | gen_l2_troubleshoot.py |
EtherChannel など L2 の故障 |
| DMVPN TS | gen_dmvpn_ts.py |
DMVPN + IPsec に14種の故障を注入 |
| DHCP TS | gen_dhcp_ts.py |
DHCPv4(配布/リレー/ACL)に8故障×3レイヤを注入 |
| FNF TS | gen_fnf_ts.py |
仕様書突き合わせ型 Flexible NetFlow TS(10故障4レイヤ・--faults で複合化) |
| セキュリティ TS | gen_urpf_ts.py |
uRPF anti-spoofing の故障(strict過剰/ACL例外誤り/未設定/loose過緩の4種・非対称ルーティング題材) |
| フィルタ道場 | gen_list_dojo.py |
prefix-list / AS-path / ACL の基本形ドリル3部作(acl_model.py = ACL 意味評価器で採点) |
| S2S VPN 設計構築 | gen_s2svpn.py |
要件書形式・技術選定自由の設計構築問。--day2 で運用チケット3本(支店追加/split化/重複NAT)を追加出題 |
| サーバ / 監視 | gen_dnsdhcp_*.py / gen_radius_build.py / gen_snmpv3_ts.py / zbx_setup.py |
Linux サーバ(BIND/DHCP/FreeRADIUS)構築・TS、SNMPv3/Zabbix 監視 |
Cisco 機器を題材に Ansible 自体を段階学習するコース。トポロジは固定で「変わるのは Ansible 技術だけ」。
インベントリ → アドホック → Playbook → 変数優先順位 → 冪等性 の順に進む。ENCOR/ENARSI 側にも「OSPF/BGP を Ansible で自動構築する」自動化問題(*-AUTO-*)がある。
問題一覧は ls problems/、各問題の詳細は problems/<ID>/task.md(.j2) と solution.md を参照。
| 必要なもの | 用途 |
|---|---|
| Cisco CML 2.x | ネットワーク機器を動かす基盤(API 有効・ログイン可能なこと) |
| CML ノードイメージ | ルータ/スイッチ用に IOL(iol-xe / ioll2-xe)、または IOSv/IOSvL2。Linux 問題用に Ubuntu クラウドイメージ |
| Linux コントローラ(Ubuntu 想定) | Ansible + pyATS を動かすホスト。CML と管理ネットワークで疎通できること |
| Python 仮想環境 | .venv に ansible-core、pyATS/Genie、cisco.ios / cisco.cml コレクション、virl2_client |
使用する CML イメージ ID は
group_vars/all/main.ymlのdevice_profilesで定義。手元の CML に登録済みの正確な ID に合わせて調整してください。
git clone <this-repo> && cd CCNP01
# 1) Python 仮想環境(ansible-core / pyATS / cisco.ios / cisco.cml / virl2_client を導入)
python3 -m venv .venv && . .venv/bin/activate
pip install ansible-core pyats[full] && ansible-galaxy collection install cisco.ios cisco.cml
# 2) ★ローカル環境設定(CML接続情報・管理IPプール)を自分の値で作成
cp group_vars/all/local.yml.example group_vars/all/local.yml
$EDITOR group_vars/all/local.yml # cml_host / cml_username / cml_password と mgmt_* を編集group_vars/all/local.yml は .gitignore 済み(コミットされない)。CML の場所・ログイン・管理ネットワークといった各自の環境依存値はここだけに置きます。雛形は group_vars/all/local.yml.example。
機器のログイン認証は Ansible Vault(group_vars/all/vault.yml)に格納。学習用の固定値なので vault パスワードは CCNP、playbook 実行時に --vault-password-file <(printf 'CCNP\n') を付けます(各ノードの config テンプレートが同じ user/secret を焼き込む前提。変える場合はテンプレート側も合わせて変更)。
scripts/lab.sh がライフサイクル(build → up → 作業コピー配布 → 撤去)を一括管理します。
# 出題(build_topology → lab_up → lab/<ID>/ に問題.md と作業コピーを配置)
scripts/lab.sh provision ENCOR-OSPF-01 # variant 指定は第2引数: provision <ID> <variant>
# 受験者は CML コンソール / SSH で機器にログインして解答
# - SSH 例: ssh <user>@<割当てられた管理IP>
# - 自動化ラボ(ANSIBLE-*/AUTO-*)は lab/<ID>/ を編集して playbook を流す
# 採点
ansible-playbook playbooks/grade.yml -e problem=ENCOR-OSPF-01 \
--vault-password-file <(printf 'CCNP\n')
# パラメータ化問題は -e variant=sXXXX も付ける
# 片付け(CMLラボを削除して管理IPを解放。problems/<ID> 自体は残る)
scripts/lab.sh teardown ENCOR-OSPF-01
scripts/lab.sh status # 稼働ラボ・管理IPの空き・作業コピー一覧lab.sh を使わず手で回す場合:
V="--vault-password-file <(printf 'CCNP\n')"
ansible-playbook playbooks/build_topology.yml -e problem=<ID> [-e variant=<v>] [-e node_image=iosv] $V
ansible-playbook playbooks/lab_up.yml -e problem=<ID> $V # 停止/撤去: -e lab_state=stopped|absent
ansible-playbook playbooks/grade.yml -e problem=<ID> $V # -e max_attempts= -e settle_delay= で収束調整複数ラボの同時稼働に対応。管理IPは
topologies/mgmt_alloc.pyのリース台帳(_state/mgmt_leases.json)が first-fit で調停するため、旧ラボを撤去せずに新ラボを起動しても管理IP衝突は起きません。teardown 漏れはmgmt_alloc.py gcで回収できます。同時稼働の上限は環境依存で、コードにハードコードした定数はありません。実質的には次の2つで決まります。
- 管理IPプールのサイズ =
group_vars/all/local.ymlのmgmt_poolの要素数(環境ごとに各自が設定・.gitignore済。天井は管理セグメントのサブネット幅)。mgmt_alloc.pyはプール不足のときだけ割当を停止する(要求ノード数 > 空きIP数 → rc=2、CML には触らない)。- CML の同時起動ノード数 = CML ライセンスグレードの制約(例: Personal は 20 ノード)。プロジェクト側はこれを関知せず、超過分は CML が拒否する。Enterprise 等では実質プールサイズが律速。
CCNP01/
├── ansible.cfg # network device 向け接続設定
├── inventory.yml # 名前/グループのみ(管理IPは動的割当)
├── group_vars/ host_vars/ # 接続変数・device_profiles・vault(機器認証) / local.yml(環境依存・gitignore)
├── roles/baseline/ # day0 baseline テンプレート(baseline_router/switch/server.cfg.j2)
├── playbooks/
│ ├── build_topology.yml # day0 描画 + gen_cml_lab.py → _generated/<ID>/lab.yaml
│ ├── lab_up.yml # CML へ import + 起動(管理IP割当・プリフライト)
│ ├── grade.yml + _grade_attempt*.yml # 採点(ssh / telnet / console の3収集パス)
│ ├── solve_generated.yml / fix_generated.yml # 生成問題の模範解答/故障修正の自動投入
│ └── verify_failover*.yml # フェイルオーバ(shut→疎通→復旧)の能動検証
├── topologies/
│ ├── conventions.md # 採番・配線・命名規約
│ ├── gen_cml_lab.py # problem.yml → CML ラボ YAML
│ ├── grade.py + netmodel.py # 採点エンジン(Genie 構造化 / 大域不変条件)
│ ├── mgmt_alloc.py # 管理IPリース台帳(複数ラボ同時稼働の調停)
│ ├── gen_*.py # 各種生成器(34本)
│ ├── *_ops.py # 大型シナリオの構築/運用ヘルパ(campus/sda/evpn/um2/fgt*/sdwan/s2svpn)
│ └── _generated/<ID>/ # build で出る中間物(mgmt_map / lab.yaml / 描画済 task.md 等・gitignore)
├── problems/<ID>/ # 問題パック(自己完結)
│ ├── CATALOG.md # 出題可問題の正準台帳(実機検証済のみ掲載)
│ └── _drafts/ # 未着手課題の詳細設計メモ(BACKLOG.md からリンク)
├── poc/<テーマ>/ # 作問前の PoC 置き場(実機検証ログ・試行スクリプト)
├── um2/ # UM2 シリーズの設計書・構成図
├── scripts/lab.sh # 出題/片付けラッパ
└── lab/<ID>/ # provision が作る使い捨て作業コピー(gitignore)
1問 = problems/<ID>/ の自己完結ディレクトリ。
| ファイル | 役割 |
|---|---|
problem.yml |
メタ(id/exam/topics/difficulty/target_nodes/points)+ lab.links(配線。a_if/b_if は slot 番号) |
task.md または task.md.j2 |
受験者向け問題文(日本語)。.j2 は params で数値が変わる |
initial/<NODE>.cfg.j2 |
出題時に重ねる「穴あき」config |
grading.yml または grading.yml.j2 |
採点基準(フィールドアサート型スキーマ) |
solution.md |
模範解答 |
params/ |
パラメータ化(base.yml 既定値 + _gen.yml ランダム化スキーマ) |
controller/ controller_solution/ blanks.yml |
自動化ラボ用の穴あき workspace と完成形 |
initial/grading/task を .j2 化し {{ params.xxx }} を参照。-e variant=<name>(既定 base)で値を切替。
ランダム生成は gen_params.py --problem <ID> --seed N → params/sN.yml(同 seed = 同値 = 再現可能)。
show出力を Genie で構造化 →find(glob でオブジェクト選択)+match(同一オブジェクト上でフィールド相関)で判定。配点は all-or-nothing。grade.ymlが 全PASS or max_attempts まで settle_delay 秒間隔でリトライ(収束待ち)。- Genie パーサが無い/壊れる箇所は
raw:正規表現(contains / not_contains / regex …)で判定。「特定経路だけ不在」「単一経路化(ECMP排除)」「能動 ping の成否」もこれで表現。 netmodel.py= ネット全体の大域不変条件(ループ不在 / 最適性 / 到達性)を採点する別系統。- 収集パスは
problem.ymlのaccess:で分岐:ssh(既定)/telnet(IOL L2スイッチ)/console(IOSv)。
grading.yml スキーマ例:
defaults: { genie_os: iosxe }
checks:
- name: "RT01-RT02 OSPF 隣接 FULL"
node: RT01
command: show ip ospf neighbor
parser: show ip ospf neighbor # command=実機へ送る文字列 / parser=Genieキー(別物)
find: "interfaces.*.neighbors.*"
match: { state: { startswith: "FULL" } }
points: 30
- name: "特定経路だけ不在(raw で判定)"
node: RT04
command: show ip route 35.35.35.35
raw: [ { regex: "not in table" } ]
points: 10規約(要点 / 詳細は conventions.md)
- 命名:
RTxx=router /SWxx=switch(接頭辞でロール自動判定)。CMLラボ名はCCNP-LAB-<md5(id)[:8]>で不透明化(画面に技術名が出て解法バレを防ぐ)。 - 採番: Loopback0 =
x.x.x.x/32(RT01→1.1.1.1)。ルータ間 =10.1.<a><b>.0/30(小番号側が .1)。 - イメージ: トポロジ単位で統一。優先
CLI -e node_image>problem.yml> group既定。物理IF名は device_profiles で論理↔物理を吸収。
- 複数ラボ同時稼働可(管理IPは
mgmt_alloc.pyのリース台帳が調停)。上限は環境依存でコード定数はない:mgmt_pool(local.yml)の要素数と、CML グレードの同時起動ノード数(例: Personal 20)で決まる。teardown 漏れはmgmt_alloc.py gcで回収し、不要ラボはscripts/lab.sh teardown <ID>で撤去する。 - baseline 末尾に
endを置かない(day0 は baseline+initial を連結。中間end以降が無視される)。 - IOL はリンクダウンを対向に伝播しない(片端 shutdown でも対向 line protocol は up)。フェイルオーバ設計は remote link-down 検知に依存しないこと。
raw採点は IOS のコマンド省略形・別表記に脆い → 各語を<最短プレフィクス>[\w-]*で緩める。task.md.j2にリテラル{{ ____ }}を書くと描画器が変数解釈して失敗 →{% raw %}…{% endraw %}で囲む。- 採点チェックが多く2分超になる問題は、実行ツールの timeout を上げる。
学習・作問目的の個人プロジェクトです。Cisco IOS 等のイメージは各自が正規に入手・ライセンスした CML 環境で使用してください(本リポジトリにイメージは含みません)。