Linux Network Namespaces và veth Pairs: Tự xây dựng mạng Container từ con số không

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Cách Linux cô lập mạng của Container

Khi bạn chạy lệnh docker run hoặc lập lịch cho một Kubernetes pod, các container runtime như containerd và CRI-O không hề dùng đến phép thuật nào cả. Chúng cấu hình các thành phần mạng nguyên bản được tích hợp trực tiếp vào nhân Linux. Để phân tách lưu lượng mạng giữa các tenant, các đội ngũ hạ tầng thường lựa chọn giữa ba kiến trúc chính:

  • Shared Host Networking: Container dùng chung network stack trực tiếp với máy host (ví dụ cờ --net=host của Docker). Hướng tiếp cận này mang lại độ trễ gần như bằng không (thường dưới 5 microsecond), nhưng lại xóa bỏ ranh giới bảo mật và dễ gây xung đột cổng (port collision) giữa các microservice.
  • Cô lập hoàn toàn bằng Máy ảo (Full Virtual Machine Isolation): Các hypervisor như KVM khởi tạo máy ảo với NIC ảo, TAP interface và kernel máy khách riêng biệt. Phương pháp này mang lại mức độ cô lập phần cứng triệt để nhưng lại lãng phí bộ nhớ. Ngay cả một hệ điều hành khách tối giản cũng ngốn từ 150 MB đến 512 MB RAM nền và mất vài giây để khởi động.
  • Network Namespaces kết hợp Virtual Ethernet (veth) Pairs: Kernel phân vùng các tài nguyên mạng—bảng định tuyến, quy tắc tường lửa, interface và socket—thành các sandbox logic riêng biệt. Việc liên kết các sandbox này bằng các cáp ethernet ảo (veth) mang lại khả năng cô lập ở cấp độ container với thời gian khởi động chỉ tính bằng mili-giây và mức phụ hao tài nguyên không đáng kể.

Hiểu rõ cơ chế này của kernel sẽ giúp bạn xử lý sự cố CNI plugin bị lỗi trên production, debug tình trạng rớt gói tin qua Docker bridge và cô lập workload mà không cần phụ thuộc vào các công cụ cồng kềnh của bên thứ ba.

Đánh đổi khi quản trị thủ công Network Namespaces

Trước khi bắt tay vào kết nối các interface, hãy cân nhắc những điểm đánh đổi khi tự tay quản lý các thành phần mạng của kernel:

Ưu điểm

  • Mức tiêu hao tài nguyên không đáng kể: Việc tạo một network namespace chỉ tốn vài kilobyte bộ nhớ kernel (struct net). Bạn có thể chạy hàng nghìn namespace mà không làm ảnh hưởng đáng kể đến CPU hay RAM của host.
  • Network Stack độc lập: Mỗi namespace có thiết bị loopback riêng, bảng ARP độc lập, danh sách socket tách biệt và các chuỗi bộ lọc iptables/nftables hoàn toàn riêng biệt.
  • Khả năng tương thích phổ quát: Mọi cấu hình đều dựa trên các tiện ích cốt lõi tiêu chuẩn như iproute2 và iptables. Không cần bất kỳ container daemon hay CNI binary nào.

Nhược điểm

  • Trạng thái tạm thời (Ephemeral): Các namespace tạo qua ip netns chỉ tồn tại trên RAM. Khi khởi động lại host, chúng sẽ mất sạch trừ khi bạn lưu lại bằng systemd unit hoặc script khởi động riêng.
  • Khó mở rộng định tuyến: Định tuyến tĩnh và cấu hình bridge sẽ trở nên rất phức tạp và dễ gãy khi bạn mở rộng lên tới hàng chục endpoint. Các môi trường production thực tế luôn cần một control plane tự động hóa như Calico hoặc Cilium.
  • Yêu cầu đặc quyền cao: Việc cấu hình interface và bảng định tuyến đòi hỏi quyền root hoặc capability CAP_NET_ADMIN.

Kiến trúc: Virtual Bridge, Hai Namespaces và NAT

Để tái lập cách các runtime như Docker xây dựng mạng bridge mặc định, chúng ta sẽ kết nối hai namespace độc lập thông qua một software bridge trên Linux và định tuyến các gói tin đi ra internet công cộng bằng NAT masquerading.

Một lưu ý quan trọng khi thao tác: việc chỉnh sửa quy tắc iptables forwarding và default route trên VPS từ xa có thể làm ngắt kết nối phiên SSH của bạn. Hãy luôn thử nghiệm các lệnh này trên máy ảo cục bộ, server staging hoặc qua console out-of-band trước khi thao tác trên node production.

Dưới đây là mô hình mạng mục tiêu:

  • Hai network namespace cô lập: ns-red và ns-blue.
  • Một software bridge (br0) trên host đóng vai trò như một virtual switch Layer 2.
  • Hai cặp veth point-to-point nối từng namespace tới br0.
  • Kích hoạt chuyển tiếp IPv4 ở cấp độ host cùng quy tắc iptables MASQUERADE để định tuyến gói tin ra ngoài qua interface mạng chính (như eth0).

Các bước triển khai chi tiết

Bước 1: Khởi tạo các Network Namespace

Đầu tiên, kiểm tra các namespace hiện có trên máy và tạo hai môi trường cô lập:

# Kiểm tra các namespace hiện có
sudo ip netns list

# Tạo hai namespace cô lập
sudo ip netns add ns-red
sudo ip netns add ns-blue

# Xác nhận đã tạo
sudo ip netns list

Các namespace mới tạo sẽ có interface loopback ở trạng thái chưa kích hoạt. Bạn có thể kiểm tra điều này bên trong ns-red:

sudo ip netns exec ns-red ip link show

Thiết bị lo đang ở trạng thái DOWN. Hãy bật nó lên ở cả hai namespace để các tiến trình nội bộ có thể phân giải IPC và bind vào localhost:

sudo ip netns exec ns-red ip link set dev lo up
sudo ip netns exec ns-blue ip link set dev lo up

Bước 2: Khởi tạo Virtual Bridge

Tạo một software bridge trên host. Thiết bị này hoạt động như một switch vật lý Layer 2 và đồng thời đóng vai trò làm default gateway (192.168.50.1) cho cả hai namespace:

# Tạo bridge interface
sudo ip link add name br0 type bridge

# Gán địa chỉ IP gateway cho subnet (192.168.50.0/24)
sudo ip addr add 192.168.50.1/24 dev br0

# Bật bridge interface hoạt động
sudo ip link set dev br0 up

Bước 3: Kết nối các Namespace bằng veth Pairs

Hãy hình dung cặp veth như một sợi dây cáp mạng ảo hai chiều. Bất kỳ khung dữ liệu (frame) nào đi vào đầu này sẽ lập tức đi ra ở đầu kia. Chúng ta sẽ tạo hai cặp để kết nối các namespace với bridge:

# Tạo veth pair cho ns-red
sudo ip link add veth-red type veth peer name veth-red-br

# Tạo veth pair cho ns-blue
sudo ip link add veth-blue type veth peer name veth-blue-br

Gắn các đầu phía host vào br0, sau đó đưa các đầu peer còn lại vào namespace đích tương ứng:

# Gắn đầu phía host vào bridge
sudo ip link set veth-red-br master br0
sudo ip link set veth-blue-br master br0

# Bật cả hai endpoint phía bridge
sudo ip link set dev veth-red-br up
sudo ip link set dev veth-blue-br up

# Chuyển đầu phía container vào namespace tương ứng
sudo ip link set veth-red netns ns-red
sudo ip link set veth-blue netns ns-blue

Bước 4: Gán địa chỉ IP và kiểm tra Ping giữa các Namespace

Sau khi các đầu kết nối đã vào đúng vị trí, hãy gán IP tĩnh từ dải 192.168.50.0/24 và bật các link bên trong từng namespace:

# Cấu hình ns-red với IP 192.168.50.10
sudo ip netns exec ns-red ip addr add 192.168.50.10/24 dev veth-red
sudo ip netns exec ns-red ip link set dev veth-red up

# Cấu hình ns-blue với IP 192.168.50.20
sudo ip netns exec ns-blue ip addr add 192.168.50.20/24 dev veth-blue
sudo ip netns exec ns-blue ip link set dev veth-blue up

Kiểm tra kết nối thông mạng giữa hai namespace qua bridge:

sudo ip netns exec ns-red ping -c 3 192.168.50.20

Các frame ICMP rời veth-red, đi tới br0 trên host, được chuyển tiếp dựa theo bảng học địa chỉ MAC và cập bến veth-blue bên trong ns-blue với độ trễ thường dưới 0.05 ms.

Bước 5: Cấu hình Default Gateway và Định tuyến NAT

Hiện tại hai namespace đã có thể trò chuyện với nhau, nhưng chúng vẫn chưa thể kết nối tới các subnet bên ngoài hay internet. Hãy trỏ default route của chúng về địa chỉ IP của bridge:

sudo ip netns exec ns-red ip route add default via 192.168.50.1
sudo ip netns exec ns-blue ip route add default via 192.168.50.1

Tiếp theo, kích hoạt tính năng chuyển tiếp gói tin IPv4 (packet forwarding) trong Linux kernel để host có thể định tuyến lưu lượng giữa các interface:

sudo sysctl -w net.ipv4.ip_forward=1

Kiểm tra tên interface vật lý dùng để kết nối ra ngoài mặc định:

ip route show default | awk '{print $5}'

Giả định cổng uplink chính của bạn là eth0, hãy thêm các quy tắc iptables để masquerade gói tin đi ra và cho phép chuyển tiếp hai chiều:

# Masquerade lưu lượng đi ra qua eth0
sudo iptables -t nat -A POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE

# Cho phép chuyển tiếp giữa bridge và interface mạng ngoài
sudo iptables -A FORWARD -i br0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT

Bước 6: Kiểm tra kết nối mạng ngoài và Cấu hình DNS

Ping thử một địa chỉ IP công cộng từ bên trong ns-red để xác minh định tuyến và NAT đã hoạt động:

sudo ip netns exec ns-red ping -c 3 1.1.1.1

Để phân giải được tên miền, hãy tạo file resolv.conf riêng cho namespace:

sudo mkdir -p /etc/netns/ns-red
echo "nameserver 1.1.1.1" | sudo tee /etc/netns/ns-red/resolv.conf

Bất cứ khi nào bạn chạy lệnh bên trong ns-red, ip netns exec sẽ tự động bind-mount /etc/netns/ns-red/resolv.conf đè lên /etc/resolv.conf. Kiểm tra khả năng phân giải DNS hoàn chỉnh bằng curl:

sudo ip netns exec ns-red curl -I https://kernel.org

Bước 7: Dọn dẹp môi trường

Sau khi thử nghiệm xong, việc dọn dẹp chỉ mất vài giây. Xóa một namespace sẽ tự động hủy interface veth trực thuộc cùng đầu peer tương ứng trên host:

# Xóa các namespace (thao tác này cũng đồng thời xóa các cặp veth-red và veth-blue)
sudo ip netns del ns-red
sudo ip netns del ns-blue

# Xóa bridge interface
sudo ip link set dev br0 down
sudo ip link del dev br0

# Xóa các quy tắc iptables
sudo iptables -t nat -D POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE
sudo iptables -D FORWARD -i br0 -o eth0 -j ACCEPT
sudo iptables -D FORWARD -i eth0 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT

Bây giờ bạn đã nắm vững mô hình hoạt động cốt lõi của mạng container hiện đại. Những gì bạn vừa thực hiện qua hàng chục câu lệnh CLI ở trên chính là những gì Docker và các Kubernetes CNI tự động hóa ngầm phía sau mỗi khi một container được khởi chạy.

Share: