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í của từng loại không gian tên

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ế

utspid 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.