第340篇:EVPN VXLAN 数据中心互联案例

关键词

EVPN、VXLAN、DCI、数据中心互联、VXLAN隧道、Overlay、多活数据中心、大二层


一、案例背景

1.1 客户需求

某企业数据中心互联需求:

企业信息:

行业:互联网 数据中心:3 个(DC1-主、DC2-备、DC3-备) 服务器:2,000+ 台 虚拟机:5,000+ 要求: └─ VM 可在任意 DC 间自由迁移 └─ 业务多活部署 └─ 故障自动切换 └─ 支持网络自动化(CI/CD集成)

当前网络现状:

DC1:传统三层架构,使用 VLAN DC2:传统三层架构,使用 VLAN DC3:传统三层架构,使用 VLAN DCI:MPLS VPN 互联 问题: └─ VM 迁移时需要更改 IP 地址 └─ VLAN 4094 限制 └─ STP 阻塞端口 └─ 三层互联延迟大 └─ 不支持大规模多活

1.2 为什么要用 EVPN VXLAN

EVPN VXLAN 的优势:

  大二层网络:
  ┌──────────────────────────────────────────┐
  │  VXLAN 将 L2 报文封装在 UDP 中传输       │
  │  突破 VLAN 4094 限制(支持 16M VNI)    │
  │  跨 DC 的二层连通(VM 不修改 IP)        │
  │  无 STP 阻塞(所有链路可用)              │
  └──────────────────────────────────────────┘

  控制面优于传统 VXLAN:
  ┌──────────────────────────────────────────┐
  │  传统 VXLAN:             EVPN VXLAN:    │
  │  ┌─ 数据面洪泛学习       ┌─ BGP 控制面   │
  │  ├─ ARP 广播            ├─ ARP 抑制     │
  │  ├─ 无过滤策略           ├─ 路由策略    │
  │  └─ 排障困难             └─ 可视化好    │
  └──────────────────────────────────────────┘

  多活数据中心的关键能力:
  ┌──────────────────────────────────────────┐
  │  Anycast Gateway(分布式网关)            │
  │  └─ 每个 Leaf 都可以是网关               │
  │  └─ VM 迁移后网关不变                    │
  │                                           │
  │  Type-2/Type-3 路由                       │
  │  └─ Type-2:MAC/IP 路由                  │
  │  └─ Type-3:Inclusive Multicast          │
  │                                           │
  │  DF Election(指定转发者)                │
  │  └─ BGP DF Election                      │
  │  └─ 避免多归接入导致广播泛洪              │
  └──────────────────────────────────────────┘

二、架构设计

2.1 整体拓扑

EVPN VXLAN DCI 架构:

┌────── DC1 ──────┐ ┌────── DC2 ──────┐
[Spine-1] [Spine-2] 【DCI】[Spine-1] [Spine-2]
│ │
└────── DC3 ──────┘
[Spine-1] [Spine-2]
[Leaf1] [Leaf2]
X
[Server5] [Svr6]

DCI 互联: ┌─ 每对 Spine 之间通过 DCI 链路互联 ├─ DCI 链路:100GE × 4(不同路由的裸纤) ├─ 协议:EBGP between DCs └─ Underlay:IS-IS Level-2

2.2 网络设计

关键设计参数:

  Underlay:
  ┌──────────────────────────────────────────┐
  │  协议:IS-IS Level-2                     │
  │  互联 IP:10.0.0.0/31(点对点)          │
  │  LoopBack:10.255.0.0/32(VTEP 源地址)  │
  │  ECMP:8 路(Spine-Leaf 全互联)          │
  │  BFD:50ms × 3                           │
  └──────────────────────────────────────────┘

  Overlay(EVPN):
  ┌──────────────────────────────────────────┐
  │  地址族:L2VPN EVPN                      │
  │  VTEP 源:LoopBack 0                    │
  │  VXLAN 封装:VNI 10000-20000             │
  │  控制面:EBGP(跨 DC)/ IBGP(DC 内)    │
  │                                           │
  |  AS 规划:                                │
  |  ┌─ DC1:AS 65001                        │
  |  ├─ DC2:AS 65002                        │
  |  ├─ DC3:AS 65003                        │
  |  └─ RR:AS 65000(可选)                 │
  └──────────────────────────────────────────┘

  VNI 规划:
  ┌──────────────────────────────────────────┐
  │  VNI 10000:Prod-Web(10.10.0.0/16)    │
  │  VNI 10001:Prod-APP(10.11.0.0/16)    │
  │  VNI 10002:Prod-DB(10.12.0.0/16)    │
  │  VNI 20000:Storage-Net(172.16.0.0/12)│
  │                                           │
  │  RD 规划:                                │
  │  └─ DC1:10.255.0.1:VNI                  │
  │  └─ DC2:10.255.0.2:VNI                  │
  │  └─ DC3:10.255.0.3:VNI                  │
  │                                           │
  │  RT 规划:                                │
  │  └─ Import/Export:100:VNI               │
  └──────────────────────────────────────────┘

三、核心配置

3.1 Leaf 交换机配置

# evpn_leaf_config.py — EVPN Leaf 配置

def generate_evpn_leaf():
    """Leaf 交换机 EVPN 配置"""
    config = """
sysname LEAF-1-DC1

# ========== Underlay IS-IS ==========
interface LoopBack0
 ipv6 enable
 ipv6 address 2001:DB8:255:1::1/128

interface 100GE1/0/0
 description to-SPINE-1
 ipv6 enable
 ipv6 address 2001:DB8:10::1/127
 isis ipv6 enable 1
 isis circuit-level level-2

interface 100GE2/0/0
 description to-SPINE-2
 ipv6 enable
 ipv6 address 2001:DB8:10::3/127
 isis ipv6 enable 1
 isis circuit-level level-2

isis 1
 is-level level-2
 network-entity 49.0001.0000.0255.0001.00
 ipv6 enable topology ipv6
 bfd all-interfaces enable

# ========== Overlay EVPN ==========

# BGP EVPN 配置
bgp 65001
 router-id 10.255.1.1
 peer 10.255.0.1 as-number 65001          # DC1 RR
 peer 10.255.0.1 connect-interface LoopBack0
 #
 # L2VPN 地址族
 l2vpn-family evpn
  policy vpn-target
  peer 10.255.0.1 enable
  peer 10.255.0.1 reflect-client

# ========== VXLAN ==========

bridge-domain 10000
 vxlan vni 10000
 evpn
  route-distinguisher 10.255.1.1:10000
  vpn-target 100:10000 export-extcommunity
  vpn-target 100:10000 import-extcommunity

# ========== NVE 接口 ==========

interface Nve1
 source 10.255.1.1
 vni 10000 head-end peer-list protocol bgp

# ========== 业务接口 ==========

interface 25GE1/0/1
 description Server-Prod-Web
 port link-type trunk
 port trunk allow-pass vlan 100
 #
 bridge-domain 10000
  l2 binding vlan 100

# ========== Anycast Gateway ==========

interface Vlanif100
 description Prod-Web-Gateway
 ip address 10.10.0.254 255.255.255.0
 vrrp vrid 100 virtual-ip 10.10.0.254
"""
    return config

3.2 Spine 交换机配置

def generate_spine_config():
    """Spine 交换机 EVPN 配置(跨 DC RR)"""
    config = """
sysname SPINE-1-DC1

# ========== IS-IS Underlay ==========
interface LoopBack0
 ipv6 enable
 ipv6 address 2001:DB8:255:1::10/128

interface 100GE1/0/0
 description to-DC2-SPINE-1
 ipv6 enable
 ipv6 address 2001:DB8:100::1/127
 isis ipv6 enable 1
 isis circuit-level level-2

interface 100GE2/0/0
 description to-DC3-SPINE-1
 ipv6 enable
 ipv6 address 2001:DB8:200::1/127
 isis ipv6 enable 1

# ========== 跨 DC EBGP EVPN ==========

bgp 65001
 router-id 10.255.0.1
 #
 # DC 内 IBGP
 peer 10.255.1.1 as-number 65001
 peer 10.255.1.1 connect-interface LoopBack0
 peer 10.255.1.2 as-number 65001
 peer 10.255.1.2 connect-interface LoopBack0
 #
 # 跨 DC EBGP(扮演 RR 角色)
 peer 2001:DB8:100::2 as-number 65002      # DC2 Spine
 peer 2001:DB8:100::2 ebgp-max-hop 10
 peer 2001:DB8:200::2 as-number 65003      # DC3 Spine
 peer 2001:DB8:200::2 ebgp-max-hop 10
 #
 l2vpn-family evpn
  # 允许跨域 EVPN 路由交换
  undo policy vpn-target
  #
  peer 10.255.1.1 enable
  peer 10.255.1.1 reflect-client
  peer 10.255.1.2 enable
  peer 10.255.1.2 reflect-client
  #
  peer 2001:DB8:100::2 enable              # DC2
  peer 2001:DB8:200::2 enable              # DC3
"""
    return config

四、VM 迁移验证

4.1 迁移前后验证

# verify_vm_migration.py — VM 迁移验证脚本

from netmiko import ConnectHandler
import paramiko
import time
import json


class VMMigrationVerifier:
    """VM 迁移验证器"""

    def __init__(self, vm_ip: str, vm_mac: str, vni: int):
        self.vm_ip = vm_ip
        self.vm_mac = vm_mac
        self.vni = vni

    def check_vm_location(self) -> str:
        """通过 MAC 地址查询 VM 所在 Leaf"""
        # 模拟:查询 EVPN 的 MAC 表
        # 实际环境中需要对接 SDN 控制器
        return "LEAF-1-DC1"

    def check_mac_table(self, device: dict) -> dict:
        """检查设备的 MAC 表"""
        conn = ConnectHandler(**device)
        output = conn.send_command(
            f"display mac-address {self.vm_mac}"
        )
        conn.disconnect()
        return {"output": output}

    def check_evpn_route(self, device: dict) -> dict:
        """检查 EVPN Type-2 路由"""
        conn = ConnectHandler(**device)
        output = conn.send_command(
            f"display bgp evpn all routing-table"
            f" mac-route {self.vm_mac}"
        )
        conn.disconnect()
        return {"output": output}

    def verify_connectivity(
        self, test_ip: str
    ) -> bool:
        """验证 VM 连通性"""
        import subprocess
        result = subprocess.run(
            ["ping", "-n", "3", test_ip],
            capture_output=True, text=True,
        )
        return result.returncode == 0

    def monitor_during_migration(
        self, target_ip: str,
        duration_sec: int = 120
    ):
        """监测迁移过程中的网络变化"""
        import threading
        import subprocess

        results = {
            "total_pings": 0,
            "success_count": 0,
            "fail_count": 0,
            "max_latency": 0,
            "fail_periods": [],
        }

        fail_start = None

        for i in range(duration_sec * 10):  # 100ms 间隔
            start = time.time()
            result = subprocess.run(
                ["ping", "-n", "1", target_ip],
                capture_output=True, text=True,
            )
            elapsed = (time.time() - start) * 1000

            results["total_pings"] += 1
            if result.returncode == 0:
                results["success_count"] += 1
                results["max_latency"] = max(
                    results["max_latency"], elapsed
                )
                if fail_start:
                    results["fail_periods"].append({
                        "start": fail_start,
                        "end": time.time(),
                        "duration": time.time() - fail_start,
                    })
                    fail_start = None
            else:
                results["fail_count"] += 1
                if not fail_start:
                    fail_start = time.time()

            time.sleep(0.09)  # 100ms 总间隔

        if fail_start:
            results["fail_periods"].append({
                "start": fail_start,
                "end": time.time(),
                "duration": time.time() - fail_start,
            })

        return results

4.2 自动化迁移检查

# auto_check_evpn.py — EVPN 自动化检查

class EVPNHealthChecker:
    """EVPN 健康检查"""

    def __init__(self, devices: dict):
        self.devices = devices

    def check_vxlan_tunnel(self, device: dict) -> dict:
        """检查 VXLAN 隧道"""
        conn = ConnectHandler(**device)
        output = conn.send_command(
            "display vxlan tunnel"
        )
        conn.disconnect()

        tunnels = []
        for line in output.splitlines():
            if "up" in line.lower():
                tunnels.append(line.strip())

        return {
            "total_tunnels": len(tunnels),
            "up_tunnels": len(
                [t for t in tunnels if "up" in t.lower()]
            ),
        }

    def check_evpn_peers(self, device: dict) -> dict:
        """检查 EVPN 邻居"""
        conn = ConnectHandler(**device)
        output = conn.send_command(
            "display bgp evpn peer"
        )
        conn.disconnect()

        established = sum(
            1 for line in output.splitlines()
            if "Established" in line
        )

        return {"evpn_peers": established}

    def check_vni_status(self, device: dict) -> dict:
        """检查 VNI 状态"""
        conn = ConnectHandler(**device)
        output = conn.send_command(
            "display vxlan vni"
        )
        conn.disconnect()

        vnis = {}
        for line in output.splitlines():
            if "VNI" in line and "up" in line:
                parts = line.split()
                vnis[parts[0]] = parts[1] if len(parts) > 1 else "up"

        return {"vnis": vnis}

    def full_check(self) -> dict:
        """全量检查"""
        results = {}

        for name, info in self.devices.items():
            print(f"检查 {name}...")
            results[name] = {
                "tunnels": self.check_vxlan_tunnel(info),
                "peers": self.check_evpn_peers(info),
                "vnis": self.check_vni_status(info),
            }

        return results

五、排障指南

5.1 常见问题

EVPN VXLAN 排障指南:

问题 1:VXLAN 隧道不 UP

排查步骤: 1. display vxlan tunnel 2. display nve interface 3. ping VTEP 源地址(Underlay 可达性) 4. display bgp evpn peer 常见原因: └─ VTEP 源地址 Underlay 不可达 └─ NVE 接口未配置 source └─ EVPN 邻居未 Established

问题 2:VM 迁移后网络不通

排查步骤: 1. 检查新 Leaf 的 MAC 表 2. 检查 EVPN Type-2 路由 3. 检查 ARP 表(抑制) 4. 检查 Anycast Gateway 常见原因: └─ ARP 抑制导致旧 ARP 未刷新 └─ Anycast Gateway 配置不一致 └─ MAC 迁移时间未到

问题 3:跨 DC 流量绕路

排查步骤: 1. traceroute 检查路径 2. display bgp evpn routing-table 3. 检查 RT 的 Import/Export 常见原因: └─ DF Election 未正确选择 └─ 跨 DC EVPN 路由策略不当

5.2 验证脚本

# evpn_verification.sh — EVPN 验证脚本

#!/bin/bash

# EVPN 一键验证
DEVICE=$1
echo "=== EVPN 状态检查: $DEVICE ==="

echo "1. NVE 接口状态"
ssh $DEVICE "display nve interface"

echo "2. VXLAN 隧道"
ssh $DEVICE "display vxlan tunnel"

echo "3. EVPN 邻居"
ssh $DEVICE "display bgp evpn peer"

echo "4. EVPN 路由统计"
ssh $DEVICE "display bgp evpn all routing-table statistics"

echo "5. MAC 表(VNI 10000)"
ssh $DEVICE "display mac-address vni 10000"

echo "6. ARP 表"
ssh $DEVICE "display arp vni 10000"

echo "7. VNI 状态"
ssh $DEVICE "display vxlan vni"

echo "=== 检查完成 ==="

六、经验总结

EVPN VXLAN DCI 关键经验:

  1. Underlay 是一切的基础
  ┌─ Underlay 不通,Overlay 白搭
  ├─ IS-IS 推荐,OSPF 也可以
  ├─ BFD 必须使能(50ms × 3)
  └─ ECMP 路径数要合理

  2. RT 设计要全局统一
  ┌─ RT 决定了哪些 Leaf 收到哪些 MAC/IP
  ├─ 建议所有 DC 使用统一的 RT 规划
  ├─ Type-2(MAC/IP)和 Type-3(IMET)区分清楚
  └─ 跨 DC EBGP 要注意 undo policy vpn-target

  3. Anycast Gateway 要一致
  ┌─ 所有 Leaf 上同一个 VNI 的网关 IP/MAC 一致
  ├─ VRRP 的 virtual-ip 和 virtual-mac 一致
  ├─ VM 迁移后网关不变
  └─ ARP 抑制表要刷新

  4. 跨 DCI 的考虑
  ┌─ DCI 链路带宽和延迟
  ├─ 跨 DC 的 MAC 迁移时间
  ├─ 广播域范围控制
  └─ 跨 DC 流量是否绕路

  5. 运维
  ┌─ EVPN 排障比传统 VXLAN 更容易(控制面可见)
  ├─ 关注 Type-2/Type-3 路由数量
  ├─ 关注 MAC 漂移频率
  └─ 建设 EVPN 可视化监控平台

下篇预告:第341篇《VXLAN负载不均故障排查案例》——深入分析VXLAN网络中负载不均的根因,提供从ECMP哈希到流分布的排查方法。


下篇预告:第341篇《VXLAN负载不均故障排查案例》——深入分析VXLAN网络中负载不均的根因,提供从ECMP哈希到流分布的排查方法。