Container là cgroup (phần 34) cộng với không gian tên. Phần 34 đo phần giới hạn tài nguyên; bài này đo phần cách ly.
Chi phí tạo tiến trình theo từng loại
clone() với các cờ khác nhau, đo thời gian tạo rồi thu hồi một tiến trình con:
| Tạo kèm không gian tên | µs mỗi lần | Phần thêm |
|---|---|---|
| không tạo gì | 100,5 | mốc so sánh |
+ uts (tên máy) |
96,5 | miễn phí |
+ pid (bảng tiến trình) |
99,7 | miễn phí |
+ ipc |
109,1 | +9 µs |
+ mount (hệ thống tệp) |
151,7 | +51 µs |
+ net (mạng) |
640,7 | +540 µs |
+ mount, uts, ipc, pid |
193,6 | +93 µs |
+ cả năm |
644,0 | +544 µs |
Lặp lại ba lần cho hai dòng quan trọng: không có không gian tên 97,0 / 91,3 / 80,4 µs; đủ cả năm 668,5 / 632,2 / 655,5 µs.
Bốn loại kia cộng lại tốn 93 µs. Riêng mạng tốn 540 µs — gấp gần sáu lần.
Vì sao mạng đắt đến thế
uts và pid gần như chỉ là cấp phát một cấu trúc và tăng bộ đếm tham chiếu. mount phải sao chép cây gắn kết, nên tốn hơn — 51 µs cho một cây nhỏ, và sẽ tốn hơn nữa nếu máy chủ có hàng trăm điểm gắn.
Không gian tên mạng thì phải dựng cả một ngăn xếp mạng mới: bảng định tuyến, bảng netfilter, giao diện lo, các bộ đếm thống kê, cấu trúc theo dõi kết nối. Và khi huỷ, tất cả phải được dọn — quá trình dọn này chạy qua synchronize_rcu() và là phần đắt nhất.
Đây là lý do trong nhiều báo cáo, việc xoá hàng loạt container còn chậm hơn việc tạo chúng.
Hai hệ quả thực tế
Hệ thống tạo container liên tục trả 540 µs mỗi lần chỉ cho phần mạng. Với CI chạy hàng nghìn job, hoặc nền tảng hàm serverless khởi động container theo yêu cầu, con số đó cộng dồn thành thứ đáng nhìn. Nửa mili giây không lớn với một lần, nhưng nó là chi phí cố định không giảm được bằng cách tối ưu ứng dụng.
Nếu tác vụ không cần cách ly mạng, đừng tạo không gian tên mạng:
docker run --network host ...
spec:
hostNetwork: true # Kubernetes
Đổi lại là container dùng chung ngăn xếp mạng với máy chủ: nó thấy mọi giao diện, và nó phải tự tránh xung đột cổng. Với dịch vụ hệ thống (thu thập số liệu, proxy, agent) thì đó thường là đánh đổi đúng.
Chi phí lúc chạy
Không gian tên mạng còn tốn thêm ở mỗi gói tin, vì gói phải đi qua cặp veth và cầu nối:
| Đường đi | Thông lượng |
|---|---|
--network host (không có không gian tên mạng riêng) |
14.286 / 11.612 / 15.156 MB/s |
Mạng bridge (qua veth và cầu nối) |
7.906 / 7.006 / 9.099 MB/s |
Khoảng 1,7 lần.
Cần nói rõ giới hạn của phép so sánh này: bên trái là loopback trong cùng một không gian tên, bên phải đi qua veth rồi cầu nối. Hai đường khác nhau về bản chất, nên 1,7 lần là mức chênh của cả đường đi chứ không phải chi phí riêng của veth. Tôi không tách được hai phần đó với công cụ có sẵn.
Điều chắc chắn: đường qua cầu nối làm nhiều việc hơn, và với dịch vụ cần thông lượng tối đa giữa hai container trên cùng máy, --network host là lựa chọn đáng cân nhắc.
Bảy loại không gian tên
ls -l /proc/self/ns/
lsns # liet ke moi khong gian ten dang co
| Loại | Cách ly cái gì | Đo được |
|---|---|---|
uts |
tên máy, tên miền | miễn phí |
pid |
số hiệu tiến trình | miễn phí |
ipc |
hàng đợi thông điệp, bộ nhớ chung | +9 µs |
mnt |
cây gắn kết hệ thống tệp | +51 µs |
net |
ngăn xếp mạng | +540 µs |
user |
ánh xạ UID/GID | không đo |
cgroup |
gốc của cây cgroup | không đo |
time |
đồng hồ khởi động (nhân 5.6+) | không đo |
Ba loại cuối tôi không đo. user đặc biệt đáng quan tâm vì nó là nền tảng của container không đặc quyền, và nó thêm chi phí ở mỗi lần kiểm tra quyền chứ không chỉ lúc tạo — một dạng chi phí khác hẳn, và cần cách đo khác.
Chia sẻ không gian tên
Không gian tên tồn tại độc lập với tiến trình. Một tiến trình vào được không gian tên có sẵn:
nsenter -t <pid> -n ip addr # vao khong gian ten MANG cua tien trinh do
nsenter -t <pid> -m -p -n /bin/sh # vao mount, pid, net
Đây là công cụ gỡ lỗi tốt nhất cho container không có shell hay không có công cụ mạng: bạn chạy tcpdump của máy chủ bên trong không gian tên mạng của container.
Kubernetes dùng chính cơ chế này: mọi container trong một pod dùng chung không gian tên mạng, nên chúng thấy nhau qua localhost — và cũng vì thế mà chúng không được nghe cùng một cổng.
# xem container nao dung chung khong gian ten mang voi container khac
docker run --network container:<ten-container-khac> ...
Thử ba mươi giây
Xem tiến trình của bạn đang ở những không gian tên nào và ai dùng chung:
echo "--- khong gian ten cua shell nay ---"
ls -l /proc/self/ns/ | awk '{print " " $9, $10, $11}'
echo "--- tong hop toan may ---"
lsns -o NS,TYPE,NPROCS,PID,COMMAND 2>/dev/null | head -15
echo "--- so khong gian ten mang dang ton tai ---"
lsns -t net 2>/dev/null | wc -l
ip netns list 2>/dev/null | wc -l
Số ở dòng cuối xấp xỉ số container đang chạy. Nếu nó lớn hơn nhiều so với số container bạn biết, có những không gian tên mạng mồ côi — thường do container đã chết mà quá trình dọn chưa xong, và chúng vẫn giữ tài nguyên.
Phần sau: điểm chuẩn cho đúng — những sai lầm tôi đã mắc trong ba mươi sáu phần trước.