Bài 01

Docker cơ bản: container vs VM, kiến trúc, lifecycle

13 tháng 5, 2026 · 120 phút đọc

Bài 01 — Docker cơ bản: container vs VM, kiến trúc, lifecycle

Đây là bài đầu tiên của chặng Container. Mục tiêu: sau bài này bạn hiểu container thực sự là gì (không phải VM thu nhỏ), docker run được app bất kỳ, vào shell container debug được, và monitor được container đang chạy.

Đừng vội đi đến Dockerfile, Compose, K8s. Container cơ bản phải vững — không thì sau này nhìn vào K8s sẽ thấy magic, không hiểu vì sao.


Vấn đề trước khi có Docker

Hồi 2012, mình deploy 1 app PHP cho khách. Code chạy ngon trên laptop. SSH vào server prod, copy code lên, chạy:

php index.php
# PHP Parse error: syntax error, unexpected '?'

Lỗi syntax? Code mình test 100 lần rồi! Check version:

php -v
# PHP 5.4.16 on server
# PHP 7.0.8 on laptop

Laptop PHP 7, server PHP 5.4. Toán tử ?? (null coalescing) chỉ có từ PHP 7. Code valid trên laptop, invalid trên server.

Giải pháp lúc đó: upgrade PHP server → break 3 app cũ → rollback → tìm cách downgrade laptop → mất 2 ngày.

Đây là chuyện kinh điển: "trên máy mình chạy được". Container ra đời để kết thúc vấn đề này — đóng gói app + runtime + dependency thành 1 đơn vị, chạy ở đâu cũng như nhau.


Container vs VM — khác biệt cốt lõi

Phải hiểu rõ phần này trước khi đi tiếp. Nhiều người tưởng container là "VM nhẹ" — sai và dẫn tới hiểu lầm cả đời.

Virtual Machine

VM là một máy ảo được chạy trên OS chính. Có đầy đủ các thành phần như CPU, RAM, disk, kernel — tức là 1 hệ điều hành hoàn chỉnh chạy bên trong 1 hệ điều hành khác.

┌─────────────────────────────────────────┐
│           Host OS (CentOS 9)             │
│  ┌───────────────────────────────────┐  │
│  │   Hypervisor (VMware/KVM/VBox)    │  │
│  └───────────────────────────────────┘  │
│  ┌─────────────┐  ┌─────────────┐      │
│  │   VM 1      │  │   VM 2      │      │
│  │ ┌─────────┐ │  │ ┌─────────┐ │      │
│  │ │ App A   │ │  │ │ App B   │ │      │
│  │ ├─────────┤ │  │ ├─────────┤ │      │
│  │ │ Libs    │ │  │ │ Libs    │ │      │
│  │ ├─────────┤ │  │ ├─────────┤ │      │
│  │ │ Guest OS│ │  │ │ Guest OS│ │      │
│  │ │ (Ubuntu)│ │  │ │ (Debian)│ │      │
│  │ ├─────────┤ │  │ ├─────────┤ │      │
│  │ │ Kernel  │ │  │ │ Kernel  │ │      │
│  │ │ riêng   │ │  │ │ riêng   │ │      │
│  │ └─────────┘ │  │ └─────────┘ │      │
│  └─────────────┘  └─────────────┘      │
│  ┌───────────────────────────────────┐  │
│  │       Hardware (CPU, RAM, Disk)   │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Mỗi VM có kernel riêng — tức là khi mở 1 VM, bạn bật thêm 1 OS chạy song song với host. Tốn RAM, tốn CPU, khởi động chậm (vài chục giây đến vài phút).

Container

Container không phải là máy ảo. Chỉ là một tiến trình (process) chạy trên hệ điều hành. Bản thân container không có kernel hệ điều hành — nó chia sẻ kernel với host.

┌─────────────────────────────────────────┐
│           Host OS (CentOS 9)             │
│  ┌───────────────────────────────────┐  │
│  │       Docker Engine                │  │
│  └───────────────────────────────────┘  │
│  ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│  │ Container│ │ Container│ │Container│ │
│  │ ┌──────┐ │ │ ┌──────┐ │ │┌──────┐ │ │
│  │ │App A │ │ │ │App B │ │ ││App C │ │ │
│  │ ├──────┤ │ │ ├──────┤ │ │├──────┤ │ │
│  │ │Libs  │ │ │ │Libs  │ │ ││Libs  │ │ │
│  │ └──────┘ │ │ └──────┘ │ │└──────┘ │ │
│  └──────────┘ └──────────┘ └─────────┘ │
│  ┌───────────────────────────────────┐  │
│  │     Kernel (CHIA SẺ chung)         │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │       Hardware (CPU, RAM, Disk)   │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Vài đặc điểm:

  • Container không có kernel riêng — dùng chung kernel với host.
  • Process này được giới hạn tài nguyên sử dụng trên OS — qua cgroups.
  • Container dừng và thoát như đóng process — không có cơ chế "shutdown OS".

So sánh chi tiết

Tiêu chí VM Container
Kernel Mỗi VM có kernel riêng Chia sẻ kernel với host
Khởi động 30s - vài phút < 1 giây
Kích thước GB (cả OS) MB (chỉ app + libs)
RAM tốn Vài trăm MB - GB idle Vài MB idle
Cô lập Mạnh (kernel-level) Yếu hơn (process-level, namespace)
Run OS khác? Có (Windows trên Linux, ngược lại) Không (Linux container chỉ chạy trên Linux kernel)
Số lượng/host ~10-50 ~hàng trăm đến nghìn
Use case Cần OS khác, cô lập strict Đóng gói app, microservices

⚠️ Container không thay thế hoàn toàn VM. Vẫn cần VM khi:

  • Chạy OS khác (Windows app trên Linux host).
  • Yêu cầu cô lập security mạnh (multi-tenant).
  • Run kernel modules custom.

Production hiện đại thường lai: VM bên ngoài (cô lập tenant), container bên trong VM (deploy app).


Docker là gì

Docker là một nền tảng mã nguồn mở giúp đóng gói, phân phối và chạy ứng dụng trong các container. Container giúp đảm bảo rằng ứng dụng có thể chạy đồng nhất trên mọi môi trường (máy cá nhân, server, cloud...).

Docker không phải là container — Docker là tool quản lý container. Bản thân container technology là feature của Linux kernel (namespaces + cgroups) có từ trước Docker. Docker là layer phía trên giúp developer dùng dễ.


Kiến trúc Docker

Docker sử dụng kiến trúc client-server. Docker Client liên lạc với Docker Daemon, Docker Daemon thực hiện các tác vụ build, run, distribute container.

┌──────────────────────────────────────────────────────────────┐
│                    DOCKER HOST                                │
│                                                                │
│  ┌──────────────┐         ┌─────────────────────────────┐    │
│  │ Docker CLI   │ ──REST──▶│   Docker Daemon (dockerd)   │    │
│  │ (docker cmd) │   API    │   - Qun lý container       │    │
│  └──────────────┘          │   - Build image             │    │
│         ▲                  │   - Qun lý volume, network │    │
│         │                  └──────┬──────────────────────┘    │
│         │                         │                            │
│         │                         ▼                            │
│   user types:               ┌──────────┐   ┌──────────┐       │
│   docker run nginx          │container1│   │container2│       │
│                             └──────────┘   └──────────┘       │
└─────────────────────────────────────┬─────────────────────────┘
                                      │
                                      │ pull image
                                      ▼
                         ┌──────────────────────────┐
                         │   Docker Registry         │
                         │   (Docker Hub, AWS ECR)   │
                         │                           │
                         │   📦 nginx:latest         │
                         │   📦 mariadb:11           │
                         │   📦 your-app:v1.0        │
                         └──────────────────────────┘

Docker Engine

Docker Engine là phần mềm chính của Docker, bao gồm:

  • Docker Daemon (dockerd): chạy background, quản lý container, image, volume, network.
  • Docker CLI: command-line interface — tool docker mà bạn gõ trong terminal.
  • REST API: cho phép Docker Client (CLI hoặc bất kỳ tool nào) giao tiếp với Daemon.

Quan trọng: Daemon mới làm thật, CLI chỉ gửi lệnh qua API. Hiểu cái này để biết: lúc systemctl stop docker → daemon chết → CLI không làm được gì.

Docker Registry

Kho lưu trữ Docker Image. Ví dụ:

  • Docker Hub — public registry, mặc định khi pull (docker pull nginx).
  • AWS ECR — private registry trên AWS.
  • GitHub Container Registry — private registry trên GitHub.
  • Harbor, GitLab Registry — self-hosted.

Khi chạy container, nếu image chưa có trên máy, Docker sẽ tự động pull từ registry.

Docker Objects

4 đối tượng chính Docker quản lý:

  • Docker Image: tệp chứa mã nguồn, thư viện, môi trường và mọi cài đặt cần thiết để chạy app. Image là template tĩnh (như class trong OOP).

  • Docker Container: instance của image đang chạy. Hoạt động độc lập, nhẹ, chia sẻ kernel với host. Dễ di chuyển giữa môi trường. Container là instance (như object trong OOP).

  • Docker Volume: lưu trữ dữ liệu bên ngoài container, tránh mất dữ liệu khi container bị xóa.

  • Docker Network: quản lý cách các container giao tiếp với nhau hoặc với bên ngoài.

Quan hệ image-container:

       Image (template)
   nginx:latest (1 image)
            │
            │ docker run nginx  (chạy nhiều lần)
            ▼
    ┌───────┬───────┬───────┐
    ▼       ▼       ▼       ▼
  container container container container
   web1    web2    web3    web4

1 image có thể tạo ra nhiều container, mỗi container hoạt động độc lập.


Cơ chế hoạt động — Container chạy như thế nào

Khi bạn chạy docker run nginx:

1. Docker CLI gửi lệnh tới Docker Daemon (qua REST API)
        │
        ▼
2. Daemon kiểm tra image 'nginx' có trên máy không
        │
        ├── Có → bước 3
        └── Không → pull từ Docker Registry (Docker Hub)
        │
        ▼
3. Daemon tạo container timage, thiết lập:
   - Namespace (cô lập PID, Network, Filesystem, IPC...)
   - Cgroups (giới hạn CPU, RAM, IO)
        │
        ▼
4. Container chạy với environment riêng,
   nhưng vẫn dùng chung kernel với host

Namespace — cơ chế cô lập

Namespace là feature kernel Linux, cho phép cô lập tài nguyên giữa các process. Mỗi container có namespace riêng cho:

  • PID namespace — container chỉ thấy process của chính nó, không thấy process khác của host. PID 1 trong container không phải systemd, mà là app chính.
  • Network namespace — container có network stack riêng (IP, interface, routing table, port).
  • Mount namespace — container có filesystem riêng (qua image layers).
  • UTS namespace — hostname riêng.
  • IPC namespace — inter-process communication riêng.
  • User namespace — user/group ID riêng.

Visual: mỗi container như được "che" trong bong bóng riêng:

┌─────────────────── HOST OS (Linux Kernel) ──────────────────┐
                                                               
  PID Namespaces (mỗi container 1 view khác nhau):            
                                                               
  Container A view:        Container B view:    Host view:    
  ┌──────────────┐         ┌──────────────┐    ┌────────────┐│
   PID 1: nginx           PID 1: mysql     │PID 1: systd││
   PID 8: workr           PID 5: mysqld│    │PID 234: ngx││
   PID 9: workr                            │PID 456: mys││
  └──────────────┘         └──────────────┘    │PID 999: ssh││
                                                 └────────────┘│
                                                               
  Network Namespaces:                                          
  ┌──────────────┐         ┌──────────────┐    ┌────────────┐│
   eth0: 172.0.2          eth0: 172.0.3    │eth0: 10.0.5││
   lo: 127.0.0.1          lo: 127.0.0.1    │docker0:172 ││
  └──────────────┘         └──────────────┘    └────────────┘│
                                                               
  Mount Namespaces:                                            
  ┌──────────────┐         ┌──────────────┐    ┌────────────┐│
  │/: nginx FS            │/: mysql FS       │/: host FS  ││
  │(read-only)            │(read-only)                   ││
  └──────────────┘         └──────────────┘    └────────────┘│
└──────────────────────────────────────────────────────────────┘

   CÙNG kernel, KHÁC view  mỗi container như 1 mini-server riêng
# Trong container nginx:
docker exec nginx-1 ps aux
# UID   PID  PPID  ...  COMMAND
# root    1     0  ...  nginx: master process
# nginx   8     1  ...  nginx: worker process
# nginx   9     1  ...  nginx: worker process

# Container chỉ thấy nginx — không thấy systemd, sshd, etc của host

Cgroups — giới hạn tài nguyên

Cgroups (Control Groups) là feature kernel Linux để giới hạn CPU, RAM, IO của process.

Khi tạo container, Docker đặt nó vào 1 cgroup → giới hạn tài nguyên:

docker run --memory=512m --cpus=2 nginx
# Container này chỉ được dùng tối đa 512MB RAM, 2 CPU cores

Không có cgroups → 1 container ngốn hết RAM host → mọi container khác chết. Đây là lý do luôn đặt resource limit trong production.

Visual cgroups:

              Total Host Resources
              ┌─────────────────────────────┐
              │  16 CPU cores               │
              │  64 GB RAM                  │
              └─────────────────────────────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
        ┌────────┐  ┌────────┐  ┌────────┐
        │Cgroup A│  │Cgroup B│  │Cgroup C│
        │        │  │        │  │        │
        │CPU: 2  │  │CPU: 4  │  │CPU: 1  │
        │RAM: 4GB│  │RAM: 8GB│  │RAM: 2GB│
        └───┬────┘  └───┬────┘  └───┬────┘
            │           │            │
            ▼           ▼            ▼
        Container A  Container B  Container C
        (nginx)      (mysql)      (redis)

  Tng cp: 7 CPU + 14GB RAM (còn 9 CPU + 50GB free)
  
  Nếu A ngn 100% CPUchtn 2 cores ca nó.
  B, C không bị ảnh hưởng.

Kernel sharing — không phải VM

Vì container chia sẻ kernel host:

  • ✅ Khởi động nhanh (không boot OS).
  • ✅ Nhẹ (vài MB thay vì GB).
  • Không chạy Windows container trên Linux host (kernel khác hoàn toàn).
  • Container Linux chạy trên macOS/Windows thực ra dùng VM ẩn (Docker Desktop tạo VM Linux nhỏ bên dưới).

Tại sao nên dùng Docker

5 lý do chính:

  1. Chạy đồng nhất trên mọi môi trường — laptop, server, cloud đều y hệt.
  2. Nhẹ hơn VM — khởi động giây, RAM idle vài MB.
  3. Dễ scale & triển khai — chạy thêm container = vài giây.
  4. Quản lý & rollback dễ — image có version, deploy sai rollback bằng pull image cũ.
  5. Dễ chia sẻ & CI/CD — push image lên registry, mọi nơi pull về dùng.

Khi nào nên dùng Docker

  • ✅ Cần môi trường đồng nhất giữa DEV, TEST, PROD.
  • ✅ Cần triển khai nhanh và tự động.
  • ✅ Làm việc với microservices (mỗi service 1 container).
  • ✅ Muốn tiết kiệm tài nguyên hơn so với VM.
  • ✅ Cần CI/CD tự động (build image trong pipeline).

Khi nào không nên dùng Docker:

  • App single user trên desktop (Word, Photoshop) — không cần.
  • Workload cần truy cập hardware trực tiếp (driver kernel).
  • Compliance yêu cầu cô lập strict (multi-tenant security) — dùng VM thay.

Cài đặt Docker trên CentOS 9

sudo dnf update -y

# Add Docker official repo
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# Cài Docker Engine
sudo dnf install -y docker-ce docker-ce-cli containerd.io

# Start & enable
sudo systemctl start docker
sudo systemctl enable docker

# Verify
sudo docker version
sudo docker run hello-world

Output hello-world cuối:

Hello from Docker!
This message shows that your installation appears to be working correctly.

→ Docker đã chạy. Đây cũng là test container đầu tiên của bạn.

Cho user thường (không cần sudo)

Mặc định docker cần sudo. Thêm user vào group docker:

sudo usermod -aG docker $USER
newgrp docker

docker version   # Không cần sudo nữa

⚠️ Security warning: user trong group docker về cơ bản có quyền root tương đương (vì Docker daemon chạy root, ai gọi được daemon = root). Production phải cẩn thận group này.

Câu lệnh check trạng thái

docker info       # Trạng thái chung của Docker Engine
docker --help     # Trợ giúp lệnh

Cấu trúc cú pháp lệnh Docker

docker <management_command> <command> [options]

3 phần:

  • Management command — đối tượng chính cần quản trị: container, image, network, volume.
  • Command — chức năng phụ gắn vào đối tượng chính: run, ls, stop, rm.
  • Options — tham số phụ: -d, -p, --name.

Ví dụ:

docker container run --publish 8080:80 --detach --name web nginx
        ↑           ↑          ↑              ↑          ↑      ↑
   management    command     option         option    option   image

⚠️ Legacy syntax vẫn dùng được nhưng không khuyến nghị:

docker run -d -p 8080:80 nginx   # cũ, không có "container"
docker container run -d -p 8080:80 nginx   # mới, đầy đủ

2 cái cho ra kết quả như nhau. Modern Docker khuyến khích explicit container/image/network/volume để dễ đọc.


Container đầu tiên

docker container run --publish 8080:80 --detach nginx

Phân tích:

  • docker container run — tạo container mới và chạy ngay.
  • --publish 8080:80 (hoặc -p) — phơi (publish) port. Map port 8080 của host → port 80 của container. User truy cập http://<host>:8080 sẽ vào nginx trong container.
  • --detach (hoặc -d) — chạy ẩn (background), không chiếm terminal.
  • nginx — tên image. Docker tự pull từ Docker Hub nếu chưa có.

Đặt tên container:

docker container run --publish 8080:80 --name web-1 --detach nginx

--name web-1 — đặt tên web-1 thay vì để Docker tự sinh tên random.

Test: vào browser http://localhost:8080 → thấy trang welcome của nginx.

Lệnh kiểm tra container

docker container ls         # Liệt kê container đang chạy
docker container ls -a      # Liệt kê tất cả (kể cả đã dừng)

Output:

CONTAINER ID   IMAGE   COMMAND                  STATUS         PORTS                  NAMES
abc123def456   nginx   "/docker-entrypoint.…"   Up 2 minutes   0.0.0.0:8080->80/tcp   web-1

Dừng/start/xoá container

docker container stop web-1       # Dừng (gracefully)
docker container start web-1      # Start lại
docker container restart web-1    # Restart

docker container rm web-1         # Xoá (phải stop trước)
docker container rm -f web-1      # Force xoá (kể cả đang chạy)

Có thể dùng container ID hoặc tên. ID chỉ cần vài ký tự đầu (unique là đủ):

docker container stop abc1   # OK nếu chỉ có 1 container ID bắt đầu bằng abc1

Container Lifecycle — vòng đời

                ┌───────────────────────────────────────┐
                │                                        │
                ▼                                        │
        ┌──────────────┐    docker create     ┌─────────────┐
        │   (no exist) │ ───────────────────► │   Created   │
        └──────────────┘                       └──────┬──────┘
                ▲                                     │
                │                                     │ docker start
                │ docker rm                           ▼
                │                              ┌─────────────┐
                │                  ┌──pause───►│   Running   │◄──┐
                │                  │           └──────┬──────┘   │
                │           ┌──────┴──────┐          │           │
                │           │   Paused    │          │ docker    │
                │           └──────┬──────┘          │ stop      │ docker
                │                  │ unpause          │           │ start
                │                  └──────────────────┤           │
                │                                     ▼           │
                │                              ┌─────────────┐    │
                └──────────────────────────────│   Stopped   │────┘
                              docker rm        └─────────────┘
                                                      ▲
                                                      │ (auto khi
                                                      │  app exit
                                                      │  hoặc OOM)
                              ┌──────────────────────────┐
        docker run ──────────►│ Created → Running        │
        (shortcut combo)      └──────────────────────────┘

5 trạng thái:

  • Created — đã docker create, chưa start.
  • Running — đang chạy (docker run = create + start).
  • Paused — tạm dừng, RAM giữ nguyên (docker pause).
  • Stopped — đã dừng, RAM mất. Có thể start lại.
  • Removed — đã rm, biến mất hoàn toàn.

docker run = shortcut của docker create + docker start. Hiếm khi dùng create riêng (trừ khi cần prepare container nhưng chưa muốn chạy).


Image vs Container — đừng nhầm

Image Container
Ứng dụng (file đóng gói) Process khi chạy ứng dụng đó
Tĩnh, không thay đổi Có state, đang chạy
Lưu trên registry Chạy trên host
1 image Tạo được nhiều container từ 1 image
# 1 image, 3 container chạy song song
docker container run --name web-1 -p 8081:80 -d nginx
docker container run --name web-2 -p 8082:80 -d nginx
docker container run --name web-3 -p 8083:80 -d nginx

docker container ls
# Thấy 3 container, đều từ image nginx, ports khác nhau

Liệt kê image trên máy:

docker image ls
# REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
# nginx        latest    abc123...      2 weeks ago   142MB
# hello-world  latest    def456...      6 months ago  13.3kB

Vào shell container — debug bên trong

Có 2 trường hợp:

TH1: Container đang chạy — vào shell hiện tại

Dùng docker container exec:

docker container exec -it web-1 bash
# root@abc123:/#  ← bạn đang ở trong container

# Test:
ls /
ps aux              # chỉ thấy nginx process, không thấy host
exit                # thoát shell, container vẫn chạy
  • exec — execute command trong container đang chạy.
  • -it — interactive + tty (cần thiết để có shell tương tác).
  • bash — shell muốn vào.

⚠️ Một số image tối giản (alpine) không có bash, chỉ có sh:

docker container exec -it alpine-container sh

TH2: Tạo container mới và vào shell luôn

Dùng docker container run -it ... bash:

docker container run -it ubuntu bash
# root@abc123:/#

# Container này tạo riêng để debug, exit sẽ tắt luôn
  • Image OS (ubuntu, centos, debian) → bash là CMD mặc định, có thể vào shell ngay.
  • Image app (nginx, mariadb) → CMD mặc định là chạy app, phải thêm bash để override.

COMMAND — lệnh khởi tạo container

COMMAND là lệnh khởi tạo đầu tiên khi container chạy. Mỗi image có CMD default:

  • nginx → CMD = nginx -g 'daemon off;'
  • ubuntu → CMD = bash

Khi run, có thể override:

docker container run nginx              # CMD mặc định: chạy nginx
docker container run nginx bash         # Override: chạy bash thay vì nginx
                                         # → container exit ngay vì bash thoát
docker container run -it nginx bash     # Override + tty → vào shell

Thoát terminal container

2 cách thoát khi -it:

  • exit — thoát terminal VÀ tắt luôn container (vì shell là process chính, shell tắt → container tắt).
  • Ctrl+P, Ctrl+Q — detach, không tắt container. Container vẫn chạy background, bạn có thể docker attach lại sau.
docker container run -it ubuntu bash
# root@abc:/# echo "đang trong container"
# root@abc:/# [Ctrl+P, Ctrl+Q]    ← detach

docker container ls                # ubuntu vẫn chạy
docker container attach abc       # attach lại vào container

Lưu ý: chỉ image OS như ubuntu mới chạy được -it bash. Image như nginx đã chạy nginx rồi, override bash sẽ không có ý nghĩa nhiều (nginx không chạy).


Monitor container

Logs

docker container logs web-1            # Xem log
docker container logs -f web-1         # Follow (như tail -f)
docker container logs --tail 100 web-1 # 100 dòng cuối
docker container logs --since 5m web-1 # 5 phút gần đây

docker logs đọc stdout/stderr của process chính trong container. Đây là lý do app trong container nên log ra stdout, không ghi file (xem section "12-factor app").

Process list trong container

docker container top web-1
# UID    PID    PPID   ...   CMD
# root   1234   1200   ...   nginx: master
# nginx  1245   1234   ...   nginx: worker
# nginx  1246   1234   ...   nginx: worker

Hiển thị process đang chạy trong container (PID trong container namespace).

Lưu ý: PID này khác với PID host. Trên host, ps aux | grep nginx sẽ thấy PID khác:

ps aux | grep nginx
# root    25543  ...  nginx: master process

→ Container top thấy PID 1234, host ps thấy PID 25543 — cùng process, khác namespace nên PID khác nhau.

Performance — CPU, RAM, IO

docker container stats              # Live tất cả container
docker container stats web-1        # Live 1 container cụ thể
docker container stats --no-stream  # Snapshot, không live update

Output:

CONTAINER ID   NAME    CPU %   MEM USAGE / LIMIT   MEM %   NET I/O      BLOCK I/O
abc123         web-1   0.01%   3.7MiB / 1.93GiB    0.19%   1.2kB/0B     0B/0B

→ Container web-1 đang dùng 0.01% CPU, 3.7MB RAM. Rất nhẹ — đặc trưng container.

Inspect — xem cấu hình chi tiết

docker container inspect web-1

Output JSON dài, chứa toàn bộ config: IP, mount, network, env vars, command, restart policy... Dùng --format để filter:

docker container inspect --format '{{.NetworkSettings.IPAddress}}' web-1
# 172.17.0.2

docker container inspect --format '{{.State.Status}}' web-1
# running

Hữu ích trong script automation.


Câu chuyện thực tế

Lần đầu mình thuyết phục team chuyển sang Docker, sếp hỏi:

"Container có gì hay hơn VM? Tao thấy giống nhau mà?"

Mình demo:

Test 1: Khởi động.

# VM:
virsh start ubuntu-vm
# ... khoảng 25 giây sau ...
# VM sẵn sàng

# Container:
docker run -d --name test ubuntu sleep 3600
# < 1 giây

Sếp: "OK, container nhanh hơn."

Test 2: RAM idle.

# 1 VM Ubuntu chạy không làm gì
free -h trong VM
# Used: ~ 400MB

# 1 container Ubuntu chạy sleep
docker stats --no-stream test
# MEM USAGE: 1.5MiB

Sếp: "...container ngốn ít hơn 250 lần?"

Test 3: Scale.

# Tạo 50 container Ubuntu:
for i in $(seq 1 50); do
    docker run -d --name ub-$i ubuntu sleep 3600
done
# < 30 giây xong

free -h  # Host vẫn còn 80% RAM free

Sếp: "50 con? Trên VMware tao mở 5 con đã đầy RAM rồi."

Đó là moment quyết định chuyển stack sang Docker. 1 năm sau, team có 200+ container chạy trên 3 server, thay vì 30 VM trên 10 server. Tiết kiệm 70% hardware cost.

Bài học:

  1. Container khác VM bản chất, không chỉ "nhẹ hơn". Hiểu kernel sharing là gì → giải thích được hết các trade-off.

  2. Demo bằng số liệu, không phải lý thuyết. Sếp không quan tâm "namespace là gì", quan tâm "tiết kiệm bao nhiêu tiền".

  3. Container không thay thế VM hoàn toàn. Lúc đó vẫn giữ 5 VM cho legacy app + Windows services. Container không phải silver bullet.


Lab — thực hành đầy đủ

Lab 1: Run nginx + browser test

# Pull và run
docker container run --publish 8080:80 --name web --detach nginx

# Test
curl http://localhost:8080
# <html>...Welcome to nginx!...

# Xem log
docker container logs web

# Xem stats
docker container stats --no-stream web

# Stop và cleanup
docker container stop web
docker container rm web

Lab 2: Vào shell container

# Chạy ubuntu, vào shell
docker container run -it --name lab-ubuntu ubuntu bash

# Trong container:
apt update && apt install -y curl
curl https://google.com -I
# HTTP/2 301

# Thoát mà giữ container chạy: Ctrl+P, Ctrl+Q

# Bên ngoài:
docker container ls
# Thấy lab-ubuntu vẫn UP

# Attach lại:
docker container attach lab-ubuntu

# Exit hẳn:
exit
docker container ls -a
# Status: Exited

Lab 3: Nhiều container từ 1 image

# 3 nginx khác port
docker container run -d -p 8081:80 --name web-1 nginx
docker container run -d -p 8082:80 --name web-2 nginx
docker container run -d -p 8083:80 --name web-3 nginx

docker container ls
# 3 container, đều IMAGE nginx

# Customize từng cái:
docker container exec web-1 sh -c "echo 'Web 1' > /usr/share/nginx/html/index.html"
docker container exec web-2 sh -c "echo 'Web 2' > /usr/share/nginx/html/index.html"
docker container exec web-3 sh -c "echo 'Web 3' > /usr/share/nginx/html/index.html"

# Test
curl http://localhost:8081   # Web 1
curl http://localhost:8082   # Web 2
curl http://localhost:8083   # Web 3

# Cleanup
docker container rm -f web-1 web-2 web-3

Lab 4: Resource limit

# Giới hạn 100MB RAM, 0.5 CPU
docker container run -d \
    --name limited \
    --memory=100m \
    --cpus=0.5 \
    nginx

docker container stats --no-stream limited
# MEM LIMIT: 100MiB
# CPU %: max 50%

docker container rm -f limited

Lab 5: Monitor — quan sát PID khác giữa host và container

docker container run -d --name observed nginx

# PID trong container
docker container top observed
# PID 1: nginx master

# PID trên host
docker container inspect --format '{{.State.Pid}}' observed
# 12345 (ví dụ)

# Trên host, process này tồn tại
ps aux | grep 12345

# Cleanup
docker container rm -f observed

→ Cùng nginx, container thấy PID 1, host thấy PID 12345. PID namespace là đây.


Pitfalls

  1. Tưởng container là VM nhỏ — dẫn tới nhiều hiểu lầm. Container = process bị cô lập, không có kernel riêng.

  2. Quên -d (detach) → container chạy foreground, đóng terminal là container tắt. Production luôn -d.

  3. Quên -p map port → service trong container không tiếp cận được từ ngoài. docker run nginx (không -p) → port 80 trong container, nhưng host không expose.

  4. Quên --name → Docker tự sinh tên random kiểu vibrant_einstein → khó tham chiếu, khó script.

  5. docker run thay vì docker start cho container đã tồn tại → tạo container mới (trùng tên fail). Đã có container thì docker start <name>, đừng docker run lại.

  6. exit trong shell container → tắt luôn container. Muốn giữ chạy thì Ctrl+P, Ctrl+Q.

  7. Mount sai path → app trong container không thấy data. Sẽ học chi tiết ở Bài 03.

  8. Không set resource limit → 1 container ngốn hết RAM, mọi container khác chết. Production phải --memory--cpus.

  9. Image to GB không hiểu sao → dùng base image lớn (ubuntu 70MB) thay vì alpine (5MB). Bài 02 sẽ tối ưu.

  10. User trong group docker mà không biết = root → security risk nghiêm trọng. Production limit group này.

  11. docker logs không thấy gì → app log vào file thay vì stdout. Container app phải log stdout/stderr (12-factor app).

  12. Container Linux trên Windows/Mac thực ra dùng VM ẩn — Docker Desktop tạo VM Linux nhỏ. Performance kém hơn Linux native.


Tóm tắt

  • Container ≠ VM. Container là process bị cô lập (qua namespace + cgroups), chia sẻ kernel với host.
  • Docker = tool quản lý container, không phải bản thân container technology.
  • Kiến trúc Docker: Client (CLI) ↔ Daemon ↔ Registry. CLI gửi lệnh, Daemon làm việc thật.
  • 4 Docker objects: Image (template), Container (instance), Volume (data), Network (kết nối).
  • docker container run -d -p HOST:CONTAINER --name X image — chạy container thông dụng nhất.
  • docker container exec -it X bash — vào shell container đang chạy.
  • exit tắt container, Ctrl+P,Q detach. Nhớ kỹ.
  • docker logs -f, docker stats, docker top, docker inspect — bộ tool monitor cơ bản.
  • PID 1 trong container = app chính, không phải systemd. Container không có init system.
  • Production luôn --memory, --cpus để giới hạn tài nguyên.

Bài tiếp: 02 — Image & Dockerfile: từ Docker Hub đến image của riêng mình.

Bài tiếp theo →
Bài 02
Zalo tư vấn