Hãy nghĩ về cách thư đi lại trong một khu văn phòng. host là hai đội ngồi chung một sàn trống — đưa tay là tới, nhanh nhất, nhưng không có vách nên họ giẫm lên dây điện thoại của nhau. bridge là mỗi đội một phòng riêng, thư qua một phòng chuyển phát đóng dấu và định tuyến giùm — chậm hơn tí xíu. none là một buồng cách âm, điện thoại chỉ reo cho chính mình. overlay là gửi giao liên chạy sang toà nhà bên kia thành phố bằng một túi ngoại giao niêm phong — nó tự chiếm chỗ trong túi, nên mỗi phong bì nhét vào được ít hơn. Docker có đúng bốn kiểu mạng đó, gặp hằng ngày: bridge (mặc định), host, none và overlay. Chúng khác nhau ở đường gói tin đi, và đường đi đó tốn tiền.
Tôi đo bằng iperf3 trong 5 giây, container nói chuyện với container:
| Mạng | Thông lượng | Độ trễ (ping, trung bình) |
|---|---|---|
host |
130,91 Gbit/s | 0,020 ms |
| bridge mặc định | 107,26 Gbit/s | 0,034 ms |
| bridge tự tạo | 101,64 Gbit/s | 0,033 ms |
overlay (hai nút) |
50,90 Gbit/s | 0,183 ms |
Một lưu ý về phạm vi trước khi đọc tiếp: máy tôi là macOS nên Docker chạy trong máy ảo Linux, và mọi phép đo ở trên là trong cùng một máy — không có card mạng vật lý nào tham gia. Những con số này đo chi phí phần mềm của từng đường đi, không phải tốc độ mạng thật của bạn.
bridge: veth, NAT, và cái giá của nó
Mặc định mỗi container được gắn một đầu của cặp veth, đầu kia cắm vào cầu docker0. Gói tin đi qua cầu, qua netfilter, có thể qua NAT.
Chi phí đo được so với host: mất khoảng 18% thông lượng và thêm 70% độ trễ. Nghe nhiều, nhưng 0,034 ms so với 0,020 ms là chênh 14 micro giây. Một truy vấn CSDL tầm thường mất 200 micro giây; một lời gọi HTTP nội bộ mất hàng mili giây. Chi phí của bridge nằm dưới ngưỡng bạn đo được trong ứng dụng thật.
Bridge mặc định và bridge tự tạo cho thông lượng như nhau (107 so với 102 Gbit/s, chênh lệch nằm trong dao động). Khác biệt thật giữa chúng không phải hiệu năng — mà là DNS và cách ly, phần sau sẽ đo.
Cách ly là thứ bridge tự tạo cho bạn
Container nằm trên netlab, thử ping từ ba nơi:
| Gọi từ | Kết quả |
|---|---|
cùng mạng netlab |
tới được |
mạng netlab2 |
không tới được |
| bridge mặc định | không tới được |
Đây là hàng rào thật, không phải quy ước. Muốn thông thì phải nối tường minh:
docker network connect netlab2 <container>
Sau lệnh đó container có hai địa chỉ IP, mỗi mạng một cái, và cả hai bên đều gọi tới được. Đó là cách dựng những kiến trúc kiểu "web nói được với cả CSDL lẫn cache, nhưng CSDL không nói được với cache".
host: nhanh nhất, và mất hết cách ly
--network host bỏ hẳn namespace mạng riêng — container dùng thẳng ngăn xếp mạng của máy chủ. Không veth, không NAT, không gì cả. Nhanh nhất trong bảng.
Đổi lại bạn mất mọi thứ:
- Không cách ly. Container thấy mọi giao diện mạng của máy chủ.
-pmất tác dụng. Không có gì để ánh xạ; ứng dụng nghe cổng nào là chiếm cổng đó trên máy chủ.- Đụng cổng. Hai container cùng nghe 8080 thì cái thứ hai chết.
- Không chạy trên Docker Desktop macOS/Windows đúng như bạn nghĩ, vì "host" ở đó là máy ảo Linux chứ không phải máy tính của bạn.
Đáng dùng khi bạn thật sự cần: thiết bị mạng, giám sát gói tin, hoặc một dịch vụ thông lượng cực cao mà 18% là số tiền lớn. Còn lại thì không.
none: có ngăn xếp mạng, nhưng đi đâu cũng không được
giao dien mang: lo tunl0@NONE gre0@NONE gretap0@NONE ...
ra Internet: HONG
loopback: OK
Chỉ có lo và mấy giao diện tunnel rỗng. Loopback vẫn chạy — nên một tiến trình vẫn tự nói chuyện với chính nó qua 127.0.0.1 được, chỉ là không ai bên ngoài vào được và nó không ra được đâu cả.
Hợp cho các job xử lý dữ liệu thuần tính toán: đọc từ volume, ghi ra volume, không cần mạng. Đây là lớp bảo vệ mạnh và rẻ — kết hợp với --read-only và --cap-drop=ALL (phần 31) thì container gần như không còn bề mặt tấn công nào.
overlay: cái giá của việc đi xuyên nhiều máy
Overlay cho container trên các máy khác nhau nói chuyện như cùng một mạng LAN. Nó bọc gói tin trong VXLAN.
Tôi dựng một swarm hai nút để đo, và so với bridge trong cùng một nút của chính swarm đó:
| Thông lượng | Độ trễ | MTU | |
|---|---|---|---|
| bridge (cùng nút) | 106,65 Gbit/s | 0,043 ms | 1500 |
| overlay (hai nút) | 50,90 Gbit/s | 0,183 ms | 1450 |
Overlay chỉ đạt 48% thông lượng và 4,3 lần độ trễ. Đây là chênh lệch duy nhất trong bài đủ lớn để bạn cảm thấy.
1450 mới là con số cần nhớ
Dòng MTU quan trọng hơn cả hai cột kia. VXLAN thêm 50 byte header, nên phần dữ liệu còn 1450.
Hậu quả rất khó chẩn đoán: ứng dụng nào tự đặt kích thước gói theo giả định MTU 1500, hoặc bật cờ "không phân mảnh", sẽ treo giữa chừng thay vì báo lỗi. Bắt tay TLS xong xuôi, request nhỏ chạy tốt, rồi một response lớn ngồi im tới khi hết giờ. Nếu bạn gặp "chỉ hỏng với payload lớn" trên overlay, hãy nghi MTU trước tiên.
Trên hạ tầng mà bản thân đường truyền đã bọc thêm một lớp nữa (VPN, một số mạng đám mây), MTU thực tế còn nhỏ hơn 1450 và bạn phải khai tay:
docker network create -d overlay --opt com.docker.network.driver.mtu=1400 ten-mang
Chọn cái nào
| Tình huống | Mạng |
|---|---|
| Gần như mọi trường hợp | bridge tự tạo |
| Job tính toán không cần mạng | none |
| Cần ngăn xếp mạng của máy chủ | host |
| Container trải trên nhiều máy | overlay |
Đừng dùng bridge mặc định cho việc thật. Nó không cho phân giải tên và không cách ly — hai thứ bạn được miễn phí khi tự tạo một mạng:
docker network create ung-dung
docker run -d --network ung-dung --name db postgres:16-alpine
docker run -d --network ung-dung --name web vi-du:1.0
Trong Compose thì bạn đã có sẵn: Compose tự tạo một mạng riêng cho mỗi project.
Muốn tự thấy khác biệt giữa hai loại bridge và soi luôn MTU, chạy vài dòng là rõ:
docker network create thu
docker run -d --name may-chu --network thu alpine sleep 300
docker run --rm --network thu alpine ping -c3 may-chu # theo ten: chay
docker run --rm --network bridge alpine ping -c3 may-chu # bridge mac dinh: hong
docker run --rm --network thu alpine ip -o link show eth0 | grep -o 'mtu [0-9]*'
docker rm -f may-chu && docker network rm thu
Dòng thứ hai với thứ ba cho thấy ngay bridge tự tạo phân giải được tên còn bridge mặc định thì không, và dòng cuối cho bạn con số MTU thật của mạng đang dùng.
Mẫu số chung
Bài học đầu tiên, đọc thẳng từ con số 18%: một tỉ lệ phần trăm mà không kèm con số tuyệt đối và cái ngân sách bao quanh nó thì chỉ là một lời hù, không phải một tín hiệu. "bridge chậm hơn host 18%" nghe đủ để người ta đòi chuyển sang host — cho tới khi thấy 18% ấy là 14 micro giây, nằm gọn dưới một truy vấn CSDL 200 micro giây, chìm nghỉm trong một lời gọi HTTP tính bằng mili giây. Chi phí thật hay không phụ thuộc hoàn toàn vào chỗ nó rơi vào. Cùng cái bẫy "phần trăm to, tuyệt đối bé" ở khắp nơi: "hàm này nhanh gấp đôi" mà nó chiếm 0,1% thời gian chạy, "giảm 40% một khoản chỉ đáng 3% tổng", tối ưu một vòng lặp mà nút thắt thật nằm ở cú gọi mạng kế bên. Nguyên tắc: trước khi trả giá (mất cách ly, mất tính đơn giản) để ăn một phần trăm, quy nó ra con số tuyệt đối rồi đặt cạnh ngân sách thật — và nhớ rằng cái 18% ấy được mua bằng chính lớp veth/NAT đang cho bạn cách ly và phân giải tên; lớp phí bạn định cắt thường là lớp đang chịu lực.
Điều thứ hai, một cái bẫy chẩn đoán: con số ai cũng trích dẫn (Gbit/s) hầu như không bao giờ là con số dựng bạn dậy lúc 3 giờ sáng (MTU 1450). Overlay chậm bằng nửa là chuyện biết trước và chịu được; cái thật sự cắn là 50 byte header của VXLAN ăn mất phần dữ liệu, khiến mọi thứ "chạy ngon với gói nhỏ rồi treo cứng với gói lớn" — bắt tay TLS xong, request bé trơn tru, rồi một response lớn ngồi im tới lúc hết giờ, không một dòng lỗi. Đây là dạng hỏng tệ nhất: nó im lặng và chỉ lộ ra ở biên. Cùng kiểu "một ràng buộc âm thầm chỉ cắn ở rìa" ở giới hạn kích thước gói tin, độ dài tối đa một cột, trần payload của một API, một cái timeout chỉ chạm khi dữ liệu đủ lớn. Nguyên tắc: khi thứ gì đó "chỉ hỏng với cái lớn", hãy nghi một cái trần bị thu hẹp ở tầng dưới trước khi nghi logic của mình — và đừng để cột tiêu đề của bảng đo che mất cái cột lặng lẽ quyết định.
Phần sau sẽ đo kỹ chính cái DNS nội bộ làm cho việc gọi theo tên chạy được, cùng cái bẫy cache khiến nó thỉnh thoảng trỏ sai.