Thực tế: Container chỉ là một “ảo ảnh”
Chạy lệnh ps aux bên trong một Docker container và bạn có thể chỉ thấy ba tiến trình. Chạy nó trên máy host, và bạn sẽ thấy hàng trăm tiến trình. Đây không phải là phép thuật. Kernel Linux thực chất không hề có đối tượng nào gọi là ‘container’. Thay vào đó, container chỉ là các tiến trình tiêu chuẩn được bao bọc bởi hai tính năng cụ thể của kernel: Control Groups (cgroups) để giới hạn tài nguyên phần cứng và Namespaces để cô lập, thường kết hợp với các giải pháp lưu trữ container như OverlayFS.
Namespaces đánh lừa một tiến trình khiến nó tin rằng mình đang sở hữu các tài nguyên hệ thống riêng biệt. Chúng cung cấp lớp ảo hóa giúp ngăn chặn máy chủ web trong một container nhìn thấy các tệp cơ sở dữ liệu trong một container khác. Mặc dù kernel hiện hỗ trợ tám loại namespace, chúng ta sẽ tập trung vào sáu loại cốt lõi tạo nên xương sống cho các container engine hiện đại: UTS, PID, NET, Mount, IPC và User.
Thiết lập môi trường thực hành
Namespaces không phải là thứ bạn cài đặt qua apt; chúng đã là một phần của kernel Linux kể từ phiên bản 2.6.24. Tuy nhiên, bạn cần các công cụ ở tầng user-space để thao tác với chúng. Nếu bạn đang chạy một bản phân phối hiện đại như Ubuntu 22.04 hoặc Fedora 38, các công cụ này đã có sẵn trong các gói util-linux và iproute2.
Tôi khuyên bạn nên thử nghiệm các lệnh này trên một VM không dùng cho production. Tôi đã sử dụng một instance t3.medium tiêu chuẩn với 2 vCPU và 4GB RAM cho các ví dụ sau.
# Đảm bảo môi trường của bạn đã sẵn sàng
sudo apt update && sudo apt install util-linux iproute2 -y
# Kiểm tra xem kernel của bạn có hỗ trợ các namespace cần thiết không
lsns --version
Lệnh unshare tạo ra các namespace mới. Ngược lại, nsenter cho phép bạn bước vào bên trong một namespace hiện có. Hãy coi unshare như việc xây dựng một căn phòng và nsenter như việc bước qua cánh cửa đó.
Hướng dẫn thực hành 6 loại Namespace thiết yếu
1. UTS Namespace (Cô lập Hostname)
UTS (UNIX Timesharing System) namespace là loại dễ hình dung nhất. Nó cho phép một tiến trình có hostname riêng biệt. Điều này cực kỳ quan trọng khi chạy nhiều instance của một ứng dụng vốn tự định danh thông qua lệnh $(hostname).
# Khởi chạy một bash shell trong một UTS namespace mới
sudo unshare --uts /bin/bash
# Chỉ thay đổi tên bên trong shell này
hostname isolated-node-01
# Xác nhận thay đổi
hostname
# Gõ 'exit' để quay lại máy host; tên của máy host vẫn không thay đổi
exit
2. PID Namespace (Cô lập Process ID)
Trong một PID namespace, một tiến trình được gán lại thành PID 1. Đây là nền tảng của các hệ thống init trong container. Trên máy host, tiến trình của bạn có thể là PID 10452, nhưng bên trong namespace, nó nghĩ rằng mình là tiến trình đầu tiên của hệ thống.
# Tạo một PID namespace mới và fork tiến trình
sudo unshare --pid --fork --mount-proc /bin/bash
# Xem danh sách các tiến trình đã được cô lập
ps aux
Flag --mount-proc là bắt buộc. Vì công cụ ps đọc dữ liệu từ thư mục /proc, bạn phải mount một phiên bản /proc riêng tư để ẩn các tiến trình khác của máy host khỏi tầm nhìn.
3. Network Namespace (Cô lập Network Stack)
Network namespaces cung cấp một stack mạng riêng tư, bao gồm các địa chỉ IP và bảng định tuyến duy nhất. Bạn có thể quản lý chúng hiệu quả bằng cách làm chủ lệnh ss và ip để xử lý sự cố mạng. Trong một thử nghiệm trên server Ubuntu 22.04, việc cô lập các microservice vào các NET namespace riêng đã giúp giảm xung đột cổng xuống bằng không, ngay cả khi nhiều dịch vụ cùng cố gắng bind vào cổng 8080.
Để kết nối stack bị cô lập này với internet, bạn sử dụng một cặp Virtual Ethernet (veth):
# Tạo một namespace cho web server
sudo ip netns add web-ns
# Tạo một 'sợi cáp' ảo (cặp veth)
sudo ip link add veth-host type veth peer name veth-child
# Cắm một đầu vào namespace
sudo ip link set veth-child netns web-ns
# Gán IP để tạo một subnet riêng 10.0.0.0/24
sudo ip addr add 10.0.0.1/24 dev veth-host
sudo ip link set veth-host up
# Cấu hình interface bên trong
sudo ip netns exec web-ns ip addr add 10.0.0.2/24 dev veth-child
sudo ip netns exec web-ns ip link set veth-child up
# Ping 'container' từ máy host
ping -c 2 10.0.0.2
4. Mount Namespace (Cô lập Hệ thống tệp)
Mount namespaces là loại đầu tiên được thêm vào Linux từ năm 2002. Chúng cho phép một tiến trình mount hoặc unmount các hệ thống tệp mà không ảnh hưởng đến máy host. Docker sử dụng tính năng này để mount hệ thống tệp gốc (rootfs) của container như một lớp tạm thời, đảm bảo tệp /etc/mtab của máy host luôn sạch sẽ.
5. IPC Namespace (Giao tiếp liên tiến trình)
IPC namespaces cô lập các đối tượng System V IPC và hàng đợi tin nhắn POSIX. Điều này ngăn một tiến trình trong container này vô tình truy cập vào các phân đoạn bộ nhớ dùng chung của container khác. Nó đóng vai trò như một rào cản bảo mật quan trọng trong môi trường multi-tenant, nơi các người dùng khác nhau chia sẻ cùng một kernel.
6. User Namespace (Bảo mật & Quyền hạn)
Đây là một bước tiến lớn về bảo mật. User namespaces cho phép một người dùng có quyền root (UID 0) bên trong namespace trong khi vẫn là một người dùng tiêu chuẩn, ít đặc quyền (UID 1000) trên máy host. Nếu kẻ tấn công thoát khỏi container, chúng sẽ rơi vào máy host mà không có bất kỳ quyền quản trị nào, giúp tăng cường bảo mật hệ thống một cách đáng kể.
# Ánh xạ người dùng hiện tại của bạn thành root bên trong một namespace mới
unshare --user --map-root-user /bin/bash
# Kiểm tra danh tính của bạn; nó sẽ hiển thị uid=0(root)
id
Giám sát và Debug các Namespace của bạn
Quản lý nhiều namespace có thể gây nhầm lẫn. Tôi sử dụng ba kỹ thuật cụ thể để theo dõi những gì đang chạy trên các node production của mình, tương tự như cách tôi giải mã các bí ẩn hiệu năng Linux khi gặp sự cố phức tạp.
1. Liệt kê tất cả các Namespace đang hoạt động
Lệnh lsns là cách nhanh nhất để kiểm tra hệ thống của bạn. Nó hiển thị mọi namespace đang hoạt động, loại của nó và PID của tiến trình đang sử dụng tệp và cổng đó.
sudo lsns
2. Kiểm tra thông qua /proc
Mỗi tiến trình đều có một thư mục ns. Nếu bạn nghi ngờ hai tiến trình đang dùng chung một network stack, hãy kiểm tra số inode của chúng. Nếu các số này trùng nhau, chúng nằm trong cùng một namespace.
# Thay 1234 bằng PID mục tiêu của bạn
ls -l /proc/1234/ns
3. Truy cập vào một Namespace đang chạy
Nếu mạng của một container bị lỗi, hãy sử dụng nsenter để nhảy vào namespace của nó và chạy các công cụ chẩn đoán như tcpdump hoặc ip addr.
# Truy cập vào network namespace của một tiến trình cụ thể
sudo nsenter -t 1234 --net /bin/bash
Một mẹo chuyên nghiệp: luôn đặt tên rõ ràng cho các network namespace của bạn bằng cách sử dụng ip netns add. Mặc dù unshare phù hợp để kiểm tra nhanh, nhưng các namespace có tên định danh sẽ dễ dàng dọn dẹp hơn nhiều. Tôi luôn chạy một script dọn dẹp sau các buổi thực hành để loại bỏ các cặp veth không sử dụng và ngăn chặn tình trạng ‘interface bloat’ (tràn ngập interface) trên hệ thống host.
Hiểu được các nguyên lý cơ bản này sẽ thay đổi cách bạn nhìn nhận về cloud. Docker không còn là một engine bí ẩn nữa; nó là một bộ điều phối thông minh của các tính năng kernel tiêu chuẩn này. Cho dù bạn đang debug một vấn đề CNI phức tạp trong Kubernetes hay thắt chặt bảo mật cho một sandbox, namespaces chính là công cụ mà bạn sẽ tin dùng nhất.

