Cài Đặt và Cấu Hình Rootless Docker: Chạy Docker Không Cần Root Để Bảo Vệ Host

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Quick start: Dựng Rootless Docker trong 5 phút

Chỉ cần một máy chủ Ubuntu 22.04 hoặc Debian 12 với tài khoản user thường, bạn có thể khởi chạy Docker daemon ngay mà không cần cấp quyền root lâu dài.

Bước 1: Cài đặt các gói phụ trợ

Rootless Docker cần uidmap để ánh xạ UID/GID và slirp4netns để tạo ngăn xếp mạng ảo ở user space. Chạy lệnh sau để cài đặt:

sudo apt-get update
sudo apt-get install -y uidmap dbus-user-session slirp4netns

Kiểm tra file cấu hình dải subuid đã sẵn sàng cho user của bạn chưa:

grep $(whoami) /etc/subuid /etc/subgid

Nếu màn hình xuất ra định dạng tương tự ubuntu:100000:65536, hệ thống đã sẵn sàng cấp phát 65.536 UID ảo cho container.

Bước 2: Tải và chạy script cài đặt

Thoát hoàn toàn khỏi quyền sudo. Hãy chạy lệnh dưới tư cách user thường:

curl -fsSL https://get.docker.com/rootless | sh

Bước 3: Khai báo socket và kích hoạt service

Script sẽ tạo một socket riêng tại thư mục runtime của user. Bạn thêm biến môi trường vào ~/.bashrc để Docker CLI nhận diện:

echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc
source ~/.bashrc

Bật service chạy nền thông qua systemd user instance:

systemctl --user enable --now docker
systemctl --user status docker

Bước 4: Xác thực hoạt động

Kiểm tra bằng một container mẫu:

docker run --rm hello-world

Dòng chữ chào mừng xuất hiện nghĩa là daemon đã chạy ổn định. Toàn bộ tiến trình này nằm trọn trong user namespace riêng biệt.

Bản chất rủi ro: Vì sao Docker mặc định lại nguy hiểm?

Docker group thực chất là quyền Root trá hình

Nhiều sysadmin thường tiện tay gán user vào group docker để gõ lệnh không cần sudo. Việc này tạo ra một lỗ hổng bảo mật lớn. Bất kỳ ai kiểm soát được Docker socket đều có thể mount thư mục gốc / của host vào container và chỉnh sửa file /etc/shadow chỉ bằng một câu lệnh.

Nguy cơ container escape

Ứng dụng web luôn đối mặt với nguy cơ bị khai thác RCE. Đôi khi, lỗi nằm ngay ở tầng runtime như lỗ hổng runc nổi tiếng CVE-2019-5736 hay lỗi rò rỉ file descriptor CVE-2024-21626 (Leaky Vessels).

Nếu Docker daemon chạy quyền root của host, kẻ tấn công sau khi thoát khỏi container (breakout) sẽ lập tức nắm quyền điều khiển toàn bộ server. Hậu quả là mã độc chiếm quyền kiểm soát, dữ liệu nhạy cảm bị đánh cắp.

Cơ chế tự vệ của Rootless Docker

Rootless Docker đảo ngược hoàn toàn bài toán này nhờ Linux User Namespaces. Bên trong container, app vẫn thấy mình là root (UID 0). Tuy nhiên, đứng từ máy host nhìn vào, tiến trình đó chỉ mang UID thông thường (ví dụ: UID 1001).

Giả sử hacker chọc thủng container và nhảy ra ngoài host. Hắn vẫn kẹt lại ở quyền user 1001. Hệ thống không bị ảnh hưởng vì kẻ tấn công không thể ghi đè file hệ thống hay can thiệp tiến trình của user khác.

Cấu hình production: Port, Linger và Giới hạn tài nguyên

1. Mở các port đặc quyền dưới 1024

Linux mặc định chặn user thường bind vào các cổng dưới 1024 như 80 hay 443. Muốn chạy Nginx hay Traefik trực tiếp, bạn cần hạ ngưỡng port đặc quyền của kernel:

echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/50-rootless-docker.conf
sudo sysctl --system

Sau lệnh này, Rootless Docker thoải mái map port 80 và 443 ra ngoài mà không gặp lỗi permission denied.

2. Giữ container sống khi logout SSH (Linger)

Mặc định, systemd sẽ dọn dẹp toàn bộ tiến trình của user ngay khi phiên SSH ngắt kết nối. Để container tiếp tục chạy 24/7 như một service độc lập, bạn cần bật tính năng lingering:

loginctl enable-linger $(whoami)

3. Kiểm soát RAM và CPU với cgroup v2

Để giới hạn tài nguyên cho container ở chế độ rootless, host bắt buộc phải kích hoạt cgroup v2. Kiểm tra nhanh bằng lệnh:

docker info | grep -i cgroup

Nếu kết quả trả về Cgroup Version: 2 kèm cảnh báo thiếu controller, hãy cấu hình systemd delegation để user thường quản lý được tài nguyên CPU và Memory:

sudo mkdir -p /etc/systemd/system/[email protected]
cat <<EOF | sudo tee /etc/systemd/system/[email protected]/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids
EOF
sudo systemctl daemon-reload

Bây giờ bạn có thể giới hạn tài nguyên bình thường:

docker run -d --name cache-redis --memory="512m" --cpus="1.0" redis:alpine

Kinh nghiệm thực chiến khi vận hành Rootless

Chuyển đổi mượt mà giữa Rootful và Rootless

Nếu server chạy song song cả hai bản Docker (một bản root hệ thống và một bản rootless riêng), hãy dùng Docker Context để chuyển đổi qua lại thay vì export biến môi trường liên tục:

# Tạo context cho rootless
docker context create rootless-local --description "User daemon" --docker "host=unix://$XDG_RUNTIME_DIR/docker.sock"

# Đổi context làm việc
docker context use rootless-local

Debug Docker Engine API qua Unix Socket

Trong các pipeline CI/CD nội bộ, việc gọi trực tiếp Docker API qua socket giúp tự động hóa nhiều tác vụ. Bạn có thể kiểm tra danh sách container bằng curl:

curl --unix-socket $XDG_RUNTIME_DIR/docker.sock http://localhost/containers/json

Dữ liệu JSON trả về từ Docker API thường rất dài và rối mắt. Bạn có thể dán payload vào toolcraft.app JSON Formatter để định dạng lại cây thư mục mount và thông số network. Cách này trực quan, dễ phát hiện sai lệch cấu hình mà không cần cài thêm extension nặng máy.

Đánh đổi về hiệu năng mạng

Lớp mạng ảo slirp4netns xử lý packet hoàn toàn ở user-space nên sẽ có độ trễ cao hơn một chút so với bridge network truyền thống. Thông lượng (throughput) có thể giảm từ 15% đến 30%, đồng thời tiêu tốn thêm khoảng 5-10% CPU khi tải nặng.

Nếu hệ thống chạy các ứng dụng microservices xử lý hàng chục nghìn request mỗi giây, hãy cân nhắc chuyển sang driver pasta (mặc định trên Docker Engine 24.0+ khi có sẵn package) hoặc sử dụng --net=host khi cần tối đa hiệu năng.

Share: