Docker EngineCài xong rồi, nhưng bạn vừa đặt cái gì lên máy mình?
Ba dòng lệnh dán từ một bài blog cũ vẫn chạy được. Cái giá trả sau là ổ đĩa đầy vào một đêm nào đó, và một cái socket có quyền ngang root mà không ai nói cho bạn biết. Tờ này mổ xẻ năm chương trình thật sự đang chạy — bằng năm bản vẽ bạn tự bấm.
Ba dòng lệnh dán từ một bài blog năm 2017
Bạn tìm "cài docker ubuntu", mở kết quả đầu tiên, dán ba dòng vào terminal. Máy chạy một lúc rồi im. Bạn gõ docker run hello-world.
$ docker run hello-world permission denied while trying to connect to the Docker daemon socket
Bạn tìm tiếp, thấy người ta bảo thêm sudo. Thêm vào thì chạy được. Bạn đóng terminal và đi làm việc khác.
Tờ 01 trả lời container là gì. Tờ này trả lời cái gì chạy trên máy bạn để container tồn tại được. Câu thứ hai mới là thứ quyết định bạn gỡ rối được hay chỉ biết dán lệnh.
Vấn đề của ba dòng lệnh kia không phải là chúng sai. Vấn đề là sau khi chạy xong, bạn không biết mình vừa đặt lên máy năm chương trình, một socket có quyền ngang root, hai dịch vụ systemd, và một thư mục sẽ phình dần tới khi hết ổ.
Trước khi gõ lệnh nào, hãy trả lời hai câu hỏi
"Cài Docker" không phải một việc. Nó là ít nhất bốn việc khác nhau, tuỳ vào máy bạn đang ngồi và mục đích dùng. Chọn nhầm nhánh thì mọi hướng dẫn phía sau đều lệch.
Chưa chọn nhánh nào.
Điểm hay bị hiểu nhầm nhất nằm ở nhánh Windows và macOS: Docker Desktop không chạy container trực tiếp trên hệ điều hành của bạn. Nó dựng sẵn một máy ảo Linux nhỏ và chạy Docker Engine bên trong đó. Cái bạn gõ lệnh là client; cái thực sự làm việc nằm trong máy ảo.
| Nhánh | Cái bạn thật sự cài | Container chạy ở đâu |
|---|---|---|
| Windows · WSL 2 | Docker Desktop | Máy ảo Linux do Desktop dựng |
| macOS (Apple Silicon) | Docker Desktop | Máy ảo Linux do Desktop dựng |
| Ubuntu để bàn | Docker Engine hoặc Desktop | Thẳng trên nhân Linux của máy |
| VPS / máy chủ | Chỉ Docker Engine | Thẳng trên nhân Linux của máy |
Cái quầy và những người bạn không nhìn thấy
Docker là bộ phận một cửa của phường
Bạn cần sao y một giấy tờ ở phường. Bạn không đi tìm phòng lưu trữ, không tự mở tủ hồ sơ, không tự đóng dấu. Bạn ra bộ phận một cửa, điền tờ khai, nộp qua ô cửa, lấy số rồi ngồi đợi. Bên trong có người giữ kho hồ sơ, có người ký, có người mang đi đóng dấu. Bạn không gặp ai trong số đó. Bạn chỉ thấy đúng một ô cửa.
| Ở phường | Trên máy bạn |
|---|---|
| Tờ khai bạn điền | Lệnh docker run … gõ ở CLI |
| Ô cửa nhận hồ sơ | Socket /var/run/docker.sock |
| Nhân viên quầy — nhận, kiểm, quyết định | dockerd — máy chủ API của Docker |
| Bộ phận lưu trữ, giữ kho và sổ theo dõi | containerd — giữ image, giữ vòng đời container |
| Người trực ở lại canh từng hồ sơ | containerd-shim-runc-v2 — mỗi container một cái |
| Cán bộ đi làm đúng một việc rồi rút | runc — dựng namespace, cgroup, rồi tự thoát |
Ba chỗ, và cả ba đều tốn tiền nếu hiểu nhầm.
Một — quầy một cửa có kiểm tra bạn là ai. Docker socket thì không. Nó không phân quyền theo hành động: ai chạm được vào socket là làm được mọi thứ, kể cả gắn thư mục gốc của máy chủ vào một container rồi ghi thẳng vào đó. Vào nhóm docker không phải là "root nhẹ hơn" — nó là root, chỉ đi vòng.
Hai — ở phường bạn phải chờ tới lượt, và cái quầy nằm cùng toà nhà với bạn. dockerd phục vụ song song nhiều client, và client không bắt buộc ngồi cùng máy. Đặt biến DOCKER_HOST trỏ sang máy khác là bạn đang điều khiển Docker ở đầu kia đất nước bằng đúng cái CLI đó.
Ba — cán bộ thực địa về là hồ sơ khép lại. runc thì ngược hẳn: nó chết ngay sau khi container khởi động, mà container vẫn sống khoẻ. Thứ ở lại làm cha nuôi cho tiến trình bên trong là containerd-shim. Đây là lý do khởi động lại dockerd không giết container nào — chuyện mà ẩn dụ hành chính không giải thích nổi.
Từ phím Enter tới một tiến trình thật
Năm cái tên trong bảng trên không phải sơ đồ trên giấy. Chúng là năm chương trình đang có mặt trên máy, và bạn nhìn thấy được bằng ps.
Bước 1. CLI không tạo container. Nó dựng lệnh của bạn thành một lời gọi HTTP POST containers create.
$ ps -eo pid,ppid,comm --sort=pid | grep -E 'dockerd|containerd'
Đọc kỹ cột thứ hai: dockerd, containerd và mọi containerd-shim đều có PPID bằng 1. Không cái nào là con của cái nào. Chúng là các tiến trình độc lập nói chuyện với nhau qua socket — nên tắt cái này không kéo theo cái kia.
Cách kiểm nhanh xem một máy đang dùng Docker hay Kubernetes trên cùng containerd: xem tham số -namespace của các tiến trình shim. moby là Docker, k8s.io là Kubernetes.
Hai kho, hai kết quả rất khác nhau
Trên Ubuntu có đúng hai đường cài phổ biến, và chúng cho ra hai bộ công cụ khác nhau. Đây là chỗ nhiều người vấp mà không biết mình vấp.
Hàng 4: docker buildx. Kho Ubuntu: thường không có. Kho chính thức: có, gói riêng. Cùng số phiên bản engine, khác bộ đồ nghề. Lỗi chỉ lộ ra lúc build theo một hướng dẫn hiện đại.
Khác biệt tốn thời gian nhất là bộ plugin. Bản đóng gói theo bản phân phối thường thiếu buildx — thứ mà mọi hướng dẫn build image hiện nay đều giả định là có sẵn.
$ docker --version Docker version 29.1.3, build 29.1.3-0ubuntu3~24.04.2 $ docker buildx version docker: unknown command: docker buildx $ docker info | sed -n '/Plugins:/,/Server:/p' Plugins: compose: Docker Compose (Docker Inc.) Version: 2.40.3+ds1-0ubuntu1~24.04.1 Path: /usr/libexec/docker/cli-plugins/docker-compose trust: Manage trust on Docker images (Docker Inc.) Version: 29.1.3
Hãy chọn kho chính thức của Docker. Không phải vì bản phân phối làm ẩu, mà vì bạn muốn tự quyết thời điểm nâng cấp engine, và muốn bộ plugin khớp với tài liệu bạn đang đọc.
Cài Docker Engine trên Ubuntu 24.04
Bốn bước. Chạy từng bước một, đọc kết quả từng bước, đừng dán cả khối.
$ for p in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do > sudo apt-get remove -y $p > done
Bước này không xoá dữ liệu trong /var/lib/docker. Nó chỉ gỡ các gói cũ để chúng không tranh chấp tên lệnh với bản sắp cài.
$ sudo apt-get update $ sudo apt-get install -y ca-certificates curl $ sudo install -m 0755 -d /etc/apt/keyrings $ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc $ sudo chmod a+r /etc/apt/keyrings/docker.asc
Hướng dẫn nào còn dùng apt-key add là hướng dẫn đã hỏng. apt-key bị khai tử vì nó đưa khoá vào kho tin cậy toàn cục — khoá của Docker khi đó ký được cho mọi kho khác trên máy. Cách đúng là đặt khoá vào /etc/apt/keyrings/ rồi trỏ signed-by= cho riêng kho đó.
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null $ sudo apt-get update $ sudo apt-get install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin
$ sudo usermod -aG docker $USER $ newgrp docker $ id uid=1000(doanhcd) gid=1000(doanhcd) groups=1000(doanhcd),27(sudo),100(users),112(docker)
Không có newgrp docker (hoặc đăng xuất rồi vào lại) thì phiên terminal hiện tại vẫn giữ danh sách nhóm cũ, và bạn vẫn nhận permission denied dù lệnh usermod đã chạy đúng. Đây là lý do thật đằng sau chín mươi phần trăm câu hỏi "em làm đúng hết mà vẫn lỗi".
Hai việc phải làm ngay, trước khi chạy container đầu tiên
Việc thứ nhất là giới hạn log. Mặc định Docker ghi log container ra file JSON không giới hạn dung lượng. Một dịch vụ nói nhiều chạy vài tháng là ăn hết ổ, và triệu chứng lúc đó trông không giống lỗi log chút nào.
$ sudo tee /etc/docker/daemon.json >/dev/null <<'JSON' { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "live-restore": true } JSON $ sudo systemctl restart docker $ docker info --format '{{.LoggingDriver}} · live-restore={{.LiveRestoreEnabled}}' json-file · live-restore=true
Mốc 1 trên 10: systemd. Nhân Linux xong việc, systemd nhận quyền điều phối. Chưa có gì của Docker ở đây.
Việc thứ hai là hiểu cái mình vừa nhận khi vào nhóm docker. Đây không phải chuyện lý thuyết bảo mật — nó là đường leo quyền ngắn nhất trên một máy Linux có cài Docker.
Chưa chọn nút nào.
$ ls -l /var/run/docker.sock srw-rw---- 1 root docker 0 Aug 21 14:53 /var/run/docker.sock $ getent group docker docker:x:112:doanhcd
Chủ sở hữu là root, nhóm là docker, và nhóm có quyền đọc ghi. Ai trong nhóm docker cũng gửi được lệnh cho một tiến trình đang chạy bằng root. Không có bước xác thực nào ở giữa.
Đừng thêm người khác vào nhóm docker như một cách "cho tiện". Trên máy nhiều người dùng hoặc máy chủ CI, hãy dùng chế độ rootless (dockerd-rootless-setuptool.sh install, gói docker-ce-rootless-extras).
Bốn lệnh kiểm chứng, và cách đọc kết quả
Cài xong không có nghĩa là chạy đúng. Bốn lệnh dưới đây kiểm bốn thứ khác nhau, theo đúng thứ tự từ nông tới sâu.
$ docker version Client: Version: 29.1.3 API version: 1.52 Go version: go1.24.4 OS/Arch: linux/amd64 Context: default Server: Engine: Version: 29.1.3 API version: 1.52 (minimum version 1.44) OS/Arch: linux/amd64 containerd: Version: 2.2.1 runc: Version: 1.3.4-0ubuntu1~24.04.1 docker-init: Version: 0.19.0
Hai khối Client và Server chính là hai đầu của cái quầy trong phần ẩn dụ. Khối containerd và runc xác nhận đúng chuỗi bàn giao ở phần 04 — đây cũng là bằng chứng Docker hiện đại không tự chạy container, nó uỷ quyền xuống dưới.
$ docker info | sed -n '/^Server:/,/Kernel/p' | head -18 Server: Containers: 6 Running: 6 Images: 153 Server Version: 29.1.3 Storage Driver: overlayfs driver-type: io.containerd.snapshotter.v1 Logging Driver: json-file Cgroup Driver: systemd Cgroup Version: 2
$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 4f55086f7dd0: Pull complete Digest: sha256:5dd0d3e6e255913fc30f90b9f2b1d359cc2cbdb48090cc4b65f1676e203243cc Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 3. The Docker daemon created a new container from that image… 4. The Docker daemon streamed that output to the Docker client…
Bốn bước tiếng Anh mà hello-world in ra chính là chuỗi bàn giao bạn vừa xem, viết bằng lời. Điểm đáng chú ý ở dòng cuối: output đi ngược từ container qua daemon rồi mới ra terminal của bạn — bạn không nói chuyện trực tiếp với container bao giờ cả.
$ docker compose version Docker Compose version 2.40.3+ds1-0ubuntu1~24.04.1
Viết liền docker compose (dấu cách) là v2, chạy như một plugin của CLI. Viết docker-compose (dấu gạch nối) là v1 — bản Python đã ngừng hỗ trợ.
Từ Compose v2, file compose.yml không còn khoá version:. Khoá đó là di sản của Compose Spec cũ; giữ lại chỉ tạo cảnh báo. Thấy version: "3.8" ở đầu file nghĩa là tài liệu đó viết trước 2023.
Năm sai lầm tốn thời gian nhất khi cài
Dán curl … | sudo bash cho máy chủ thật
Script tiện lợi ấy được chính Docker khuyến nghị chỉ dùng để thử nhanh, không dùng cho môi trường chạy thật. Nó tự chọn kho, tự chọn phiên bản, và bạn giao quyền root cho một nội dung tải về mà chưa đọc.
Cách sửaDùng bốn bước ở phần 06. Mất thêm hai phút, đổi lại bạn biết chính xác kho nào, khoá nào, gói nào.
Cài từ kho bản phân phối rồi ngạc nhiên vì thiếu công cụ
apt install docker.io cho ra engine chạy được nhưng bộ plugin không đầy đủ — thường thiếu buildx. Lỗi chỉ lộ ra lúc bạn build image theo một hướng dẫn hiện đại.
Cách sửaTrên máy để học thì chấp nhận được; trên máy làm việc và máy chủ thì dùng kho chính thức để bộ đồ nghề khớp với tài liệu.
Coi nhóm docker là "sudo nhẹ"
Vào nhóm docker là có quyền tương đương root, chỉ đi bằng đường khác. Bất kỳ ai trong nhóm đều gắn được thư mục gốc của máy chủ vào container rồi ghi lên đó.
Cách sửaChỉ thêm tài khoản quản trị. Máy nhiều người dùng hoặc runner CI thì chuyển sang rootless.
Quên nạp lại danh sách nhóm
Chạy usermod -aG docker $USER xong thử ngay trong terminal đang mở, vẫn permission denied, rồi kết luận là lệnh không ăn.
Cách sửanewgrp docker, hoặc đăng xuất rồi đăng nhập lại. Kiểm bằng id phải thấy docker trong danh sách nhóm.
Bỏ qua daemon.json và trả giá bằng ổ đĩa
Không giới hạn log thì /var/lib/docker/containers/*/*-json.log lớn dần không có trần. Máy hết chỗ vào một đêm nào đó, và triệu chứng đầu tiên thường là dịch vụ khác chết chứ không phải Docker.
Cách sửaĐặt max-size và max-file ngay lúc cài, kèm live-restore: true. Bảy dòng, một lần, dùng mãi.
Kiểm tra hiểu bài
Năm câu, không giới hạn thời gian. Mỗi câu giải thích cả đáp án đúng lẫn vì sao các đáp án kia sai — đó mới là chỗ đổi được cách nghĩ.
sudo systemctl restart docker trên máy có live-restore: true. Các container đang chạy vẫn phục vụ bình thường. Vì sao?Chọn 1docker.Chọn nhiềucompose.yml dùng với Docker Compose v2 cần khai khoá version: ở đầu file thì Compose mới hiểu đúng cú pháp.Đúng / Saidocker version, bạn chỉ thấy khối Client: rồi một dòng lỗi kết nối, không có khối Server:. Điều này cho biết gì?Chọn 1apt install docker.io từ kho của bản phân phối, điều nào sau đây có khả năng khác so với máy cài từ kho chính thức của Docker?Chọn nhiềuNăm điều mang về
"Docker" là năm chương trình, không phải một. CLI gửi yêu cầu, dockerd nhận, containerd giữ kho và sổ, shim canh từng container, runc dựng rồi rút.
Trên Windows và macOS luôn có một máy ảo Linux ở dưới. Docker Desktop dựng nó cho bạn. Container Linux cần nhân Linux, không có ngoại lệ.
Kho cài quyết định bộ đồ nghề bạn có. Cùng phiên bản engine, khác plugin — và bạn chỉ biết lúc thiếu.
Nhóm docker là quyền root. Chấp nhận có ý thức, hoặc chuyển sang rootless. Đừng chấp nhận vì không biết.
Giới hạn log ngay lúc cài. Đây là việc rẻ nhất trong tờ này và là việc đắt nhất nếu bỏ qua.