Giao diện
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 nginxtừ đầ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
:latesttrong 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 (
dockerCLI) — 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ảng | Kênh giao tiếp | Đường dẫn / Địa chỉ |
|---|---|---|
| Linux | Unix socket | /var/run/docker.sock |
| macOS (Docker Desktop) | Unix socket (trong VM) | /var/run/docker.sock |
| Windows (Docker Desktop) | Named pipe | //./pipe/docker_engine |
| Remote | TCP | tcp://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ệm | Chi tiết |
|---|---|
| Image management | Pull, build, cache, garbage collect |
| Container lifecycle | Create, start, stop, restart, kill, remove |
| Network | Tạo và quản lý bridge, overlay, macvlan networks |
| Volume | Tạo và mount volume cho persistent data |
| Security | Cấ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 OOP | Khái niệm Docker | Giải thích |
|---|---|---|
| Class (lớp) | Image | Template chỉ đọc, định nghĩa "ứng dụng trông như thế nào" |
| Object (đối tượng) | Container | Instance đang chạy, có state riêng, writable layer |
| new ClassName() | docker run image | Tạo instance mới từ template |
| Package Registry (npm, PyPI) | Docker Registry | Nơi lưu trữ và phân phối template |
| Garbage Collection | docker rm / docker rmi | Dọ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:
| Registry | URL | Dùng khi |
|---|---|---|
| Docker Hub | hub.docker.com | Image công cộng (nginx, postgres, redis) |
| Amazon ECR | *.dkr.ecr.*.amazonaws.com | Workload trên AWS |
| Google GCR / Artifact Registry | gcr.io / *-docker.pkg.dev | Workload trên GCP |
| GitHub GHCR | ghcr.io | Open source, GitHub Actions CI/CD |
| Self-hosted | Harbor, Nexus, GitLab Registry | Bả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.03.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 SIZE — nginx: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.toolLab 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 infosẽ hiệnOperating System: Docker Desktopnhư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 :latest là nginx 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 Image | Kích thước | Dùng khi |
|---|---|---|
node:18 | ~350MB | Development, cần đầy đủ tools |
node:18-slim | ~180MB | Staging, giảm bớt tools |
node:18-alpine | ~130MB | Production, đủ cho hầu hết app |
gcr.io/distroless/nodejs18 | ~120MB | Production 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
muslthayglibc— 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 installTại sao? Tag :latest là mutable — 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ấtCopy-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
:latesttrong 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
scpfile 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:
| Metric | Trước Docker | Sau Docker + Registry |
|---|---|---|
| Deploy time | 30 phút / service | 2 phút / service |
| Rollback time | 30 phút (rebuild) | 30 giây (pull version cũ) |
| Environment parity | Không đảm bảo | 100% — cùng image digest |
| Disk usage (50 services) | 50 × full OS install | Shared layers, ~60% tiết kiệm |
| Audit trail | Khô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