Làm chủ MACVLAN và IPVLAN trên Linux: Tối ưu mạng hiệu năng cao không lo nghẽn Bridge

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

Khi Linux Bridge nghẽn cứng lúc 2 giờ sáng

Lúc đó là 2:14 sáng ngày thứ Ba thì chuông cảnh báo PagerDuty reo lên. Cổng thanh toán của chúng tôi—đang xử lý khoảng 120.000 gói tin mỗi giây trên sáu container Docker qua đường truyền 10GbE—bị rớt khoảng 8% tổng số giao dịch gửi đến. Các chỉ số trên máy host trông vô cùng tồi tệ: CPU core 0 và core 1 chạm ngưỡng 100%, bị chiếm trọn bởi ksoftirqd. Phân vị độ trễ (latency percentiles) tăng vọt từ mức ổn định 1,8 ms lên hơn 450 ms.

Log dmesg trên host ngay lập tức chỉ rõ thủ phạm:

# dmesg -T | tail -n 2
[Tue Oct 28 02:14:11 2025] nf_conntrack: table full, dropping packet
[Tue Oct 28 02:14:12 2025] nf_conntrack: table full, dropping packet

# top -b -n 1 | head -n 12
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   14 root      20   0       0      0      0 R  98.7   0.0  12:43.11 ksoftirqd/0
   21 root      20   0       0      0      0 R  97.4   0.0  11:15.82 ksoftirqd/1

Bridge mặc định của Docker (docker0) đã sụp đổ dưới áp lực quá tải của conntrack. Ở chế độ bridge mặc định, mỗi gói tin đều phải đi qua các cặp veth, chuyển tiếp qua các hook bridge phần mềm, chạm tới các bảng Netfilter NAT và cấp phát một bản ghi trong bảng trạng thái conntrack. Việc tăng nf_conntrack_max từ 65.536 lên 262.144 cũng chỉ giúp bạn cầm cự thêm được khoảng hai mươi phút trước khi RAM cạn kiệt và tình trạng nghẽn softirq tái diễn.

Với các API CRUD thông thường xử lý khoảng 2.000 request mỗi giây, overhead của bridge hầu như không đáng kể. Nhưng ở tốc độ đường truyền dây (line rate)—với các broker Kafka, Redis cluster hoặc các node tiếp nhận streaming—bridge sẽ trở thành một gánh nặng lớn. Giải pháp ở đây là gì? Bỏ qua hoàn toàn các virtual bridge và cơ chế connection tracking bằng cách gắn trực tiếp namespace của container vào card mạng vật lý (NIC) với MACVLAN hoặc IPVLAN.

Kiến trúc: So sánh MACVLAN và IPVLAN

Cả hai driver đều loại bỏ các chặng trung gian, nhưng cách chúng xử lý header ở Layer 2 và Layer 3 lại hoàn toàn khác biệt.

Đường đi truyền thống qua Bridge

Cơ chế mạng tiêu chuẩn của Docker định tuyến các gói tin qua một luồng xử lý phần mềm dễ tắc nghẽn:

NIC vật lý (eth0) <--> iptables (NAT / MASQUERADE) <--> Linux Bridge (docker0) <--> veth peer <--> eth0 của Container

Mỗi khung dữ liệu (frame) đều phải sao chép qua bộ đệm gói tin, thực hiện các bước xác thực checksum và làm CPU phải context switch liên tục giữa các namespace kernel. MACVLAN và IPVLAN loại bỏ hoàn toàn quy trình này bằng cách kết nối trực tiếp vào hàng đợi driver vật lý.

MACVLAN: Nhiều địa chỉ MAC riêng biệt trên một Interface

MACVLAN gán các địa chỉ MAC Layer 2 ảo riêng biệt cho từng sub-interface gắn liền với interface cha vật lý như eth0. Mỗi container sẽ nhận một địa chỉ IP trực tiếp từ subnet vật lý của bạn:

Switch Upstream <--> NIC vật lý (eth0)
                         |-- eth0.1 (MAC: 02:42:0a:00:00:02, IP: 192.168.10.10) -> Container A
                         |-- eth0.2 (MAC: 02:42:0a:00:00:03, IP: 192.168.10.11) -> Container B

Switch top-of-rack của bạn sẽ nhìn thấy một cổng vật lý chứa nhiều địa chỉ MAC phân biệt. MACVLAN hỗ trợ bốn chế độ hoạt động:

  • Chế độ Bridge (Mặc định): Các container trên cùng một interface cha giao tiếp cục bộ trực tiếp trong bộ nhớ kernel. Các frame không bao giờ rời khỏi NIC của host.
  • VEPA (Virtual Ethernet Port Aggregator): Lưu lượng giữa các container trên cùng host được đẩy ra switch vật lý rồi hairpin (vòng ngược) trở lại. Điều này cho phép bạn áp dụng các chính sách bảo mật trực tiếp trên firewall phần cứng.
  • Chế độ Private: Chặn toàn bộ liên lạc giữa các container trên cùng một interface cha, ngay cả khi switch upstream cố tình hairpin gói tin trở lại.
  • Chế độ Passthru: Chuyển toàn bộ interface vật lý độc quyền cho một namespace container duy nhất, bỏ qua hoàn toàn các sub-interface ảo.

Điểm cần lưu ý: MACVLAN yêu cầu các cổng switch vật lý phải cho phép chế độ promiscuous mode hoặc dung lượng bảng CAM đủ lớn. Triển khai MACVLAN trên AWS VPC, Google Cloud Platform hoặc Microsoft Azure sẽ thất bại ngay lập tức, vì các hypervisor đám mây sẽ tự động drop bất kỳ gói tin nào mang địa chỉ MAC chưa được cấp phép.

IPVLAN: Dùng chung một MAC, Phân tách bằng nhiều IP

IPVLAN giúp vượt qua rào cản lọc MAC trên hạ tầng đám mây. Thay vì sinh ra các địa chỉ Layer 2 mới, mỗi sub-interface IPVLAN sẽ sao chép địa chỉ MAC của NIC cha (eth0). Chỉ có địa chỉ IP ở Layer 3 là thay đổi:

Switch Upstream <--> NIC vật lý (eth0) [MAC: 52:54:00:ab:cd:ef]
                         |-- ipv0 (MAC: 52:54:00:ab:cd:ef, IP: 192.168.10.10) -> Container A
                         |-- ipv1 (MAC: 52:54:00:ab:cd:ef, IP: 192.168.10.11) -> Container B

Quá trình demultiplexing diễn ra ngay tại tầng network. Kernel sẽ kiểm tra IP đích bên trong frame gửi đến và định tuyến trực tiếp vào container tương ứng. IPVLAN cung cấp hai chế độ chính:

  • Chế độ L2: Hoạt động tương tự như MACVLAN bridge, nhưng chỉ dùng một địa chỉ MAC duy nhất. Các truy vấn broadcast và ARP vẫn hoạt động bình thường, gateway vật lý mặc định sẽ định tuyến lưu lượng ra bên ngoài. Chế độ này vận hành rất mượt mà trong các mạng doanh nghiệp có bật cơ chế bảo mật cổng 802.1X.
  • Chế độ L3: Biến host thành một router nội bộ. Toàn bộ broadcast domain và ARP domain đều biến mất hoàn toàn. Kernel Linux chuyển tiếp các gói tin gửi đến chỉ dựa vào bảng định tuyến IP đích, mang lại thông lượng tối đa cho hệ thống microservices mật độ cao.

Hướng dẫn triển khai từng bước

Việc chuyển đổi driver chỉ mất năm phút cấu hình và không gây downtime đối với các mạng bridge hiện có.

1. Xác định các thông số mạng

Thu thập tên interface cha, subnet và default gateway của bạn:

# ip -br addr show dev eth0
eth0             UP             192.168.10.50/24 fe80::5054:ff:fe12:3456/64

# ip route show default
default via 192.168.10.1 dev eth0 proto dhcp metric 100

Ở đây, interface cha là eth0, gateway là 192.168.10.1 và dải CIDR vật lý là 192.168.10.0/24.

2. Cấu hình MACVLAN trong Docker

Tách riêng một dải IP độc lập bên trong subnet vật lý của bạn. Sử dụng --ip-range giúp ngăn Docker cấp phát nhầm các địa chỉ đã được máy chủ tĩnh hoặc DHCP pool sử dụng:

docker network create -d macvlan \
  --subnet=192.168.10.0/24 \
  --gateway=192.168.10.1 \
  --ip-range=192.168.10.192/27 \
  -o parent=eth0 \
  prod-macvlan-net

Cấu hình này dành riêng 32 địa chỉ (192.168.10.192 đến 192.168.10.223) chỉ phục vụ cho container. Khởi chạy một instance:

docker run -d --name payment-processor \
  --net=prod-macvlan-net \
  --ip=192.168.10.195 \
  nginx:alpine

Kiểm tra interface của container:

docker exec payment-processor ip addr show eth0

Container sở hữu một địa chỉ MAC riêng biệt và IP tĩnh 192.168.10.195. Các host khác trong mạng vật lý có thể truy cập trực tiếp IP này—không cần publish cổng bằng -p 80:80, không cần quy tắc NAT iptables và không tốn bản ghi conntrack nào.

3. Khắc phục lỗi giao tiếp giữa Host và Container

Thử ping từ Docker host tới 192.168.10.195, bạn sẽ thấy gói tin bị drop 100%. Lý do là kernel Linux chủ động chặn luồng gói tin trực tiếp giữa interface cha vật lý và các endpoint con MACVLAN để ngăn chặn vòng lặp broadcast (broadcast loop).

Để thiết lập kết nối hai chiều, hãy cấu hình một liên kết MACVLAN shim chuyên dụng ngay trên host:

# 1. Thêm một interface macvlan ảo vào host
sudo ip link add macvlan-shim link eth0 type macvlan mode bridge

# 2. Gán một IP host dự phòng từ subnet vật lý
sudo ip addr add 192.168.10.220/32 dev macvlan-shim
sudo ip link set macvlan-shim up

# 3. Định tuyến lưu lượng container qua interface shim
sudo ip route add 192.168.10.192/27 dev macvlan-shim

Kiểm tra lại kết nối từ host:

# ping -c 3 192.168.10.195
PING 192.168.10.195 (192.168.10.195) 56(84) bytes of data.
64 bytes from 192.168.10.195: icmp_seq=1 ttl=64 time=0.048 ms
64 bytes from 192.168.10.195: icmp_seq=2 ttl=64 time=0.039 ms
64 bytes from 192.168.10.195: icmp_seq=3 ttl=64 time=0.041 ms

Độ trễ giảm mạnh từ 1,8 ms xuống chỉ còn 41 micro-giây. Các kết nối từ host đến container giờ đây được định tuyến mượt mà thông qua shim interface.

4. Triển khai IPVLAN chế độ L2

Đối với các máy ảo trên cloud hoặc switch áp dụng chính sách port security (sticky MAC), hãy dùng IPVLAN L2:

docker network create -d ipvlan \
  --subnet=192.168.10.0/24 \
  --gateway=192.168.10.1 \
  --ip-range=192.168.10.224/27 \
  -o parent=eth0 \
  -o ipvlan_mode=l2 \
  prod-ipvlan-l2

Khởi chạy container trên mạng mới:

docker run -d --name metrics-collector \
  --net=prod-ipvlan-l2 \
  --ip=192.168.10.225 \
  alpine sleep 3600

So sánh địa chỉ phần cứng giữa host và guest container:

# MAC vật lý của host
cat /sys/class/net/eth0/address

# MAC của container
docker exec metrics-collector cat /sys/class/net/eth0/address

Cả hai đều trả về cùng một chuỗi MAC giống nhau (ví dụ: 52:54:00:12:34:56). Switch vật lý chỉ ghi nhận duy nhất một địa chỉ phần cứng, nhưng vẫn chuyển tiếp lưu lượng chính xác đến từng IP riêng biệt mà không gặp lỗi bị chặn lọc.

Quy tắc lựa chọn driver

Đừng tốn thời gian tinh chỉnh bảng conntrack khi khối lượng công việc đã vượt quá khả năng chịu tải của bridge. Thay vào đó, hãy chọn driver phù hợp với hạ tầng vật lý của bạn:

  • Dùng MACVLAN trên máy chủ bare-metal hoặc các cụm VMware/Proxmox riêng—nơi đội ngũ mạng cho phép nhiều địa chỉ MAC trên mỗi port switch và các dịch vụ yêu cầu tính năng khám phá mạng broadcast nguyên bản ở Layer 2.
  • Dùng IPVLAN L2 trên public cloud (AWS EC2, GCP Compute Engine), interface không dây (Wi-Fi) hoặc mạng doanh nghiệp có chính sách MAC port security nghiêm ngặt.
  • Dùng IPVLAN L3 cho các backend microservice mật độ cao, nơi bạn có thể peer các route BGP với router upstream nhằm loại bỏ hoàn toàn nhiễu broadcast và chi phí duy trì bảng ARP.

Hãy luôn ghi lại cấu hình shim host trong các playbook quản lý hạ tầng, phân chia dải CIDR một cách cẩn thận và thường xuyên theo dõi số gói tin bị drop qua lệnh ip -s link show eth0.

Share: