Skip to content

Zoom-in: Docker Container

Karify98·
Cover Image for 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.

Vấn đề còn lại: cần sự cô lập của VM nhưng nhẹ và nhanh hơn nhiều.

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:

PID Mỗi container có danh sách process riêng — process trong container không thấy process ngoài
NET Mỗi container có network interface riêng, IP riêng — giao tiếp qua bridge network do Docker tạo
MNT Mỗi container thấy filesystem riêng — không thấy file của container khác hay host
UTS Mỗi container có hostname riêng

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.

Vấn đề còn lại: cô lập process rồi, nhưng nếu một container dùng hết CPU hay RAM thì sao?

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.

Vấn đề còn lại: process đã bị cô lập, tài nguyên đã bị giới hạn — nhưng filesystem trong container đến từ đâu?

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