第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三种方案的选择。