Appearance
Buổi 7: Docker trong Production
🎯 Mục tiêu
- Biết cách push image lên Docker Hub và private registry
- Cấu hình health checks cho containers
- Thiết lập restart policies đúng cách
- Giới hạn resource (CPU, Memory) cho containers
- Áp dụng security best practices
1. Docker Registry
Docker Hub
bash
# Đăng nhập Docker Hub
$ docker login
Username: myusername
Password: ********
Login Succeeded
# Tag image theo format: username/image:tag
$ docker tag my-app:latest myusername/my-app:1.0
$ docker tag my-app:latest myusername/my-app:latest
# Push lên Docker Hub
$ docker push myusername/my-app:1.0
$ docker push myusername/my-app:latest
# Pull về máy khác
$ docker pull myusername/my-app:1.0Luồng CI/CD với Registry
Developer CI/CD Registry Production
│ │ │ │
│ git push │ │ │
│────────────────▶│ │ │
│ │ docker build │ │
│ │ docker push │ │
│ │──────────────────▶│ │
│ │ │ docker pull │
│ │ │◀───────────────────│
│ │ │ docker run │
│ │ │───────────────────▶│Private Registry
bash
# Chạy registry riêng
$ docker run -d -p 5000:5000 --name registry registry:2
# Tag cho private registry
$ docker tag my-app:latest localhost:5000/my-app:1.0
# Push
$ docker push localhost:5000/my-app:1.0
# Pull
$ docker pull localhost:5000/my-app:1.0So sánh các Registry phổ biến
| Registry | Miễn phí | Private repos | Use case |
|---|---|---|---|
| Docker Hub | ✅ 1 private | Trả phí | Public images |
| GitHub Container Registry | ✅ | ✅ | GitHub projects |
| GitLab Registry | ✅ | ✅ | GitLab CI/CD |
| AWS ECR | Trả phí | ✅ | AWS ecosystem |
| Google Artifact Registry | Trả phí | ✅ | GCP ecosystem |
| Self-hosted | ✅ | ✅ | Full control |
2. Health Checks
Tại sao cần Health Check?
Không có Health Check Có Health Check
┌───────────────────────┐ ┌───────────────────────┐
│ Container: Running ✅ │ │ Container: Running ✅ │
│ │ │ Health: Healthy ✅ │
│ Nhưng app bên trong │ │ │
│ đã crash/hang! 😱 │ │ App crash → Unhealthy │
│ │ │ → Auto restart ♻️ │
│ Không ai biết... │ │ → Alert team 🔔 │
└───────────────────────┘ └───────────────────────┘Health Check trong Dockerfile
dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
# Health check mỗi 30s
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "server.js"]Health Check trong docker-compose.yml
yaml
services:
api:
build: ./api
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 10s
start_period: 10s
retries: 3
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5Các trạng thái Health
| Trạng thái | Ý nghĩa |
|---|---|
starting | Đang trong start_period, chưa check |
healthy | Health check thành công |
unhealthy | Health check thất bại liên tục (retries) |
bash
# Kiểm tra health status
$ docker inspect --format='{{.State.Health.Status}}' api
healthy
# Xem health check logs
$ docker inspect --format='{{json .State.Health}}' api | jq3. Restart Policies
Các chính sách restart
yaml
services:
api:
restart: unless-stopped # Khuyến nghị cho production| Policy | Restart khi crash | Restart khi Docker restart | Restart khi stop thủ công |
|---|---|---|---|
no | ❌ | ❌ | ❌ |
on-failure | ✅ | ❌ | ❌ |
always | ✅ | ✅ | ✅ |
unless-stopped | ✅ | ✅ | ❌ |
Ví dụ
bash
# Docker CLI
$ docker run -d --restart unless-stopped --name api my-api
# Kiểm tra restart count
$ docker inspect -f '{{.RestartCount}}' api💡 Khuyến nghị
Dùng unless-stopped cho production. Container tự restart khi crash hoặc khi Docker daemon restart, nhưng không restart nếu bạn docker stop thủ công.
4. Resource Limits
Giới hạn Memory
bash
# CLI
$ docker run -d --memory=512m --memory-swap=1g --name api my-api
# Composeyaml
services:
api:
image: my-api
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.5'Giới hạn CPU
bash
# Giới hạn 1.5 CPU cores
$ docker run -d --cpus=1.5 --name api my-api
# Giới hạn CPU shares (relative weight)
$ docker run -d --cpu-shares=512 --name api my-apiBảng khuyến nghị resources
| Service | Memory | CPU | Ghi chú |
|---|---|---|---|
| Node.js API | 256M–512M | 0.5–1.0 | Tuỳ workload |
| PostgreSQL | 512M–2G | 1.0–2.0 | Cần RAM cho cache |
| Redis | 128M–512M | 0.25–0.5 | In-memory store |
| Nginx | 64M–128M | 0.25 | Rất nhẹ |
| MongoDB | 512M–2G | 1.0–2.0 | Cần RAM |
Theo dõi tài nguyên
bash
# Real-time monitoring
$ docker stats
# Output:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O
a1b2c3d4e5f6 api 2.5% 128MiB / 512MiB 25% 1.2MB / 648KB
b2c3d4e5f6a7 db 1.0% 256MiB / 1GiB 25% 500KB / 200KB
c3d4e5f6a7b8 redis 0.1% 8MiB / 128MiB 6.25% 100KB / 50KB5. Security Best Practices
1. Không chạy với root
dockerfile
# ❌ BAD: Chạy với root (mặc định)
FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# ✅ GOOD: Chạy với user non-root
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]2. Dùng image chính thức và pinned version
dockerfile
# ❌ BAD
FROM node:latest
FROM myregistry/custom-node
# ✅ GOOD
FROM node:20.11.1-alpine3.19
FROM python:3.12.1-slim-bookworm3. Scan vulnerabilities
bash
# Docker Scout (tích hợp sẵn)
$ docker scout cves my-app:latest
# Trivy (open-source)
$ docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image my-app:latest4. Read-only filesystem
bash
# Container chỉ đọc
$ docker run -d --read-only --tmpfs /tmp my-app
# Composeyaml
services:
api:
image: my-api
read_only: true
tmpfs:
- /tmp
- /var/run5. Không mount Docker socket
bash
# ❌ NGUY HIỂM – cho phép container kiểm soát Docker host
$ docker run -v /var/run/docker.sock:/var/run/docker.sock my-appChecklist security
| # | Kiểm tra | Trạng thái |
|---|---|---|
| 1 | User non-root | ☐ |
| 2 | Pinned image version | ☐ |
| 3 | Scan vulnerabilities | ☐ |
| 4 | .dockerignore (không leak secrets) | ☐ |
| 5 | Không hardcode secrets | ☐ |
| 6 | Read-only filesystem | ☐ |
| 7 | Resource limits | ☐ |
| 8 | Health checks | ☐ |
| 9 | Không mount Docker socket | ☐ |
| 10 | Dùng COPY thay vì ADD | ☐ |
6. Production docker-compose.yml mẫu
yaml
services:
api:
image: myregistry/api:${VERSION:-latest}
ports:
- "3000:3000"
environment:
NODE_ENV: production
DATABASE_URL: postgres://app:${DB_PASS}@db:5432/myapp
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
read_only: true
tmpfs:
- /tmp
user: "1000:1000"
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
volumes:
pgdata:🏋️ Bài tập thực hành
Bài 1: Push image lên Docker Hub
- Tạo tài khoản Docker Hub (nếu chưa có)
- Build image từ Buổi 2
- Tag image:
docker tag myapp username/myapp:1.0 - Push:
docker push username/myapp:1.0 - Pull về kiểm tra:
docker pull username/myapp:1.0
Bài 2: Health Checks
- Viết endpoint
/healthcho Node.js app - Thêm
HEALTHCHECKvào Dockerfile - Build và chạy
- Kiểm tra:
docker inspect --format='{{.State.Health.Status}}' app
Bài 3: Resource Limits
- Chạy container với
--memory=128m - Thử stress test:
docker exec app node -e "let a=[]; while(true) a.push(new Array(1e6))" - Xem container bị kill (OOMKilled)
- Kiểm tra:
docker inspect -f '{{.State.OOMKilled}}' app
Bài 4: Security audit
- Lấy Dockerfile đã viết
- Áp dụng checklist security ở mục 5
- Scan image bằng
docker scout cves - Fix tất cả issues tìm được