Docker và container: nhìn từ bên trong
Sê-ri mổ xẻ container thực sự là gì — namespace, cgroup, image layer, mạng — bằng demo chạy thật, không dừng ở lệnh docker run.
12/12 phần đã đăng
DevOps
1
Container không phải máy ảo: nó chỉ là một tiến trình bị cô lập
"Container là VM nhẹ" là hiểu lầm phổ biến nhất về Docker. Thực ra container là một tiến trình thường trên host, được cô lập bằng namespace và giới hạn bằng cgroup — dùng chung kernel host. Bài này chạy thật: alpine và ubuntu cùng thấy một kernel, tự tạo cô lập bằng unshare không cần docker (shell thành PID 1), và đo khởi động 164ms.
22/09/2026
· 5 phút đọc
2
PID namespace: ứng dụng trong container là PID 1 — và vì sao điều đó khiến docker stop chậm
Trong container, ứng dụng của bạn chạy với PID 1 như init của cả một hệ. Kernel bảo vệ PID 1: nó BỎ QUA các tín hiệu không có handler — nên app không xử lý SIGTERM sẽ phớt lờ docker stop. Bài này chạy thật: PID 1 sống sót sau SIGTERM, docker stop mất 3.1s (chờ grace rồi SIGKILL), và --init (tini) kéo xuống 0.1s.
22/09/2026
· 6 phút đọc
3
Mount namespace: vì sao mỗi container có cây thư mục gốc / của riêng nó
Container dùng chung kernel host nhưng thấy một hệ thống file hoàn toàn riêng — / của nó khác / của host. Bài này chạy thật ba tầng cơ chế: alpine thấy rootfs riêng không có thư mục của host, mount namespace cô lập bảng mount (tmpfs chỉ ns đó thấy), và chroot tự dựng đổi / thành một rootfs tối giản. Nền tảng của filesystem container.
22/09/2026
· 5 phút đọc
4
Network namespace: vì sao mỗi container có địa chỉ IP của riêng nó
Container có eth0, IP, bảng định tuyến và cổng của riêng nó — hai container đều mở cổng 80 mà không đụng nhau. Bài này chạy thật: container thấy eth0 172.17.0.5, network namespace mới chỉ có loopback (bị cắt mạng), và tự dựng cặp veth nối hai namespace với ping 0% loss — chính là cách Docker nối container vào bridge của host.
22/09/2026
· 5 phút đọc
5
cgroups: giới hạn CPU và bộ nhớ container — và vì sao vượt RAM thì bị giết, vượt CPU chỉ chậm
namespace cô lập tầm nhìn; cgroup là nửa còn lại — giới hạn tài nguyên. Bài này chạy thật: --cpus 0.25 làm cùng workload chậm đúng 4 lần, giới hạn nằm trong file /sys/fs/cgroup (memory.max, cpu.max), và container vượt --memory 32m bị kernel OOM kill thật (OOMKilled=true, exit 137). Hai kiểu vượt khác nhau: RAM bị giết, CPU chỉ throttle.
22/09/2026
· 5 phút đọc
6
Image layers và overlayfs: vì sao 100 container cùng image không tốn 100 lần đĩa
Image Docker là chồng nhiều layer chỉ-đọc; container chỉ thêm một lớp ghi mỏng chồng lên. Bài này chạy thật: docker history phơi bày từng layer, tự dựng overlayfs để thấy copy-on-write (sửa file thì copy sang upper, lower giữ nguyên), và ba container cùng image mỗi cái chỉ thêm 4.1kB trong khi 67.9MB image dùng chung.
22/09/2026
· 5 phút đọc
7
Dockerfile cache: vì sao đổi một dòng code khiến build cài lại toàn bộ dependencies
Mỗi lệnh Dockerfile là một layer được cache; đổi một layer làm hỏng cache của MỌI layer sau nó. Đặt COPY . . trước khi cài dependencies là lỗi kinh điển: sửa một dòng code buộc build cài lại toàn bộ deps. Bài này đo thật hai Dockerfile cùng app: sau khi sửa code, bản sai mất 3.8s (cài lại deps), bản đúng chỉ 0.6s.
22/09/2026
· 6 phút đọc
8
Build context: vì sao docker build gửi cả thư mục lên daemon, và .dockerignore cứu bạn
Dấu chấm trong docker build . là build context — docker nén CẢ thư mục và gửi lên daemon trước khi build, kể cả file không dùng tới. Bài này đo thật: một thư mục 70MB (node_modules + .git) gửi nguyên context 70.01MB dù Dockerfile chỉ COPY app.py; thêm .dockerignore, context còn 4.096kB — và quan trọng hơn, nó chặn .env/.git lọt vào image.
22/09/2026
· 5 phút đọc
9
Multi-stage build: vì sao image Go của bạn nặng 400MB thay vì 3MB
Để BUILD một chương trình cần trình biên dịch và cả toolchain hàng trăm MB; để CHẠY chỉ cần binary vài MB. Nếu build và chạy cùng một image, image cuối mang theo cả toolchain. Bài này build một chương trình Go thật hai cách: một-stage cho image 407MB, multi-stage (copy binary sang scratch) cho 3.4MB — cùng chạy y hệt, giảm ~120 lần.
22/09/2026
· 5 phút đọc
10
Container networking: gọi nhau bằng tên, và vì sao mạng bridge mặc định không có DNS
Trong một mạng bridge tuỳ chỉnh, container gọi nhau bằng tên (Docker có DNS nội bộ); nhưng mạng bridge MẶC ĐỊNH thì không — gọi bằng tên báo bad address. Bài này chạy thật: container gọi nhau qua tên trên mạng riêng, lỗi trên mạng mặc định, và -p publish cổng để host vào được container (curl trả 200).
22/09/2026
· 5 phút đọc
11
Volume vs bind mount: vì sao dữ liệu ghi trong container biến mất khi xoá
Dữ liệu ghi vào lớp container là tạm — xoá container là mất sạch, kể cả database. Bài này chạy thật: ghi /data.txt rồi xoá container thì file biến mất; named volume giữ dữ liệu qua vòng đời container; và bind mount cho host với container thấy cùng file hai chiều. Cùng cách chọn volume hay bind mount cho từng việc.
22/09/2026
· 6 phút đọc
12
Bảo mật container: vì sao container mặc định chạy root, và ba lớp phòng thủ giảm rủi ro
Mặc định container chạy dưới quyền root — kẻ khai thác được app là có root trong container. Bài này đo thật ba lớp phòng thủ: --user chạy non-root (ghi /etc bị Permission denied), --cap-drop ALL bỏ quyền đặc quyền (root cũng không chown được), và --read-only khoá rootfs (không ghi được binary). Phòng thủ theo lớp để một container bị chiếm không thành thảm hoạ.
22/09/2026
· 6 phút đọc