Podman là gì

Dòng lệnh của Podman rất giống Docker, khác biệt lớn nhất là nó không có daemon thường trú. Docker dựa vào một dockerd chạy thường trú với quyền root để quản lý container, Podman không có vai trò này. Điều này mang lại hai hệ quả trực tiếp: container có thể được tạo và chạy bởi người dùng thông thường (chế độ rootless), và cũng có thể giao cho systemd quản lý như một dịch vụ.

Podman còn có vài lệnh con mà Docker không có, ví dụ pod (nhóm container), kube, quadlet và auto-update, hai lệnh sau sẽ được dùng ở dưới.

Cài đặt trên Ubuntu 26.04

sudo apt install podman

Tôi cài trong container Ubuntu 26.04.1 LTS (arm64) được bản podman 5.7.0. Trong cùng kho phần mềm còn có podman-compose (1.5.0), podman-docker, buildah, skopeo, crun và netavark. Chạy podman info với quyền root, hiển thị Rootless là false, trình điều khiển lưu trữ là overlay, runtime là crun, backend mạng là netavark.

Kiểm tra:

podman --version
podman run --rm docker.io/library/alpine:3.20 echo hello-from-podman

Nếu in ra hello-from-podman là đã chạy được container. Tôi viết tên image đầy đủ là docker.io/library/alpine:3.20, kèm cả tên miền của kho để tránh mơ hồ nhất. Nói rõ về môi trường kiểm tra của tôi: tôi kiểm tra lồng nhau trong một container Docker khác, bị giới hạn bởi cgroup nên phải thêm một tham số nữa mới chạy được, còn bạn trên máy thật thì không cần.

Biết dùng docker là biết dùng podman

podman --help liệt kê các lệnh con gần như bao quát những gì Docker dùng hàng ngày: build, run, create, start, stop, restart, rm, ps, images, rmi, pull, push, logs, exec, inspect, cp, port, stats, top, volume, network, secret v.v. podman run cũng hỗ trợ -p/--publish, -v/--volume, --userns, còn --restart có thể nhận always, no, never, on-failure, unless-stopped.

Có một điểm cần chú ý: podman ps chỉ thấy container do người dùng hiện tại tạo ra. Container do người dùng thông thường tạo thì người dùng khác không thấy được, Podman chạy bằng root cũng không thấy. Khi gặp câu hỏi "container đi đâu rồi", hãy xác nhận trước là mình đã chạy lệnh bằng người dùng nào.

Hai cách chuyển tiếp

Cài podman-docker. Sau khi cài gói này, /usr/bin/docker là một shell script rất ngắn: nó kiểm tra xem /etc/containers/nodocker (hoặc ~/.config/containers/nodocker) có tồn tại không, nếu không có thì in ra standard error một dòng Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg., sau đó exec /usr/bin/podman "$@". Vì vậy docker --version thực chất in ra podman version 5.7.0. Thấy thông báo phiền thì chỉ cần tạo file đó:

sudo apt install podman-docker
sudo touch /etc/containers/nodocker

Nó chỉ chuyển lệnh sang Podman thôi. Mỗi lệnh con docker và tham số mà script của bạn dùng có tương đương trong Podman hay không thì phải tự thử; còn các chương trình phụ thuộc vào API của daemon Docker (ví dụ docker.sock) thì tôi chưa kiểm chứng.

Dùng podman compose. podman compose không tự thực hiện compose, phần trợ giúp ghi rõ nó chạy qua một trình cung cấp bên ngoài, ví dụ docker-compose hoặc podman-compose. Tôi chạy podman compose version trong container, nó in ra đường dẫn của trình cung cấp bên ngoài đang được gọi, rồi in số phiên bản của chính trình đó (Docker Compose version 2.40.3+ds1-0ubuntu1). Vì vậy trước khi dùng, máy phải có sẵn một trình cung cấp compose.

Trước khi rootless hãy cấu hình subuid và subgid

Khi chạy Podman bằng người dùng thông thường, nó sẽ tự động tạo user namespace cho người dùng đó, phạm vi khả dụng được ghi trong /etc/subuid và /etc/subgid. Trang manual yêu cầu: người dùng phải có nhiều đoạn phạm vi UID/GID trong hai file này. Cách thêm theo manual:

sudo usermod --add-subuids 10000-75535 tên_người_dùng
sudo usermod --add-subgids 10000-75535 tên_người_dùng

Vị trí dữ liệu của rootless cũng khác với root: cấu hình đọc từ $XDG_CONFIG_HOME, nếu chưa đặt thì ở ~/.config/containers; image lưu ở $XDG_DATA_HOME, nếu chưa đặt thì ở ~/.local/share/containers/storage; dữ liệu tạm đặt trong ${XDG_RUNTIME_DIR}/containers. Khi truy vấn vấn đề của rootless, hãy xem mấy thư mục này trước.

Dùng Quadlet để container khởi động cùng hệ thống

podman generate systemd trong phần trợ giúp của 5.7.0 đã bị đánh dấu [DEPRECATED]. Cách làm hiện nay là Quadlet: nó là systemd generator của Podman, đọc các file .container, .volume, .network, .build, .pod, .kube, .artifact, và sinh ra một file .service cùng tên cho mỗi file. Các file này được đọc lúc khởi động máy và khi thực thi systemctl daemon-reload, cả systemd cấp hệ thống lẫn cấp người dùng đều hỗ trợ.

Đặt ở đâu. Đường dẫn tìm kiếm cho root theo thứ tự ưu tiên là: /run/containers/systemd/ (tạm thời, để thử nghiệm), /etc/containers/systemd/ (do quản trị viên định nghĩa), /usr/share/containers/systemd/ (do bản phân phối định nghĩa). Người dùng thông thường là $XDG_RUNTIME_DIR/containers/systemd/, ~/.config/containers/systemd/, cùng các thư mục dưới /etc/containers/systemd/users/.

Viết thế nào. Dưới đây là một ví dụ tôi đã thực sự dùng, lưu thành /etc/containers/systemd/web.container:

[Unit]
Description=Demo web server

[Container]
Image=docker.io/library/nginx:1.27-alpine
PublishPort=8080:80
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

Phần [Container] là cú pháp riêng của Quadlet, Image= chỉ định image, PublishPort= tương ứng với --publish của podman run. Các phần [Service], [Install] sẽ được chuyển nguyên vẹn cho systemd, nên Restart=always viết giống như khi viết service systemd thường lệ.

Sinh ra cái gì. Không phải viết tay file service đó, muốn xem kết quả sinh ra thì dùng dry-run, nó chỉ in ra chứ không cài đặt:

/usr/lib/systemd/system-generators/podman-system-generator --dryrun

Với file ở trên, trong web.service mà nó xuất ra, ExecStart là:

/usr/bin/podman run --name systemd-%N --replace --rm --cgroups=split --sdnotify=conmon -d --label io.containers.autoupdate=registry --publish 8080:80 docker.io/library/nginx:1.27-alpine

Ngoài ra còn thêm Type=notify, Delegate=yes, KillMode=mixed, ExecStop là /usr/bin/podman rm -v -f -i systemd-%N, và trong [Unit] thêm After=network-online.target. Từ đó đọc được ba điểm: tên container cố định là systemd-<tên_unit>; container mang --rm, dịch vụ vừa dừng là container bị xóa ngay, không để lại một đống container đã thoát; AutoUpdate=registry biến thành nhãn io.containers.autoupdate=registry, đây chính là công tắc cập nhật tự động.

Kết quả sinh ra này tôi đã kiểm chứng bằng dry-run. Môi trường kiểm tra của tôi không chạy systemd nên chưa từng thực sự khởi động nó bằng systemctl. Quy trình chuẩn là sudo systemctl daemon-reload cộng với sudo systemctl start web.service, nếu container không lên thì dùng journalctl -u web.service xem log. Muốn xem container do unit cấp hệ thống tạo ra, hãy chạy sudo podman ps với quyền root, và đừng quên điều đã nói ở trên: container của các người dùng khác nhau không nhìn thấy nhau.

Ngoài ra còn có nhóm lệnh con podman quadlet gồm install, list, print, rm, dùng để quản lý chính các file này.

Cập nhật tự động

Phần trợ giúp của podman auto-update nói rằng nó xử lý các container do systemd unit chạy, chiến lược cập nhật được chỉ định bằng nhãn io.containers.autoupdate. AutoUpdate=registry ở trên sinh ra chính là nhãn này. Kích thủ công một lần:

sudo podman auto-update

Dùng sudo vì container do unit cấp hệ thống tạo ra thuộc về root. Ý nghĩa đầy đủ của chính sách registry, cũng như cách làm cho nó chạy theo định kỳ, tôi không đi sâu kiểm chứng, hãy xem man podman-auto-update.

Vài câu hỏi thường gặp

  • Lệnh docker mỗi lần chạy lại thêm một dòng thông báo: tạo /etc/containers/nodocker (cho mọi người dùng) hoặc ~/.config/containers/nodocker (cho người dùng hiện tại).
  • podman compose không tìm thấy trình cung cấp: cài trước podman-compose hoặc docker-compose.
  • Container rootless không khởi động được: kiểm tra xem trong /etc/subuid, /etc/subgid người dùng hiện tại có nhiều đoạn phạm vi hay không.
  • Sửa file .container nhưng không có tác dụng: file Quadlet chỉ được đọc lúc khởi động máy và lúc daemon-reload, sau khi sửa hãy chạy sudo systemctl daemon-reload trước, rồi khởi động lại dịch vụ tương ứng.