Zoom-in: Docker Container

docker run, app chạy ngay. Trên máy mình, máy đồng nghiệp, hay server production — cùng kết quả.
graph LR
D(["🐳 docker run"]) -->|"chạy"| C(["📦 Container"])
C -->|"works everywhere"| R(["✅ Kết quả"])
style D fill:#1e293b,stroke:#475569,color:#cbd5e1
style C fill:#1e293b,stroke:#475569,color:#cbd5e1
style R fill:#1e293b,stroke:#475569,color:#cbd5e1
Phóng to dần vào black box đó.
Layer 1 — Vấn đề gốc: "works on my machine"
Trước Docker, môi trường là thứ không thể kiểm soát được. App chạy trên máy dev vì có đúng phiên bản Python, đúng dependency, đúng biến môi trường. Server production thiếu một thứ — app crash.
graph LR
Dev["💻 Dev\nPython 3.11\nlib-v2.3"] -->|"hoạt động"| OK["✅ Works"]
Prod["🖥️ Server\nPython 3.9\nlib-v1.8"] -->|"crash"| Fail["❌ ImportError"]
style Dev fill:#1a3a2a,stroke:#22c55e,color:#86efac
style Prod fill:#3b1a1a,stroke:#ef4444,color:#fca5a5
style OK fill:#1a3a2a,stroke:#22c55e,color:#86efac
style Fail fill:#3b1a1a,stroke:#ef4444,color:#fca5a5
Giải pháp ban đầu là máy ảo (VM) — đóng gói cả hệ điều hành cùng app. Hoạt động, nhưng mỗi VM nặng vài GB và boot mất hàng phút.
Layer 2 — Namespace: cô lập mà không cần VM
Container không phải công nghệ mới — nó dùng tính năng có sẵn trong Linux kernel từ năm 2008: namespace.
Namespace chia kernel thành các "không gian" riêng biệt cho mỗi process:
Không có hệ điều hành riêng — tất cả container dùng chung Linux kernel của host. Đây là lý do container nhẹ hơn VM nhiều lần.
Layer 3 — cgroups: giới hạn tài nguyên
cgroups (control groups) là tính năng Linux kernel để giới hạn và theo dõi tài nguyên của một nhóm process.
graph TD
Host["🖥️ Host (16GB RAM, 8 CPU)"]
Host --> C1["📦 Container A\n≤ 2GB RAM\n≤ 2 CPU"]
Host --> C2["📦 Container B\n≤ 4GB RAM\n≤ 4 CPU"]
Host --> C3["📦 Container C\n≤ 1GB RAM\n≤ 1 CPU"]
style Host fill:#1a3a2a,stroke:#22c55e,color:#86efac
style C1 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style C2 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style C3 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
docker run --memory=2g --cpus=2 translates thành cgroup rules cho Linux kernel. Khi container vượt quá giới hạn RAM, kernel OOM killer kết thúc process trong container — không ảnh hưởng đến container khác.
Layer 4 — Union filesystem: layer image
Một container image không phải file đơn — nó là tập hợp các layer xếp chồng lên nhau.
Layer 4: App code (5MB) ← thay đổi nhiều nhất
Layer 3: pip dependencies (120MB)
Layer 2: Python 3.11 (50MB)
Layer 1: Ubuntu base (29MB) ← ít thay đổi nhất
Khi pull image, Docker chỉ download những layer chưa có. Khi build image mới chỉ thay đổi app code, layer 1-3 được dùng lại từ cache — build nhanh hơn nhiều.
Khi container chạy, Docker thêm một writable layer ở trên cùng. Mọi thay đổi trong container (ghi file, tạo thư mục) xảy ra trong layer này — image bên dưới không thay đổi. Khi container dừng, writable layer bị xóa.
Full picture
Từ docker run đến process chạy trong container.
sequenceDiagram
participant CLI as docker run
participant D as Docker Daemon
participant K as Linux Kernel
CLI->>D: docker run --memory=2g myapp
D->>K: Tạo PID namespace mới
D->>K: Tạo NET namespace + bridge interface
D->>K: Tạo MNT namespace + union filesystem
D->>K: Tạo cgroup (≤ 2GB RAM)
D->>K: fork() + exec() process trong namespace
K-->>CLI: Container ID abc123
Note over K: Process chạy trong "bong bóng" cô lập<br/>nhưng vẫn dùng kernel của host
Container không phải VM — không có hypervisor, không có guest OS. Chỉ là process trên Linux với namespace và cgroup được cấu hình đặc biệt.
Ba nhầm lẫn phổ biến
Container và VM là như nhau, chỉ khác tên
VM chạy hệ điều hành riêng — cô lập hoàn toàn, khởi động mất phút, nặng vài GB. Container dùng chung kernel host — nhẹ hơn, nhanh hơn, nhưng cô lập ít hơn. Một process thoát khỏi container namespace (container escape) có thể ảnh hưởng đến host — không phải vấn đề với VM.
Docker image là immutable tuyệt đối
Image layer bên dưới là immutable, nhưng writable layer khi container chạy thì không. Dữ liệu ghi trong container mất khi container dừng — cần volume hoặc bind mount để giữ data. Nhiều người bắt đầu mất data khi restart container vì nhầm điều này.
Container an toàn theo mặc định
Container chạy với root user bên trong nếu không cấu hình ngược lại. Nếu container bị chiếm quyền, attacker có root trong container — và nếu có volume mount hoặc privileged mode, có thể leo thang lên host. Luôn chạy container với non-root user trong production.
Takeaway
Container tồn tại vì Linux kernel đã có sẵn các công cụ để tạo ra môi trường cô lập mà không cần máy ảo: namespace cho sự cô lập, cgroups cho giới hạn tài nguyên, union filesystem cho layer image tái sử dụng được. Docker chỉ là interface thân thiện đóng gói ba thứ đó lại.
Khi debug container, câu hỏi đúng: "vấn đề nằm ở namespace, cgroup, hay filesystem layer?" — docker inspect, docker stats, và docker exec sẽ cho thấy trạng thái của từng lớp.
Bài viết được hỗ trợ bởi Amy 🌸 - AI Assistant. Nội dung đã được kiểm duyệt bởi tác giả.
Related Posts
Podman: Container Không Cần Daemon, Bảo Mật Hơn Docker?
Podman chạy container không cần daemon root, giảm bề mặt tấn công và tương thích hoàn toàn với Docker CLI. Đã đến lúc chuyển đổi?
Docker v29 Phá Vỡ Backward Compatibility: 3 Thay Đổi Lớn và Cách Migrate An Toàn
Docker Engine v29 chuyển containerd image store thành default, nâng minimum API version lên 1.44, và thêm nftables support. Đây là hướng dẫn migrate cho developer và DevOps.
Docker vs Podman vs containerd 2026: Chọn Runtime Nào Cho Production?
Docker vẫn phổ biến nhất, nhưng Podman 5 rootless và containerd 2.0 đang thay đổi cuộc chơi. So sánh chi tiết từ security, performance đến Kubernetes compatibility.