71 điểm bởi GN⁺ 2026-01-21 | 9 bình luận | Chia sẻ qua WhatsApp
  • Ngay cả khi chạy docker run ubuntu, container vẫn chia sẻ kernel Linux của host; Ubuntu chỉ cung cấp các công cụ không gian người dùng
  • Kết quả uname -r hiển thị phiên bản kernel của host, còn chỉ /etc/os-release mới cho thấy thông tin Ubuntu
  • VM khởi động với kernel riêng và mất vài phút để boot, trong khi container khởi động trong vài mili giây và chia sẻ kernel của host bằng cơ chế cô lập ở cấp hệ điều hành không cần ảo hóa phần cứng, nên overhead thấp
  • Nhờ tính ổn định của ABI system call trên Linux, nhiều container từ các distro khác nhau có thể chạy trên cùng một kernel
  • Trên môi trường RAM 16GB, giới hạn thực tế là khoảng 50-100 container nhẹ, 10-30 container tầm trung, và 5-10 container lớn
  • Việc hiểu kiến trúc này rất quan trọng vì lỗ hổng kernel có thể ảnh hưởng đến mọi container, và việc chọn base image tác động trực tiếp đến tính tương thích và bảo mật

Ý nghĩa của việc “chạy Ubuntu”

  • Khi chạy docker run ubuntu:22.04, bạn sẽ có bash prompt trông như Ubuntu và có thể dùng apt update cũng như cài package
  • Nhưng nếu chạy uname -r bên trong container, bạn sẽ thấy phiên bản kernel của host (ví dụ: 6.5.0-44-generic)
  • File /etc/os-release hiển thị Ubuntu 22.04, nhưng kernel là của máy host; phần “Ubuntu” chỉ là filesystem cấu thành không gian người dùng

Container vs máy ảo: so sánh kiến trúc

  • VM ảo hóa phần cứng, còn container ảo hóa hệ điều hành
  • Những khác biệt chính:
    • Kernel: mỗi VM có kernel riêng, container dùng chung kernel của host
    • Thời gian khởi động: VM mất vài phút, container chỉ mất vài mili giây
    • Overhead bộ nhớ: VM là 512MB-4GB, container là 1-10MB
    • Dung lượng đĩa: VM dùng 10-100GB, image container là 10-500MB
    • Mức độ cô lập: VM ở cấp phần cứng, container ở cấp tiến trình
    • Hiệu năng: VM có overhead khoảng 5-10%, container đạt hiệu năng gần native

Các thành phần thực sự của base image

  • Nội dung tarball được tải về khi pull ubuntu:22.04:
  • 1. Binary thiết yếu (/bin, /usr/bin)

    • /bin/bash (shell), /bin/ls (liệt kê file), /bin/cat (hiển thị file)
    • /usr/bin/apt (trình quản lý package), /usr/bin/dpkg (công cụ package Debian)
  • 2. Shared library (/lib, /usr/lib)

    • glibc và các thư viện dùng chung mà chương trình khác liên kết tới
    • /lib/x86_64-linux-gnu/libc.so.6 (thư viện C - nền tảng của mọi chương trình C)
    • Các thư viện thiết yếu như libpthread.so.0, libm.so.6
  • 3. File cấu hình (/etc)

    • /etc/apt/sources.list (kho package)
    • /etc/passwd (cơ sở dữ liệu người dùng)
    • /etc/resolv.conf (cấu hình DNS, thường được mount từ host)
  • 4. Cơ sở dữ liệu package

    • /var/lib/dpkg/status (các package đã cài)
    • /var/lib/apt/lists/ (cache package khả dụng)
  • Không bao gồm kernel, bootloader hay driver

Kernel giữ nguyên, mọi thứ khác thay đổi

  • Những gì Linux kernel cung cấp: lập lịch tiến trình, quản lý bộ nhớ, thao tác filesystem, network stack, device driver, system call
  • Khi tiến trình trong container gọi open(), read(), fork(), chúng được chuyển trực tiếp tới kernel của host
  • Kernel không biết và cũng không quan tâm tiến trình đó thuộc “container Ubuntu” hay “container Alpine”
  • Tính ổn định của giao diện system call

    • ABI syscall của Linux rất ổn định
    • Lý do một binary biên dịch bằng glibc 2.31 (Ubuntu 20.04) vẫn chạy được trên kernel của Ubuntu 24.04:
      • kernel duy trì khả năng tương thích ngược
      • số hiệu system call không thay đổi
      • tính năng mới được thêm vào, nhưng tính năng cũ hầu như không bị loại bỏ
    • Đó cũng là lý do có thể chạy container Ubuntu 18.04 trên host đang dùng kernel 6.5

Tự thực hành: cùng kernel, khác không gian người dùng

  • Nếu chạy cùng một truy vấn kernel trên nhiều base image, bạn sẽ thấy tất cả đều dùng chung kernel của host
  • ubuntu:22.04, debian:12, alpine:3.19, fedora:39, archlinux:latest đều cho cùng một phiên bản kernel (6.5.0-44-generic)
  • Điểm khác biệt giữa các container là cấu trúc userland như binary uname, libc, v.v.

Vì sao container cực kỳ hiệu quả

  • 1. Không bị trùng lặp kernel

    • Mỗi VM phải nạp toàn bộ kernel vào bộ nhớ (khoảng 100-500MB)
    • 10 VM sẽ tiêu tốn bộ nhớ cho 10 kernel, còn 10 container chỉ dùng một kernel duy nhất
  • 2. Khởi động tức thì

    • Trình tự boot của VM: BIOS → bootloader → kernel → hệ thống init → service
    • Container chỉ cần gọi fork()exec()tiến trình có thể tồn tại trong vài mili giây
    • VM điển hình boot trong 30-60 giây / container khởi động khoảng 0.347 giây
  • 3. Chia sẻ image layer

    • Nếu chạy 100 container từ ubuntu:22.04, base image layer chỉ tồn tại một lần trên đĩa
    • Mỗi container chỉ có thêm một lớp copy-on-write mỏng cho các thay đổi riêng
  • 4. Chia sẻ bộ nhớ qua kernel

    • Page cache của kernel được dùng chung
    • Nếu 50 container cùng đọc một file, kernel chỉ cache một lần
    • Khi dùng cùng shared library, các trang bộ nhớ có thể được chia sẻ bằng copy-on-write

Tính toán giới hạn số lượng container có thể chạy

  • Phân tích bộ nhớ (theo VM 16GB RAM)

    • Tổng RAM: 16,384 MB
    • Overhead của host OS: -1,024 MB
    • Docker daemon: -256 MB
    • Overhead của container runtime: -512 MB
    • Dung lượng khả dụng cho container: 14,592 MB
  • Mức dùng bộ nhớ theo loại container

    • Tối thiểu (sleep): khoảng 1MB
    • Alpine + app nhỏ: khoảng 25MB
    • Ubuntu + app Python: khoảng 120MB
    • Ubuntu + app Java: khoảng 500MB
    • Dịch vụ Node.js: khoảng 200MB
  • Mức tối đa theo lý thuyết

    • Container tối thiểu (1MB): 14,592 container
    • Alpine + app nhỏ (25MB): 583 container
    • Ubuntu + Python (120MB): 121 container
    • Java microservice (500MB): 29 container
  • Giới hạn thực tế

    • Ngoài bộ nhớ còn phải tính đến:
      • Lập lịch CPU: quá nhiều container tranh chấp sẽ gây tăng đột biến độ trễ
      • File descriptor: ulimit mặc định là 1024
      • Cổng mạng: chỉ có 65,535 cổng cho port mapping
      • PID: bị giới hạn bởi /proc/sys/kernel/pid_max (mặc định: 32,768)
      • Disk I/O: overhead của OverlayFS, phải dò qua nhiều layer
    • Với workload thực tế trên VM 16GB, giới hạn hữu dụng thường là:
      • Container nhẹ (API, worker): 50-100
      • Container trung bình (DB, cache): 10-30
      • Container lớn (mô hình ML, ứng dụng JVM): 5-10

Tính tương thích giữa các distro Linux

  • Cam kết ABI của kernel

    • Linux duy trì giao diện syscall ổn định
    • Binary biên dịch cho kernel cũ vẫn chạy được trên kernel mới
    • Binary của Ubuntu 18.04 vẫn chạy bình thường trên kernel 6.5
  • Khi nào tính tương thích bị phá vỡ

    • Yêu cầu tính năng kernel: container cần một tính năng mà kernel không có (ví dụ: io_uring cần kernel 5.1+)
    • Phụ thuộc kernel module: Wireguard cần kernel module wireguard, container NVIDIA cần driver kernel nvidia
    • Giới hạn seccomp/capability: khi host chặn syscall cần thiết của container (ví dụ: muốn dùng ptrace thì cần --cap-add SYS_PTRACE)

Hướng dẫn chọn base image

Base image Kích thước Trình quản lý package Mục đích sử dụng
scratch 0 MB Không có Binary Go/Rust biên dịch tĩnh
alpine 7 MB apk Container tối giản, musl libc
distroless 20 MB Không có Ưu tiên bảo mật, không có shell hay trình quản lý package
debian-slim 80 MB apt Cân bằng giữa kích thước và tương thích
ubuntu 78 MB apt Thân thiện cho phát triển
fedora 180 MB dnf Package mới, SELinux
  • Khi nào dùng từng image

    • scratch: dành cho binary biên dịch tĩnh, chỉ chứa binary mà không có hệ điều hành nào
    • alpine: image tối thiểu nhưng vẫn cần truy cập shell; do dùng musl libc thay vì glibc nên có thể phát sinh một số vấn đề tương thích
    • distroless: image production thiên về bảo mật, không có shell và package manager nên khó debug hơn nhưng an toàn hơn

Ranh giới giữa user space và kernel

  • Những gì đến từ base image (user space)

    • Shell (/bin/bash, /bin/sh)
    • Thư viện C (glibc, musl)
    • Trình quản lý package (apt, apk, yum)
    • Utility cốt lõi (ls, cat, grep)
    • Cấu hình hệ thống init (thường không phải chính systemd)
    • Người dùng và nhóm mặc định (/etc/passwd)
    • Đường dẫn thư viện và cấu hình
  • Những gì đến từ host (kernel)

    • Lập lịch tiến trình và quản lý bộ nhớ
    • Network stack (TCP/IP, routing)
    • Thao tác filesystem (đọc, ghi, mount)
    • Tính năng bảo mật (namespace, cgroups, seccomp)
    • Device driver (GPU, mạng, lưu trữ)
    • Quản lý thời gian và đồng hồ
    • Mã hóa và sinh số ngẫu nhiên
  • Ảo giác do namespace tạo ra

    • Kernel cung cấp namespace để container có cảm giác như được cô lập hoàn toàn
    • Một tiến trình hiện là PID 1 bên trong container có thể mang PID cao hơn trên host (ví dụ: 45678)
    • Kernel duy trì mapping: PID 1 trong container → PID 45678 trên host
    • Đó là cách cơ chế cô lập hoạt động mà không cần ảo hóa

Ý nghĩa trong môi trường production

  • 1. Lỗ hổng kernel ảnh hưởng tới mọi container

    • Nếu kernel của host có lỗ hổng, mọi container đều bị phơi bày
    • Bắt buộc phải duy trì việc vá lỗi cho host
  • 2. Kernel của host giới hạn tính năng của container

    • Muốn dùng io_uring thì host cần kernel 5.1+
    • Tính năng eBPF cần kernel 4.15+ với các tùy chọn tương ứng được bật
  • 3. Tầm quan trọng của glibc vs musl

    • Alpine dùng musl libc
    • Một số binary biên dịch cho glibc có thể không chạy được
    • Ví dụ: chạy binary glibc trên Alpine có thể báo lỗi thiếu file /lib/x86_64-linux-gnu/libc.so.6
  • 4. “Hệ điều hành” của container chỉ là khái niệm mang tính tổ chức

    • Từ góc nhìn của kernel, “container Ubuntu” và “container Debian” không có khác biệt
    • Tất cả chỉ là các tiến trình thực hiện syscall

Những hiểu lầm phổ biến

  • ❌ “Container là VM nhẹ”: container là các tiến trình có cơ chế cô lập nâng cao, còn VM ảo hóa phần cứng và chạy kernel riêng
  • ❌ “Mỗi container có kernel riêng”: mọi container đều dùng chung kernel của host; “OS” của container chỉ là các file trong user space
  • ❌ “Chạy container Ubuntu = chạy Ubuntu”: thực tế là đang chạy các công cụ Ubuntu trên kernel của host; nếu host là Debian thì bạn thực sự đang chạy kernel Debian
  • ❌ “Base image chứa một hệ điều hành hoàn chỉnh”: base image chỉ chứa các công cụ user space tối thiểu, không có kernel, bootloader hay driver
  • ❌ “Nhiều container hơn = tốn nhiều bộ nhớ hơn”: nhờ layer dùng chung và page cache của kernel, các container thường chia sẻ bộ nhớ rất hiệu quả

Tóm tắt cốt lõi

  • Docker base image là ảnh chụp filesystem của các thành phần user space trong một distro Linux
    • Các binary, thư viện và cấu hình khiến Ubuntu trông giống Ubuntu
  • Kernel, tức hệ điều hành thực sự, được chia sẻ với host
  • Kiến trúc này mang lại:
    • thời gian khởi động tính bằng mili giây (không cần boot kernel)
    • overhead bộ nhớ tối thiểu (một kernel, các trang bộ nhớ dùng chung)
    • mật độ triển khai lớn (hàng trăm container trên mỗi host)
    • hiệu năng gần native (syscall trực tiếp tới kernel)
  • Đánh đổi là mức độ cô lập yếu hơn VM — vì container dùng chung kernel nên một khai thác kernel có thể ảnh hưởng tới mọi container
  • Với phần lớn workload, đánh đổi này là xứng đáng

9 bình luận

 
bbulbum 2026-01-22

Nhân + công cụ = bản phân phối
Vậy thì cái này cũng là Ubuntu đúng không..

 
sacredshine 2026-01-21

Vì vậy cũng có những bài hướng dẫn kiểu tự tay tạo Docker trực tiếp trên Linux, rồi cô lập thư mục và xử lý đủ thứ như người dùng với nhóm các kiểu.

 
dongho42 2026-01-21

Hữu ích đấy

 
seunggi 2026-01-21

리눅스 네임스페이스, cgroups, 및 chroot를 사용하여 자체 Docker를 구축하세요.

Vì vậy, nói hơi quá một chút thì có thể xem chroot + cgroup = docker.

 
euphcat 2026-01-21

Actually thì nó hơi giống systemd-nspawn hơn một chút đó ☝️🤓

 
hohemian 2026-01-22

Thực ra

 
euphcat 2026-01-22

Châm biếm / Tự giễu

 
crawler 2026-01-21

Thật sự rất thú vị.

Có vẻ như bài viết giải thích theo tiêu chuẩn LINUX,
nhưng trong trường hợp chạy trên Windows thì có phải sẽ chia sẻ kernel ảo được tạo bằng WSL2 giống như trong bài viết không?

Nếu Docker xuất hiện lỗ hổng khiến có thể can thiệp vào kernel, thì liệu có nên xem Windows — nơi được ảo hóa thêm một lớp so với Linux — là mạnh hơn về mặt bảo mật hay không, điều này cũng làm tôi tò mò.

 
minsuchae 2026-01-22

Tôi hơi bất ngờ vì phản ứng của các bình luận phía trên.
Tôi cứ nghĩ là mọi người đương nhiên đều biết điều đó rồi mới dùng chứ.
Kernel Linux là của host, còn phần còn lại là mang vào các công cụ được dùng trong bản phân phối Linux.

Theo như tôi biết thì WSL2 chạy bằng cách ảo hóa trên Hyper-V.
Windows - Linux trong máy ảo - rồi bên trong đó lại là Container...

Về cơ bản, root bên trong Container không phải là root thật của toàn bộ hệ thống, nên mặc định sẽ không thể tùy ý can thiệp vào kernel.
Tuy vậy, nếu xuất hiện lỗ hổng thì vẫn rất nguy hiểm.

Xét về mặt hiệu năng thì Windows chậm hơn một chút cũng là vì phải đi qua thêm một lớp ảo hóa nữa.