Post

Docker hoạt động như thế nào ở tầng OS

Docker hoạt động như thế nào ở tầng OS

Docker hoạt động như thế nào ở tầng OS?

Giới thiệu

Docker là một ứng dụng containerization, nó giúp tạo ra một môi trường ảo hóa độc lập cho ứng dụng. Docker được viết ban đầu trên Linux và cho tới tận bây giờ, Linux vẫn là môi trường mà Docker chạy “native” nhất.

Bài viết này sẽ đi sâu vào cách Docker hoạt động ở tầng hệ điều hành, giúp bạn hiểu rõ bản chất và lý do tại sao Docker lại phổ biến đến vậy.

1. User Space và Kernel Space

Để hiểu được Docker hoạt động như thế nào, bạn cần nắm vững khái niệm về hai tầng quan trọng trong kiến trúc hệ điều hành:

User Space (Không gian người dùng)

User space là tầng chứa các ứng dụng và các dịch vụ liên quan tới người dùng. Đây là nơi các chương trình như:

  • Web browsers (Chrome, Firefox)
  • Text editors (VSCode, Vim)
  • Command-line tools (bash, grep, ls, apt)
  • Application servers (Nginx, Apache, Node.js)

hoạt động và tương tác với người dùng.

Kernel Space (Không gian nhân)

Kernel space là tầng chứa các dịch vụ và các ứng dụng liên quan tới hệ thống, bao gồm:

  • Kernel code: Nhân hệ điều hành
  • Drivers: Trình điều khiển phần cứng
  • Memory management: Quản lý bộ nhớ
  • Network stack: Xử lý mạng
  • File system: Quản lý tập tin
  • Process scheduling: Lập lịch tiến trình
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────┐
│         USER SPACE                      │
│  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐    │
│  │ App1 │ │ App2 │ │ App3 │ │ App4 │    │
│  └──────┘ └──────┘ └──────┘ └──────┘    │
├─────────────────────────────────────────┤
│         KERNEL SPACE                    │
│  ┌────────────────────────────────┐     │
│  │  Kernel, Drivers, Memory Mgmt  │     │
│  │  Network Stack, File System    │     │
│  └────────────────────────────────┘     │
├─────────────────────────────────────────┤
│         HARDWARE                        │
│  CPU, RAM, Disk, Network Card...        │
└─────────────────────────────────────────┘

2. Docker Image: Đóng gói User Space

Bản chất của Docker Image

Các image của Docker bản chất là đóng gói của tầng User Space. Điều này có nghĩa là:

Ví dụ với image ubuntu:20.04:

  • Có chứa: apt, bash, ls, grep, và các công cụ user-space của Ubuntu
  • Không chứa: Kernel code của Ubuntu
1
2
3
4
5
# Khi bạn pull một image Ubuntu
docker pull ubuntu:20.04

# Image này chỉ khoảng 72MB
# Trong khi Ubuntu ISO đầy đủ thường > 2GB

Tại sao Image nhẹ hơn OS đầy đủ?

Thành phầnOS đầy đủDocker Image
KernelKhông
DriversKhông
User-space tools
Desktop EnvironmentKhông
Dung lượng2-4 GB50-200 MB

3. Container: Chia sẻ Kernel với Host

Cơ chế hoạt động

Khi các image được deploy lên, nó sẽ tạo ra một container mới và SỬ DỤNG CHUNG kernel với hệ thống host để chạy các ứng dụng.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────┐
│                    HOST OS (Linux)              │
│                                                 │
│  ┌──────────────┐  ┌──────────────┐             │
│  │ Container 1  │  │ Container 2  │             │
│  │              │  │              │             │
│  │ Ubuntu       │  │ Alpine       │             │
│  │ User Space   │  │ User Space   │             │
│  └──────────────┘  └──────────────┘             │
│                                                 │
│  ────────────────────────────────────────────   │
│           SHARED KERNEL (Linux Kernel)          │
│  ────────────────────────────────────────────   │
│                                                 │
│                    HARDWARE                     │
└─────────────────────────────────────────────────┘

Ứng dụng trong Container

Khi các ứng dụng chạy trong container:

  • Chúng hoạt động giống như các ứng dụng bình thường ở máy host
  • Các lời gọi hệ thống (system calls) từ ứng dụng trong Docker có thể gọi trực tiếp xuống kernel như các ứng dụng ở máy host
  • Không có overhead từ việc giả lập phần cứng hay chạy kernel riêng
1
2
3
4
5
6
7
# Ví dụ: Process trong container
docker run -d nginx

# Trên host, bạn có thể thấy process nginx
ps aux | grep nginx

# Nginx trong container gọi system calls trực tiếp xuống host kernel

4. Ba nhiệm vụ chính của Docker

Docker thực hiện ba công việc chính để tạo ra môi trường container độc lập:

4.1. Quản lý isolation bằng Namespace và Cgroup

Namespace - Cô lập tiến trình

Namespace được sử dụng để quản lý các tiến trình, giúp các ứng dụng:

  • Độc lập với nhau
  • Không thể vượt quyền để ảnh hưởng tới máy host
  • Có “view” riêng về hệ thống

Các loại namespace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# PID Namespace - Process ID isolation
# Container có PID namespace riêng
docker exec my-container ps aux
# PID 1 trong container ≠ PID 1 trên host

# Network Namespace - Network isolation
# Container có network stack riêng
docker exec my-container ip addr

# Mount Namespace - File system isolation
# Container có mount points riêng

# UTS Namespace - Hostname isolation
# Container có hostname riêng
docker exec my-container hostname

# IPC Namespace - Inter-Process Communication isolation
# User Namespace - User ID isolation

Ví dụ minh họa:

1
2
3
4
5
6
7
# Trên host, bạn có process với PID 12345
ps aux | grep nginx

# Trong container, process này có PID 1
docker exec my-nginx ps aux
# USER  PID  COMMAND
# root   1   nginx: master process

Cgroup - Giới hạn tài nguyên

Cgroup (Control Groups) được sử dụng để quản lý tài nguyên tối đa mà một container có thể sử dụng:

1
2
3
4
5
6
7
8
9
10
11
# Giới hạn CPU
docker run --cpus="1.5" my-app
# Container chỉ được dùng tối đa 1.5 CPU cores

# Giới hạn RAM
docker run --memory="512m" my-app
# Container chỉ được dùng tối đa 512MB RAM

# Giới hạn I/O
docker run --device-read-bps /dev/sda:1mb my-app
# Giới hạn tốc độ đọc disk

Kiểm tra giới hạn của container:

1
2
3
4
5
# Xem thông tin cgroup của container
docker stats my-container

CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT     MEM %
abc123         my-container  10.5%     256MiB / 512MiB      50%

4.2. Đóng gói thư viện

Trong file image, Docker đã bao gồm đầy đủ các thư viện cần thiết để chạy ứng dụng, độc lập với host.

Ví dụ:

1
2
3
4
5
6
7
8
9
# Dockerfile cho ứng dụng Python
FROM python:3.9

# Các thư viện Python cần thiết được đóng gói trong image
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY app.py .
CMD ["python", "app.py"]
1
2
3
4
5
6
7
8
9
10
11
12
13
┌────────────────────────────────────┐
│        Container                   │
│                                    │
│  App Code: app.py                  │
│  Python Runtime: 3.9               │
│  Dependencies: Flask, requests...  │
│  System libs: libc, libssl...      │
│                                    │
└────────────────────────────────────┘
         ↓ Sử dụng kernel
┌────────────────────────────────────┐
│         Host Linux Kernel          │
└────────────────────────────────────┘

Lợi ích:

  • Không conflict với thư viện trên host
  • “Works on my machine” → “Works everywhere”
  • Dễ dàng di chuyển giữa các môi trường

4.3. Quản lý tập tin bằng OverlayFS

Docker sử dụng OverlayFS (hoặc các storage drivers tương tự) để quản lý file system hiệu quả.

Cấu trúc Layers

File image Docker được chia thành nhiều layers (lớp):

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────┐
│  Layer 4: Writable Container Layer  │  ← Ghi file mới
├─────────────────────────────────────┤
│  Layer 3: Copy app.py               │  ← Read-only
├─────────────────────────────────────┤
│  Layer 2: pip install packages      │  ← Read-only
├─────────────────────────────────────┤
│  Layer 1: FROM python:3.9           │  ← Read-only
└─────────────────────────────────────┘

Cơ chế Copy-on-Write

Khi ghi file (từ ứng dụng):

  • Docker tạo một layer mới (writable layer)
  • File mới được lưu vào layer này
  • Các layer cũ vẫn giữ nguyên (read-only)

Khi đọc file:

  • Docker đọc từ trên xuống (top-down)
  • Nếu layer trên cùng có file → dùng file đó
  • Nếu không → tìm ở layer dưới
  • Tiếp tục cho đến khi tìm thấy
1
2
3
4
5
6
7
8
9
10
# Ví dụ: Modify file trong container
docker run -it ubuntu bash

# Trong container
echo "Hello" > /test.txt
# File này được ghi vào writable layer

# File /etc/hosts từ base image
cat /etc/hosts
# File này đọc từ read-only layer

Tiết kiệm bộ nhớ và tăng tốc độ

Với cơ chế layer, Docker đạt được hiệu quả cao:

1
2
3
4
5
6
7
8
# Chạy 1000 containers từ cùng 1 image
for i in {1..1000}; do
  docker run -d nginx
done

# Không cần copy 1000 lần image gốc!
# Image gốc là read-only và được share
# Chỉ writable layer cho mỗi container là khác nhau

So sánh dung lượng:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Image nginx: 142MB
docker images nginx
REPOSITORY   TAG       SIZE
nginx        latest    142MB

# 1 container nginx chạy
docker run -d nginx
docker ps -s
CONTAINER ID   SIZE
abc123         2MB (virtual 142MB)
# Size thực tế chỉ 2MB (writable layer)
# Virtual size 142MB (shared với image)

# 1000 containers
# Tổng dung lượng ≈ 142MB + (1000 × 2MB) = ~2.1GB
# Thay vì 142GB nếu copy đầy đủ!

5. Docker vs Virtual Machine (VM)

So sánh kiến trúc

Virtual Machine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌───────────────┐  ┌───────────────┐
│     VM 1      │  │     VM 2      │
│               │  │               │
│ App A         │  │ App B         │
│ Libs          │  │ Libs          │
│ Guest OS      │  │ Guest OS      │
│ Kernel        │  │ Kernel        │
└───────────────┘  └───────────────┘
        ↓                  ↓
┌─────────────────────────────────┐
│        Hypervisor               │
├─────────────────────────────────┤
│        Host OS                  │
│        Host Kernel              │
├─────────────────────────────────┤
│        Hardware                 │
└─────────────────────────────────┘

Docker Containers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────┐  ┌──────────┐  ┌──────────┐
│Container1│  │Container2│  │Container3│
│          │  │          │  │          │
│ App A    │  │ App B    │  │ App C    │
│ Libs     │  │ Libs     │  │ Libs     │
└──────────┘  └──────────┘  └──────────┘
        ↓           ↓            ↓
┌─────────────────────────────────────┐
│         Docker Engine               │
├─────────────────────────────────────┤
│         Host OS                     │
│         Shared Kernel               │
├─────────────────────────────────────┤
│         Hardware                    │
└─────────────────────────────────────┘

Bảng so sánh chi tiết

Tiêu chíVirtual MachineDocker Container
KernelMỗi VM có kernel riêngShare kernel với host
Dung lượng lưu trữRất lớn (GBs)Nhỏ (MBs)
Thời gian khởi độngPhútGiây
Memory usageCao (mỗi VM cần RAM riêng)Thấp (share kernel)
Performance overheadCao (giả lập phần cứng)Thấp (native)
IsolationHoàn toàn (có kernel riêng)Process-level
Boot processPhải khởi động cả kernel, BIOSChỉ start process
PortabilityKhó (phụ thuộc hypervisor)Dễ (image portable)

Ví dụ thực tế

Khởi động thời gian:

1
2
3
4
5
6
7
# VM
time VBoxManage startvm "Ubuntu VM"
# Real time: ~45 seconds

# Docker
time docker run -d nginx
# Real time: ~2 seconds

Dung lượng:

1
2
3
4
5
6
7
8
9
10
# VM: Ubuntu Server
# Virtual disk: 10-20GB
# RAM allocated: 2GB
# Total: 2GB RAM + 10-20GB Disk

# Docker: ubuntu:20.04
docker images ubuntu
# REPOSITORY   TAG       SIZE
# ubuntu       20.04     72.8MB
# RAM: share với host, chỉ dùng khi cần

6. Docker trên Windows và macOS

Vấn đề cơ bản

Docker xuất phát từ Linux, các image được xây dựng dựa trên Linux, chứ không phải Windows hay macOS. Do đó, Docker trên các nền tảng này không thể chạy native.

Docker trên Windows

Cách tiếp cận cũ: Hyper-V

Trước đây, Docker sử dụng Hyper-V để chạy máy ảo Linux:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────┐
│          Windows Host               │
│                                     │
│  ┌──────────────────────────────┐   │
│  │   Hyper-V Virtual Machine    │   │
│  │                              │   │
│  │   ┌──────────────────────┐   │   │
│  │   │  Linux VM            │   │   │
│  │   │                      │   │   │
│  │   │  Docker Daemon       │   │   │ 
│  │   │  Containers          │   │   │ 
│  │   └──────────────────────┘   │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘

Nhược điểm:

  • Nặng nề (phải chạy cả VM)
  • Chậm
  • Tốn tài nguyên

Cách tiếp cận hiện tại: WSL 2

Windows hiện có tích hợp sẵn WSL 2 (Windows Subsystem for Linux):

1
2
3
4
5
6
# Kiểm tra WSL
wsl --list --verbose

# NAME                   STATE           VERSION
# * Ubuntu-20.04         Running         2
#   docker-desktop       Running         2
1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────┐
│          Windows 11                 │
│                                     │
│  ┌──────────────────────────────┐   │
│  │   WSL 2                      │   │
│  │   (Lightweight VM)           │   │
│  │                              │   │
│  │   Docker Daemon              │   │
│  │   Containers                 │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘

Cải thiện:

  • Nhẹ hơn Hyper-V
  • Tích hợp tốt hơn
  • Performance tốt hơn

Hạn chế:

  • Vẫn không thể native
  • WSL 2 vẫn phải chạy trên môi trường Windows
  • Có overhead so với Linux thuần

Docker trên macOS

macOS sử dụng ảo hóa của Apple (Hypervisor.framework):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────┐
│            macOS                    │
│                                     │
│  ┌──────────────────────────────┐   │
│  │   macOS Virtualization       │   │
│  │   (Hypervisor.framework)     │   │
│  │                              │   │
│  │   ┌──────────────────────┐   │   │
│  │   │  Linux VM            │   │   │
│  │   │  Docker Daemon       │   │   │
│  │   │  Containers          │   │   │
│  │   └──────────────────────┘   │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘

Docker Daemon phải chạy trong máy ảo Linux, không chạy trực tiếp trên macOS.

Hạn chế:

  • Không native
  • Overhead từ virtualization
  • Performance không bằng Linux

Windows Containers

Docker cho phép chạy native với Windows thông qua Windows Containers:

1
2
3
4
5
6
# Switch sang Windows containers
& $Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon

# Sử dụng Windows base images
docker pull mcr.microsoft.com/windows/nanoserver:ltsc2022
docker pull mcr.microsoft.com/windows/servercore/iis

Đặc điểm:

  • Chạy native trên Windows
  • Sử dụng Windows kernel
  • Chỉ hoạt động với images được build cho Windows

Use cases:

  • .NET Framework applications
  • IIS web servers
  • Windows-specific workloads

Hạn chế:

  • Không thể chạy Linux containers
  • Ít phổ biến hơn Linux containers
  • Image size lớn hơn
  • Ecosystem nhỏ hơn

Kết luận về các nền tảng

1
2
3
4
5
# Performance comparison (relative)
Linux:   ████████████████████ 100% (Native)
WSL 2:   ████████████████░░░░ 80-85%
macOS:   ███████████████░░░░░ 75-80%
Hyper-V: ██████████░░░░░░░░░░ 50-60%

Trong production:

  • Linux chiếm ưu thế tuyệt đối
  • Hầu hết các cloud providers chạy Linux containers
  • Windows containers chỉ dùng cho workloads đặc thù

Đây cũng là lý do nhiều developers chuyển sang dùng Linux:

  • Native Docker experience
  • Performance tốt nhất
  • Đặc biệt phù hợp với máy yếu (không cần overhead của VM)
This post is licensed under CC BY 4.0 by the author.