Câu giải thích quen thuộc nhất về Docker — "container là một máy ảo nhẹ" — cũng là câu sai nhất, và hiểu sai điểm này khiến bạn không lý giải được vì sao container khởi động trong mili giây, vì sao container Linux không chạy được trên kernel Windows, hay vì sao container "thấy" ít tiến trình hơn host. Sự thật đơn giản và mạnh mẽ hơn: container chỉ là một tiến trình bình thường trên host, được kernel cô lập và giới hạn bằng hai tính năng có sẵn. Bài này (phần 1 loạt "Docker và container: nhìn từ bên trong") chứng minh điều đó bằng demo chạy thật — kể cả tự dựng "container" mini không cần Docker.

VM và container khác nhau ở tầng nào

  • Máy ảo (VM): hypervisor ảo hoá phần cứng, mỗi VM chạy một kernel riêng và cả một hệ điều hành đầy đủ. Cô lập rất mạnh, nhưng nặng: tốn RAM cho mỗi kernel, khởi động phải boot cả OS (tính bằng giây tới phút).
  • Container: không ảo hoá phần cứng, không có kernel riêng. Nó là một tiến trình chạy trực tiếp trên kernel host, chỉ được kernel cô lập tầm nhìn (namespace) và giới hạn tài nguyên (cgroup). Nhẹ, nhanh, nhưng cô lập ở mức tiến trình chứ không phải máy.

Nói cách khác: Docker không "chạy" container theo nghĩa máy ảo. Nó cấu hình các tính năng kernel để một tiến trình tưởng rằng nó có cả hệ thống cho riêng mình.

Ảnh chụp đoạn mã nền tối minh hoạ container không phải máy ảo nó chỉ là tiến trình bị cô lập, hiểu lầm phổ biến container là VM nhẹ sai VM có kernel riêng ảo hoá phần cứng qua hypervisor nặng chậm container là tiến trình thường trên host chỉ được cô lập bằng namespace nhìn thấy gì cộng cgroup dùng bao nhiêu chung kernel, hai trụ cột của container đều là tính năng kernel Linux namespace cô lập tầm nhìn PID mạng mount user hostname cgroup giới hạn tài nguyên CPU RAM IO docker chỉ là công cụ dựng sẵn hai thứ này cộng quản lý image, tự tạo cô lập bằng tay không cần docker unshare --pid --fork --mount-proc sh tạo PID namespace mới shell thành PID 1 chỉ thấy tiến trình của chính nó đây chính là thứ docker làm bên dưới docker run alpine uname -r kernel bằng kernel host không riêng, hệ quả không boot kernel khởi động tính bằng mili giây VM giây chung kernel host container Linux không chạy kernel Windows mac trên máy khác OS Docker chạy 1 VM Linux ẩn bên dưới

Hình 1: VM có kernel riêng qua hypervisor; container là tiến trình host cô lập bằng namespace (tầm nhìn) + cgroup (tài nguyên), chung kernel. Có thể tự dựng cô lập bằng unshare — chính là thứ Docker làm bên dưới.

Đo thật: ba bằng chứng container không phải VM

Ảnh chụp bảng kết quả chạy thật docker cộng unshare output thật, một mọi container dùng chung kernel host bằng chứng không phải VM docker run alpine uname -r ra 7.0.12-linuxkit docker run ubuntu uname -r ra 7.0.12-linuxkit alpine và ubuntu khác distro nhưng cùng một kernel bằng của host, hai cô lập bằng namespace không cần docker unshare ngoài ps thấy tiến trình host-side trong PID namespace mới PID của shell bằng 1 ps -e chỉ thấy tiến trình của chính nó 1 ps shell thành PID 1 tầm nhìn tiến trình bị cô lập hoàn toàn, ba khởi động tính bằng mili giây VM tính bằng giây docker run --rm alpine true ra 164 ms gồm cả tạo cộng chạy cộng xoá container không boot kernel nào cả, kết container bằng tiến trình host cộng namespace tầm nhìn cộng cgroup tài nguyên docker chỉ dựng sẵn hai thứ đó bản thân là tính năng kernel

Hình 2: Chạy thật — alpine và ubuntu cùng trả kernel 7.0.12-linuxkit (kernel host); trong PID namespace mới tạo bằng unshare, shell thành PID 1 và chỉ thấy tiến trình của chính nó; docker run alpine true mất 164 ms.

  • Chung kernel host: docker run alpine uname -r và docker run ubuntu uname -r trả về cùng một phiên bản kernel (7.0.12-linuxkit) — dù alpine và ubuntu là hai distro khác hẳn. Nếu là VM, mỗi cái sẽ có kernel riêng. Chúng dùng chung kernel vì đó là kernel của host. (Lưu ý: linuxkit là kernel của VM Linux nhẹ mà Docker Desktop dùng trên macOS/Windows — xem phần đánh đổi.)
  • Cô lập bằng namespace, không cần Docker: chạy unshare --pid --fork --mount-proc sh tạo một PID namespace mới; shell trở thành PID 1 và ps chỉ thấy tiến trình của chính nó. Đây chính xác là cơ chế Docker dùng — bạn vừa tự tạo phần lõi của một "container" bằng một lệnh.
  • Khởi động mili giây: docker run --rm alpine true mất 164 ms cho cả tạo + chạy + xoá container. Không có kernel nào được boot — chỉ là fork một tiến trình rồi gắn namespace/cgroup. VM cần boot cả OS nên tính bằng giây.

Đánh đổi cần cân nhắc

Cô lập của container yếu hơn VM — vì chung kernel. Mọi container chia sẻ kernel host, nên một lỗ hổng leo thang đặc quyền ở tầng kernel có thể ảnh hưởng cả host và các container khác. VM cô lập mạnh hơn (ranh giới phần cứng ảo). Với workload cần cô lập bảo mật cao (chạy code không tin cậy của nhiều khách hàng), người ta dùng VM hoặc sandbox kiểu gVisor/Kata (container chạy trong micro-VM). Container mặc định đánh đổi cô lập lấy hiệu năng.

Container Linux cần kernel Linux. Vì chung kernel host, container Linux không chạy trực tiếp trên kernel Windows hay macOS. Docker Desktop trên hai OS đó thật ra chạy một VM Linux nhẹ ẩn bên dưới (chính là linuxkit thấy ở demo), rồi container chạy trong VM đó. Đây là lý do file I/O qua bind mount trên Mac/Windows chậm hơn Linux gốc — nó đi qua ranh giới VM.

"Nhẹ" không có nghĩa "miễn phí". Container không boot OS, nhưng vẫn tốn RAM/CPU cho tiến trình bên trong, và một image cồng kềnh vẫn chiếm nhiều đĩa. "Nhẹ" là so với VM về overhead cô lập, không phải lời hứa ứng dụng của bạn sẽ nhẹ. Các phần sau của loạt sẽ đo image layer và cách giảm kích thước.

Ba ý mang về

  1. Container là tiến trình host bị cô lập, không phải VM: đo thật, alpine và ubuntu cùng thấy kernel 7.0.12-linuxkit của host — không có kernel riêng như VM. Docker chỉ cấu hình hai tính năng kernel: namespace + cgroup.
  2. Cô lập là tính năng kernel, tự dựng được: đo thật unshare --pid cho shell thành PID 1 chỉ thấy tiến trình của chính nó — không cần Docker; Docker chỉ dựng sẵn và quản lý image trên nền đó.
  3. Nhẹ nhờ không boot kernel, nhưng cô lập yếu hơn VM: đo thật docker run mất 164 ms (VM tính bằng giây); đổi lại chung kernel nên cô lập bảo mật kém hơn — workload không tin cậy nên cân nhắc VM/micro-VM.

Nguồn

Phần sau ta đào sâu vào PID namespace: vì sao tiến trình trong container thấy mình là PID 1, điều gì đặc biệt ở PID 1 (xử lý tín hiệu, tiến trình mồ côi), và vì sao ứng dụng container hay cần một "init" nhỏ.