Skip to content

Virtualization vs Containerization — Tại Sao Container Thắng Foundation

Bạn vừa gia nhập đội DevOps của một công ty e-commerce đang vận hành 20 máy ảo (VM) trên AWS. Mỗi tháng, hóa đơn EC2 lên tới $18,000. CTO giao bạn một nhiệm vụ: "Giảm chi phí infrastructure xuống 50% mà không ảnh hưởng hiệu năng." Câu trả lời nằm ở containerization — và bài này sẽ giải thích tại sao.

Nguyên tắc xuyên suốt: Container không phải "VM nhẹ hơn" — nó là một mô hình hoàn toàn khác để cô lập và chạy ứng dụng. Hiểu sự khác biệt cốt lõi này là nền tảng cho toàn bộ hành trình Docker.


🎯 Mục tiêu

  • Hiểu mô hình kiến trúc của VM (hardware → hypervisor → guest OS → app) và Container (hardware → host OS → runtime → app)
  • Giải thích được tại sao VM nặng (duplicate kernel, duplicate OS, boot chậm) và tại sao container nhẹ (shared kernel, process isolation, khởi động < 1 giây)
  • Nắm trực giác về namespaces ("mỗi container nghĩ nó là cả thế giới") và cgroups ("quota tài nguyên")
  • Thực hành chạy container đầu tiên với docker run và khám phá bên trong
  • Nhận biết anti-pattern phổ biến: đối xử container như VM

1. Bức Tranh Toàn Cảnh — VM vs Container

🏗️ Kiến Trúc Virtual Machine (VM)

Trước container, chúng ta chạy ứng dụng trên máy ảo. Mỗi VM là một máy tính hoàn chỉnh được mô phỏng bằng phần mềm — có BIOS, kernel, OS, và tất cả system services riêng.

┌──────────────────────────────────────────────────────────┐
│                    VIRTUAL MACHINES                       │
│                                                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │   App A      │  │   App B      │  │   App C      │    │
│  │  (Node.js)   │  │  (Python)    │  │   (Java)     │    │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤      │
│  │  Libs/Deps   │  │  Libs/Deps   │  │  Libs/Deps   │    │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤      │
│  │  Guest OS    │  │  Guest OS    │  │  Guest OS    │    │
│  │ (Ubuntu 22)  │  │ (CentOS 9)  │  │ (Debian 12)  │    │
│  │  ~2-10 GB    │  │  ~2-10 GB   │  │  ~2-10 GB    │    │
│  └─────────────┘  └─────────────┘  └─────────────┘      │
│  ┌──────────────────────────────────────────────────┐    │
│  │           Hypervisor (VMware / KVM / Hyper-V)     │    │
│  └──────────────────────────────────────────────────┘    │
│  ┌──────────────────────────────────────────────────┐    │
│  │                  Host OS (Linux)                   │    │
│  └──────────────────────────────────────────────────┘    │
│  ┌──────────────────────────────────────────────────┐    │
│  │              Physical Hardware (Server)            │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘

Phân tích chi phí mỗi VM:

  • Guest OS chiếm 2-10 GB disk mỗi VM → 3 VM = 6-30 GB chỉ cho OS
  • RAM riêng cho mỗi kernel + system services → 512 MB - 2 GB overhead/VM
  • Boot time 30-60 giây — phải khởi động cả một hệ điều hành
  • CPU overhead ~15% cho hypervisor translation

📦 Kiến Trúc Container

Container dùng một cách tiếp cận hoàn toàn khác: chia sẻ kernel của host OS và chỉ đóng gói những gì ứng dụng thực sự cần.

┌──────────────────────────────────────────────────────────┐
│                      CONTAINERS                           │
│                                                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │   App A      │  │   App B      │  │   App C      │    │
│  │  (Node.js)   │  │  (Python)    │  │   (Java)     │    │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤      │
│  │  Libs/Deps   │  │  Libs/Deps   │  │  Libs/Deps   │    │
│  │   ~50 MB     │  │   ~80 MB     │  │  ~150 MB     │    │
│  └─────────────┘  └─────────────┘  └─────────────┘      │
│          │                │                │              │
│          └────────────────┼────────────────┘              │
│                           │                               │
│  ┌──────────────────────────────────────────────────┐    │
│  │        Container Runtime (Docker Engine)           │    │
│  └──────────────────────────────────────────────────┘    │
│  ┌──────────────────────────────────────────────────┐    │
│  │          Host OS — MỘT Kernel Duy Nhất            │    │
│  │              (Linux Kernel 6.x)                    │    │
│  └──────────────────────────────────────────────────┘    │
│  ┌──────────────────────────────────────────────────┐    │
│  │              Physical Hardware (Server)            │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘

Lợi ích ngay lập tức:

  • Không có Guest OS → container chỉ 50-200 MB thay vì 2-10 GB
  • Chia sẻ kernel → không có overhead cho kernel riêng, RAM hiệu quả hơn
  • Khởi động < 1 giây → chỉ cần start process, không boot cả OS
  • CPU overhead ~2% → gần như native performance

📊 So Sánh Trực Quan — VM vs Container

Tiêu chíVirtual MachineContainer
Kích thước2-10 GB (cả OS)50-500 MB (chỉ app + deps)
Thời gian khởi động30-60 giây< 1 giây
RAM overhead512 MB - 2 GB/VM5-50 MB/container
CPU overhead~15% (hypervisor)~2% (gần native)
Mức độ cô lậpRất cao (kernel riêng)Tốt (shared kernel, namespace isolation)
Mật độ trên server10-20 VM/server100-1000+ container/server
PortableFile VMDK/OVA nặng GBImage layer, push/pull nhanh

💰 Case Study — E-commerce Migration

Quay lại bài toán đầu bài: công ty e-commerce với 20 VM trên AWS.

TRƯỚC (20 VMs):
┌─────────────────────────────────────────────┐
│  20 × EC2 t3.large ($60/tháng mỗi cái)     │
│  = $1,200/tháng compute                     │
│  + 20 × 50 GB EBS = 1 TB storage            │
│  + OS licensing, patching 20 OS riêng biệt  │
│  + Thời gian deploy: 5-10 phút/service       │
│  TỔNG: ~$18,000/tháng (bao gồm ops cost)    │
└─────────────────────────────────────────────┘

SAU (Containers trên ECS/EKS):
┌─────────────────────────────────────────────┐
│  3 × EC2 t3.xlarge ($120/tháng mỗi cái)    │
│  = $360/tháng compute                       │
│  Chạy 60+ container trên 3 node             │
│  + Shared storage, layered images            │
│  + Một OS để patch, auto-scaling nhanh       │
│  + Thời gian deploy: 10-30 giây/service      │
│  TỔNG: ~$7,500/tháng (giảm ~58%)            │
└─────────────────────────────────────────────┘

Kết quả thực tế:

  • 💰 Tiết kiệm ~58% chi phí infrastructure hàng tháng
  • Deploy nhanh hơn 20-30x (giây thay vì phút)
  • 🔄 Auto-scaling phản ứng trong giây thay vì phút
  • 🛡️ Ít OS cần patch hơn → giảm attack surface

2. Namespaces — "Mỗi Container Nghĩ Nó Là Cả Thế Giới"

Bạn có thể đặt câu hỏi: nếu container chia sẻ kernel với host, làm sao chúng không "thấy" nhau?

Câu trả lời là Linux namespaces — một tính năng của kernel cho phép tạo ra các "thế giới ảo" riêng biệt cho mỗi process group.

🌍 Mental Model — Căn Hộ Trong Chung Cư

Hãy tưởng tượng một chung cư (host server):

CHUNG CƯ (Host Server — Một Linux Kernel)
┌─────────────────────────────────────────────────┐
│                                                  │
│  ┌──────────────┐  ┌──────────────┐             │
│  │  Căn hộ A     │  │  Căn hộ B     │             │
│  │ (Container 1) │  │ (Container 2) │             │
│  │               │  │               │             │
│  │ • Địa chỉ     │  │ • Địa chỉ     │             │
│  │   riêng (IP)  │  │   riêng (IP)  │  ← Network  │
│  │ • Phòng riêng │  │ • Phòng riêng │    Namespace │
│  │   (filesystem)│  │   (filesystem)│  ← Mount    │
│  │ • Số nhà riêng│  │ • Số nhà riêng│    Namespace │
│  │   (hostname)  │  │   (hostname)  │  ← UTS      │
│  │ • Cư dân riêng│  │ • Cư dân riêng│    Namespace │
│  │   (processes) │  │   (processes) │  ← PID      │
│  │               │  │               │    Namespace │
│  └──────────────┘  └──────────────┘             │
│                                                  │
│  ┌──────────────────────────────────────────┐   │
│  │  Hạ tầng chung: điện, nước, internet      │   │
│  │  (Shared Linux Kernel)                     │   │
│  └──────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

Mỗi căn hộ (container) có không gian riêng biệt — người ở căn A không thể nhìn thấy bên trong căn B. Nhưng tất cả chia sẻ hạ tầng chung (kernel) của tòa nhà.

🔍 6 Loại Namespace Quan Trọng

NamespaceCô lập cái gìVí dụ trực giác
PIDProcess IDsContainer thấy PID 1 là app của nó, không thấy process của host
NETNetwork stackContainer có IP, port, routing table riêng
MNTFilesystem mountsContainer có / filesystem riêng, không thấy host filesystem
UTSHostnameContainer có hostname riêng (web-server-1), không phải hostname host
IPCInter-process communicationShared memory giữa các process trong container, cô lập với ngoài
USERUser/Group IDsRoot trong container ≠ root trên host (security quan trọng!)

🎭 PID Namespace — Ảo Thuật Số Tiến Trình

Đây là namespace ấn tượng nhất để demo. Bên trong container, mọi thứ bắt đầu từ PID 1:

HOST nhìn thấy:                  CONTAINER nhìn thấy:
┌──────────────────┐             ┌──────────────────┐
│ PID 1: systemd   │             │ PID 1: nginx     │ ← "Tôi là process
│ PID 234: sshd    │             │ PID 2: worker    │    đầu tiên!"
│ PID 567: dockerd │             │ PID 3: worker    │
│ PID 890: nginx ──┼─ thực ra ──┤                   │
│ PID 891: worker  │  cùng      │ Không thấy gì    │
│ PID 892: worker  │  process   │ khác ngoài 3     │
│ ...100+ process  │             │ process này       │
└──────────────────┘             └──────────────────┘

Container nghĩ nginx là PID 1 — process đầu tiên và duy nhất quan trọng. Trên host, nginx thực ra là PID 890. Namespace tạo ra ảo giác hoàn hảo.

3. Cgroups — "Quota Tài Nguyên"

Nếu namespaces trả lời câu hỏi "container thấy gì?", thì cgroups (control groups) trả lời câu hỏi "container được dùng bao nhiêu?".

🎛️ Mental Model — Quota Điện Nước Trong Chung Cư

Quay lại ví dụ chung cư: mỗi căn hộ có công tơ điệnquota nước riêng.

CGROUPS = QUOTA TÀI NGUYÊN
┌──────────────────────────────────────────────────┐
│                                                   │
│  Container A (Web API)        Container B (DB)    │
│  ┌─────────────────┐        ┌─────────────────┐  │
│  │ CPU: max 2 cores │        │ CPU: max 4 cores │  │
│  │ RAM: max 512 MB  │        │ RAM: max 2 GB    │  │
│  │ Disk I/O: 100 MB/s│       │ Disk I/O: 500 MB/s│ │
│  │ Network: 100 Mbps│        │ Network: 1 Gbps  │  │
│  └─────────────────┘        └─────────────────┘  │
│                                                   │
│  Nếu Container A dùng hết 512 MB RAM:            │
│  → OOM Killer giết container A                    │
│  → Container B KHÔNG bị ảnh hưởng                 │
│  → Hệ thống host vẫn ổn định                     │
│                                                   │
└──────────────────────────────────────────────────┘

📋 Các Tài Nguyên Cgroups Quản Lý

Tài nguyênCgroup controllerVí dụ giới hạnDocker flag
CPUcpu, cpusetMax 2 cores, 50% CPU time--cpus=2
MemorymemoryMax 512 MB, bao gồm cả swap--memory=512m
Disk I/OblkioGiới hạn read/write bandwidth--device-read-bps
Networknet_cls, net_prioƯu tiên traffic class(thường qua plugin)
PIDspidsMax số process trong container--pids-limit=100

🛡️ Tại Sao Cgroups Quan Trọng Trong Production?

Không có cgroups, một container "nổi loạn" có thể ăn hết RAM của host → crash toàn bộ server → tất cả container khác cũng chết. Cgroups đảm bảo fault isolation:

bash
# Container bị giới hạn 256 MB RAM
docker run --memory=256m --name hungry-app my-app

# Nếu app cố dùng 512 MB → OOM kill CHỈ container này
# Host vẫn bình thường, các container khác không bị ảnh hưởng

4. Namespaces + Cgroups = Container

Bây giờ bạn hiểu hai mảnh ghép quan trọng nhất:

CONTAINER = NAMESPACES + CGROUPS + CHROOT (đơn giản hóa)

┌────────────────────────────────────────────────┐
│                                                 │
│   Namespaces (CÔ LẬP)    Cgroups (GIỚI HẠN)  │
│   ┌──────────────────┐   ┌──────────────────┐  │
│   │ "Tôi thấy gì?"   │   │ "Tôi dùng bao    │  │
│   │                   │   │  nhiêu?"          │  │
│   │ • PID riêng       │   │ • CPU: 2 cores    │  │
│   │ • Network riêng   │   │ • RAM: 512 MB     │  │
│   │ • Filesystem riêng│   │ • Disk: 100 MB/s  │  │
│   │ • Hostname riêng  │   │ • PIDs: max 100   │  │
│   └──────────────────┘   └──────────────────┘  │
│                                                 │
│   → Kết hợp lại = MỘT CONTAINER                │
│   → Tất cả chạy trên CÙNG MỘT KERNEL          │
│                                                 │
└────────────────────────────────────────────────┘

Container không phải phép thuật — nó là sự kết hợp thông minh của các tính năng đã có sẵn trong Linux kernel từ lâu. Docker chỉ là công cụ giúp đóng gói và sử dụng các tính năng này một cách dễ dàng.

5. 🔬 Lab — Chạy Container Đầu Tiên Và Khám Phá

Giờ là lúc tự tay chứng minh những gì đã học. Bạn sẽ chạy một container Alpine Linux siêu nhẹ (~5 MB) và khám phá cách namespace hoạt động.

Bước 1: Chạy container và vào shell

bash
# --rm: tự xóa container khi thoát
# -it: interactive + tty (cho bạn shell)
# alpine: image siêu nhẹ (~5 MB)
# sh: chạy shell bên trong container
docker run --rm -it alpine sh

Bạn sẽ thấy prompt thay đổi — bạn đang "bên trong" container.

Bước 2: Kiểm tra PID namespace

bash
# Bên trong container:
ps aux

Kết quả mong đợi:

PID   USER     TIME  COMMAND
    1 root      0:00 sh          ← PID 1 là shell của bạn!
    7 root      0:00 ps aux      ← Chỉ 2 process

So sánh: trên host, chạy ps aux sẽ thấy hàng trăm process. Container chỉ thấy đúng process của nó — đó là PID namespace đang hoạt động.

Bước 3: Kiểm tra hostname (UTS namespace)

bash
# Bên trong container:
hostname
# Kết quả: một chuỗi hex ngẫu nhiên, ví dụ "a3f8d2e1b4c9"
# KHÔNG PHẢI hostname của máy host

Bước 4: Khám phá filesystem (Mount namespace)

bash
# Bên trong container:
ls /
# Kết quả: bin dev etc home lib media mnt opt proc root run ...
# Đây là filesystem CỦA CONTAINER — không phải host

# Kiểm tra distro
cat /etc/os-release
# NAME="Alpine Linux"
# Dù host bạn chạy Ubuntu, Fedora, hay Windows + WSL

Bước 5: Kiểm tra /proc (process information)

bash
# Xem thông tin process namespace
ls /proc/
# Chỉ thấy thư mục cho PID 1 và PID hiện tại

# Xem cgroups giới hạn
cat /proc/1/cgroup
# Thấy các cgroup controllers đang áp dụng

# Xem memory limit (nếu có)
cat /proc/meminfo | head -5

Bước 6: Kiểm tra network namespace

bash
# Bên trong container:
ip addr
# Kết quả: eth0 với IP riêng (172.17.0.x)
# Hoàn toàn tách biệt với network của host

# Thử ping ra ngoài
ping -c 2 google.com
# Hoạt động! Container có network nhưng bị isolate

Bước 7: Thoát container

bash
exit
# Container bị xóa ngay (nhờ flag --rm)
# Chạy "docker ps -a" trên host → không còn container nào

💡 Mẹo Lab

Mở 2 terminal song song. Terminal 1 chạy container. Terminal 2 chạy trên host:

bash
# Terminal 2 (host) — tìm process container trên host
docker top <container_id>
# Bạn sẽ thấy PID thật trên host ≠ PID 1 trong container

Đây chính là bằng chứng trực tiếp của PID namespace.

6. Fast Exercise — Kiểm Tra Hiểu Biết

📝 Câu hỏi: Tại sao docker run nhanh hơn khởi động VM?

Chọn câu trả lời đúng nhất:

  • [ ] A. Docker sử dụng phần cứng mạnh hơn VMware
  • [ ] B. Docker nén ứng dụng nhỏ hơn nên load nhanh hơn
  • [x] C. Container chỉ cần start một process trên kernel có sẵn, trong khi VM phải boot cả một hệ điều hành (BIOS → bootloader → kernel → init system → services)
  • [ ] D. Docker bỏ qua bước security check nên nhanh hơn
💡 Giải thích chi tiết

Đáp án C — Khi bạn chạy docker run alpine sh:

  1. Docker Engine nói với Linux kernel: "Tạo namespace mới, áp cgroups, và chạy process sh"
  2. Kernel tạo namespace + cgroups trong microseconds
  3. Process sh bắt đầu chạy → xong!

Tổng thời gian: < 1 giây

Khi bạn khởi động VM:

  1. Hypervisor allocate RAM, CPU ảo → vài giây
  2. Virtual BIOS POST → vài giây
  3. Bootloader (GRUB) load kernel → vài giây
  4. Linux kernel khởi tạo hardware ảo → 10+ giây
  5. Init system (systemd) start services → 10-20 giây
  6. App bắt đầu chạy → cuối cùng!

Tổng thời gian: 30-60 giây

Container không boot OS — nó chỉ fork một process trên OS đang chạy sẵn.

📝 Câu hỏi 2: Container A chạy Ubuntu, Container B chạy Alpine, cả hai trên host CentOS. Có bao nhiêu kernel?

  • [ ] A. 3 kernel (Ubuntu + Alpine + CentOS)
  • [x] B. 1 kernel (CentOS kernel của host)
  • [ ] C. 2 kernel (Ubuntu + Alpine, CentOS chỉ là host)
  • [ ] D. Tùy thuộc vào cấu hình Docker
💡 Giải thích chi tiết

Đáp án B — Chỉ có 1 kernel duy nhất: kernel CentOS của host.

Container Ubuntu và Alpine chỉ chứa userspace tools (apt, apk, libraries) — KHÔNG chứa kernel. Khi container gọi system call, nó gọi trực tiếp kernel CentOS của host.

Đây là sự khác biệt cốt lõi với VM: VM có kernel riêng, container thì không.

7. Gotchas & Anti-patterns

⚠️ Cạm bẫy

Sai lầm phổ biến nhất của người chuyển từ VM sang container:

bash
# ❌ SAI — Đang nghĩ container là VM
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    openssh-server \
    systemd \
    cron \
    supervisor
# Cài SSH để "vào container debug"
# Cài systemd để "quản lý services"
# Cài cron để "chạy scheduled tasks"

EXPOSE 22
CMD ["/usr/bin/supervisord"]

Tại sao sai:

  • Container chạy MỘT process chính (PID 1). Đó là design, không phải hạn chế
  • SSH → dùng docker exec thay thế, zero-config, tự động có
  • Systemd → container không cần init system, Docker quản lý lifecycle
  • Cron → dùng cron trên host hoặc Kubernetes CronJob
  • Supervisor → nếu cần nhiều process, tách thành nhiều container
bash
# ✅ ĐÚNG — Container chạy đúng 1 việc
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Quy tắc vàng: Một container = Một process = Một trách nhiệm.

💡 Performance — Số Liệu Thực Tế

Startup Time

VMContainer
Cold start30-60 giây0.1-1 giây
Warm start10-20 giây< 0.1 giây

Resource Overhead

VMContainer
CPU~15% cho hypervisor~2% (gần native)
RAM512 MB - 2 GB/instance5-50 MB/instance
Disk2-10 GB/instance50-500 MB/instance

Mật Độ Trên Server (32 GB RAM, 16 cores)

VMContainer
Instances8-15100-500+
Tận dụng tài nguyên60-70%90-95%

Kết luận: Container cho phép chạy gấp 10-50x số lượng workload trên cùng phần cứng, với hiệu năng gần như native.

🔴 Anti-pattern: Đối Xử Container Như VM

Dấu hiệu bạn đang "VM-ify" container:

1. Cài nhiều service trong một container

bash
# ❌ Container "béo phì"
FROM ubuntu:22.04
RUN apt-get install -y nginx mysql-server redis-server php-fpm
CMD ["supervisord"]
# → 1 container chạy 4 service = KHÔNG

2. Cài SSH server để "truy cập" container

bash
# ❌ Đã có docker exec
RUN apt-get install -y openssh-server
# → Dùng: docker exec -it <container> bash

3. Dùng systemd làm init system

bash
# ❌ Container không cần init system đầy đủ
CMD ["/sbin/init"]
# → Docker IS init system cho container

4. Lưu dữ liệu bên trong container

bash
# ❌ Container là ephemeral — data sẽ mất khi container bị xóa
# → Dùng volumes: docker run -v /host/data:/app/data

Cách đúng — "Microservice Mindset"

❌ VM mindset:                    ✅ Container mindset:
┌──────────────────┐             ┌──────┐ ┌──────┐ ┌──────┐
│  nginx            │             │ nginx │ │ app  │ │ redis│
│  app              │     →       └──┬───┘ └──┬───┘ └──┬───┘
│  redis            │                │        │        │
│  mysql            │                └────────┼────────┘
│  cron             │                    Docker Network
└──────────────────┘
Một "server" chứa tất cả        Mỗi service = Một container

8. Under The Hood — OCI Runtime Spec & runc

Bạn không cần hiểu sâu phần này ở Phase 1, nhưng nên biết nó tồn tại để hiểu tại sao container ecosystem hoạt động được.

📐 OCI — Open Container Initiative

Docker không phải tiêu chuẩn duy nhất. Năm 2015, Docker cùng các công ty khác (Google, Red Hat, CoreOS...) thành lập OCI để tạo ra tiêu chuẩn mở cho container.

OCI định nghĩa hai spec quan trọng:

OCI Standards:
┌──────────────────────────────────────────────┐
│                                               │
│  1. Runtime Spec                              │
│     "Container phải được tạo và chạy          │
│      như thế nào"                              │
│     → Namespaces, cgroups, lifecycle           │
│                                               │
│  2. Image Spec                                │
│     "Container image phải được đóng gói        │
│      như thế nào"                              │
│     → Layers, manifest, config                │
│                                               │
└──────────────────────────────────────────────┘

⚙️ runc — "Bộ Máy Thực Sự" Phía Sau Docker

Khi bạn chạy docker run, Docker Engine không trực tiếp tạo container. Nó gọi runc — một low-level runtime implement OCI Runtime Spec.

docker run alpine sh


Docker CLI → Docker Daemon (dockerd)


             containerd (container manager)


               runc (OCI runtime)


            Linux Kernel: clone() + namespaces + cgroups


              Container Process (sh)

Tại sao điều này quan trọng?

  • Portability: Image build bằng Docker chạy được trên Podman, CRI-O, hoặc bất kỳ OCI-compatible runtime
  • No vendor lock-in: Bạn không bị "khóa" vào Docker. Có thể chuyển sang Podman mà không thay đổi image
  • Kubernetes: Dùng containerd hoặc CRI-O (không cần Docker!) nhưng vẫn chạy cùng OCI images

💡 Ghi nhớ nhanh

Bạn không cần nhớ chi tiết về runc hay containerd ở giai đoạn này. Chỉ cần biết:

  1. Docker image tuân theo chuẩn mở OCI → chạy được ở nhiều nơi
  2. Docker không phải "phép thuật" — bên dưới là Linux kernel features (namespaces + cgroups)
  3. Container ecosystem có nhiều lựa chọn runtime — Docker chỉ là phổ biến nhất

9. Quiz — Tổng Hợp Kiến Thức

🧠 Quiz

Câu 1: Tính năng nào của Linux kernel cho phép container có PID, network, filesystem riêng?

  • [ ] A. SELinux
  • [x] B. Namespaces
  • [ ] C. Cgroups
  • [ ] D. iptables

💡 Giải thích: Namespaces tạo ra sự cô lập (container thấy gì). Cgroups kiểm soát giới hạn tài nguyên (container dùng bao nhiêu). SELinux là mandatory access control. iptables quản lý firewall rules.


Câu 2: Bạn chạy docker run --memory=256m my-app. App cố dùng 512 MB RAM. Điều gì xảy ra?

  • [ ] A. Docker từ chối chạy container
  • [ ] B. Container chạy bình thường, dùng 512 MB
  • [x] C. Linux OOM Killer giết process trong container
  • [ ] D. Host server bị crash

💡 Giải thích: Cgroups memory controller phát hiện container vượt quota → trigger OOM Killer → giết process vi phạm. Chỉ container đó bị ảnh hưởng, host và container khác vẫn ổn.


Câu 3: Team bạn muốn "SSH vào container" để debug. Cách đúng là gì?

  • [ ] A. Cài openssh-server trong Dockerfile
  • [x] B. Dùng docker exec -it <container> sh
  • [ ] C. Expose port 22 của container
  • [ ] D. Dùng VMware để remote desktop vào container

💡 Giải thích: docker exec cho phép chạy command trong container đang chạy, không cần cài thêm bất kỳ service nào. Cài SSH server trong container là anti-pattern (thêm attack surface, thêm process, tốn resource).


Câu 4: Công ty bạn dùng Docker để build image. Năm sau muốn chuyển sang Podman. Image cũ còn dùng được không?

  • [x] A. Có — cả Docker và Podman đều tuân theo OCI Image Spec
  • [ ] B. Không — phải rebuild toàn bộ image cho Podman
  • [ ] C. Có, nhưng phải convert format bằng tool riêng
  • [ ] D. Tùy thuộc vào base image

💡 Giải thích: Nhờ OCI (Open Container Initiative), container image là chuẩn mở. Image build bằng Docker chạy được trên Podman, CRI-O, containerd — và ngược lại.

10. Scenario — Production Decision

🎯 Scenario Choice — 🏢 Bạn là DevOps Engineer, CTO hỏi:

Tình huống

Công ty có 3 ứng dụng:

  1. Web API (Node.js) — stateless, cần scale nhanh theo traffic
  2. Database (PostgreSQL) — stateful, cần persistent storage
  3. ML Training (Python + GPU) — cần access trực tiếp GPU hardware

CTO hỏi: "Cái nào nên chạy VM, cái nào nên chạy container?"

💡 Phân tích và lời giải
Ứng dụngChạy trênLý do
Web API✅ ContainerStateless, cần scale nhanh (< 1s), đóng gói dễ, auto-scaling hoàn hảo
Database✅ Container + VolumeContainer với persistent volume. StatefulSet trong K8s. Nhiều công ty production đã chạy DB trong container (nhưng cần hiểu volume management)
ML Training⚠️ VM hoặc Container + GPU passthroughNếu cần full GPU access, VM cho phép passthrough trực tiếp. Tuy nhiên, NVIDIA Container Toolkit đã hỗ trợ GPU trong container rất tốt (--gpus all)

Câu trả lời tốt nhất: Cả 3 đều có thể chạy container, nhưng Database và ML Training cần configuration đặc biệt (volumes cho DB, GPU passthrough cho ML). Container không phải lúc nào cũng "tốt hơn" — mà là phù hợp hơn cho phần lớn use case.

11. Tóm Tắt — Ghi Nhớ Nhanh

┌─────────────────────────────────────────────────────────┐
│                     KEY TAKEAWAYS                        │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  1. VM = máy tính hoàn chỉnh được mô phỏng              │
│     Container = process được cô lập trên shared kernel   │
│                                                          │
│  2. Namespaces = "container thấy gì" (isolation)         │
│     Cgroups = "container dùng bao nhiêu" (limits)        │
│                                                          │
│  3. Container startup < 1s, VM startup 30-60s            │
│     Container overhead ~2%, VM overhead ~15%              │
│                                                          │
│  4. Container KHÔNG phải VM:                             │
│     • Không cài SSH → dùng docker exec                   │
│     • Không cài systemd → Docker quản lý lifecycle       │
│     • Một container = Một process = Một trách nhiệm      │
│                                                          │
│  5. OCI = chuẩn mở → không bị lock-in vào Docker        │
│                                                          │
└─────────────────────────────────────────────────────────┘