Skip to content

Docker Architecture — Client, Daemon, Registry

Tình huống mở đầu

Thứ Hai đầu tuần, team bạn deploy microservice mới lên staging. Bạn gõ docker run myapp:v2.1 — terminal trả về container ID trong chưa đầy 2 giây. Nhưng đồng nghiệp ngồi cạnh chạy cùng lệnh lại nhận lỗi Cannot connect to the Docker daemon. Cả hai đều cài Docker, cả hai đều có quyền — vậy tại sao một người chạy được, một người thì không?

Câu trả lời nằm ở kiến trúc client-server mà Docker sử dụng. Hiểu rõ kiến trúc này không chỉ giúp bạn debug nhanh hơn, mà còn là nền tảng để vận hành container ở production — nơi một registry sập có thể khiến 50 microservices không thể deploy.

🎯 Mục tiêu

Sau bài này, bạn sẽ:

  • Vẽ được kiến trúc Docker 3 tầng: Client → Daemon → Container Runtime
  • Phân biệt rõ ràng Image, Container, và Registry — bộ ba cốt lõi
  • Hiểu luồng xử lý khi gõ docker run nginx từ đầu đến cuối
  • Sử dụng docker pull, docker images, docker inspect để khám phá image layers
  • Nhận diện anti-pattern khi dùng tag :latest trong CI/CD pipeline

🏗️ Phần 1: Concept — Kiến trúc Docker 3 tầng

1.1 Mô hình Client-Server

Docker không phải một chương trình monolith. Nó hoạt động theo mô hình client-server, trong đó:

  • Docker Client (docker CLI) — giao diện bạn tương tác hàng ngày
  • Docker Daemon (dockerd) — "bộ não" chạy ngầm, quản lý mọi thứ
  • Container Runtime (containerd + runc) — thực thi container ở mức OS
┌─────────────────────────────────────────────────────────────────┐
│                        MÁY TÍNH CỦA BẠN                        │
│                                                                 │
│  ┌──────────────┐     REST API       ┌──────────────────────┐  │
│  │              │  (/var/run/docker   │                      │  │
│  │  docker CLI  │───.sock hoặc TCP)──▶│   Docker Daemon      │  │
│  │  (client)    │                     │   (dockerd)          │  │
│  │              │◀────── JSON ────────│                      │  │
│  └──────────────┘    Response         │  ┌────────────────┐  │  │
│                                       │  │  Image Store   │  │  │
│                                       │  │  Network Mgr   │  │  │
│                                       │  │  Volume Mgr    │  │  │
│                                       │  └────────────────┘  │  │
│                                       └──────────┬───────────┘  │
│                                                  │              │
│                                         gRPC API │              │
│                                                  ▼              │
│                                       ┌──────────────────────┐  │
│                                       │    containerd        │  │
│                                       │  (container runtime) │  │
│                                       │         │            │  │
│                                       │         ▼            │  │
│                                       │  ┌────────────┐     │  │
│                                       │  │   runc     │     │  │
│                                       │  │ (OCI spec) │     │  │
│                                       │  └────────────┘     │  │
│                                       └──────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

1.2 Giao tiếp Client Daemon

Docker Client và Daemon giao tiếp qua REST API:

Nền tảngKênh giao tiếpĐường dẫn / Địa chỉ
LinuxUnix socket/var/run/docker.sock
macOS (Docker Desktop)Unix socket (trong VM)/var/run/docker.sock
Windows (Docker Desktop)Named pipe//./pipe/docker_engine
RemoteTCPtcp://host:2376 (TLS)

💡 Tại sao lại là Unix socket?

Unix socket nhanh hơn TCP vì không cần qua network stack. Đồng thời, file permission trên socket (srw-rw----) kiểm soát ai được phép giao tiếp với daemon — đây là lý do bạn cần thêm user vào group docker hoặc dùng sudo.

1.3 Docker Daemon — "Bộ não" của hệ thống

Docker Daemon (dockerd) là process chạy nền, chịu trách nhiệm:

Trách nhiệmChi tiết
Image managementPull, build, cache, garbage collect
Container lifecycleCreate, start, stop, restart, kill, remove
NetworkTạo và quản lý bridge, overlay, macvlan networks
VolumeTạo và mount volume cho persistent data
SecurityCấu hình AppArmor, seccomp, user namespace

1.4 Điều gì xảy ra khi bạn gõ docker run nginx?

Đây là luồng xử lý từng bước — nắm rõ flow này giúp bạn debug chính xác khi có lỗi:

Bước 1: docker CLI parse lệnh


Bước 2: CLI gửi POST /containers/create đến Daemon


Bước 3: Daemon kiểm tra image "nginx:latest" có trong local cache?

         ├── CÓ → Dùng image từ cache

         └── KHÔNG → Pull từ Docker Hub (registry.docker.io)


                      Download từng layer (parallel)


                      Verify SHA256 digest mỗi layer


                      Lưu vào local image store


Bước 4: Daemon yêu cầu containerd tạo container


Bước 5: containerd gọi runc để tạo process cô lập
         (namespaces + cgroups + chroot)


Bước 6: runc khởi chạy process nginx bên trong container


Bước 7: CLI nhận container ID → hiển thị ra terminal

⚠️ Lưu ý quan trọng

Bước 3 có thể chậm đáng kể nếu image lớn và chưa có cache. Image nginx khoảng 70MB, nhưng image node:18 có thể lên tới 350MB+. Đây là lý do bạn nên pre-pull image trên CI runner hoặc dùng base image nhỏ (Alpine).


🧠 Phần 2: Concept — Bộ ba Image / Container / Registry

2.1 Mô hình tư duy: OOP Docker

Nếu bạn đã quen với lập trình hướng đối tượng, hãy ánh xạ như sau:

Khái niệm OOPKhái niệm DockerGiải thích
Class (lớp)ImageTemplate chỉ đọc, định nghĩa "ứng dụng trông như thế nào"
Object (đối tượng)ContainerInstance đang chạy, có state riêng, writable layer
new ClassName()docker run imageTạo instance mới từ template
Package Registry (npm, PyPI)Docker RegistryNơi lưu trữ và phân phối template
Garbage Collectiondocker rm / docker rmiDọn dẹp instance / template không dùng

2.2 Image — Template chỉ đọc, xếp lớp

Image là read-only template được tạo thành từ nhiều layer chồng lên nhau:

┌───────────────────────────────────┐
│  Layer 4: COPY app.js /app/       │  ← Bạn thêm code
├───────────────────────────────────┤
│  Layer 3: RUN npm install         │  ← Cài dependencies
├───────────────────────────────────┤
│  Layer 2: RUN apt-get update      │  ← Cập nhật packages
├───────────────────────────────────┤
│  Layer 1: FROM node:18-alpine     │  ← Base image (OS + Node.js)
└───────────────────────────────────┘
        Image = Tổng hợp các layer
         (Chỉ đọc, không thể sửa)

Đặc điểm quan trọng:

  • Mỗi layer là diff so với layer trước — giống git commit
  • Layer được chia sẻ giữa các image — nếu 10 image cùng dùng node:18-alpine, layer đó chỉ lưu một lần trên disk
  • Image được định danh bởi SHA256 digest (ví dụ: sha256:a1b2c3d4...), không phải tên tag

2.3 Container — Instance đang chạy

Container là một process cô lập chạy từ image, với một writable layer ở trên cùng:

┌─────────────────────────────────────┐
│  Writable Layer (Container Layer)   │  ← Dữ liệu runtime, logs, temp files
│  (Mất khi container bị xoá!)       │
├═════════════════════════════════════┤
│  Layer 4: COPY app.js /app/         │
├─────────────────────────────────────┤  Image Layers
│  Layer 3: RUN npm install           │  (Read-Only)
├─────────────────────────────────────┤
│  Layer 2: RUN apt-get update        │
├─────────────────────────────────────┤
│  Layer 1: FROM node:18-alpine       │
└─────────────────────────────────────┘

Một image → nhiều container. Giống như từ một class User, bạn tạo được 100 đối tượng user1, user2, ... — mỗi container có PID riêng, filesystem riêng, network namespace riêng.

2.4 Registry — "App Store" cho Docker Images

Registry là kho lưu trữ và phân phối image. Cơ chế push / pull tương tự git push / git pull:

┌──────────────┐         docker push         ┌──────────────────────┐
│              │ ──────────────────────────▶  │                      │
│  Dev Machine │                              │   Docker Registry    │
│  (local)     │ ◀──────────────────────────  │   (Docker Hub /      │
│              │         docker pull           │    ECR / GCR / GHCR) │
└──────────────┘                              └──────────────────────┘
       │                                                │
       │  docker run                                    │
       ▼                                                │
┌──────────────┐                              ┌──────────────────────┐
│  Container   │                              │  Production Server   │
│  (running)   │                              │  (docker pull → run) │
└──────────────┘                              └──────────────────────┘

Các registry phổ biến:

RegistryURLDùng khi
Docker Hubhub.docker.comImage công cộng (nginx, postgres, redis)
Amazon ECR*.dkr.ecr.*.amazonaws.comWorkload trên AWS
Google GCR / Artifact Registrygcr.io / *-docker.pkg.devWorkload trên GCP
GitHub GHCRghcr.ioOpen source, GitHub Actions CI/CD
Self-hostedHarbor, Nexus, GitLab RegistryBảo mật nội bộ, compliance

2.5 Sơ đồ tổng hợp: Image Container Registry

                    ┌─────────────────────┐
                    │   DOCKER REGISTRY   │
                    │   (Docker Hub, ECR) │
                    └────────┬────────────┘

                  docker pull│  docker push

                    ┌────────▼────────────┐
                    │      IMAGE          │
                    │  (read-only layers) │
                    │  nginx:1.25-alpine  │
                    └────────┬────────────┘

                  docker run │  (có thể tạo nhiều)

              ┌──────────────┼──────────────┐
              │              │              │
     ┌────────▼──────┐ ┌────▼───────┐ ┌───▼─────────┐
     │  Container A  │ │ Container B│ │ Container C  │
     │  (port 8080)  │ │ (port 8081)│ │ (port 8082)  │
     │  + writable   │ │ + writable │ │ + writable   │
     │    layer       │ │   layer    │ │   layer      │
     └───────────────┘ └────────────┘ └──────────────┘

⚙️ Phần 3: Syntax — Khám phá Image với CLI

3.1 docker pull — Tải image từ registry

bash
# Pull image với tag cụ thể (KHUYẾN NGHỊ)
docker pull nginx:1.25-alpine

# Pull image với digest chính xác (CHẮC CHẮN NHẤT)
docker pull nginx@sha256:a1b2c3d4e5f6...

# Pull từ registry khác Docker Hub
docker pull ghcr.io/my-org/my-app:v2.1.0

3.2 docker images — Liệt kê image trên máy

bash
# Xem tất cả image
docker images

# Output mẫu:
# REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
# nginx        1.25-alpine    a1b2c3d4e5f6   2 weeks ago    43.2MB
# nginx        latest         f7g8h9i0j1k2   2 weeks ago    192MB
# node         18-alpine      l3m4n5o6p7q8   5 days ago     177MB

# Lọc theo repository
docker images nginx

# Xem image ID đầy đủ (không truncate)
docker images --no-trunc

💡 Đọc output docker images

Chú ý cột SIZEnginx:1.25-alpine chỉ 43MB trong khi nginx:latest lên tới 192MB. Đó là vì Alpine Linux dùng musl thay vì glibc, giảm đáng kể kích thước base image. Trong production, mỗi MB đều quan trọng khi bạn scale tới hàng trăm container.

3.3 docker inspect — Xem chi tiết bên trong image

bash
# Xem toàn bộ metadata của image
docker inspect nginx:1.25-alpine

# Xem chỉ các layer (RootFS)
docker inspect nginx:1.25-alpine --format '{{json .RootFS.Layers}}' | python -m json.tool

# Xem architecture
docker inspect nginx:1.25-alpine --format '{{.Architecture}}'

# Xem exposed ports
docker inspect nginx:1.25-alpine --format '{{json .Config.ExposedPorts}}'

Output mẫu cho layers:

json
[
    "sha256:4693057ce236...",
    "sha256:2e7e0145b196...",
    "sha256:9a7c7e65d0c1...",
    "sha256:b3f18e342c15...",
    "sha256:e15c3d1e0487..."
]

Mỗi phần tử trong mảng là digest SHA256 của một layer — đây chính là cơ chế content-addressable storage mà Docker dùng.


🔬 Phần 4: Lab — Thực hành khám phá Docker Architecture

Lab 1: Pull, Inspect, Run

Tình huống: Bạn vừa join team và cần hiểu image nginx đang dùng trong production.

bash
# Bước 1: Pull image cụ thể
docker pull nginx:1.25-alpine

# Bước 2: Kiểm tra số lượng layer
docker inspect nginx:1.25-alpine --format '{{len .RootFS.Layers}}' 
# → 7 (hoặc tương tự tuỳ version)

# Bước 3: Xem history — mỗi instruction trong Dockerfile tạo 1 layer
docker history nginx:1.25-alpine

# Bước 4: Chạy container
docker run -d --name web-test -p 8080:80 nginx:1.25-alpine

# Bước 5: Kiểm tra container đang chạy
docker ps

# Bước 6: Kiểm tra response
curl http://localhost:8080
# → Welcome to nginx!

# Bước 7: Xem chi tiết container (chú ý writable layer)
docker inspect web-test --format '{{json .GraphDriver.Data}}' | python -m json.tool

Lab 2: So sánh hai tag cùng image

bash
# Pull cả hai tag
docker pull nginx:1.25-alpine
docker pull nginx:latest

# So sánh kích thước
docker images nginx --format "table {{.Tag}}\t{{.Size}}\t{{.ID}}"
# TAG            SIZE      IMAGE ID
# 1.25-alpine    43.2MB    a1b2c3d4e5f6
# latest         192MB     f7g8h9i0j1k2

# So sánh số layer
echo "Alpine layers: $(docker inspect nginx:1.25-alpine --format '{{len .RootFS.Layers}}')"
echo "Latest layers: $(docker inspect nginx:latest --format '{{len .RootFS.Layers}}')"

Lab 3: Xác minh layer sharing

bash
# Pull hai image cùng base
docker pull node:18-alpine
docker pull node:20-alpine

# Xem layers của cả hai
docker inspect node:18-alpine --format '{{json .RootFS.Layers}}' > /tmp/layers18.json
docker inspect node:20-alpine --format '{{json .RootFS.Layers}}' > /tmp/layers20.json

# So sánh — các layer chung sẽ trùng SHA256
diff /tmp/layers18.json /tmp/layers20.json

💡 Layer sharing tiết kiệm gì?

Nếu 10 microservices đều dùng node:18-alpine làm base, layer Alpine Linux (~5MB) chỉ lưu một lần trên disk. Trên production cluster với 50 services, layer sharing có thể tiết kiệm hàng GB disk và giảm thời gian pull đáng kể.

Dọn dẹp sau lab:

bash
docker stop web-test && docker rm web-test

🧩 Phần 5: Fast Exercise — Docker Daemon chạy ở đâu?

🧠 Quiz — 🎯 Scenario: Docker Desktop trên macOS

Câu hỏi: Khi bạn dùng Docker Desktop trên macOS (chip Intel hoặc Apple Silicon), Docker Daemon (dockerd) chạy ở đâu?

  • [ ] A. Trực tiếp trên macOS, giống một ứng dụng native
  • [ ] B. Trong một container Docker khác (Docker-in-Docker)
  • [x] C. Bên trong một Linux VM nhẹ được Docker Desktop quản lý
  • [ ] D. Trên cloud server của Docker Inc.

Giải thích: Docker daemon yêu cầu Linux kernel để tạo namespaces và cgroups. macOS dùng kernel XNU (không phải Linux), nên Docker Desktop tạo một lightweight Linux VM (dùng Apple Virtualization Framework trên Apple Silicon, hoặc HyperKit trên Intel). Docker CLI trên macOS giao tiếp với daemon bên trong VM qua Unix socket được forward.

Hệ quả thực tế:

  • File mount từ macOS vào container phải đi qua VM → chậm hơn so với Linux native
  • Memory cấp cho Docker Desktop thực chất là cấp cho VM
  • docker info sẽ hiện Operating System: Docker Desktop nhưng kernel là Linux

🧠 Quiz — 🎯 Câu hỏi nhanh 2

Câu hỏi: Hai container chạy từ cùng một image có chia sẻ image layers không?

  • [x] A. Có — image layers là read-only, được chia sẻ. Mỗi container chỉ thêm writable layer riêng
  • [ ] B. Không — mỗi container copy toàn bộ image layers cho riêng mình
  • [ ] C. Chỉ chia sẻ layer đầu tiên (base image)
  • [ ] D. Tuỳ thuộc cấu hình storage driver

Giải thích: Docker sử dụng union filesystem (overlay2 trên Linux hiện đại). Tất cả image layers là read-only và được chia sẻ giữa mọi container chạy từ cùng image. Mỗi container chỉ cần thêm một thin writable layer (~vài KB ban đầu) ở trên cùng. Đây là lý do bạn có thể chạy 50 container từ cùng image mà không tốn 50x disk space.


🕳️ Phần 6: Sai lầm điển hình

⚠️ Cạm bẫy

Vấn đề: Cùng một image có thể có nhiều tag, và tag có thể bị ghi đè bất cứ lúc nào.

bash
# Cùng Image ID, hai tag khác nhau
docker tag nginx:1.25-alpine myregistry/nginx:stable
docker tag nginx:1.25-alpine myregistry/nginx:production

docker images | grep nginx
# REPOSITORY            TAG          IMAGE ID
# nginx                 1.25-alpine  a1b2c3d4e5f6
# myregistry/nginx      stable       a1b2c3d4e5f6  ← CÙNG ID!
# myregistry/nginx      production   a1b2c3d4e5f6  ← CÙNG ID!

Tại sao nguy hiểm? Tag :latest hoặc :stable có thể trỏ tới image khác nhau tuỳ thời điểm bạn pull. Hôm nay :latestnginx 1.25, tuần sau có thể là nginx 1.26 — và build của bạn sẽ chạy khác đi mà không ai biết.

Cách đúng: Luôn dùng digest hoặc version tag cụ thể trong production:

bash
# ❌ SAI — Không biết chính xác đang dùng version nào
docker pull nginx:latest

# ✅ ĐÚNG — Biết chính xác version
docker pull nginx:1.25.3-alpine

# ✅ CHẮC CHẮN NHẤT — Pin bằng digest
docker pull nginx@sha256:6a40ba4df4e8...

⚠️ Cạm bẫy

bash
# Kiểm tra Docker chiếm bao nhiêu disk
docker system df

# TYPE           TOTAL     ACTIVE    SIZE      RECLAIMABLE
# Images         47        5         12.4GB    9.8GB (79%)
# Containers     12        3         1.2GB     800MB (66%)
# Build Cache    -         -         3.5GB     3.5GB (100%)

# Dọn dẹp an toàn (chỉ xoá unused)
docker system prune -f

# Dọn dẹp kỹ hơn (bao gồm unused volumes)
docker system prune --volumes -f

💡 Phần 7: Tips & Best Practices

💡 Performance — Chọn base image đúng cách

Nguyên tắc: Dùng image tag cụ thể, ưu tiên Alpine hoặc Distroless.

Base ImageKích thướcDùng khi
node:18~350MBDevelopment, cần đầy đủ tools
node:18-slim~180MBStaging, giảm bớt tools
node:18-alpine~130MBProduction, đủ cho hầu hết app
gcr.io/distroless/nodejs18~120MBProduction cao cấp, không shell, không package manager

Tại sao quan trọng?

  • Image nhỏ hơn = pull nhanh hơn = deploy nhanh hơn = scale nhanh hơn
  • Ít packages = ít CVE = ít bề mặt tấn công
  • Alpine dùng musl thay glibc — 99% Node.js/Go app chạy bình thường, nhưng một số native C extension có thể gặp vấn đề

🚨 Anti-pattern: Dùng :latest trong CI/CD

Vấn đề: Build hôm nay pass, build ngày mai fail — cùng code, cùng Dockerfile.

dockerfile
# ❌ ANTI-PATTERN — Build không reproducible
FROM node:latest
COPY . /app
RUN npm install

Tại sao? Tag :latestmutable — nó bị ghi đè mỗi khi có version mới. Khi CI/CD pipeline pull :latest ở hai thời điểm khác nhau, bạn có thể nhận được image khác nhau hoàn toàn.

Hậu quả thực tế: Team của bạn build thành công lúc 9h sáng. Lúc 2h chiều, upstream image update :latest từ Node 18.19 lên 18.20 — và npm install fail vì breaking change trong native addon build. Cả team mất 3 giờ debug mà root cause chỉ là base image thay đổi.

dockerfile
# ✅ ĐÚNG — Pin version cụ thể
FROM node:18.19.1-alpine3.19
COPY . /app
RUN npm install

# ✅ TỐT HƠN — Pin bằng digest cho reproducibility tuyệt đối
FROM node@sha256:abcdef1234567890...
COPY . /app
RUN npm install

🔍 Phần 8: Under the Hood — Cơ chế bên trong

8.1 Content-Addressable Storage

Docker lưu trữ mọi thứ (image, layer, config) dựa trên SHA256 digest — giá trị hash của nội dung:

Content: [bytes of layer data]


   SHA256 hash function


Digest: sha256:a3ed95caeb02ffe68cdd9fd...


Stored at: /var/lib/docker/overlay2/a3ed95caeb02.../

Lợi ích:

  • Deduplication: Hai layer có cùng nội dung → cùng digest → lưu một lần
  • Integrity: Nếu dữ liệu bị corrupt, digest sẽ không khớp → Docker phát hiện ngay
  • Caching: Pull image chỉ cần tải layer chưa có — Docker so sánh digest với local store

8.2 Union Filesystem (overlay2)

Docker dùng overlay2 (trên Linux hiện đại) để xếp chồng nhiều layer thành một filesystem thống nhất:

Container nhìn thấy:         Thực tế trên disk:
┌──────────────────┐
│ /app/server.js   │    ←── Writable layer (upperdir)
│ /app/node_modules│    ←── Image layer 3
│ /usr/local/bin/  │    ←── Image layer 2
│ /etc/alpine-rel  │    ←── Image layer 1 (lowerdir)
└──────────────────┘

overlay2 merge tất cả → Container process thấy 1 filesystem duy nhất

Copy-on-Write (CoW): Khi container muốn sửa file từ image layer (read-only), Docker copy file đó lên writable layer rồi mới sửa. File gốc trong image layer không bị thay đổi — đảm bảo các container khác không bị ảnh hưởng.

8.3 Layer Sharing — Tiết kiệm tài nguyên thực tế

Image A: node:18-alpine          Image B: custom-app:v2
┌─────────────────────┐          ┌─────────────────────┐
│ Layer 4: app code   │          │ Layer 5: app code   │
├─────────────────────┤          ├─────────────────────┤
│ Layer 3: npm install│          │ Layer 4: pip install│
├─────────────────────┤          ├─────────────────────┤
│ Layer 2: Node.js    │          │ Layer 3: Python     │
├─────────────────────┤          ├─────────────────────┤
│ Layer 1: Alpine 3.19│◀── SHARED ──▶│ Layer 1: Alpine 3.19│
└─────────────────────┘          └─────────────────────┘

Trên disk: Layer "Alpine 3.19" chỉ lưu MỘT lần (~5MB)

Phần 9: Checklist ghi nhớ

✅ Checklist triển khai

Kiến trúc Docker

  • [ ] Docker hoạt động theo mô hình client-server: CLI → Daemon → Runtime
  • [ ] Docker Daemon quản lý images, containers, networks, volumes
  • [ ] Client và Daemon giao tiếp qua REST API (Unix socket hoặc TCP)
  • [ ] containerd + runc là container runtime thực thi container

Bộ ba Image / Container / Registry

  • [ ] Image = template chỉ đọc, gồm nhiều layer chồng nhau
  • [ ] Container = process cô lập từ image + writable layer
  • [ ] Registry = kho lưu trữ và phân phối image (Docker Hub, ECR, GHCR)
  • [ ] Một image có thể tạo nhiều container (class → nhiều object)

Best Practices cho Production

  • [ ] Luôn dùng tag version cụ thể, KHÔNG dùng :latest trong CI/CD
  • [ ] Ưu tiên Alpine hoặc Distroless base image để giảm attack surface
  • [ ] Chạy docker system prune định kỳ để dọn dẹp disk
  • [ ] Dùng digest (sha256:...) khi cần reproducibility tuyệt đối

Debug & Inspection

  • [ ] docker inspect để xem metadata, layers, config của image/container
  • [ ] docker history để xem từng instruction trong Dockerfile đã tạo layer nào
  • [ ] docker system df để kiểm tra disk usage

🏢 Phần 10: Business Case — SaaS với 50+ Microservices

Tình huống: VNPay-like SaaS Platform

Hãy tưởng tượng bạn quản lý hạ tầng cho một SaaS fintech với 52 microservices: payment gateway, fraud detection, notification, reporting, user management, ...

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

  • Deploy bằng scp file JAR/ZIP lên server → không biết chính xác version nào đang chạy
  • "Máy tôi chạy được" nhưng staging fail → environment không đồng nhất
  • Rollback mất 30 phút vì phải build lại từ source

Giải pháp với Docker Registry + Image Versioning:

┌──────────────────────────────────────────────────────────────┐
│                     CI/CD Pipeline                            │
│                                                              │
│  git push → Build Image → Tag: payment-svc:v3.2.1-abc1234  │
│                              → Push to Private ECR Registry  │
│                                                              │
│  Staging:  docker pull payment-svc:v3.2.1-abc1234           │
│  Prod:     docker pull payment-svc:v3.2.1-abc1234           │
│                                                              │
│  CÙNG digest SHA256 → ĐẢM BẢO cùng binary                  │
└──────────────────────────────────────────────────────────────┘

Kết quả đạt được:

MetricTrước DockerSau Docker + Registry
Deploy time30 phút / service2 phút / service
Rollback time30 phút (rebuild)30 giây (pull version cũ)
Environment parityKhông đảm bảo100% — cùng image digest
Disk usage (50 services)50 × full OS installShared layers, ~60% tiết kiệm
Audit trailKhông cóMọi image có tag + digest + build metadata

Bài học rút ra:

  • Tagging convention: {service}:{semver}-{git-short-sha} → truy xuất ngược từ container đang chạy về commit code
  • Immutable tags: Không bao giờ ghi đè tag đã push — chỉ tạo tag mới
  • Registry replication: Private registry replicate giữa 2 region → nếu region chính sập, vẫn pull được image

🔗 Liên kết học tiếp