Chạy hai container web, mỗi cái đều lắng nghe cổng 80 — và chúng không xung đột. Container có địa chỉ IP riêng, khác host. docker exec vào một container và bạn thấy eth0, bảng định tuyến, iptables của riêng nó. Làm sao có được điều đó khi tất cả chạy trên một máy, một card mạng vật lý? Câu trả lời là network namespace — kernel cho mỗi container một stack mạng hoàn toàn độc lập — cộng với veth, cặp "cáp ảo" nối container về host. Bài này (phần 4 loạt Docker) chạy thật để dựng lại cơ chế đó, kể cả tự tay tạo veth và ping qua namespace.
Mỗi container một stack mạng độc lập
Network namespace cô lập toàn bộ mạng: interface, địa chỉ IP, bảng định tuyến, bảng iptables, và cả không gian cổng. Nghĩa là cổng 80 trong container A và cổng 80 trong container B là hai thứ khác nhau, không đụng nhau. Đo thật một container:
docker run alpine ip addr # eth0 inet 172.17.0.5/16 (IP của riêng nó)
# default via 172.17.0.1 (gateway = bridge docker0)
Container có eth0 với IP 172.17.0.5, và gateway 172.17.0.1 — chính là bridge docker0 của host.

Hình 1: Mỗi container một stack mạng riêng (eth0/IP/route/cổng); unshare --net tạo namespace rỗng chỉ có loopback; veth là cặp cáp ảo nối container về bridge docker0 của host, ra Internet qua NAT.
Đo thật: từ namespace rỗng tới veth thông mạng

Hình 2: Chạy thật — container có eth0 172.17.0.5/16, gateway 172.17.0.1; net namespace mới (unshare --net) chỉ có lo (không eth0, bị cắt mạng); cặp veth 10.10.0.1↔10.10.0.2 nối hai namespace, ping 0% loss (0,041 ms).
- Container có eth0/IP riêng:
172.17.0.5/16, gateway172.17.0.1. Đây là mạng riêng của container, không dùng chung với host. - Namespace rỗng bị cắt mạng:
unshare --nettạo một network namespace mới — nó chỉ cólo(loopback, còn DOWN), không cóeth0. Một tiến trình trong namespace này hoàn toàn bị cắt khỏi mạng cho tới khi ai đó nối cho nó một interface. - veth nối hai namespace:
ip link add veth0 type veth peer name veth1tạo một cặp interface như hai đầu của một sợi cáp — gói vào đầu này ra đầu kia. Đẩyveth1vào namespacens1, đặt IP hai đầu (10.10.0.1ở host,10.10.0.2trong ns1), rồiping— 0% packet loss, 0,041 ms. Đây chính xác là cách Docker nối một container: một đầu veth ở trong network namespace của container (thànheth0), đầu kia cắm vào bridgedocker0của host.
Bức tranh đầy đủ của mạng Docker
Ghép lại: mỗi container có network namespace riêng, một đầu veth trở thành eth0 của container, đầu kia cắm vào bridge docker0 — một switch ảo trên host. Mọi container trên cùng bridge nói chuyện với nhau qua đó; và để ra Internet, gói đi qua bridge rồi được NAT (masquerade bằng iptables) sang IP của host. Đây là toàn bộ "phép màu" mạng bridge mặc định của Docker — chỉ là namespace + veth + bridge + NAT, tất cả là tính năng kernel Linux.
Đánh đổi cần cân nhắc
--network host bỏ cô lập mạng để lấy hiệu năng. Với --network host, container dùng chung network namespace của host — không veth, không NAT, không bridge. Nhanh hơn (bỏ một lớp), nhưng mất cô lập: container thấy mọi interface host và cổng của nó đụng trực tiếp cổng host. Chỉ dùng khi thực sự cần hiệu năng mạng tối đa hoặc truy cập mạng host, và hiểu rõ đánh đổi bảo mật.
NAT bridge mặc định che IP container khỏi bên ngoài. Vì container nằm sau NAT, bên ngoài không tự kết nối vào được — bạn phải -p (publish) để mở đường (chủ đề bài networking). Đây vừa là bảo vệ (container không lộ ra ngoài trừ khi bạn muốn) vừa là nguồn bối rối phổ biến ("sao gọi container từ máy khác không được?"). Hiểu NAT là hiểu vì sao cần publish cổng.
veth có chi phí; mạng dày đặc cần cân nhắc. Mỗi cặp veth và mỗi lần qua bridge/NAT tốn một chút CPU. Với hầu hết ứng dụng, chi phí này không đáng kể. Nhưng ở quy mô rất lớn (hàng nghìn container, throughput cực cao), người ta cân nhắc các chế độ mạng khác (macvlan, ipvlan, hoặc eBPF-based như Cilium) để giảm lớp. Bridge + veth là mặc định tốt, không phải lựa chọn duy nhất.
Ba ý mang về
- Mỗi container một stack mạng độc lập: đo thật, container có
eth0 172.17.0.5/16và gateway riêng — IP, route, cổng đều riêng, nên hai container cùng mở cổng 80 không đụng nhau. - Network namespace mới bị cắt mạng cho tới khi được nối: đo thật
unshare --netchỉ cólo, khôngeth0— cô lập mạng hoàn toàn là điểm khởi đầu. - veth là cặp cáp ảo nối container về host: đo thật tự dựng veth
10.10.0.1↔10.10.0.2ping 0% loss — Docker dùng đúng cơ chế này để cắm container vào bridgedocker0, ra Internet qua NAT.
Nguồn
- man7.org — network_namespaces(7): https://man7.org/linux/man-pages/man7/network_namespaces.7.html
- man7.org — veth(4): https://man7.org/linux/man-pages/man4/veth.4.html
- Docker docs — Bridge network driver: https://docs.docker.com/engine/network/drivers/bridge/
Phần sau ta xem nửa còn lại của container: cgroups — cách giới hạn CPU và bộ nhớ thật, đo khi container vượt --memory bị OOM kill và khi vượt --cpus bị điều tiết (throttle).