TECH PLAY

Docker

イベント

該当するコンテンツが見つかりませんでした

マガジン

技術ブログ

はじめに 2026年5月14-15日(木金)に名古屋の中日ホール&カンファレンスにてクラウドネイティブ会議が開催されました。本記事では同イベントで行われた発表の中から、さくらインターネット研究所の小田知央さん(@ […]
Containerlab と Juniper の無償仮想イメージ vJunos を使用し、データセンターネットワーク(NW)の定番アーキテクチャ「Leaf-Spine 構成」をゼロから構築するハンズオン記事です。eBGP による Underlay 構成を皮切りに、EVPN/VXLAN によるテナント L2 拡張、ESI-LAG を用いた冗長化、VRF Route Leaking によるインターネット接続ゲートウェイ模擬まで、クラウド NW の中核技術をひと通り体験できます。ハードウェア不要・無償イメージのみで動く手順と全設定例を掲載しています。 はじめに 前提知識 全体像 構築するトポロジ ポイント 進め方 前提とする環境 vJunos イメージの準備(vrnetlab でコンテナ化) 1. 環境構築 — Containerlab で機器を起動 1.1 トポロジ定義(clos-network.clab.yml) 1.2 デプロイ ✅ 完成状態チェックリスト 2. Underlay の構築 — eBGP で Loopback 同士を疎通させる 2.1 IP アドレス設計 2.2 各ノードの IP / Loopback 設定 2.3 eBGP の設定 ポイント spine1 leaf1-1 2.4 動作確認 ✅ 完成状態チェックリスト 3. Overlay の構築 — EVPN/VXLAN 3.1 アーキテクチャの整理 3.2 C-Plane: iBGP EVPN ピア leaf2-1 rr1 hv1(FRR) 確認 3.3 D-Plane: VXLAN セグメントを作る hv1:Linux Kernel + FRR leaf2-1 bm1 動作確認 3.4 EVPN Multihoming(ESI-LAG)で BM の接続を冗長化 方針 leaf2-1(既存設定の置き換え) leaf2-2(新規) bm1(Linux Bond) 切断試験 ✅ 完成状態チェックリスト(Overlay全体) 4. Border Leaf と Internet Gateway 4.1 leaf3-1(Border Leaf として VTEP 化) 4.2 inet-gw1: 共通の下準備(I/F + ASN) 4.3 inet-gw1: VRF と Route Leaking 4.4 inet-gw1: 外部 ISP との eBGP 4.5 isp1 (外部 ISP 模擬) 4.6 bm1 (Global IP 付与とデフォルトゲートウェイ) 4.7 動作確認 ✅ 完成状態チェックリスト(Border GW) 全体の完成確認 おまけ: ハマったら見るところ(Troubleshooting) Junos 側で何が起きているか覗くコマンド FRR 側 まとめ はじめに こんにちは。NTTドコモビジネスのクラウド、 SDPFクラウド/サーバー (以降、SDPF クラウド)の内製開発に従事している堀岡勇杜と申します。 私が主に担当しているのは、ESI(Elastic Service Infrastructure)という名称の、クラウドのNWオーケストレータの開発です。ESI チームの業務内容については、 こちらの記事 で詳しく紹介しています。 ※ 本記事で出てくるEVPN Multihoming の関連用語であるESI(Ethernet Segment Identifier)とは全くの別物です。 突然ですが、データセンター NW の定番アーキテクチャである Leaf-Spine(CLOS) と、その上で動く EVPN/VXLAN は、クラウド NW の中核技術です。しかし実機やシミュレータがなければ、その挙動を肌で理解するのは大変難しいです。私自身も入社当時(2025年4月)はクラウド NW については完全な初学者だったのですが、この 1 年間で勉強させていただき、その全貌の一端が理解できるようになってきました。本記事を通じて、私が得た知見を共有できましたら幸いです。さまざまな用語が出てきますが、それらを全て解説するのは不可能であるため、気になる点は適宜調べながら進めていただければと思います。 用語解説 Leaf-Spine … サーバーを収容する「Leaf(葉)」スイッチと、Leaf 同士をつなぐ「Spine(背骨)」スイッチの 2 階層でネットワークを組む CLOS の定番設計。どのサーバー間も同じホップ数で通信でき、帯域を横に足しやすいのが特長です。SDPFクラウドでは、Spineのさらに上のSuper Spineが存在しており、3 階層の CLOS になっています。 EVPN/VXLAN … 物理的に離れたサーバー同士を「同じ LAN にいるかのように」つなぐ仮想ネットワーク技術です。EVPN が「誰がどこにいるか」の情報交換(制御プレーン)、VXLAN が実データのトンネル転送(データプレーン)を担います。 本記事では、OSS である Containerlab と Juniper Networks 社の vJunos (無償の仮想ルータ/スイッチイメージ)を使用し、典型的な Leaf-Spine 構成をゼロから組み立てます。最終的には以下を達成します。 eBGP による Underlay の経路交換 iBGP + EVPN Type-2/3 による MAC/IP 学習(C-Plane = 制御プレーン) VXLAN によるテナント(利用者)トラフィックのカプセル化(D-Plane = データプレーン) ESI-LAG による EVPN Multihoming(サーバーの複数スイッチへの冗長接続) Border Leaf + VRF Route Leaking (仮想ルーティングテーブル間の経路共有)によるインターネット接続ゲートウェイ模擬 「SDPFクラウドの SDN コントローラや NW オーケストレータが裏で何をしているのか」をハンズオンで体験し、クラウド NW の裏側を覗いていただける構成になっています。 ⚠️ 本記事は学習用ラボ構成を前提としています 動作させることを優先しているため、本番設計とは異なる選択をしている箇所が多々あります(例: MTU デフォルトのまま、BGP 認証なし、など) 前提知識 本記事の主な対象読者は、 BGP の基本概念(AS・ピアリング・経路広告)を理解している、データセンター NW の Overlay 技術を手を動かして学びたいインフラエンジニアや学生 です。以下の知識があるとスムーズに進められます。 分野 期待するレベルの目安 Linux 操作 コマンドライン操作( ip , ping , tcpdump など)、Network Namespace の概念 TCP/IP 基礎 IP アドレス・サブネットマスク・ルーティングの仕組み、L2(Ethernet)と L3(IP)の違い BGP 基礎 AS(Autonomous System)・ASN・eBGP / iBGP の区別、経路広告・ピアリングの概念 Docker 基礎 docker ps / docker pull などの基本操作、コンテナとイメージの違い 各セクションに設けている「用語解説」や「Q&A」は補足情報です。すでにご存知の方は読み飛ばしてください。 全体像 構築するトポロジ 最終的なゴールは、 物理的に異なるラックに収容された HV1(ハイパーバイザー上の VM)と BM1(ベアメタルサーバー)が同一の L2 ネットワーク上で通信でき、さらにそこからインターネットへ抜けられる 状態を作ることです。これを実現するために、Underlay(物理 IP 転送の土台)→ Overlay(EVPN/VXLAN による L2 延伸)→ Internet GW(VRF Route Leaking による外部接続)の順に積み上げていきます。 主な登場人物は以下の通りです。 ノード 役割 ASN Loopback spine1 Spine #1 64512 10.255.255.1 spine2 Spine #2 64513 10.255.255.2 leaf1-1 Underlay Leaf(HV=ハイパーバイザー収容) 65000 10.255.255.11 leaf2-1 / leaf2-2 Overlay Leaf(BM=ベアメタルサーバー収容、ESI-LAG ペア検証) 65000 10.255.255.21 / .22 leaf3-1 Border Leaf(Internet GW 収容) 65000 10.255.255.31 rr1 Route Reflector(経路情報を集めて他に配る中継役) 65000 10.255.255.101 hv1 ハイパーバイザー模擬(Linux + FRR、VTEP=VXLANトンネルの端点 を持つ) 65000 10.255.255.201 bm1 ベアメタルサーバー模擬(Linux) — — inet-gw1 Internet Gateway 模擬(vJunos-router) 65002 198.51.100.1 isp1 外部 ISP 模擬 65001 192.0.2.1 用語解説 Underlay(アンダーレイ) … 物理スイッチ・ルータが実際に IP パケットを転送する「土台」のネットワーク層です。本記事では Leaf-Spine 間の P2P リンクと eBGP がこれにあたります。 Overlay(オーバーレイ) … Underlay の上に「論理的に重ねた」仮想ネットワーク層です。本記事では VXLAN がデータのカプセル化(D-Plane)、EVPN が経路情報の交換(C-Plane)を担い、テナントの L2 セグメントを物理的に離れたノード間へ延伸します。Underlay が「道路」なら Overlay は「宅配便」のようなイメージです。 ポイント Spine ごとに ASN を分ける (64512 / 64513)。Leaf 側は multipath multiple-as で Spine 2 台への ECMP(Equal-Cost Multi-Path)を有効にしています。この設定により、障害発生時はベストパス再選定なしで残存パスに切り替わります。 Leaf は全て同じ ASN(65000) にして増設コストを下げています。詳細は以下を参照。 RR は Underlay にも参加 しますが、直接データを転送せず Overlay の経路反射(他のノードの経路情報をまとめて再配布する役割)を担当します。 HV 側は FRR で EVPN ピアリングを代用 し、RR と EVPN ピアを張ります(実際のクラウドでは SDN コントローラが HV 上の経路を収集し EVPN に変換します)。 Q. なぜ Leaf を全て同じ ASN にするのか? RFC 7938 は「Leaf ごとに別 ASN」を推奨していますが、本構成では共通 ASN(65000)を採用しました。トレードオフは次の通り。 共通 ASN(本記事) 別 ASN(RFC 7938 推奨) 増設運用 ✅ Leaf テンプレ流用、Spine 側も peer-as 1 つ ❌ Leaf ごとに ASN 採番、Spine 側も増やすたび neighbor 別設定 AS-PATH ループ防止 ❌ as-override で無効化される ✅ そのまま効く トラブルシュート ❌ AS-PATH に Leaf 識別子が出ない ✅ どの Leaf 経由か AS-PATH で分かる 進め方 Containerlab で機器を起動 Underlay(IP / eBGP)構築 Overlay(iBGP EVPN / VXLAN / ESI-LAG)構築 Border Leaf + Internet GW 構築 各セクションの末尾には「動作確認」と「完成状態チェックリスト」を載せています。 前提とする環境 以下を準備してください。 項目 内容 マシンの推奨スペック メモリ 32GB 以上(vJunos は 1 ノードあたり 4GB ほど使います)、ディスク空き容量 20GB 以上 (vJunos の qcow2 は 1 イメージ約 5GB、Docker ビルド後は合計 10GB 超になります) ホスト OS Linux(Ubuntu 24.04 で検証)。Docker が動く環境ならOK Docker v29.2.0 で検証 Containerlab v0.75.0 で検証。 インストール手順 vJunos イメージ vJunos-switch-25.4R1.12 , vJunos-router-25.4R1.12 (手順は以下) ゲスト OS 軽量 Linux nicolaka/netshoot (HV / BM 用)。コンテナ内の FRR は v10.6.1 で検証 エディタ VS Code + Containerlab 拡張があると便利 vJunos イメージの準備(vrnetlab でコンテナ化) vJunos は Juniper が 無償で公開している仮想ルータ/スイッチイメージ です。ただし配布形式は qcow2(KVM 用)なので、Containerlab で使うには vrnetlab で Docker イメージに変換する必要があります。詳細は こちら を参照。 # 1. vrnetlab をクローン git clone https://github.com/srl-labs/vrnetlab && cd vrnetlab/juniper # 2. Juniper 公式サイトからダウンロードした qcow2 を vrnetlab 下に配置 # https://support.juniper.net/support/downloads で vjunos-switch (または vjunos-router) で検索 cp ~/Downloads/vJunos-router-25.4R1.12.qcow2 vjunosrouter/ cp ~/Downloads/vJunos-switch-25.4R1.12.qcow2 vjunosswitch/ # 3. Docker イメージをビルド(それぞれ数分かかる) cd vjunos-switch && make && cd .. cd vjunos-router && make && cd .. # 4. ビルド結果を確認 docker images | grep vjunos # vrnetlab/juniper_vjunos-switch 25.4R1.12 ... # vrnetlab/juniper_vjunos-router 25.4R1.12 ... # HV / BM 用イメージ docker pull nicolaka/netshoot:v0.15 ⚠️ vJunos のライセンスについて vJunos は 評価・検証・学習用途で無償利用可能 です(商用サポートなし)。以下の点に注意してください。 機能制限はほぼありませんが、 commit 時に warning: requires 'bgp' license 等の警告が出ます。 これはライセンスキーを投入していないだけで、BGP 自体は問題なく動作します (本記事の全手順はライセンスなしで完走できます) 本番環境や商用目的での利用には正規ライセンスが必要です。詳しくは Juniper vJunos ページ を参照してください 1. 環境構築 — Containerlab で機器を起動 この章では、下図のように Spine / Leaf / RR / サーバー模擬など全 11 ノードを Containerlab で一括起動し、SSH でログインできる状態を目指します。 1.1 トポロジ定義(clos-network.clab.yml) name : clos-network topology : nodes : # --- SPINE --- spine1 : kind : juniper_vjunosswitch image : vrnetlab/juniper_vjunos-switch:25.4R1.12 spine2 : kind : juniper_vjunosswitch image : vrnetlab/juniper_vjunos-switch:25.4R1.12 # --- LEAF --- leaf1-1 : kind : juniper_vjunosswitch image : vrnetlab/juniper_vjunos-switch:25.4R1.12 leaf2-1 : kind : juniper_vjunosswitch image : vrnetlab/juniper_vjunos-switch:25.4R1.12 leaf2-2 : kind : juniper_vjunosswitch image : vrnetlab/juniper_vjunos-switch:25.4R1.12 leaf3-1 : kind : juniper_vjunosswitch image : vrnetlab/juniper_vjunos-switch:25.4R1.12 # --- Route Reflector --- rr1 : kind : juniper_vjunosrouter image : vrnetlab/juniper_vjunos-router:25.4R1.12 # --- HV / BM 模擬 --- hv1 : kind : linux image : nicolaka/netshoot:v0.15 bm1 : kind : linux image : nicolaka/netshoot:v0.15 # --- Internet Gateway / 外部 ISP 模擬 --- inet-gw1 : kind : juniper_vjunosrouter image : vrnetlab/juniper_vjunos-router:25.4R1.12 isp1 : kind : juniper_vjunosrouter image : vrnetlab/juniper_vjunos-router:25.4R1.12 links : # Spine <-> Leaf - endpoints : [ "spine1:eth1" , "leaf1-1:eth1" ] - endpoints : [ "spine1:eth2" , "leaf2-1:eth1" ] - endpoints : [ "spine1:eth3" , "leaf2-2:eth1" ] - endpoints : [ "spine1:eth4" , "leaf3-1:eth1" ] - endpoints : [ "spine2:eth1" , "leaf1-1:eth2" ] - endpoints : [ "spine2:eth2" , "leaf2-1:eth2" ] - endpoints : [ "spine2:eth3" , "leaf2-2:eth2" ] - endpoints : [ "spine2:eth4" , "leaf3-1:eth2" ] # Spine <-> RR - endpoints : [ "spine1:eth10" , "rr1:eth1" ] - endpoints : [ "spine2:eth10" , "rr1:eth2" ] # Server / GW - endpoints : [ "leaf1-1:eth3" , "hv1:eth1" ] - endpoints : [ "leaf2-1:eth3" , "bm1:eth1" ] - endpoints : [ "leaf2-2:eth3" , "bm1:eth2" ] - endpoints : [ "leaf3-1:eth3" , "inet-gw1:eth1" ] - endpoints : [ "inet-gw1:eth2" , "isp1:eth1" ] 1.2 デプロイ clab deploy -t clos-network.clab.yml # トポロジを Web UI で確認(リモートサーバーの場合はポートフォワード) clab graph -t clos-network.clab.yml # 全部壊してやり直したくなった時は clab destroy -t clos-network.clab.yml ノードへの SSH は ssh admin@clab-clos-network-spine1 (初期パスワードは admin@123 )。なお、Linux コンテナ(hv1 / bm1)は docker exec -it clab-clos-network-hv1 bash のように直接シェルに入る方が確実です。 ContainerlabのVS Code拡張を入れておくと、コンテナをまとめて管理できて便利です。 Spine・Leaf・RR・Inet-GW・ISPにおいて、あらかじめ以下の手順で root パスワードを設定して commit しておきます。これは以降の設定をコミットする上で必須の操作です。admin ユーザーのパスワード変更ではないことに留意。 configure set system root-authentication plain-text-password commit ✅ 完成状態チェックリスト clab deploy がエラーなく完了する docker ps で全 11 ノードが Up になっている ssh admin@clab-clos-network-spine1 でログインできる clab graph の Web UI でトポロジが想定通り表示される ここまでで、冒頭の図で示した全ノードが起動し、各機器へログインできる状態になりました。まだ NW 設定は入っていないので、次のセクションで Underlay から構築していきます。 2. Underlay の構築 — eBGP で Loopback 同士を疎通させる この章では、下図のように各ノードに IP アドレスを振り、Spine-Leaf 間で eBGP を張ることで、全ノードの Loopback 同士が疎通できる状態を目指します。この「下地」が後の Overlay(EVPN/VXLAN)の土台になります。 2.1 IP アドレス設計 P2P リンク(機器同士を 1対1 でつなぐリンク)は /30 (IP 2 個分)、Loopback は /32 (IP 1 個)。Spine 側を .1 、Leaf 側を .2 の規則で設計しています。 リンク Spine 側 Leaf 側 spine1 ↔ leaf1-1 10.1.11.1/30 10.1.11.2/30 spine1 ↔ leaf2-1 10.1.21.1/30 10.1.21.2/30 spine1 ↔ leaf2-2 10.1.22.1/30 10.1.22.2/30 spine1 ↔ leaf3-1 10.1.31.1/30 10.1.31.2/30 spine1 ↔ rr1 10.1.101.1/30 10.1.101.2/30 spine2 ↔ * 10.2.x.1/30 10.2.x.2/30(同様) leaf1-1 ↔ hv1 10.11.201.1/30 10.11.201.2/30 2.2 各ノードの IP / Loopback 設定 代表として spine1, hv1 を掲載します。他のノードも同じパターン(IP のみ差し替え)。 spine1 configure set routing-options router-id 10.255.255.1 set interfaces lo0 unit 0 family inet address 10.255.255.1/32 set interfaces ge-0/0/0 unit 0 family inet address 10.1.11.1/30 set interfaces ge-0/0/1 unit 0 family inet address 10.1.21.1/30 set interfaces ge-0/0/2 unit 0 family inet address 10.1.22.1/30 set interfaces ge-0/0/3 unit 0 family inet address 10.1.31.1/30 set interfaces ge-0/0/9 unit 0 family inet address 10.1.101.1/30 commit spine2 / leaf1-1 / leaf2-1 / leaf2-2 / leaf3-1 / rr1 のコマンドを開く spine2 configure set routing-options router-id 10.255.255.2 set interfaces lo0 unit 0 family inet address 10.255.255.2/32 set interfaces ge-0/0/0 unit 0 family inet address 10.2.11.1/30 set interfaces ge-0/0/1 unit 0 family inet address 10.2.21.1/30 set interfaces ge-0/0/2 unit 0 family inet address 10.2.22.1/30 set interfaces ge-0/0/3 unit 0 family inet address 10.2.31.1/30 set interfaces ge-0/0/9 unit 0 family inet address 10.2.101.1/30 commit leaf1-1 configure set routing-options router-id 10.255.255.11 set interfaces lo0 unit 0 family inet address 10.255.255.11/32 set interfaces ge-0/0/0 unit 0 family inet address 10.1.11.2/30 set interfaces ge-0/0/1 unit 0 family inet address 10.2.11.2/30 set interfaces ge-0/0/2 unit 0 family inet address 10.11.201.1/30 commit leaf2-1 configure set routing-options router-id 10.255.255.21 set interfaces lo0 unit 0 family inet address 10.255.255.21/32 set interfaces ge-0/0/0 unit 0 family inet address 10.1.21.2/30 set interfaces ge-0/0/1 unit 0 family inet address 10.2.21.2/30 commit leaf2-2 configure set routing-options router-id 10.255.255.22 set interfaces lo0 unit 0 family inet address 10.255.255.22/32 set interfaces ge-0/0/0 unit 0 family inet address 10.1.22.2/30 set interfaces ge-0/0/1 unit 0 family inet address 10.2.22.2/30 commit leaf3-1 configure set routing-options router-id 10.255.255.31 set interfaces lo0 unit 0 family inet address 10.255.255.31/32 set interfaces ge-0/0/0 unit 0 family inet address 10.1.31.2/30 set interfaces ge-0/0/1 unit 0 family inet address 10.2.31.2/30 commit rr1 configure set routing-options router-id 10.255.255.101 set interfaces lo0 unit 0 family inet address 10.255.255.101/32 set interfaces ge-0/0/0 unit 0 family inet address 10.1.101.2/30 set interfaces ge-0/0/1 unit 0 family inet address 10.2.101.2/30 commit hv1 (Linux なので別物) # FRR のインストール apk add frr sed -i 's/bgpd=no/bgpd=yes/' /etc/frr/daemons /usr/lib/frr/frrinit.sh start # IP 設定 ip addr add 10.255.255.201/32 dev lo ip addr add 10.11.201.2/30 dev eth1 ip link set eth1 up ip route del default ip route add default via 10.11.201.1 ⚠️ ip route del default を実行すると management NW 経由の SSH が切れます。以降 hv1 への操作は docker exec -it clab-clos-network-hv1 bash で入ってください。 直結リンクで ping が通れば OK です。 2.3 eBGP の設定 Underlay の目標は「全ノードの Loopback 同士を疎通させること」。これができれば、あとで Overlay (EVPN/VXLAN)がその「下地」の上に乗れます。Spine と Leaf で eBGP(異なる ASN 同士の BGP)を張り、Loopback 経路を広告します。 ポイント Spine ごとに ASN を分ける : spine1=64512 , spine2=64513 Spine 側に as-override :Leaf 共通 ASN 方式の代償として、BGP の「AS-PATH(経路が通ってきた ASN の履歴)に同じ ASN があるとループとみなして破棄する」ルールを回避するため、Spine 側で AS-PATH 上の Leaf ASN を Spine 自身の ASN に書き換えます Leaf 側に multipath multiple-as :spine1/spine2 の ASN が異なるため、両方を ECMP として使うために必須 ECMP 有効化 : load-balance per-packet という名前ですがこれは Junos のレガシーな仕様で、実態は 5-tuple(送信元/宛先 IP・ポート・プロトコル)のハッシュによるフロー単位の負荷分散 です 広告経路は Loopback のみ : from interface lo0.0 で Loopback に絞ることで、リンク IP が広告されず経路がスッキリします commit 時に以下の警告が出ますが、無視して OK です(詳しくは「前提とする環境」のライセンス注記を参照)。 warning: requires 'bgp' license commit complete spine1 configure # ECMP 有効化 set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB # 経路広告ポリシー(Loopback + BGP 経路) set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-BGP from protocol bgp set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-BGP then accept set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-DIRECT then accept # BGP(ASN 64512) set routing-options autonomous-system 64512 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY peer-as 65000 set protocols bgp group UNDERLAY as-override # 各 Leaf / RR への neighbor set protocols bgp group UNDERLAY neighbor 10.1.11.2 set protocols bgp group UNDERLAY neighbor 10.1.21.2 set protocols bgp group UNDERLAY neighbor 10.1.22.2 set protocols bgp group UNDERLAY neighbor 10.1.31.2 set protocols bgp group UNDERLAY neighbor 10.1.101.2 commit spine2 のコマンドを開く configure set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-BGP from protocol bgp set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-BGP then accept set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALLOW-DIRECT then accept # BGP(ASN 64513) set routing-options autonomous-system 64513 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY peer-as 65000 set protocols bgp group UNDERLAY as-override set protocols bgp group UNDERLAY neighbor 10.2.11.2 set protocols bgp group UNDERLAY neighbor 10.2.21.2 set protocols bgp group UNDERLAY neighbor 10.2.22.2 set protocols bgp group UNDERLAY neighbor 10.2.31.2 set protocols bgp group UNDERLAY neighbor 10.2.101.2 commit leaf1-1 leaf1-1 は HV1(FRR)が直接 BGP を喋らないため、HV1 Loopback 宛の static route を広告に含めます。 configure # ECMP set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB # Loopback + static を広告 set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT then accept set policy-options policy-statement EXPORT-UNDERLAY term ALL-STATIC from protocol static set policy-options policy-statement EXPORT-UNDERLAY term ALL-STATIC then accept # BGP(multiple-as は必須) set routing-options autonomous-system 65000 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath multiple-as set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY neighbor 10.1.11.1 peer-as 64512 set protocols bgp group UNDERLAY neighbor 10.2.11.1 peer-as 64513 # HV1 Loopback への static route set routing-options static route 10.255.255.201/32 next-hop 10.11.201.2 commit leaf2-1 / leaf2-2 / leaf3-1 / rr1 のコマンドを開く leaf2-1 configure set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT then accept set routing-options autonomous-system 65000 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath multiple-as set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY neighbor 10.1.21.1 peer-as 64512 set protocols bgp group UNDERLAY neighbor 10.2.21.1 peer-as 64513 commit leaf2-2 configure set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT then accept set routing-options autonomous-system 65000 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath multiple-as set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY neighbor 10.1.22.1 peer-as 64512 set protocols bgp group UNDERLAY neighbor 10.2.22.1 peer-as 64513 commit leaf3-1 configure set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT then accept set routing-options autonomous-system 65000 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath multiple-as set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY neighbor 10.1.31.1 peer-as 64512 set protocols bgp group UNDERLAY neighbor 10.2.31.1 peer-as 64513 commit rr1 configure set policy-options policy-statement PFE-LB term 1 then load-balance per-packet set routing-options forwarding-table export PFE-LB set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT from interface lo0.0 set policy-options policy-statement EXPORT-UNDERLAY term ALL-DIRECT then accept set routing-options autonomous-system 65000 set protocols bgp group UNDERLAY type external set protocols bgp group UNDERLAY multipath multiple-as set protocols bgp group UNDERLAY export EXPORT-UNDERLAY set protocols bgp group UNDERLAY neighbor 10.1.101.1 peer-as 64512 set protocols bgp group UNDERLAY neighbor 10.2.101.1 peer-as 64513 commit 2.4 動作確認 admin@spine1# run show bgp summary ... Peer AS InPkt OutPkt ... State|#Active/Received/Accepted/Damped... 10.1.11.2 65000 6 8 ... 58 Establ 10.1.21.2 65000 5 7 ... 33 Establ 10.1.22.2 65000 4 7 ... 22 Establ 10.1.31.2 65000 4 7 ... 13 Establ 10.1.101.2 65000 4 7 ... 6 Establ Spine から各 Leaf / RR とのピアが Establ (Established) になっていれば OK です。最終的に Border Leaf (leaf3-1) から HV1 の Loopback まで疎通できます。 admin@leaf3-1# run ping 10.255.255.201 source 10.255.255.31 count 3 64 bytes from 10.255.255.201: icmp_seq=0 ttl=62 time=3.254 ms ... 3 packets transmitted, 3 packets received, 0% packet loss ✅ 完成状態チェックリスト Spine から見て、全 Leaf / RR の eBGP セッションが Establ show route 10.255.255.0/24 で全ノードの Loopback が学習されている 任意の Leaf から任意の Leaf の Loopback へ ping が通る HV1 の Loopback ( 10.255.255.201/32 ) が leaf3-1 から見える ここまでで Underlay が完成し、全ノードの Loopback 同士が IP で疎通できる「下地」が整いました。次のセクションで、この上に EVPN/VXLAN の Overlay を被せます。 3. Overlay の構築 — EVPN/VXLAN ここからが核心です。Underlay の上に EVPN(C-Plane) + VXLAN(D-Plane) を被せ、テナントの L2 セグメントを複数 Leaf 間で拡張します。 Q. なぜ VLAN ではダメなのか? データセンターでは複数のテナント(利用者)が同じ物理スイッチを共有します。テナント同士のトラフィックを分離する最も基本的な手段が VLAN (Virtual LAN)です。しかし VLAN には以下の限界があります。 課題 VLAN の限界 EVPN/VXLAN での解決 ID 数 最大 4,094 個 → 大規模クラウドでは枯渇する VNI は最大約 1,600 万個(VNIについては後述) L2 の拡張 VLAN をラック間に伸ばすにはループ対策(STP)が必要になり、構成が複雑化する VXLAN で L3(IP Underlay)上にトンネルを張るため、STP 不要で任意のラック間へ L2 を延伸可能 MAC 学習のスケーラビリティ データプレーンのフラッディングで MAC を学習するため、規模拡大に伴い帯域を圧迫する EVPN が制御プレーンで MAC/IP を配布するため、不要なフラッディングを抑制できる つまり VLAN は「1 台のスイッチ内 or 隣接スイッチ間」の L2 分離 、 VXLAN は「DC 全体規模」の L2 分離 と役割が違います。本記事で構築するのは後者です。 テナントごとに L3(ルーティング)も分離 したい場合は VRF (Virtual Routing and Forwarding)を使います。VRF はスイッチ / ルータの中に「テナント専用の経路表」を作る技術で、テナント A とテナント B が同じ 192.168.1.0/24 を使っていてもルーティングが混ざりません。本記事のセクション 4 で VRF を使った外部接続を構築します。 3.1 アーキテクチャの整理 各ノードが Overlay において担う役割を整理します。 役割 C-Plane D-Plane Spine (単なる中継) (単なる IP 転送) RR (rr1) iBGP RR、EVPN 経路反射 — Underlay Leaf (leaf1-1) 参加しない 参加しない Overlay Leaf (leaf2-1/2-2/3-1) iBGP/EVPN ピア VTEP(VXLAN トンネルの出入口) HV1 (FRR) iBGP/EVPN ピア VTEP Q. なぜ leaf1-1 は EVPN に参加しないのか? HV1 が VTEP を持つので、leaf1-1 は単なる IP ルータとして VXLAN パケットを Underlay 越しに転送するだけで済みます。実環境でも同様で、HV 上のソフトウェア VTEP(Contrail vRouter 等)が VTEP の役割を担います。本記事ではそれを FRR で代用しています。 3.2 C-Plane: iBGP EVPN ピア leaf2-1 configure # RR との iBGP(Overlay) set protocols bgp group OVERLAY type internal set protocols bgp group OVERLAY local-address 10.255.255.21 set protocols bgp group OVERLAY neighbor 10.255.255.101 set protocols bgp group OVERLAY family evpn signaling # EVPN/VXLAN 基本設定 set switch-options vtep-source-interface lo0.0 set switch-options route-distinguisher 10.255.255.21:1 set protocols evpn encapsulation vxlan # IRB(L3 ゲートウェイ)を使わない宣言。Type-2 ルートに default-gateway コミュニティを付けない set protocols evpn default-gateway no-gateway-community set protocols evpn extended-vni-list all # RT を明示指定。FRR の advertise-all-vni は AS:VNI 形式(65000:10010)で RT を生成するため、 # Junos 側も同じ値を使う(Junos の vrf-target auto は AS:(VNI+0x10000000) と計算式が異なり互換性がない) set switch-options vrf-target target:65000:10010 commit leaf2-2 / leaf3-1 のコマンドを開く leaf2-2 configure set protocols bgp group OVERLAY type internal set protocols bgp group OVERLAY local-address 10.255.255.22 set protocols bgp group OVERLAY neighbor 10.255.255.101 set protocols bgp group OVERLAY family evpn signaling set switch-options vtep-source-interface lo0.0 set switch-options route-distinguisher 10.255.255.22:1 set protocols evpn encapsulation vxlan set protocols evpn default-gateway no-gateway-community set protocols evpn extended-vni-list all set switch-options vrf-target target:65000:10010 commit leaf3-1 configure set protocols bgp group OVERLAY type internal set protocols bgp group OVERLAY local-address 10.255.255.31 set protocols bgp group OVERLAY neighbor 10.255.255.101 set protocols bgp group OVERLAY family evpn signaling set switch-options vtep-source-interface lo0.0 set switch-options route-distinguisher 10.255.255.31:1 set protocols evpn encapsulation vxlan set protocols evpn default-gateway no-gateway-community set protocols evpn extended-vni-list all set switch-options vrf-target target:65000:10010 commit rr1 configure set protocols bgp group OVERLAY type internal set protocols bgp group OVERLAY cluster 10.255.255.101 set protocols bgp group OVERLAY local-address 10.255.255.101 set protocols bgp group OVERLAY neighbor 10.255.255.21 set protocols bgp group OVERLAY neighbor 10.255.255.22 set protocols bgp group OVERLAY neighbor 10.255.255.31 set protocols bgp group OVERLAY neighbor 10.255.255.201 set protocols bgp group OVERLAY family evpn signaling commit hv1(FRR) vtysh configure terminal router bgp 65000 bgp router-id 10.255.255.201 no bgp ebgp-requires-policy neighbor 10.255.255.101 remote-as 65000 neighbor 10.255.255.101 update-source 10.255.255.201 address-family l2vpn evpn neighbor 10.255.255.101 activate exit-address-family exit end write exit 確認 admin@rr1# run show bgp summary group OVERLAY ... 10.255.255.21 65000 ... Establ bgp.evpn.0: 0/0/0/0 10.255.255.22 65000 ... Establ bgp.evpn.0: 0/0/0/0 10.255.255.31 65000 ... Establ bgp.evpn.0: 0/0/0/0 10.255.255.201 65000 ... Establ bgp.evpn.0: 0/0/0/0 全ピアが Establ (Established) になれば完成です。まだ VNI を作っていないので EVPN 経路は 0 件です。 3.3 D-Plane: VXLAN セグメントを作る VNI 10010 (VLAN 10)を用意し、 VM1(HV1 上、192.168.10.10) と BM1(leaf2-1 配下、192.168.10.101) が同じ L2(同一サブネット、つまり「同じ LAN」)で通信できるようにします。 Q. VNI とは? VXLAN Network Identifier の略。VLAN ID の「拡張版」のようなもので、最大約 1,600 万のセグメントを作れます(VLAN は 4,094 まで)。 ⚠️ 本番では MTU 設計が必須 VXLAN は外側に IP(20) + UDP(8) + VXLAN(8) + Inner Ethernet(14) = 50 バイトのオーバーヘッド が乗ります。テナントが MTU 1500 で通信したいなら、Underlay の物理 MTU を 9000(Jumbo Frame) にするのが定番です。 hv1:Linux Kernel + FRR # VM 模擬の Network Namespace 作成 ip netns add vm1 ip link add veth-host type veth peer name veth-vm ip link set veth-vm netns vm1 ip netns exec vm1 ip link set dev veth-vm address 02:00:00:00:01:10 ip netns exec vm1 ip addr add 192.168.10.10/24 dev veth-vm ip netns exec vm1 ip link set veth-vm up ip netns exec vm1 ip link set lo up # VTEP(VXLAN I/F)作成 ip link add vxlan10 type vxlan id 10010 local 10.255.255.201 dstport 4789 nolearning # Bridge 経由で VM 側と VXLAN を接続 ip link add br10 type bridge ip link set veth-host master br10 ip link set vxlan10 master br10 ip link set vxlan10 up ip link set veth-host up ip link set br10 up vtysh configure terminal router bgp 65000 address-family l2vpn evpn advertise-all-vni exit-address-family end write exit leaf2-1 configure set vlans v10 vlan-id 10 set vlans v10 vxlan vni 10010 set vlans v10 vxlan ingress-node-replication # BM1 接続ポート set interfaces ge-0/0/2 unit 0 family ethernet-switching interface-mode trunk set interfaces ge-0/0/2 unit 0 family ethernet-switching vlan members v10 commit Q. ingress-node-replication とは? SDPF クラウドでは、お客さまに自由な L2 ネットワークを構成していただけるよう、Overlay ネットワークになるべく制限を与えない形でサービスを提供します。そのため、BUM(Broadcast / Unknown unicast / Multicast)トラフィックも通す必要があります。 ingress-node-replication は、この BUM パケットをフラッドする際の方式を指定する設定です。これを有効にすると、Type-3(IM)ルートで学習した各リモート VTEP に対して ユニキャストで複製送信 します。 bm1 ip link set eth1 up ip link add link eth1 name eth1.10 type vlan id 10 ip link set eth1.10 up ip addr add 192.168.10.101/24 dev eth1.10 動作確認 VM1 ↔ BM1 で ping が通り、HV1 で tcpdump を取ると UDP/4789 の VXLAN にカプセル化 されているのが分かります。 # BM1で実行 ~ # ping 192.168.10.10 64 bytes from 192.168.10.10: icmp_seq=1 ttl=64 time=3.35 ms ... # HV1で実行 ~ # tcpdump -i eth1 -n -vv udp port 4789 tcpdump: listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes ... IP (tos 0x0, ttl 253, id 21304, offset 0, flags [none], proto UDP (17), length 134) 10.255.255.21.55534 > 10.255.255.201.4789: [no cksum] VXLAN, flags [I] (0x08), vni 10010 IP (tos 0x0, ttl 64, id 44875, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.10.101 > 192.168.10.10: ICMP echo request, id 102, seq 8, length 64 ... IP (tos 0x0, ttl 64, id 21000, offset 0, flags [none], proto UDP (17), length 134) 10.255.255.201.49139 > 10.255.255.21.4789: [udp sum ok] VXLAN, flags [I] (0x08), vni 10010 IP (tos 0x0, ttl 64, id 6907, offset 0, flags [none], proto ICMP (1), length 84) 192.168.10.10 > 192.168.10.101: ICMP echo reply, id 102, seq 8, length 64 leaf2-1 側でも EVPN Type-2(MAC/IP)と Type-3(IM)の経路がリモート VTEP 宛に学習されています。 admin@leaf2-1# run show route table bgp.evpn.0 bgp.evpn.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden) + = Active Route, - = Last Active, * = Both 2:10.255.255.21:1::10010::aa:c1:ab:0a:18:4a/304 MAC/IP *[EVPN/170] 00:01:12 Indirect 2:10.255.255.201:2::0::02:00:00:00:01:10/304 MAC/IP *[BGP/170] 00:03:21, localpref 100, from 10.255.255.101 AS path: I, validation-state: unverified to 10.1.21.1 via ge-0/0/0.0 > to 10.2.21.1 via ge-0/0/1.0 2:10.255.255.21:1::10010::aa:c1:ab:0a:18:4a::192.168.10.101/304 MAC/IP *[EVPN/170] 00:01:05 Indirect 3:10.255.255.21:1::10010::10.255.255.21/248 IM *[EVPN/170] 00:03:09 Indirect 3:10.255.255.201:2::0::10.255.255.201/248 IM *[BGP/170] 00:03:10, localpref 100, from 10.255.255.101 AS path: I, validation-state: unverified > to 10.1.21.1 via ge-0/0/0.0 to 10.2.21.1 via ge-0/0/1.0 確認のポイントを整理します。 [EVPN/170] Indirect のエントリ(RD 10.255.255.21:1 )は leaf2-1 自身がローカルで生成した EVPN 経路(自分のポートに接続された BM1 の MAC/IP、および自分が属する VNI の IM ルート)。 [BGP/170] from 10.255.255.101 のエントリ(RD 10.255.255.201:2 )は RR(rr1)経由で受け取ったリモート VTEP(HV1)からの経路。 to 10.1.21.1 / to 10.2.21.1 の 2 経路が並んでいるのは、spine1・spine2 の両方を経由した ECMP が有効になっているためです。 EVPN の経路情報は BGP によって制御プレーン上でやり取りされ、実際のパケット転送は VXLAN(UDP/4789)がデータプレーンとして担います。tcpdump で確認した通り、 vni 10010 のカプセル化で転送されていることが確認できます。 3.4 EVPN Multihoming(ESI-LAG)で BM の接続を冗長化 セクション 3.3 で VXLAN の疎通は確認できましたが、このままでは BM1 が leaf2-1 の 1 本だけでぶら下がっている状態です。クラウドサービスでは物理故障の単一障害点を排除することが基本要件であり、リンクやスイッチの障害でサーバーが孤立しないよう、複数 Leaf へ冗長接続します。 ここからは BM1 を leaf2-1 / leaf2-2 の両方に接続して冗長化します。BM1 のネットワーク設定をいったん削除して bond に組み直すため、一時的に BM1 ↔ VM1 間の通信が途絶えます。 EVPN Type-1 / Type-4 を活かす ESI-LAG(All-Active) で構成します。 EVPN Multihoming の商用導入事例として、SDPF クラウド開発メンバーが JANOG48 で発表した EVPN Anycast Gateway を商用導入した話 も大変参考になります。 Q. ESI-LAG とは? 通常の LAG(Link Aggregation)は 1 台のスイッチへの束ねですが、ESI-LAG は 複数台のスイッチをまたいで LAG を組める技術です。サーバーから見ると 1 台のスイッチにつないでいるように見えます。 方針 2 台の Leaf で 同じ ESI と 同じ LACP System ID を設定 → BM1 から見ると単一の LAG 相手に見える BM1 側は Linux bond(mode=802.3ad) leaf2-1(既存設定の置き換え) configure delete interfaces ge-0/0/2 unit 0 set chassis aggregated-devices ethernet device-count 1 set interfaces ge-0/0/2 ether-options 802.3ad ae0 set interfaces ae0 unit 0 family ethernet-switching interface-mode trunk set interfaces ae0 unit 0 family ethernet-switching vlan members v10 # ESI(leaf2-1/2-2 で完全一致させる) set interfaces ae0 esi 00:00:00:00:00:00:00:00:00:01 set interfaces ae0 esi all-active set interfaces ae0 aggregated-ether-options lacp active set interfaces ae0 aggregated-ether-options lacp periodic fast set interfaces ae0 aggregated-ether-options lacp system-id 00:00:00:00:00:01 commit leaf2-2(新規) leaf2-1 と完全に同じ ESI / LACP System ID を投入します。VLAN/VNI/RT もここで作成。 configure set vlans v10 vlan-id 10 set vlans v10 vxlan vni 10010 set vlans v10 vxlan ingress-node-replication set chassis aggregated-devices ethernet device-count 1 set interfaces ge-0/0/2 ether-options 802.3ad ae0 set interfaces ae0 unit 0 family ethernet-switching interface-mode trunk set interfaces ae0 unit 0 family ethernet-switching vlan members v10 set interfaces ae0 esi 00:00:00:00:00:00:00:00:00:01 set interfaces ae0 esi all-active set interfaces ae0 aggregated-ether-options lacp active set interfaces ae0 aggregated-ether-options lacp periodic fast set interfaces ae0 aggregated-ether-options lacp system-id 00:00:00:00:00:01 commit bm1(Linux Bond) ip addr flush dev eth1.10 ip link del eth1.10 ip link add bond0 type bond mode 802.3ad miimon 100 lacp_rate 1 xmit_hash_policy layer3+4 ip link set eth1 down ip link set eth2 down ip link set eth1 master bond0 ip link set eth2 master bond0 ip link set bond0 up ip link set eth1 up ip link set eth2 up ip link add link bond0 name bond0.10 type vlan id 10 ip link set bond0.10 up ip addr add 192.168.10.101/24 dev bond0.10 切断試験 BM1 から VM1 に ping を流したまま、leaf2-1 → leaf2-2 の順にリンクを落とすと、片方が生きている間は ping が継続することを確認できます。 # leaf2-1 で接続断 → ping 継続 configure set interfaces ge-0/0/2 disable commit # leaf2-2 でも接続断 → ping 停止 configure set interfaces ge-0/0/2 disable commit # leaf2-1 を復活 → ping 復活 configure delete interfaces ge-0/0/2 disable commit # leaf2-2 で実行 configure delete interfaces ge-0/0/2 disable commit HV1 から見ると、BM1 の bond0 MAC が leaf2-1 / leaf2-2 の両方から同じ ESI で広告 されています。 ~ # vtysh -c "show bgp l2vpn evpn" Route Distinguisher: 10.255.255.21:1 *>i [2]:[10010]:[48]:[xx:xx:xx:xx:xx:xx] ← bond0 の MAC 10.255.255.21 ESI:00:00:00:00:00:00:00:00:00:01 RT:65000:10010 Route Distinguisher: 10.255.255.22:1 *>i [2]:[10010]:[48]:[xx:xx:xx:xx:xx:xx] 10.255.255.22 ESI:00:00:00:00:00:00:00:00:00:01 RT:65000:10010 💡 bond0 の MAC はカーネルが自動付与します(通常 eth1 の MAC を継承)。実際の値は ip link show bond0 で確認してください。 ESI が両ノードで一致しているため、HV1 はこの 2 経路を Aliasing (ECMP 的にロードバランス)して扱います。つまり、「leaf2-1 および leaf2-2 のいずれを経由しても、BM1 に届く」状態が完成しています。これが ESI-LAG の本質です。 ✅ 完成状態チェックリスト(Overlay全体) RR の OVERLAY グループで全 Leaf / HV1 が Establ HV1 で show evpn vni に VNI 10010 が表示される BM1 → VM1(VXLAN 経由)の ping が通る HV1 の tcpdump -i eth1 udp port 4789 で VXLAN ヘッダ(vni 10010)が見える leaf2-1/2-2 のどちらかのリンクを落としても BM1 → VM1 の通信が継続する show bgp l2vpn evpn で BM1 の MAC が leaf2-1/2-2 の両方から同じ ESI で広告されている ここまでで、EVPN/VXLAN によるテナント L2 拡張と ESI-LAG による冗長接続が完成しました。VM1(HV1 上)と BM1 が物理的に離れていても同一 L2 で通信でき、かつ片方のリンクが落ちても通信が継続する状態です。 4. Border Leaf と Internet Gateway 最後に、テナント網(VRF = 仮想ルーティングテーブル、「テナント専用の経路表」と考えてOK)と外部網(Global = インターネット側)を接続します。 leaf3-1 を VTEP 化 して Overlay の VLAN10 を Internet GW まで延伸 inet-gw1 で VRF-User と Global を Route Leaking inet-gw1 ↔ isp1 で eBGP 、isp1 から default route を受け取る 以下の内容を構成します。 4.1 leaf3-1(Border Leaf として VTEP 化) configure set vlans v10 vlan-id 10 set vlans v10 vxlan vni 10010 set vlans v10 vxlan ingress-node-replication # inet-gw1 接続ポート set interfaces ge-0/0/2 unit 0 family ethernet-switching interface-mode trunk set interfaces ge-0/0/2 unit 0 family ethernet-switching vlan members v10 commit 4.2 inet-gw1: 共通の下準備(I/F + ASN) configure set routing-options router-id 198.51.100.1 set routing-options autonomous-system 65002 # I/F set interfaces ge-0/0/0 vlan-tagging set interfaces ge-0/0/0 unit 10 vlan-id 10 set interfaces ge-0/0/0 unit 10 family inet address 192.168.10.1/24 set interfaces ge-0/0/1 unit 0 family inet address 203.0.113.2/30 set interfaces lo0 unit 0 family inet address 198.51.100.1/32 commit ge-0/0/0.10 がテナント側(leaf3-1 経由で BM1 と同セグ)、 ge-0/0/1.0 が インターネット側(外部)です。 4.3 inet-gw1: VRF と Route Leaking ユーザーの VRF( VRF-User )と Global テーブル( inet.0 = Junos の通常の経路表)を分離しつつ、必要な経路だけ相互にリーク(漏らす)します。これが「Route Leaking」で、「テナント内部の経路をインターネット側に教える(またはその逆)」ことで、テナント内のサーバーがインターネットと通信できるようになります。 configure # Route Leaking ポリシー(双方向) # Global -> VRF: default route のみ VRF に流す set policy-options policy-statement LEAK-GLOBAL-TO-VRF term ALLOW-DEFAULT from instance master set policy-options policy-statement LEAK-GLOBAL-TO-VRF term ALLOW-DEFAULT from route-filter 0.0.0.0/0 exact set policy-options policy-statement LEAK-GLOBAL-TO-VRF term ALLOW-DEFAULT then accept set policy-options policy-statement LEAK-GLOBAL-TO-VRF term DENY-REST then reject # VRF -> Global: BM1 の Global IP(/32)のみ Global へ流す set policy-options policy-statement LEAK-VRF-TO-GLOBAL term ALLOW-BM1 from instance VRF-User set policy-options policy-statement LEAK-VRF-TO-GLOBAL term ALLOW-BM1 from route-filter 198.51.100.41/32 exact set policy-options policy-statement LEAK-VRF-TO-GLOBAL term ALLOW-BM1 then accept set policy-options policy-statement LEAK-VRF-TO-GLOBAL term DENY-REST then reject # VRF 本体 set routing-instances VRF-User instance-type virtual-router set routing-instances VRF-User interface ge-0/0/0.10 set routing-instances VRF-User routing-options instance-import LEAK-GLOBAL-TO-VRF # BM1 の Global IP への static set routing-instances VRF-User routing-options static route 198.51.100.41/32 next-hop 192.168.10.101 # Global 側にも VRF からの経路を取り込む set routing-options instance-import LEAK-VRF-TO-GLOBAL commit 4.4 inet-gw1: 外部 ISP との eBGP 最後に、上で Global にリークした経路を eBGP で 外部 ISP へ広告します。 configure # 外部 ISP へ広告するポリシー(static のみ → 198.51.100.41/32 が乗る) set policy-options policy-statement ADVERTISE-TO-ISP term 1 from protocol static set policy-options policy-statement ADVERTISE-TO-ISP term 1 then accept set protocols bgp group TO-ISP type external set protocols bgp group TO-ISP peer-as 65001 set protocols bgp group TO-ISP neighbor 203.0.113.1 set protocols bgp group TO-ISP export ADVERTISE-TO-ISP commit 4.5 isp1 (外部 ISP 模擬) configure set routing-options router-id 192.0.2.1 set routing-options autonomous-system 65001 set interfaces ge-0/0/0 unit 0 family inet address 203.0.113.1/30 set interfaces lo0 unit 0 family inet address 192.0.2.1/32 set protocols bgp group TO-GW type external set protocols bgp group TO-GW peer-as 65002 set protocols bgp group TO-GW neighbor 203.0.113.2 set policy-options policy-statement SEND-DEFAULT term 1 from protocol static set policy-options policy-statement SEND-DEFAULT term 1 from route-filter 0.0.0.0/0 exact set policy-options policy-statement SEND-DEFAULT term 1 then accept set protocols bgp group TO-GW export SEND-DEFAULT set routing-options static route 0.0.0.0/0 discard commit 4.6 bm1 (Global IP 付与とデフォルトゲートウェイ) ip addr add 198.51.100.41/32 dev bond0.10 ip route replace default via 192.168.10.1 Q. なぜ /32 で付与するのか BM1 が「自分は 198.51.100.41 である」とさえ名乗れれば十分です。戻りパケットのルーティングは inet-gw1 の VRF 内に 198.51.100.41/32 next-hop 192.168.10.101 の static route があるため、ISP → inet-gw1 → (VRF) → leaf3-1 → VXLAN → leaf2-1/2-2 → BM1 と正しく転送されます。 4.7 動作確認 inet-gw1 で Global の inet.0 にも 198.51.100.41/32 が現れます (= VRF からのリーク成功)。 admin@inet-gw1# run show route 198.51.100.41 inet.0: 198.51.100.41/32 *[Static/5] > to 192.168.10.101 via ge-0/0/0.10 VRF-User.inet.0: 198.51.100.41/32 *[Static/5] > to 192.168.10.101 via ge-0/0/0.10 そして BM1 → 外部 ISP(192.0.2.1)への ping 、および逆方向の 外部 ISP → BM1(198.51.100.41)への ping がともに通れば、End-to-End の経路が完成です。 ~ # ping -c 3 -I 198.51.100.41 192.0.2.1 64 bytes from 192.0.2.1: icmp_seq=1 ttl=63 time=5.87 ms ... 3 packets transmitted, 3 received, 0% packet loss admin@isp1# run ping 198.51.100.41 count 3 64 bytes from 198.51.100.41: icmp_seq=0 ttl=63 time=6.070 ms ... 3 packets transmitted, 3 packets received, 0% packet loss ✅ 完成状態チェックリスト(Border GW) inet-gw1 の inet.0 と VRF-User.inet.0 の両方に 198.51.100.41/32 が存在 inet-gw1 ↔ isp1 の eBGP が Establ 、isp1 から default route を受信 BM1 → 192.0.2.1 (外部 ISP) の ping が通る( -I 198.51.100.41 で source 指定) 外部 ISP → 198.51.100.41 (BM1) の ping が通る ここまでで、テナント内の BM1 が VRF Route Leaking を経由して外部 ISP と双方向に通信できる End-to-End の経路が完成しました。本記事で目標としていた全構成の構築が完了です。 全体の完成確認 全セクションを通して構築した結果、冒頭で掲げたゴール — 物理的に離れた HV1 上の VM と BM1 が同一 L2 で通信でき、さらにインターネットへ抜けられる — が達成できています。完成した NW で実現できていることを整理します。 通信パス 経由するレイヤ VM1(HV1)↔ BM1 HV1 (VTEP) → VXLAN → Underlay (Spine経由) → leaf2-1/2-2 (VTEP) → BM1 BM1 → インターネット BM1 → leaf2-1/2-2 → VXLAN → leaf3-1 → inet-gw1 ( VRF Route Leaking ) → isp1 インターネット → BM1 isp1 → inet-gw1 (Global→VRF) → leaf3-1 → VXLAN → leaf2-1/2-2 → BM1 Underlay :eBGP + ECMP により、どちらの Spine を経由しても全ノードの Loopback が到達可能 Overlay :EVPN/VXLAN により、異なるラックの VM1 と BM1 が同一 L2(VNI 10010)で通信 冗長化 :ESI-LAG により、leaf2-1 または leaf2-2 のどちらか一方が故障しても BM1 の通信が継続 外部接続 :VRF Route Leaking により、テナント内部の経路とインターネット側の経路を相互にリークし、BM1 が外部と双方向に通信可能 これらが組み合わさることで、クラウド NW の基本構成 — マルチテナント対応の L2 延伸・物理冗長・外部接続 — が 1 つのラボ上で再現できています。 おまけ: ハマったら見るところ(Troubleshooting) 手順通りやってもうまくいかないとき、上から順に切り分けると早いです。 症状 確認ポイント BGP が Active から進まない 直結 ping → Loopback 間 ping → peer-as の値 → ポリシーで自分の経路を絞ってないか Underlay は OK だが Overlay iBGP が Active Loopback 間の ping、 local-address が自分の Loopback になっているか EVPN ピアは張れたが経路が来ない 両端の RT が一致しているか、 extended-vni-list の VNI 範囲 ESI-LAG で片側だけしか流れない 2 台の esi と lacp system-id が 完全に 一致しているか Junos 側で何が起きているか覗くコマンド run show bgp summary run show route table bgp.evpn.0 run show route advertising-protocol bgp <neighbor> run show route receive-protocol bgp <neighbor> run show ethernet-switching vxlan-tunnel-end-point remote run show evpn database FRR 側 vtysh -c "show bgp l2vpn evpn summary" vtysh -c "show evpn vni" vtysh -c "show evpn mac vni all" まとめ Containerlab + vJunos でクラウド NW の王道構成を一通り体験しました。しかし、本記事はあくまで学習用であり、実際の運用では次のような事項も検討が必要です。 IRB / Anycast Gateway (各 Leaf に同じ GW IP を持たせる分散 L3 ルーティング) MTU 設計 (Underlay 9000 / テナント 1500) BGP 認証 (MD5) マルチテナント運用 (VNI 数千規模の管理) セキュリティポリシー (マイクロセグメンテーション、ACL) 監視連携 本記事を通じてクラウド NW を少しでも知っていただき、「面白い!」と思っていただけましたら幸いです。 クラウド NW 開発に興味を持っていただけた学生の方は、ぜひインターンのポスト「 【B19】エンタープライズ向け大規模クラウド/ネットワークサービスを支えるコントローラ開発 」や「 【B25】エンタープライズ向け大規模クラウドサービスを支える仮想ネットワークソフトウェア開発 」にご応募ください。
こんにちは。エンタープライズ第一本部 戦略ソリューション 1 部の英です。 普段はWebアプリやスマホアプリの案件などを担当しています。あと、趣味でAIを勉強しています。 世間ではClaude Codeが幅を利かせるなか、なぜかCodexにこだわり続けている私。 そろそろ流行りに乗らねばと思い、今回はClaude Codeの記事を書いてみます。 しかも、巷ではsuperpowersなんてものが話題になっているらしいじゃないですか。 エンジニアの英知を結集したAI駆動開発のベストプラクティス、superpowers。 これがあれば、Claude Code初心者の私でもプロ並みの開発ができるはずです。 この記事を書き終わっているころには、きっとCodex派からClaude派に寝返っているでしょう。 では、さっそくやっていきましょう。 1.Claude Codeとは 2.superpowersとは 3.Claude Codeの初期セットアップ 4.superpowersのインストール 5.superpowers-brainstorming 6.superpowers-using-git-worktrees 7.superpowers-writing-plans 8.superpowers-subagent-driven-development or executing-plans 9.superpowers-test-driven-development 10.superpowers-requesting-code-review 11.superpowers-finishing-a-development-branch 12. 動作確認 Plan2について さいごに Codexとの比較 開発効率とコスト 採用情報 1.Claude Codeとは Claude Codeは、Anthropic社が提供しているAIコーディングエージェント。 ターミナル上で動作し、コードの生成・修正・調査・テスト実行などを対話形式で進めることができる。 「AIにコードを書いてもらう」だけでなく、開発作業そのものを一緒に進めることができるツール。 2.superpowersとは superpowersは、Claude Codeをよりうまく活用するためのワークフロー集。 ブレスト、計画作成、TDD、コードレビュー依頼など、開発の各フェーズで使える“型”が用意されている。 Claude Codeに任せきりにするのではなく、人間とAIがうまく役割分担するためのベストプラクティス集。 これにより、Vibe CodingやSpec駆動開発の品質の底上げが期待できる。 参考: superpowers ※MITライセンス superpowersは以下のワークフローで構成されています。 要件を整理する 作業環境を分ける 実装計画を立てる テストを書きながら実装する(TDD) レビューする ブランチを仕上げる まず、 brainstorming で実装したい内容を整理します。 いきなりコードを書き始めるのではなく、Claude Codeが質問をしながら、目的・仕様・代替案・懸念点を明確にします。 ここで固めた内容が、後続の設計や実装計画の土台になります。 次に、 using-git-worktrees で作業用の独立した環境を作成します。 新しいブランチとworktreeを用意することで、既存の作業環境を壊さずに実装を進められます。 AIに実装を任せるうえで、安全に試行錯誤できる状態を作る工程です。 その後、 writing-plans で実装計画を作成します。 変更するファイル、実装手順、検証方法を小さなタスクに分解します。 READMEでは、プロジェクト理解が浅いジュニアエンジニアでも進められるくらい明確な計画を作る、と説明されています。 After you've signed off on the design, your agent puts together an implementation plan that's clear enough for an enthusiastic junior engineer with poor taste 計画ができたら、 subagent-driven-development または executing-plans で実装を進めます。 前者はタスクごとにサブエージェントを割り当て、仕様準拠とコード品質を確認しながら進める方式です。 後者は計画に沿ってバッチ単位で実行し、人間の確認ポイントを挟みながら進める方式です。 実装中は、 test-driven-development によってTDDの流れが重視されます。 テストを先に書く、まだプロダクトコードがないのでテストが失敗する、テストがグリーン(正常に通過)するように実装を追加していくというRED-GREEN-REFACTORの流れです。 期待する振る舞いを先に定義することで、AIが「それっぽく動くコード」を書いて終わるのではなく、テストで確認可能な形で実装を進められます。 タスクの合間には、 requesting-code-review でコードレビューを行います。 実装が計画に沿っているか、バグや設計上の問題がないかを確認します。 重大な問題がある場合は、次に進む前に修正する流れになります。 最後に、 finishing-a-development-branch で開発ブランチを仕上げます。 テスト結果を確認し、マージするのか、Pull Requestを作るのか、ブランチを残すのか、破棄するのかを選択します。 不要になったworktreeの片付けまで含めて、開発作業を完了させます。 つまりsuperpowersは、 「考える → 分ける → 計画する → テストする → 実装する → レビューする → 仕上げる」 という 堅実な開発プロセスをAIエージェントに守らせるための仕組み です。 READMEにも “The agent checks for relevant skills before any task. Mandatory workflows, not suggestions.” とあるように、強制力を持ったワークフローとして設計されています。 3.Claude Codeの初期セットアップ 社内でClaude Enterpriseのライセンスを貰ったばかりでまっさらな状態。 とりあえず挨拶してトークンを無駄遣いしておきます。 VSCodeにClaude Codeの拡張機能を入れます。 ちなみに、CodexとClaude Codeのインストール数とレビューはこんな感じ。 Claude Codeのほうがシェアも評価も高い現状。 インストールが完了すると、ログイン方法を問われます。 先ほどのClaude Enterpriseのアカウントでログインします。 接続確認が表示されるので、「承認する」を押下します。 認証コードが吐かれたので、VSCodeに戻って貼り付けます。 通りました。何やら可愛らしいキャラクターが浮かんでいます。 挨拶して無駄にトークンを消費しておきましょう。 ちなみに彼は何という名前なんだろうと調べてみたところ、「Clawd(クロード)」というそうです。 Crab(蟹)のキャラクターか。かわいいですね。よろしくね、Clawd。 次はターミナルで使うためのインストール。 Pathを通し、インストール確認。 さあ、初めての起動だ。 ターミナルの色合いを選択します。(Dark mode) ログイン方法を選択します。 認証に成功しました。 読み飛ばす人も多いかもしれませんが、ここにとっても大事なことが書いてあります。 そんなmistakesを極力減らすのがこれから紹介するsuperpowersですね。 今回は初めての利用なので、推奨設定を選択します。 カレントディレクトリに対する権限を聞かれてます。ここは自由に操作していいのでYesを選択します。 セットアップが完了しました。 4.superpowersのインストール 公式のマーケットプレイスからインストールします。 このrepoだけでのインストールを選択します。 superpowersを手に入れました。 有効化します。 5.superpowers-brainstorming さて、準備が整ったのでAIとブレストしてみます。 superpowers「何について議論したいんだい?」 日本語もいけるのだろうか? ただのToDoアプリじゃつまらないので、組織向けのタスク管理ツールをテーマにしてみます。 日本語で返ってきました。 なるほど、モックを見ながら方針を決めていく方法があるそうです。せっかくなので使ってみます。 brainstorming のスキルが読みたいと言っているので許可します。 ブレストが始まりました。 中規模を選択します。 ToDoタスクの振り方、ユースケースを問われています。全部必要そうなので、そのように回答します。 あとでClaude Codeが教えてくれるのですが、「一斉12人」は「一対多」の誤字とのこと。 タスクのリマインド方法ですが、自動を選択してみます。 通知方法はサービス内にしましょう。 認証はサービス独自を選択します。 デプロイ先はAWSを想定しているのでクラウドを選択します。 タスクの進捗確認をできる人ですが、複数選択可とのことで3つ選択してみます。 スコープが広すぎるようで、MVPを聞かれています。 まあいったん一斉送信の実装から入ってほしいですね。 ※MVP(Minimum Viable Product) これは絶対に聞かれると思っていました。完了の定義です。 ここはサービス上で「対応済み」を押させる仕組みで良いでしょう。 設計の確認が始まりました。 デモアプリでMulti-AZはリッチすぎますが、プロダクト開発に使えるか評価したいので、リッチな構成でいきましょう。 「違和感ないです。」と回答します。 次はデータモデルです。 Assignmentという中間テーブルで人とタスクを紐づけていますね。そしてここでステータス管理もしています。 Notificationで通知レコードを管理する構成になっています。良いと思います。 リマインドは複数回発生する可能性があるため、このテーブルで管理します。 良い設計ですね。自分で設計するにしてもこうすると思います。 兼務とかあるとややこしくなりますが、今回はシンプル構成でいきましょう。 次はユースケースのレビューです。 ちょっと、初期開発では不要な部分があるので省略を依頼します。 次は認証・認可周りの確認です。 ここの実装を削りたいので指示します。 次はエラーハンドリング周りの確認。 次はテスト戦略です。 初期開発としては十分すぎる計画だと思います。 ここまでの内容を設計書にまとめてくれるようです。 設計書の作成が始まりました。 6.superpowers-using-git-worktrees ローカルにgitを入れてもらい、writing-plansに引き継ぎます。 7.superpowers-writing-plans 引継ぎが終わりました。 今回はPlan1とPlan2に分かれ、Plan1でMVPを開発し、Plan2の方でデプロイ周りを計画しているようです。 ちなみに、ここまでの作業をccusageで確認。約$7でした。 $7か...個人利用だと厳しいな...。 8.superpowers-subagent-driven-development or executing-plans 次はこの計画を実行に移してもらいます。 進め方は1つのエージェントで逐次実行するか、サブエージェントを駆使してパラレルに開発するかが選べます。 今回はフルパワーが見たいので、サブエージェントを選択します。 サブエージェントのスキルが読み込まれました。 9.superpowers-test-driven-development こっから、基本的にはYesを叩く作業になるのでスクショは一部割愛します。 (AIエージェントに許可する権限を確認しつつ) Task 0.1が完了しました。ここまで30分くらい。 業務PCにdockerが未インストールだったため、ここで人間側にボールが渡ってきました。 ご丁寧に手順まで記載してくれているので、対応してAIエージェントにボールを戻します。 インストールが完了したので、続きを依頼します。 wslのバージョンが古くてDockerが起動できずに人間に返ってきたので、対応して続きを依頼します。 ローカルでPostgres起動、接続確認まできました。ここまで40分くらいでしょうか。 TDD用の環境セットアップが始まりました。 環境のセットアップが終わったので、ここからサブエージェントを駆使してコーディング部分を進めてもらいます。 サブエージェントでの開発に関するスキルが読み込まれました。 パスワード認証部分の仮実装が終わったようです。 TDDでテストコード→プロダクトコードの順番でコーディングが進んでいることがファイルからもわかります。 テストコードにはハッシュ化、パスの検証の正常系、異常系が書かれてます。 ちょっとテストが少ない気もしますが、あとで伏線回収するのでスルーしてください。 10.superpowers-requesting-code-review 部品の開発のたびに、コードレビューを呼んでいますね。 画像の中央付近にテスト品質に関する記載があります。 先ほどのテストケースはこの観点に通過する品質になっていることがわかります。 Test quality — Does the test verify behavior (not implementation)? Does it cover both happy path and one negative case (wrong password)? Does it avoid mocking the thing being tested? 次は権限部分のTDDが始まりました。 先に今回の3つの権限でのテストケースを作成していることがわかります。 失敗テストの作成、失敗確認、実装、成功確認というRED-GREENのサイクルを繰り返して開発が進んでいきます。 コードレビューで、prisma側の権限とアプリ内での権限で重複管理になるから、この書き方はやめてほしいとレビューで指摘を受けてますね。 そのイシューをもとに実装者が修正に入ってます。 なんだか、チーム開発っぽくなってきましたね。 11.superpowers-finishing-a-development-branch Plan1のPhase1が終わったタイミングで、finishing-a-development-branchのスキルを読ませました。 マイグレーションテストを通してくれました。 12. 動作確認 動作確認方法を聞いて、その通りに起動します。 ログイン画面が開いたのでログインしてみます。 ログインできました。 まだ最小限の機能しかないので、引き続き開発を続けます。 全体でPhase7のうち、まだPhase1の状態です。 Phase2はタスク作成周り。 1時間かからないくらいで、Phase2が完了。 Phase3は通知周りの実装。 Phase4はリマインダー周りの実装。 Phase5はダッシュボード周りの実装。 Phase6はE2Eテストの環境構築。 E2Eを実行したところ、1件passして、2件failedになりました。 結果をコピペして、Claude Codeに返します。 2件passして、1件failedになっていました。 再度、エラーログをClaude Codeに連携して修正を依頼しました。 再実行したろころE2Eにすべて合格しました。 ちなみに、E2Eの1つ目の中身はこんな感じです。 「依頼作成 → 担当者が対応済みにする → 依頼者が進捗を確認する」一連の業務フローをブラウザ上で確認するテストになっています。 2つ目はこんな感じ。 部署管理者向けダッシュボードのE2Eテストです。 「DBを初期化→ユーザーを作成→タスクを直接DBに作成→2人分の割り当てを作成→部署管理者でログイン→ダッシュボードを開く→表示を検証(棚卸、1/2 が見えること)」という流れになっています。 3つ目はこんな感じ。 リマインダー機能のE2Eテストです。 「DBを初期化→ユーザーを作成→タスクを直接DBに作成→担当者への割り当てを作成→リマインダーAPIを実行→担当者でログイン→通知画面を確認」という流れになっています。 E2Eに通過したことを伝えると、Phase7に取り掛かりました。 数時間後、Plan1がすべて完了しました。 ちなみに、ここまでかかったコストは約$98(≒1万5千円)でした。 Plan2について AWSへのデプロイはCodexでも容易にできるので、今回の記事では割愛させていただきます。 そのあたりの権限移譲周りについては 過去の記事 を参照してください。 さいごに Codexとの比較 Codexと比べて計画立てて自律的に走る能力が高い Codexと比べてコストが高い 自走してくれるから1日当たりの作業量が多い 計画を立てることにもトークンを消費する サブエージェントを使うと稀にハングする ハングした際にはctrl+oで状態を確認し、ctrl+cで止めてから、再実行を依頼する必要があった。 さらに、この際にsuperpowersのサイクルから外れることがあった。 この場合、以下のように命令してsuperpowersのフローに強制的に戻してもらう必要があった。 プロンプト:ハングしているから一時停止しました。superpowersのスキルを確認してTDDでの開発を継続してください。 開発効率とコスト ここまで約1~2日間でした。(ほかの作業をしながら裏で動かしただけ) コストも1~2万円程度でした。 BtoC/BtoB案件の一般的な開発ではプロダクトの初期開発にかなりの時間とコストがかかります。(技術的につまることが多々ある) これが大幅に削減できると思うと、人力開発の時代にはもう戻れないなと感じます。 採用情報 先日Xで「JTCでは社内のAI利用が進んでおらず働きにくい」みたいなのが話題になってましたが、弊社はこの数年間で各種AIツールのライセンスや申請周りがかなり整備され、めちゃくちゃ働きやすいです。AIを使って仕事したいSIの方はぜひご検討ください。。 ↓ のスターを押していただけると嬉しいです。励みになります。 最後まで読んでいただき、ありがとうございました。 エンタープライズ第一本部では一緒に働いてくださる仲間を募集中です。以下のリンクからお願いします。 私たちは一緒に働いてくれる仲間を募集しています! 中途採用-エンタープライズ第一本部 新卒採用-エンタープライズ第一本部 執筆: 英 良治 (@hanabusa.ryoji) レビュー: @miyazawa.hibiki ( Shodo で執筆されました )

動画

書籍