Skip to content

Module 6: Under the Hood (Bên dưới nắp Capo)

🎓 Instructor Profile

Giáo sư Tom (Chuyên gia HDH) & Kỹ sư Raizo. Đây là module Khó nhấtHấp dẫn nhất. Chúng ta sẽ ngừng gõ lệnh Docker và đi sâu vào lõi Linux Kernel để xem bộ máy này thực sự hoạt động như thế nào.

Bạn nghĩ Docker là một công nghệ mới diệu kỳ? Không. Docker thực chất là một "nghệ thuật sắp đặt" (Orchestration Art) của các tính năng đã tồn tại trong Linux từ cả chục năm trước.

Hãy cùng mở nắp capo lên và nhìn vào động cơ V8 bên dưới.


🎭 Phần 1: The Great Illusion (Ảo ảnh vĩ đại)

Sự thật gây sốc

Trong Linux Kernel, KHÔNG CÓ cái gì gọi là "Container". Kernel chỉ biết đến Process (Tiến trình).

Container, về bản chất, chỉ là một Process bình thường (như Firefox, Chrome), nhưng bị Kernel "bịt mắt" (để không nhìn thấy process khác) và "trói tay" (để không dùng quá tài nguyên).

"Docker is just a fancy user interface for Linux Kernel features."


🧱 Phần 2: Namespaces (Những bức tường ngăn cách)

Làm sao để Process A không nhìn thấy Process B, dù chúng chạy trên cùng một máy? Câu trả lời là Namespaces (Không gian tên). Nó tạo ra sự cô lập (Isolation).

6 Loại Namespaces cốt lõi

NamespaceChức năng (Isolation)Ví dụ minh họa
PIDCô lập ID tiến trình.Trong container, process thấy mình là PID 1 (Vua). Nhưng ngoài Host, nó là PID 12345 (Dân thường).
NETCô lập Mạng.Container có eth0 riêng, IP riêng, bảng định tuyến riêng.
MNTCô lập File System.Container nhìn thấy thư mục gốc / là một thế giới hoàn toàn khác với / của Host.
UTSCô lập Hostname.Container có tên máy riêng (VD: a1b2c3d4e5f6) khác với tên máy thật.
IPCCô lập Giao tiếp.Ngăn container dùng Shared Memory để đọc trộm dữ liệu process khác.
USERCô lập User ID.Root trong container (UID 0) có thể bị map thành User thường (UID 1000) ngoài Host.
CGROUPCô lập Cgroup hierarchy.Container có cgroup tree riêng, không thể thấy hoặc can thiệp resource limits của container khác.

💡 Namespace trong thực tế

Bạn có thể xem namespaces của một process bất kỳ:

bash
# Xem namespaces của container process (thay PID thực tế)
ls -la /proc/<PID>/ns/

# Kết quả: cgroup, ipc, mnt, net, pid, user, uts
# Mỗi file là một symlink đến namespace ID

Hai process có cùng namespace ID → chúng chia sẻ namespace đó (cùng nhìn thấy nhau).


👮 Phần 3: Cgroups (Cảnh sát tài nguyên)

Namespaces chỉ giúp "che mắt" (Isolation), nhưng không ngăn được việc dùng chùa tài nguyên. Một process bị cô lập vẫn có thể ăn hết 100% RAM của máy.

Control Groups (Cgroups) sinh ra để giải quyết vấn đề Limitation (Giới hạn).

Chức năng của Cgroups

  1. Resource Limiting: Giới hạn RAM (VD: 512MB), CPU (0.5 core).
  2. Prioritization: Ưu tiên process quan trọng (VIP) được dùng nhiều CPU hơn.
  3. Accounting: Đo lường xem container đã dùng bao nhiêu tài nguyên (để tính tiền Cloud).
  4. Control: Đóng băng (Freeze) hoặc Kill cả nhóm process.

Cgroups v1 vs v2 (Cuộc chuyển giao)

Đặc điểmCgroups v1Cgroups v2
Kiến trúcNhiều hierarchy riêng biệt (cpu, memory, io riêng)Unified hierarchy duy nhất
Quản lýPhức tạp, có thể xung độtĐơn giản, nhất quán
Resource controlMỗi controller một treeTất cả controllers chung tree
Pressure Stall Info❌ Không hỗ trợ✅ PSI metrics (cpu.pressure, memory.pressure)
Thread-level controlHạn chế✅ Hỗ trợ đầy đủ
StatusLegacy (vẫn dùng rộng rãi)Default trên kernel ≥ 5.8, RHEL 9+, Ubuntu 22.04+
bash
# Kiểm tra hệ thống đang dùng cgroups v1 hay v2
stat -fc %T /sys/fs/cgroup/
# "cgroup2fs" → v2 | "tmpfs" → v1

# Xem resource limits của một container (cgroups v2)
cat /sys/fs/cgroup/system.slice/docker-<CONTAINER_ID>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<CONTAINER_ID>.scope/cpu.max

# Xem thực tế container đang dùng bao nhiêu memory
cat /sys/fs/cgroup/system.slice/docker-<CONTAINER_ID>.scope/memory.current

⚠️ Cgroups v1 → v2 Migration

Nếu upgrade OS từ Ubuntu 20.04 → 22.04, cgroups tự động chuyển từ v1 sang v2. Một số container runtimes cũ và monitoring tools có thể không tương thích. Kiểm tra kỹ trước khi upgrade production.

👉 Ví dụ: Cgroups giống như cái cầu chì (Circuit Breaker). Nếu container dùng quá dòng điện (RAM) cho phép -> Cắt (OOM Kill).


🪄 Phần 4: The Storage Magic (Phép thuật lưu trữ)

Tư duy @[/performance]: Tại sao bạn khởi động 100 container Ubuntu (mỗi cái 70MB) mà ổ cứng không bị tốn 100 x 70MB = 7GB? Thực tế chỉ tốn... vài KB.

Union File System (UnionFS) & Overlay2

Docker sử dụng cơ chế Layered Architecture (Kiến trúc phân lớp). Image gồm nhiều lớp Read-only xếp chồng lên nhau.

Copy-on-Write (CoW)

  • Khi đọc: Container đọc xuyên qua các lớp trong suốt.
  • Khi ghi: Container KHÔNG sửa trực tiếp vào Image (vì Read-only).
    1. Nó tìm file cần sửa ở lớp dưới.
    2. Copy file đó lên lớp trên cùng (Write Layer).
    3. Nó sửa (Write) vào bản copy đó.

👉 Kết quả: Nếu bạn không sửa file, bạn không tốn dung lượng đĩa mới. Rất thông minh!

Storage Drivers (Trình điều khiển lưu trữ)

Docker hỗ trợ nhiều storage drivers. Mỗi loại có ưu nhược điểm riêng:

DriverƯu điểmNhược điểmKhuyên dùng
overlay2Hiệu năng cao, stable, ít memoryCần kernel ≥ 4.0Default & Best choice
btrfsSnapshot nhanh, deduplicationPhức tạp, cần btrfs filesystemSpecialized storage
zfsData integrity, compressionRAM-hungry, cần zfs moduleEnterprise storage
devicemapperỔn định trên RHEL cũChậm hơn overlay2, deprecated❌ Legacy only
vfsKhông cần kernel feature đặc biệtKhông CoW → tốn disk gấp N lần❌ Testing only
bash
# Kiểm tra storage driver đang dùng
docker info | grep "Storage Driver"
# Output: Storage Driver: overlay2

# Xem chi tiết layers của một image
docker inspect --format='{{json .GraphDriver.Data}}' nginx | jq

# Xem dung lượng thực tế Docker đang chiếm
docker system df
docker system df -v  # Chi tiết từng image, container, volume

OverlayFS Deep Dive

Cơ chế hoạt động:

  1. Lower Dir: Chứa image layers (read-only). Nhiều container share cùng lower layers.
  2. Upper Dir: Mỗi container có riêng. Files mới hoặc sửa đổi nằm ở đây.
  3. Work Dir: Thư mục tạm cho atomic operations (rename, delete).
  4. Merged View: Kernel merge tất cả layers thành một unified view cho container.

⚙️ Phần 5: The Runtime Hierarchy (Hệ thống phân cấp)

Khi bạn gõ docker run, điều gì thực sự xảy ra? (Kiến trúc hiện đại).

  1. Docker CLI: Gửi lệnh REST API đến Daemon.
  2. Dockerd (Daemon): Tiếp nhận yêu cầu.
  3. Containerd: (High-level Runtime). Quản lý vòng đời container, kéo image từ Registry. Đây là "trái tim" mà Kubernetes cũng sử dụng (K8s đã bỏ Docker để dùng thẳng Containerd).
  4. Runc: (Low-level Runtime / OCI Runtime). Đây là "công nhân" thực sự. Nó gọi trực tiếp vào Kernel (Syscalls) để tạo Namespaces và Cgroups. Sau khi tạo xong container, Runc tự thoát (exit).

Tư duy @[/architect]: Sự tách biệt này cho phép dockerd có thể restart/update mà không làm chết các container đang chạy (Live Restore).

Docker Daemon Architecture (Kiến trúc chi tiết)

Vai trò của containerd-shim:

  • Đóng vai trò "người giám hộ" cho container process
  • Cho phép containerd restart mà container vẫn sống
  • Thu thập exit code khi container kết thúc
  • Quản lý stdio (stdin/stdout/stderr) của container

OCI Specification (Tiêu chuẩn mở)

OCI (Open Container Initiative) định nghĩa 3 tiêu chuẩn:

SpecMục đíchVí dụ
Runtime SpecCách tạo và chạy containerrunc, crun, youki, kata-containers
Image SpecFormat của container imageDocker images, Podman images
Distribution SpecCách push/pull imagesDocker Hub, GitHub Container Registry, Harbor
bash
# Xem OCI runtime config của container
docker inspect --format='{{json .HostConfig}}' my-container | jq

# Export container thành OCI bundle
mkdir my-bundle && cd my-bundle
docker export my-container | tar -xf -

# Xem OCI runtime spec mà runc sử dụng
runc spec  # Tạo config.json mẫu

💡 Tại sao OCI quan trọng?

Nhờ OCI, bạn có thể build image bằng Docker, push lên GitHub Container Registry, và chạy bằng Podman hoặc containerd (không cần Docker). Đó là sức mạnh của open standards.


🥊 The Final Challenge: "No Docker" Challenge

Bài tập tốt nghiệp dành cho True Engineer. Hãy tạo ra một container thô sơ trên Linux mà KHÔNG ĐƯỢC DÙNG lệnh docker.

Gợi ý:

  1. Tải một file hệ thống root (RootFS) của Alpine Linux.
  2. Dùng lệnh unshare để tạo Namespace mới (PID, MNT, UTS).
    bash
    sudo unshare --fork --pid --mount-proc /bin/bash
  3. Dùng lệnh chroot để đổi thư mục gốc vào RootFS đã tải.
  4. ps aux xem PID có phải là 1 không? Gõ hostname xem đổi chưa?

Nếu bạn làm được, bạn đã hiểu bản chất của Containerization. Chúc mừng bạn đã tốt nghiệp Docker Masterclass! 🎉


⚠️ Production Pitfalls (Bẫy thực chiến)

⚠️ Cạm bẫy

Vấn đề: Upgrade OS (VD: Ubuntu 20.04 → 22.04) chuyển từ cgroups v1 sang v2 mà không kiểm tra compatibility.

Hậu quả thực tế: Container runtime (Docker cũ, rkt), monitoring tools (cAdvisor phiên bản cũ), và orchestrators có thể không nhận diện được resource metrics. Containers vẫn chạy nhưng resource limits không hoạt động → memory leak gây OOM kill.

Giải pháp:

  • Kiểm tra trước: stat -fc %T /sys/fs/cgroup/
  • Update Docker Engine lên phiên bản hỗ trợ cgroups v2 (≥ 20.10)
  • Test staging trước khi upgrade production

⚠️ Cạm bẫy

Vấn đề: Sử dụng devicemapper (loop mode) hoặc vfs trên production.

Hậu quả: devicemapper loop mode không stable cho production workloads — disk I/O cực chậm, data corruption risk cao. vfs không có Copy-on-Write → mỗi container chiếm full disk space của image.

Giải pháp: Sử dụng overlay2 (default trên kernel ≥ 4.0). Kiểm tra: docker info | grep "Storage Driver". Nếu thấy devicemapper hoặc vfs, migrate ngay.

⚠️ Cạm bẫy

Vấn đề: Dùng kill <PID> trên host để kill process trong container, nhưng kill nhầm vì PID namespace khác nhau.

Hậu quả: Container PID 1 (app) trên host có thể là PID 54321. Kill PID 1 trên host = kill init system = sập toàn bộ máy.

Giải pháp:

  • Dùng docker stop/kill thay vì kill PID trực tiếp
  • Nếu cần PID host: docker inspect --format='{{.State.Pid}}' <container>
  • Hiểu rõ PID mapping: docker top <container> xem cả PID trong container và trên host

📝 Quiz: Kiểm tra kiến thức

🧠 Quiz

Câu 1: Trong Linux Kernel, "Container" thực chất là gì?

  • [ ] A) Một Virtual Machine nhẹ với kernel riêng
  • [ ] B) Một sandbox chạy trong hypervisor
  • [x] C) Một process bị cô lập bởi namespaces và bị giới hạn bởi cgroups
  • [ ] D) Một file system image được mount vào kernel

💡 Giải thích: Linux kernel không có khái niệm "container". Container chỉ là một process bình thường được "bịt mắt" bằng namespaces (cô lập PID, network, filesystem...) và "trói tay" bằng cgroups (giới hạn CPU, RAM). Docker đóng gói trải nghiệm này thành công cụ dễ dùng.

Câu 2: Khi runc tạo xong container, điều gì xảy ra với runc process?

  • [ ] A) runc tiếp tục chạy và monitor container
  • [ ] B) runc chuyển thành containerd-shim
  • [x] C) runc tự thoát (exit), containerd-shim tiếp quản giám sát container
  • [ ] D) runc bị dockerd kill để tiết kiệm tài nguyên

💡 Giải thích: runc là low-level runtime chỉ làm một việc: gọi syscalls (clone, unshare) để tạo namespaces + cgroups, rồi tự thoát. Sau đó, containerd-shim tiếp quản vai trò giám hộ — thu thập exit code, quản lý stdio, cho phép containerd restart mà không ảnh hưởng container.

Câu 3: Copy-on-Write (CoW) trong OverlayFS giúp tiết kiệm tài nguyên bằng cách nào?

  • [ ] A) Nén tất cả files trong image layers
  • [ ] B) Xóa files không sử dụng tự động
  • [x] C) Chỉ tạo bản copy của file khi container thực sự ghi/sửa file đó
  • [ ] D) Chia sẻ RAM giữa các container cùng image

💡 Giải thích: CoW có nghĩa: 100 container dùng cùng image nginx → tất cả share chung các image layers (read-only). Chỉ khi container nào sửa file (VD: edit nginx.conf), file đó mới được copy lên writable layer riêng của container đó. Nếu không sửa → 0 bytes tốn thêm. Đó là lý do bạn chạy 100 container mà ổ cứng chỉ tốn vài KB extra.


Production Readiness Checklist

✅ Checklist triển khai

Checklist Docker Internals cho Production:

  • [ ] Hiểu 7 loại namespaces và mục đích cô lập của từng loại
  • [ ] Xác nhận hệ thống đang dùng cgroups v2 (stat -fc %T /sys/fs/cgroup/)
  • [ ] Storage driver là overlay2 (docker info | grep "Storage Driver")
  • [ ] Docker Engine version hỗ trợ cgroups v2 (≥ 20.10)
  • [ ] Hiểu runtime hierarchy: CLI → dockerd → containerd → shim → runc
  • [ ] Live Restore đã bật để dockerd restart không kill containers
  • [ ] Monitoring đã thu thập cgroup metrics (memory.current, cpu.stat)
  • [ ] Team hiểu PID namespace mapping khi debug production issues
  • [ ] OCI compatibility đã verify (images portable giữa Docker/Podman/containerd)

💡 Congratulations!

Bạn đã hoàn thành Docker Masterclass — từ nền tảng đến kernel internals. Bạn không chỉ biết dùng Docker, mà còn hiểu tại sao nó hoạt động. Đó là sự khác biệt giữa một Developer và một Engineer.