Bài toán thực tế trên server production
Khoảng 6 tháng trước, hệ thống máy chủ nội bộ của mình gặp vấn đề nan giải: cần chạy song song một ứng dụng legacy dùng PHP 7.4 và một microservice Node.js 20 trên cùng một VPS. Hai dịch vụ này đòi hỏi các phiên bản thư viện hệ thống (glibc, OpenSSL) hoàn toàn xung đột nhau.
Gặp tình huống này, phản xạ đầu tiên thường là dựng Docker. Nhưng trên con VPS khiêm tốn (2 vCPU, 4GB RAM), Docker lại bộc lộ hạn chế. Bộ ba dockerd, containerd và docker-proxy ngốn ngay 150MB – 300MB RAM chỉ để duy trì trạng thái chờ. Chưa kể, Docker can thiệp sâu vào bảng iptables, đôi khi gây xung đột với firewall UFW có sẵn.
Còn nếu chạy trần trên host OS? Rủi ro dependency đá nhau hoặc một tiến trình lỗi ăn sạch 100% CPU là điều không thể chấp nhận.
Vì sao chroot và Docker đều chưa phải lựa chọn tối ưu?
Để hiểu lý do cần một giải pháp ở giữa, hãy nhìn vào hai thái cực quen thuộc:
- Hạn chế của chroot: Lệnh
chrootchỉ đổi thư mục gốc chứ không tạo namespace cô lập. Tiến trình bên trong vẫn thấy toàn bộ process của host qua/proc, dùng chung network stack và IPC. Đáng ngại hơn, tiến trình root bên trong có thể bẻ khóa (chroot escape) ra ngoài host rất dễ dàng. - Gánh nặng từ Docker: Ở tầng nhân Linux, Docker cũng chỉ gọi cgroups, namespaces, seccomp và capabilities. Đổi lại, nó bắt hệ thống gánh thêm daemon nền, overlayfs layer đôi khi phình to mất kiểm soát và network bridge riêng biệt.
Thứ mình cần rất rõ ràng: nhẹ như chroot, nhưng phải cô lập đầy đủ PID, Network, Mount và IPC. Quan trọng nhất là không chạy thêm daemon nào ngoài init system của Linux.
Ba phương án từng được đưa lên bàn cân
Trước khi đưa ra quyết định, mình đã thử nghiệm qua 3 hướng tiếp cận:
- Chroot kết hợp cgroups thủ công: Giải pháp này can thiệp trực tiếp vào
/sys/fs/cgroupvà dùng lệnhunshare. Dù cực nhẹ, việc tự viết script dọn dẹp mount point hay quản lý vòng đời tiến trình rất dễ sót tiến trình ma (zombie process). - Podman (Rootless): Podman không cần daemon, khá sạch sẽ. Dù vậy, cơ chế ánh xạ UID/GID và tầng lưu trữ
fuse-overlayfslàm giảm 15% – 20% hiệu năng disk I/O khi xử lý file dung lượng lớn. - LXC / LXD: Rất mạnh cho mô hình container hệ thống. Tuy nhiên, LXD trên Ubuntu bị đóng gói cứng vào Snap, kéo theo các mount point ảo và service phụ không mong muốn.
Giải pháp tối ưu: Triển khai systemd-nspawn
Sau nhiều vòng đo kiểm, systemd-nspawn đáp ứng trọn vẹn các tiêu chí đặt ra. Công cụ này nằm ngay trong gói systemd-container, dùng trực tiếp subsystem có sẵn của systemd mà không cần cài thêm runtime ngoại lai.
Trên con VPS Ubuntu 22.04 thực tế, chuyển từ Docker sang systemd-nspawn giúp giải phóng ngay ~380MB RAM tĩnh. Thời gian khởi động worker giảm từ 2.4 giây xuống còn dưới 0.6 giây vì không phải đi qua trung gian daemon.
Bước 1: Cài đặt công cụ nền tảng
Trên Debian/Ubuntu, bạn chỉ cần cài gói container của systemd cùng debootstrap để tạo OS tối thiểu:
sudo apt update
sudo apt install -y systemd-container debootstrap
Bước 2: Tạo root filesystem (rootfs) cho container
systemd-nspawn hoạt động trực tiếp trên một thư mục chứa đầy đủ cây thư mục Linux (rootfs). Ta sẽ bootstrap một bản Ubuntu 22.04 (Jammy) tối giản vào thư mục /var/lib/machines/app-worker:
sudo mkdir -p /var/lib/machines/app-worker
sudo debootstrap jammy /var/lib/machines/app-worker http://archive.ubuntu.com/ubuntu/
Lệnh này tải về khoảng 300MB các gói cơ bản. Thư mục /var/lib/machines là vị trí chuẩn được systemd nhận diện tự động.
Bước 3: Cấu hình ban đầu qua Interactive Shell
Truy cập vào container lần đầu để thiết lập tài khoản và các package cần thiết:
sudo systemd-nspawn -D /var/lib/machines/app-worker
Lúc này bạn đã ở trong không gian cách ly. Hãy đặt mật khẩu root và cài ứng dụng:
passwd
apt update && apt install -y curl htop
exit
Gõ exit để trở về host.
Bước 4: Khởi động container như một OS độc lập
Thêm cờ -b (boot), systemd-nspawn sẽ kích hoạt tiến trình PID 1 (systemd) riêng biệt bên trong container. Nó tự quản lý service như một VPS thu nhỏ:
sudo systemd-nspawn -b -D /var/lib/machines/app-worker
Để thoát khỏi console mà vẫn giữ container chạy nền, bạn bấm tổ hợp phím Ctrl + ] liên tiếp 3 lần.
Bước 5: Quản lý tập trung bằng machinectl
Thay vì thao tác thủ công, bạn nên dùng machinectl để điều khiển vòng đời container thuận tiện hơn:
# Khởi động container chạy nền
sudo machinectl start app-worker
# Xem danh sách máy đang hoạt động
sudo machinectl list
# Mở shell tương tác bên trong container
sudo machinectl shell app-worker
# Kiểm tra CPU, RAM tiêu thụ thực tế
sudo machinectl status app-worker
Bước 6: Tự khởi động và giới hạn tài nguyên
Để container tự bật cùng hệ thống khi reboot server, kích hoạt systemd service template có sẵn:
sudo systemctl enable --now [email protected]
Muốn khống chế container ở mức tối đa 1GB RAM và 50% CPU? Hãy tạo file drop-in override:
sudo systemctl edit [email protected]
Dán cấu hình sau rồi lưu lại:
[Service]
MemoryMax=1G
CPUQuota=50%
Reload daemon để áp dụng ngay lập tức:
sudo systemctl daemon-reload
sudo systemctl restart [email protected]
Hiệu quả thực tế sau 6 tháng
Sau nửa năm vận hành trên production, cụm server chạy ổn định mà không gặp lỗi rò rỉ bộ nhớ từ daemon nền.
Đặc biệt, việc backup trở nên cực kỳ gọn gàng. Vì toàn bộ container nằm trong một thư mục, mình chỉ cần dùng rsync định kỳ hoặc snapshot đĩa nếu dùng Btrfs/ZFS. Nếu bạn quản trị VPS nhỏ và cần môi trường cô lập nhẹ, an toàn mà không muốn trả phí tài nguyên cho Docker, systemd-nspawn chắc chắn là công cụ nên thử.

