Cấu hình ECMP Routing trên Linux: Phân phối lưu lượng qua nhiều đường mạng với lệnh ip route

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Mình từng gặp tình huống server production bị bottleneck ở network — CPU còn thừa, disk IO ổn, nhưng throughput chỉ đạt 800Mbps dù đã có 2 NIC kết nối 2 uplink khác nhau. Nguyên nhân: cả hai link đều active nhưng chỉ một cái đang thực sự mang traffic, cái còn lại ngồi chờ failover. Đó là lúc mình tìm đến ECMP.

Tại sao một default route không đủ

Khi Linux có nhiều uplink, mặc định hệ thống chỉ dùng một default route duy nhất. Route còn lại chỉ là backup, chờ link chính chết mới được dùng. Điều này dẫn đến 3 vấn đề thực tế:

  • Bandwidth bị giới hạn bởi một đường — dù có 2×1Gbps, bạn chỉ khai thác được 1Gbps
  • Khi link chính fail, cần thời gian để hệ thống detect và switch sang link backup
  • Traffic patterns không đều — một số flows bị nghẽn trong khi interface kia rảnh hoàn toàn

ECMP (Equal-Cost Multi-Path) giải quyết cả 3 vấn đề bằng cách để kernel phân phối các connection ra nhiều đường cùng lúc, dựa trên hash của packet header.

ECMP hoạt động như thế nào trên Linux kernel

Thay vì chọn một nexthop duy nhất, kernel tính hash từ tuple (src IP, dst IP, src port, dst port, protocol) của mỗi packet để quyết định đường đi. Cùng một TCP connection luôn đi qua cùng một nexthop — điều này quan trọng vì nếu packet trong cùng connection đi qua các đường khác nhau, sẽ gây out-of-order và làm giảm throughput nghiêm trọng.

Hash policy mặc định từ kernel 3.6+ dùng L3 (chỉ IP). Từ kernel 4.4+, bạn có thể bật L4 hash (IP + port) để phân phối đều hơn khi có nhiều connection đến cùng một destination.

Chuẩn bị: Kiểm tra kernel và cấu hình hash policy

Topology mình dùng trong bài:

  • Server: 192.168.1.100 (eth0) và 192.168.2.100 (eth1)
  • Gateway ISP A: 192.168.1.1 qua interface eth0
  • Gateway ISP B: 192.168.2.1 qua interface eth1

Khi thiết kế subnet cho lab, mình hay dùng toolcraft.app/vi/tools/developer/ip-subnet-calculator — nhập CIDR là ra ngay network range, broadcast, số host khả dụng, không cần tính tay.

# Kiểm tra version kernel (cần >= 3.6)
uname -r

# Xem hash policy hiện tại (0 = L3, 1 = L4)
sysctl net.ipv4.fib_multipath_hash_policy

# Bật L4 hash — phân phối đều hơn, đặc biệt khi nhiều connection đến cùng 1 IP
sysctl -w net.ipv4.fib_multipath_hash_policy=1

# Persistent qua reboot
echo "net.ipv4.fib_multipath_hash_policy=1" > /etc/sysctl.d/99-ecmp.conf
sysctl -p /etc/sysctl.d/99-ecmp.conf

Cấu hình ECMP với lệnh ip route

Thêm ECMP route cơ bản

Cú pháp nexthop cho phép khai báo nhiều đường trong một lệnh duy nhất:

# Xem route hiện tại
ip route show

# Xóa default route cũ
ip route del default

# Thêm ECMP route với 2 nexthop bằng nhau
ip route add default \
  nexthop via 192.168.1.1 dev eth0 weight 1 \
  nexthop via 192.168.2.1 dev eth1 weight 1

Điều chỉnh weight theo bandwidth thực tế

Nếu ISP A có 100Mbps và ISP B có 200Mbps, dùng weight 1:2 để phân phối theo tỷ lệ tương ứng:

# Weight tỷ lệ 1:2 — ISP B nhận gấp đôi traffic so với ISP A
ip route add default \
  nexthop via 192.168.1.1 dev eth0 weight 1 \
  nexthop via 192.168.2.1 dev eth1 weight 2

Cấu hình persistent với Netplan (Ubuntu)

Lệnh ip route mất khi reboot. Trên Ubuntu dùng Netplan:

# /etc/netplan/01-ecmp.yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 192.168.1.100/24
    eth1:
      addresses:
        - 192.168.2.100/24
  routes:
    - to: default
      via: 192.168.1.1
      metric: 100
    - to: default
      via: 192.168.2.1
      metric: 100
netplan apply

Lưu ý: Netplan không hỗ trợ cú pháp weight trực tiếp — nó tạo 2 route với cùng metric, kernel tự xử lý như ECMP. Nếu cần weight khác nhau, dùng script startup:

#!/bin/bash
# /etc/rc.local hoặc systemd ExecStartPost
ip route del default 2>/dev/null || true
ip route add default \
  nexthop via 192.168.1.1 dev eth0 weight 1 \
  nexthop via 192.168.2.1 dev eth1 weight 2
exit 0

Kiểm tra và Monitoring

Xác nhận ECMP route đã active

# Xem routing table — output phải có cú pháp nexthop
ip route show

# Output mong muốn:
# default
#     nexthop via 192.168.1.1 dev eth0 weight 1
#     nexthop via 192.168.2.1 dev eth1 weight 2

Kiểm tra packet đi qua đường nào

# Dùng ip route get để xem nexthop cho từng destination
ip route get 8.8.8.8
ip route get 1.1.1.1
ip route get 208.67.222.222

# Chạy với nhiều destination khác nhau — sẽ thấy output luân phiên qua 2 interface
# Ví dụ:
# 8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.100
# 1.1.1.1 via 192.168.2.1 dev eth1 src 192.168.2.100

Monitor bandwidth realtime trên từng interface

# Dùng ip -s để xem packet/byte count
watch -n 1 'ip -s link show eth0 && echo "---" && ip -s link show eth1'

# Hoặc nload (cần cài thêm)
nload eth0 eth1

# vnstat để xem traffic theo giờ/ngày
vnstat -i eth0 -i eth1 --live

Vấn đề thực tế: Asymmetric routing và Failover

Asymmetric routing là vấn đề hay gặp nhất với ECMP multi-ISP. Khi packet từ server đi qua eth0 nhưng reply về qua eth1 (vì ISP routing khác nhau), firewall stateful sẽ drop packet do không thấy SYN ban đầu. Fix bằng policy routing:

# Tạo 2 routing table riêng cho từng ISP
echo "100 isp1" >> /etc/iproute2/rt_tables
echo "200 isp2" >> /etc/iproute2/rt_tables

# Table isp1: mọi traffic ra qua eth0 đều reply về eth0
ip route add default via 192.168.1.1 table isp1
ip route add 192.168.1.0/24 dev eth0 src 192.168.1.100 table isp1

# Table isp2: tương tự cho eth1
ip route add default via 192.168.2.1 table isp2
ip route add 192.168.2.0/24 dev eth1 src 192.168.2.100 table isp2

# Rule routing: traffic từ IP nào thì dùng table tương ứng
ip rule add from 192.168.1.100 table isp1
ip rule add from 192.168.2.100 table isp2

ECMP không có health check tích hợp. Nếu một gateway chết, kernel vẫn tiếp tục gửi traffic đến đó. Script monitor đơn giản để tự động remove route khi gateway fail:

#!/bin/bash
# /usr/local/sbin/ecmp-monitor.sh — chạy qua cron mỗi 1 phút
GW1="192.168.1.1"; GW2="192.168.2.1"

ping -c 3 -W 2 $GW1 > /dev/null 2>&1; GW1_UP=$?
ping -c 3 -W 2 $GW2 > /dev/null 2>&1; GW2_UP=$?

if [ $GW1_UP -eq 0 ] && [ $GW2_UP -eq 0 ]; then
  # Cả 2 đều up — đảm bảo ECMP đầy đủ
  ip route replace default \
    nexthop via $GW1 dev eth0 weight 1 \
    nexthop via $GW2 dev eth1 weight 2
elif [ $GW1_UP -ne 0 ]; then
  ip route replace default via $GW2 dev eth1
  logger "ECMP: Gateway 1 down, switched to single path via $GW2"
elif [ $GW2_UP -ne 0 ]; then
  ip route replace default via $GW1 dev eth0
  logger "ECMP: Gateway 2 down, switched to single path via $GW1"
fi
# Thêm vào crontab
echo "* * * * * root /usr/local/sbin/ecmp-monitor.sh" > /etc/cron.d/ecmp-monitor
chmod +x /usr/local/sbin/ecmp-monitor.sh

ECMP là công cụ lightweight và hiệu quả — không cần thêm daemon hay software phức tạp, kernel xử lý toàn bộ việc phân phối. Kết hợp với policy routing để tránh asymmetric routing và một script monitor đơn giản là đủ cho hầu hết use case production.

Share: