Docker rootless cho phép chạy cả daemon bằng người dùng thường. Đổi lại, người ta thường nói nó chậm hơn — mạng đi qua không gian người dùng, lưu trữ phải dùng cơ chế khác.

Tôi dựng hai daemon song song để đo: docker:27-dind (rootful) và docker:27-dind-rootless, cùng phiên bản 27.5.1, cùng storage driver overlay2. Trong bản rootless, tiến trình dockerd chạy dưới người dùng rootless thông qua rootlesskit.

Hiệu năng: gần như không chênh

Khởi động container — 20 lần docker run --rm alpine true:

Thời gian Chi phí docker exec (đối chứng)
rootful 236 ms/lần 37 ms
rootless 219 ms/lần 47 ms

Rootless nhanh hơn một chút. Chênh lệch nằm trong dao động giữa các lần chạy — kết luận đúng là "không khác nhau", không phải "rootless thắng".

Build — cùng một Dockerfile không cần mạng (ghi 128 MB, tạo 2.000 tệp, tar, nén 32 MB ngẫu nhiên), --no-cache, dọn sạch cache builder trước mỗi lần:

Ba lần chạy Image ra
rootful 1,9 / 1,9 / 1,9 s 44,5 MB
rootless 1,9 / 1,8 / 1,9 s 44,5 MB

Thông lượng qua cổng được publish — kéo 512 MB qua -p 8099:8099, ba lần:

MB/s
rootful 320 / 328 / 326
rootless 318 / 337 / 339

Đây là phép đo tôi mong thấy chênh lệch nhất, vì rootless chuyển tiếp cổng bằng RootlessKit ở không gian người dùng thay vì bằng iptables của nhân. Kết quả: không đo được chênh lệch nào.

Một lưu ý về phạm vi: cả hai daemon đều chạy lồng trong container trên máy tôi, và bản rootless dùng driver mạng vpnkit. Trên máy chủ Linux thật, rootless thường dùng slirp4netns hoặc pasta, con số có thể khác. Điều tôi đo được là: với đường publish cổng thông thường, rootless không phải nút thắt.

Chỗ thật sự mất mát: cgroup

Cgroup Driver: none
Cgroup Version: 2

Rootless không tự cầm được cgroup. Không có systemd đứng ra uỷ quyền, daemon chạy không có cgroup nào cả. Hậu quả:

docker run --memory 64m --tmpfs /d alpine \
  dd if=/dev/zero of=/d/f bs=1M count=200
OOMKilled Kết quả
rootful true bị giết
rootless false 209715200 bytes copied, 0.085 s — ghi trọn 200 MB

Đọc thẳng giới hạn từ trong container:

/sys/fs/cgroup/memory.max
rootful 67108864 (đúng 64 MB)
rootless max

Và đây mới là phần tệ nhất: docker run không in ra cảnh báo nào. Tôi truyền --memory 64m, lệnh chạy bình thường, không có dòng nào nói rằng giới hạn đã bị vứt bỏ.

Cảnh báo có tồn tại, nhưng nằm trong log của daemon, ghi đúng một lần lúc khởi động:

WARNING: Running in rootless-mode without cgroups.
Systemd is required to enable cgroups in rootless-mode.

Nếu bạn dựng rootless bằng gói systemd chính thức (dockerd-rootless-setuptool.sh) và delegation được bật, cgroup hoạt động. Nếu bạn dựng bằng tay hoặc trong container như tôi, mọi cờ --memory, --cpus, --pids-limit thành lời nói suông.

Đây là kịch bản xấu điển hình: cùng một docker-compose.yml khai mem_limit, chạy trên máy rootful thì giới hạn có hiệu lực, chạy trên máy rootless thì không — và không có gì trong đầu ra cho bạn biết bạn đang ở trường hợp nào. Kiểm tra bằng một lệnh:

docker run --rm --memory 64m alpine cat /sys/fs/cgroup/memory.max

Ra max là giới hạn không hoạt động.

Bên trong container thì không khác gì

Điều này hay bị hiểu nhầm: rootless không làm container bên trong yếu đi.

CapEff của container
rootful 00000000a80425fb
rootless 00000000a80425fb

Giống hệt — vẫn đủ 14 capability như phần 31 đã đếm. ping chạy được, publish cổng 80 được, ở cả hai bên.

Cái rootless bảo vệ là máy chủ, không phải bên trong container. Nếu ai đó thoát ra khỏi container, ở bản rootful họ thành root của máy; ở bản rootless họ thành một người dùng thường không có quyền gì đặc biệt. Đó là toàn bộ giá trị của nó, và nó rất đáng.

Nó cũng khác hẳn với việc chạy tiến trình bằng người dùng không phải root bên trong container (phần 12) — hai lớp độc lập, nên làm cả hai.

Ba lần tôi đo sai trong bài này

Build rootless xong trong 0,2 giây. Nghe như tin vui, thật ra là mkdir /b bị từ chối vì uid 1000 không ghi được vào /, và build thất bại ngay ở bước đọc context. Quy tắc cũ: một con số tốt bất thường là dấu hiệu phép đo hỏng, không phải dấu hiệu thắng lợi. Tôi thêm bước kiểm tra đầu ra phải bắt đầu bằng sha256: mới tính là một lần chạy hợp lệ.

Build lần hai thất bại ở apk add. Lỗi là DNS lookup error. Suýt nữa tôi viết "rootless làm hỏng DNS lúc build" — nhưng đó là hạn chế của việc chạy daemon lồng trong daemon, không phải của rootless. Tôi bỏ mạng ra khỏi Dockerfile đo hiệu năng, và không kết luận gì về DNS.

Đo thông lượng ra 0 mili giây. date +%s%N của busybox không hỗ trợ nano giây, nó trả về chuỗi có chữ N và phép chia thành 0. Chuyển sang bấm giờ ở phía ngoài là xong.

Thử ba mươi giây

Trên máy Linux của bạn, dựng rootless bằng công cụ chính thức:

dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker info --format 'Cgroup Driver: {{.CgroupDriver}}'
docker run --rm --memory 64m alpine cat /sys/fs/cgroup/memory.max

Hai dòng cuối là bài kiểm tra quan trọng nhất. CgroupDriver ra none, hoặc memory.max ra max, nghĩa là mọi giới hạn tài nguyên bạn khai đều đang bị bỏ qua — sửa bằng cách bật cgroup delegation của systemd trước khi đưa vào dùng thật.

Phần sau bắt đầu nhóm bài về mạng, mở đầu bằng việc localhost trong container trỏ đi đâu.