第334篇:数据中心 Spine-Leaf 架构迁移案例

关键词

Spine-Leaf、CLOS 架构、数据中心网络、架构迁移、VXLAN、EVPN、Underlay、Overlay


一、案例背景

1.1 传统架构的问题

某企业数据中心传统三层架构:

核心层(Core) ┌────────────┐ ┌────────────┐ └─────┬──────┘ └──────┬─────┘ ┌─────┴──────┐ ┌──────┴─────┐ └─────┬──────┘ └──────┬─────┘ ┌─────┴──────┐ ┌──────┴─────┐ Core-SW01 (主) Agg-SW01 (汇聚层) Acc-SW01 (接入层) Core-SW02 (备) Agg-SW02 (汇聚层) Acc-SW02 (接入层)

传统架构的问题: ┌──────────────────────────────────────────┐ │ 1. 东西流量瓶颈 │ │ └─ 服务器 A → 服务器 B 的流量 │ │ 需经过 Acc→Agg→Core→Agg→Acc │ │ └─ 核心层成为性能瓶颈 │ │ │ │ 2. 扩展受限 │ │ └─ 核心层端口有限 │ │ └─ 增加新设备受限于核心端口数 │ │ └─ 带宽无法水平扩展 │ │ │ │ 3. VLAN 大二层限制 │ │ └─ VLAN 数量限制(4094) │ │ └─ VLAN 跨设备扩展困难 │ │ └─ STP 阻塞端口,浪费带宽 │ │ │ │ 4. 故障域大 │ │ └─ 广播域大(ARP 泛滥) │ │ └─ 单点故障影响范围广 │ │ └─ 核心故障导致全网瘫痪 │ └──────────────────────────────────────────┘

1.2 迁移目标

Spine-Leaf 迁移目标:

性能目标:

东西向带宽:100G × N(每台 Leaf) └─ 任意 Leaf 之间 = 所有 Spine 带宽之和 南北向带宽:100G × Spine 数量 └─ 无瓶颈设计 Leaf 到 Spine 延迟:< 5μs

架构目标:

大二层网络(VXLAN + EVPN) └─ 虚拟机可在任意 Leaf 下自由迁移 └─ 不受 VLAN 4094 限制 └─ 无 STP 阻塞 水平扩展 └─ Spine 扩展:增加 Spine 交换机 └─ Leaf 扩展:增加 Leaf 交换机 └─ 带宽线性增长 ECMP 负载均衡 └─ 所有上行链路负载分担 └─ 无链路闲置

运维目标:

自动化部署(ZTP + Ansible) 统一管理(NCE/CloudEngine) Telemetry 监控


二、Spine-Leaf 架构设计

2.1 架构拓扑

迁移后的 Spine-Leaf 架构:

┌───────┤Spine├───────┐ ┌────┴─┴─┴─┐ ┌────┴─┴─┴─┐ ┌────┴─┴─┴─┐ └──────────┘ └──────────┘ └──────────┘ 说明: ┌─ Spine:3 台 CE16800(100G 端口) ├─ Leaf:6 台 CE8860(48×25G + 6×100G) └─ 每台 Leaf 连接到所有 Spine ┌─────┤ 1 ├─────┐ Leaf 1 ┌──┬──┬┐ └──┴──┴┘ ┌───┤ ├───┐ ├───┤Spine├───┤ ├───┤Spine├───┤ S1 └─────┘ ┌─────┐ └─────┘ ┌─────┐ └─────┘ Leaf 2 ┌──┬──┬┐ S2 └──┴──┴┘ 2 3 Leaf 3 ┌──┬──┬┐ └──┴──┴┘ S3 S4 S5 S6

2.2 设备选型

华为 CloudEngine 系列选型:

Spine 交换机:

型号:CE16808 交换容量:57.6 Tbps 槽位:8 个(每个槽位 36 × 100GE) └─ 最大 288 个 100G 端口 特性: └─ VXLAN + EVPN └─ Telemetry └─ SRv6 就绪

Leaf 交换机:

型号:CE8860-48C6-EI 端口:48 × 25GE + 6 × 100GE 交换容量:2.4 Tbps └─ 48 口 25G 接服务器 └─ 6 口 100G 上联 Spine(ECMP) 特性: └─ VXLAN VTEP └─ EVPN 控制面 └─ M-LAG(服务器双归)

2.3 关键配置

# spine_leaf_config.py — Spine-Leaf 基础配置

def generate_underlay_ospf():
    """Underlay OSPF 配置"""
    # Spine 配置
    spine_config = """
sysname SPINE-1

# ========== 全局配置 ==========
interface LoopBack0
 ip address 10.0.0.1 255.255.255.255

# ========== 接口连接 Leaf ==========
interface 100GE1/0/0
 description to-LEAF-1-100GE1/0/0
 ip address 10.0.1.0 255.255.255.254
 ospf network-type p2p

interface 100GE2/0/0
 description to-LEAF-2-100GE1/0/0
 ip address 10.0.1.2 255.255.255.254
 ospf network-type p2p

# ========== OSPF Underlay ==========
ospf 1 router-id 1.1.1.1
 area 0.0.0.0
  network 10.0.0.1 0.0.0.0
  network 10.0.1.0 0.0.0.1
  network 10.0.1.2 0.0.0.1
"""

    # Leaf 配置
    leaf_config = """
sysname LEAF-1

# ========== 全局配置 ==========
interface LoopBack0
 ip address 10.0.0.10 255.255.255.255

# ========== 接口连接 Spine ==========
interface 100GE1/0/0
 description to-SPINE-1-100GE1/0/0
 ip address 10.0.1.1 255.255.255.254
 ospf network-type p2p

interface 100GE2/0/0
 description to-SPINE-2-100GE1/0/0
 ip address 10.0.1.5 255.255.255.254
 ospf network-type p2p

interface 100GE3/0/0
 description to-SPINE-3-100GE1/0/0
 ip address 10.0.1.9 255.255.255.254
 ospf network-type p2p

# ========== OSPF Underlay ==========
ospf 1 router-id 10.10.10.10
 area 0.0.0.0
  network 10.0.0.10 0.0.0.0
  network 10.0.1.1 0.0.0.0
  network 10.0.1.5 0.0.0.0
  network 10.0.1.9 0.0.0.0
"""
    return spine_config, leaf_config


def generate_vxlan_evpn():
    """VXLAN + EVPN Overlay 配置"""
    config = """
# ========== VXLAN 配置(Leaf 上) ==========
# 创建 VXLAN 网络标识符(VNI)
bridge-domain 10
 vxlan vni 10010

bridge-domain 20
 vxlan vni 10020

# ========== EVPN 配置 ==========
evpn
 vpn-instance VPN-A
  route-distinguisher 10.0.0.10:1
  vpn-target 1:1 export-extcommunity
  vpn-target 1:1 import-extcommunity

# EVPN 对等体
bgp 65001
 router-id 10.0.0.10
 peer 10.0.0.1 as-number 65001
 peer 10.0.0.1 connect-interface LoopBack0

 l2vpn-family evpn
  policy vpn-target
  peer 10.0.0.1 enable
  peer 10.0.0.1 advertise encap-type vxlan

# ========== NVE 接口 ==========
interface Nve1
 source 10.0.0.10
 vni 10010 head-end peer-list protocol bgp
 vni 10020 head-end peer-list protocol bgp

# ========== 业务接口映射 VNI ==========
interface 25GE1/0/1
 description Server-01
 port link-type trunk
 port trunk allow-pass vlan 10 20

bridge-domain 10
 l2 binding vlan 10

bridge-domain 20
 l2 binding vlan 20

# ========== Anycast Gateway ==========
interface Vlanif10
 description BD10-Gateway
 ip binding vpn-instance VPN-A
 ip address 10.10.10.254 255.255.255.0
 vrrp vrid 10 virtual-ip 10.10.10.254

interface Vlanif20
 description BD20-Gateway
 ip binding vpn-instance VPN-A
 ip address 10.10.20.254 255.255.255.0
 vrrp vrid 20 virtual-ip 10.10.20.254
"""
    return config

三、迁移方案

3.1 迁移策略

"叉车式迁移"策略(一次性搬迁):

  前提条件:
  ┌──────────────────────────────────────────┐
  │  □ 新 Spine-Leaf 网络独立建设完成        │
  │  □ 新旧网络物理隔离(不同机柜/机房)     │
  │  □ 服务器双网卡(一个接旧网,一个接新网)│
  │  □ VXLAN/EVPN 在 Leaf 上预配置完成       │
  │  □ 应用租户 VLAN 到 VNI 映射完成          │
  └──────────────────────────────────────────┘

  迁移步骤(分批迁移服务器):
  ┌──────────────────────────────────────────┐
  │  第一批:测试服务器(10 台)              │
  │  ┌─ 从旧接入交换机拔线                   │
  │  ├─ 插入新 Leaf                          │
  │  ├─ 验证连通性                           │
  │  ├─ 验证 VM 迁移                         │
  │  └─ 验证监控告警                         │
  │                                           │
  │  第二批:非关键业务(50 台)              │
  │  ┌─ 分批迁移,每批 10 台                  │
  │  ├─ 每批间隔 1 小时观察                   │
  │  └─ 监控业务是否有异常                    │
  │                                           │
  │  第三批:关键业务(30 台)                │
  │  ┌─ 在业务低峰期迁移                     │
  │  ├─ 每台迁移后立即验证                   │
  │  └─ 保留旧网作为回退路径                  │
  │                                           │
  │  第四批:旧网络下线                       │
  │  ┌─ 确认所有业务已迁移                   │
  │  ├─ 保留旧交换机 1 周                     │
  │  └─ 确认无问题后回收设备                  │
  └──────────────────────────────────────────┘

3.2 自动验证脚本

# validate_migration.py — 迁移验证脚本

from netmiko import ConnectHandler
import json
import time


def validate_leaf_connectivity(leaf_ip: str):
    """验证 Leaf 到所有 Spine 的连通性"""
    conn = ConnectHandler(
        device_type="huawei_vrp",
        host=leaf_ip,
        username="admin",
        password="admin123",
    )

    results = {
        "leaf_ip": leaf_ip,
        "spine_connectivity": {},
        "vxlan_status": {},
        "evpn_status": {},
    }

    # 验证到 Spine 的连通性
    spine_loopbacks = [
        "10.0.0.1",  # Spine-1
        "10.0.0.2",  # Spine-2
        "10.0.0.3",  # Spine-3
    ]

    for spine_ip in spine_loopbacks:
        output = conn.send_command(
            f"ping {spine_ip} -c 3"
        )
        reachable = "!" in output
        results["spine_connectivity"][spine_ip] = reachable

    # 验证 VXLAN 隧道状态
    output = conn.send_command(
        "display vxlan tunnel"
    )
    results["vxlan_status"]["tunnels"] = output

    # 验证 EVPN 邻居
    output = conn.send_command(
        "display bgp evpn peer"
    )
    results["evpn_status"]["peers"] = output

    # 验证 ECMP 负载均衡
    output = conn.send_command(
        "display ospf routing"
    )
    results["underlay_routes"] = len(output.splitlines())

    conn.disconnect()
    return results


def validate_vm_migration():
    """验证 VM 迁移后的网络连通性"""
    test_vms = [
        {"name": "VM-APP-01", "ip": "10.10.10.10"},
        {"name": "VM-DB-01", "ip": "10.10.10.20"},
        {"name": "VM-WEB-01", "ip": "10.10.20.10"},
    ]

    results = {}
    for vm in test_vms:
        # 从不同 Leaf 测试
        for leaf_ip in ["10.0.0.10", "10.0.0.11"]:
            try:
                conn = ConnectHandler(
                    device_type="huawei_vrp",
                    host=leaf_ip,
                    username="admin",
                    password="admin123",
                )
                output = conn.send_command(
                    f"ping {vm['ip']} -c 3"
                )
                reachable = "!" in output
                key = f"{vm['name']}(from {leaf_ip})"
                results[key] = {
                    "reachable": reachable,
                    "latency_ms": _extract_latency(output),
                }
                conn.disconnect()
            except Exception as e:
                results[f"{vm['name']}(from {leaf_ip})"] = {
                    "error": str(e)
                }

    return results


def _extract_latency(ping_output: str) -> float:
    """从 ping 输出提取延迟"""
    import re
    match = re.search(
        r"min/avg/max = ([\d.]+)/([\d.]+)/([\d.]+)",
        ping_output,
    )
    if match:
        return float(match.group(2))
    return 0.0

四、性能验证

4.1 性能测试

# performance_test.py — Spine-Leaf 性能测试

import subprocess
import statistics
import time


def test_east_west_bandwidth():
    """测试东西向带宽(Leaf 1 → Leaf 2)"""
    result = subprocess.run(
        ["iperf3",
         "-c", "10.10.10.20",   # 目标服务器在另一 Leaf
         "-t", "30",
         "-P", "8",              # 8 并行流
         "-i", "1"],
        capture_output=True, text=True,
    )
    return result.stdout


def test_all_to_all_latency(servers: list):
    """全互联延迟测试"""
    results = {}

    for src in servers:
        for dst in servers:
            if src["ip"] == dst["ip"]:
                continue

            # 使用 ping 测试延迟
            latencies = []
            for _ in range(10):
                start = time.time()
                p = subprocess.run(
                    ["ping", "-n", "1", dst["ip"]],
                    capture_output=True,
                )
                elapsed = (time.time() - start) * 1000
                if p.returncode == 0:
                    latencies.append(elapsed)
                time.sleep(0.1)

            key = f"{src['name']} → {dst['name']}"
            results[key] = {
                "min_rtt": min(latencies) if latencies else 0,
                "avg_rtt": statistics.mean(latencies) if latencies else 0,
                "max_rtt": max(latencies) if latencies else 0,
                "jitter": statistics.stdev(latencies) if len(latencies) > 1 else 0,
            }

    return results

五、经验总结

Spine-Leaf 迁移关键经验:

  1. Underlay 先行,Overlay 后上
  ┌─ 先确保 OSPF/IS-IS 邻居全部建立
  ├─ 验证所有 Leaf 到 Spine ECMP 负载分担
  └─ 再部署 VXLAN 和 EVPN

  2. VXLAN 隧道验证
  ┌─ 每个 Leaf 的 NVE 接口状态
  ├─ VXLAN 隧道数量 = N×(N-1)/2(N=Leaf 数)
  ├─ 验证 VNI 到 BD 的映射
  └─ 验证 Anycast Gateway

  3. 迁移时的注意事项
  ┌─ 保留回退路径(服务器双网卡保留旧连接)
  ├─ 逐批迁移,每批验证通过才继续
  ├─ 迁移过程持续监控丢包和延迟
  └─ 准备回退方案(拔回旧网线)

  4. 运维转型
  ┌─ 从 CLI 管理转向自动化平台
  ├─ VXLAN/EVPN 排障需要新技能
  ├─ 建设 Telemetry 监控体系
  └─ 定期验证 ECMP 哈希均匀性

  5. 常见问题
  ┌─ ECMP 哈希不均 → 调整哈希因子
  ├─ VXLAN 性能未达预期 → 检查 MTU 配置
  ├─ BGP EVPN 邻居震荡 → 检查 Underlay 连通性
  └─ VM 迁移后丢包 → 检查 ARP/ND 表老化

下篇预告:第335篇《MPLS_L3VPN跨域互联案例》——跨域MPLS L3VPN的互联设计与实施,分析Option A/B/C三种方案的选择。


下篇预告:第335篇《MPLS_L3VPN跨域互联案例》——跨域MPLS L3VPN的互联设计与实施,分析Option A/B/C三种方案的选择。