🎯 前言
經過前兩篇文章的學習,我們已經掌握了 Docker 的基礎概念與指令操作。本文將深入探討 Docker 的進階應用,涵蓋從開發到生產環境的完整實踐。
本文重點:
- Dockerfile 最佳實踐與優化
- 多階段建立(Multi-stage Build)
- Docker Compose 完整應用
- 網路進階配置
- 安全性強化
- 效能調優
- 生產環境部署策略
📝 Dockerfile 深度解析
Dockerfile 指令完整對照表
| 指令 | 作用 | 層級影響 | 範例 |
|---|---|---|---|
| FROM | 指定基礎映像 | 是 | FROM node:18-alpine |
| LABEL | 添加元資料 | 否 | LABEL version="1.0" |
| RUN | 執行指令 | 是 | RUN npm install |
| CMD | 容器啟動指令 | 否 | CMD ["npm", "start"] |
| ENTRYPOINT | 容器進入點 | 否 | ENTRYPOINT ["python"] |
| COPY | 複製檔案 | 是 | COPY app.py /app/ |
| ADD | 複製並解壓 | 是 | ADD archive.tar.gz /app/ |
| ENV | 設定環境變數 | 否 | ENV NODE_ENV=production |
| ARG | 建立時變數 | 否 | ARG VERSION=1.0 |
| WORKDIR | 設定工作目錄 | 否 | WORKDIR /app |
| EXPOSE | 聲明埠 | 否 | EXPOSE 8080 |
| VOLUME | 定義掛載點 | 否 | VOLUME ["/data"] |
| USER | 切換使用者 | 否 | USER appuser |
| HEALTHCHECK | 健康檢查 | 否 | HEALTHCHECK CMD curl -f http://localhost/ |
| ONBUILD | 觸發器指令 | 否 | ONBUILD COPY . /app |
| SHELL | 設定 Shell | 否 | SHELL ["/bin/bash", "-c"] |
| STOPSIGNAL | 停止信號 | 否 | STOPSIGNAL SIGTERM |
Dockerfile 最佳實踐
1. 基礎映像選擇
1# ❌ 不推薦:使用完整版本
2FROM ubuntu:latest
3
4# ✅ 推薦:使用 Alpine 基礎映像
5FROM node:18-alpine
6
7# ✅ 推薦:使用 Distroless(最小化)
8FROM gcr.io/distroless/nodejs18-debian11
9
10# ✅ 推薦:指定確切版本
11FROM python:3.11.5-slim-bookworm
映像大小對照:
| 基礎映像 | 大小 | 適用場景 |
|---|---|---|
ubuntu:latest | ~77 MB | 完整功能需求 |
node:18 | ~900 MB | 開發環境 |
node:18-slim | ~170 MB | 較小生產映像 |
node:18-alpine | ~110 MB | 最小化生產映像 |
distroless | ~50 MB | 安全性要求高 |
2. 層級優化技巧
1# ❌ 不推薦:每個 RUN 創建一層
2FROM ubuntu:20.04
3RUN apt-get update
4RUN apt-get install -y python3
5RUN apt-get install -y pip
6RUN pip install flask
7
8# ✅ 推薦:合併 RUN 指令
9FROM ubuntu:20.04
10RUN apt-get update && \
11 apt-get install -y \
12 python3 \
13 python3-pip && \
14 pip3 install flask && \
15 rm -rf /var/lib/apt/lists/*
16
17# ✅ 更好:使用 heredoc(Docker 23.0+)
18FROM ubuntu:20.04
19RUN <<EOF
20apt-get update
21apt-get install -y python3 python3-pip
22pip3 install flask
23rm -rf /var/lib/apt/lists/*
24EOF
3. 快取優化策略
1# ❌ 不推薦:先複製所有檔案
2FROM node:18-alpine
3WORKDIR /app
4COPY . .
5RUN npm install
6
7# ✅ 推薦:先複製依賴檔案,利用快取
8FROM node:18-alpine
9WORKDIR /app
10
11# 先複製依賴定義檔案
12COPY package*.json ./
13RUN npm ci --only=production
14
15# 再複製程式碼
16COPY . .
17
18# 建立時快取 node_modules
19RUN npm run build
快取策略說明:
graph TB
A[COPY package.json] -->|快取命中| B[使用快取的 npm install]
A -->|檔案變更| C[重新執行 npm install]
B --> D[COPY 原始碼]
C --> D
D -->|程式碼變更| E[重新建立]
D -->|程式碼未變| F[使用快取]
style B fill:#4ecdc4
style F fill:#4ecdc4
style C fill:#ff6b6b
style E fill:#ff6b6b
4. .dockerignore 檔案
1# .dockerignore 範例
2
3# 版本控制
4.git
5.gitignore
6.svn
7
8# 依賴目錄
9node_modules
10bower_components
11__pycache__
12*.pyc
13.Python
14
15# 建立產物
16dist
17build
18*.egg-info
19target
20
21# IDE 設定
22.idea
23.vscode
24*.swp
25*.swo
26*~
27
28# 日誌與臨時檔案
29*.log
30npm-debug.log*
31logs
32tmp
33temp
34
35# 測試相關
36coverage
37.nyc_output
38.pytest_cache
39*.test
40
41# 文件
42README.md
43CHANGELOG.md
44LICENSE
45docs
46
47# CI/CD
48.github
49.gitlab-ci.yml
50.travis.yml
51Jenkinsfile
52
53# Docker
54Dockerfile*
55docker-compose*.yml
56.dockerignore
57
58# 環境變數(敏感資訊)
59.env
60.env.local
61.env.*.local
62secrets.yml
完整的 Dockerfile 範例
Node.js 應用程式
1# 使用官方 Node.js 18 Alpine 映像
2FROM node:18-alpine AS base
3
4# 添加元資料
5LABEL maintainer="devops@example.com" \
6 version="1.0.0" \
7 description="Node.js Application"
8
9# 安裝 dumb-init(正確處理信號)
10RUN apk add --no-cache dumb-init
11
12# 設定工作目錄
13WORKDIR /app
14
15# 設定環境變數
16ENV NODE_ENV=production \
17 PORT=3000
18
19# ===== 依賴階段 =====
20FROM base AS dependencies
21
22# 複製依賴定義檔案
23COPY package*.json ./
24
25# 安裝生產依賴
26RUN npm ci --only=production && \
27 npm cache clean --force
28
29# ===== 建立階段 =====
30FROM base AS build
31
32# 複製依賴定義檔案
33COPY package*.json ./
34
35# 安裝所有依賴(包含開發依賴)
36RUN npm ci && \
37 npm cache clean --force
38
39# 複製原始碼
40COPY . .
41
42# 執行建立(如果需要)
43RUN npm run build
44
45# ===== 生產階段 =====
46FROM base AS production
47
48# 創建非 root 使用者
49RUN addgroup -g 1001 -S nodejs && \
50 adduser -S nodejs -u 1001
51
52# 從依賴階段複製 node_modules
53COPY --from=dependencies --chown=nodejs:nodejs /app/node_modules ./node_modules
54
55# 從建立階段複製建立產物
56COPY --from=build --chown=nodejs:nodejs /app/dist ./dist
57COPY --chown=nodejs:nodejs package*.json ./
58
59# 切換到非 root 使用者
60USER nodejs
61
62# 暴露埠
63EXPOSE 3000
64
65# 健康檢查
66HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
67 CMD node healthcheck.js || exit 1
68
69# 使用 dumb-init 啟動應用
70ENTRYPOINT ["dumb-init", "--"]
71CMD ["node", "dist/server.js"]
Python Flask 應用
1# ===== 建立階段 =====
2FROM python:3.11-slim AS builder
3
4# 設定工作目錄
5WORKDIR /app
6
7# 安裝建立依賴
8RUN apt-get update && \
9 apt-get install -y --no-install-recommends \
10 gcc \
11 python3-dev && \
12 rm -rf /var/lib/apt/lists/*
13
14# 複製依賴檔案
15COPY requirements.txt .
16
17# 安裝 Python 依賴到虛擬環境
18RUN python -m venv /opt/venv && \
19 /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
20
21# ===== 生產階段 =====
22FROM python:3.11-slim
23
24# 設定標籤
25LABEL maintainer="devops@example.com"
26
27# 安裝運行時依賴
28RUN apt-get update && \
29 apt-get install -y --no-install-recommends \
30 curl && \
31 rm -rf /var/lib/apt/lists/* && \
32 rm -rf /tmp/* /var/tmp/*
33
34# 創建應用使用者
35RUN useradd -m -u 1000 -s /bin/bash appuser
36
37# 設定工作目錄
38WORKDIR /app
39
40# 從建立階段複製虛擬環境
41COPY --from=builder /opt/venv /opt/venv
42
43# 設定環境變數
44ENV PATH="/opt/venv/bin:$PATH" \
45 PYTHONUNBUFFERED=1 \
46 PYTHONDONTWRITEBYTECODE=1 \
47 FLASK_APP=app.py \
48 FLASK_ENV=production
49
50# 複製應用程式碼
51COPY --chown=appuser:appuser . .
52
53# 切換到非 root 使用者
54USER appuser
55
56# 暴露埠
57EXPOSE 5000
58
59# 健康檢查
60HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
61 CMD curl -f http://localhost:5000/health || exit 1
62
63# 啟動應用
64CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]
🔨 多階段建立(Multi-stage Build)
多階段建立的優勢
graph LR
A[原始碼] --> B[建立階段]
B --> C[編譯產物]
C --> D[生產階段]
D --> E[最終映像]
B -.->|不包含| E
C -->|只複製需要的| E
style B fill:#ff6b6b
style D fill:#4ecdc4
style E fill:#a8e6cf
效益對照表:
| 項目 | 單階段建立 | 多階段建立 |
|---|---|---|
| 映像大小 | 1-2 GB | 100-300 MB |
| 建立工具 | 包含 | 不包含 |
| 安全性 | 低(包含編譯器) | 高(只有執行檔) |
| 建立時間 | 較快 | 稍慢(但可快取) |
| 維護性 | 低 | 高 |
Go 應用多階段範例
1# ===== 建立階段 =====
2FROM golang:1.21-alpine AS builder
3
4# 安裝建立工具
5RUN apk add --no-cache git ca-certificates tzdata
6
7# 設定工作目錄
8WORKDIR /build
9
10# 複製 go mod 檔案
11COPY go.mod go.sum ./
12
13# 下載依賴
14RUN go mod download && \
15 go mod verify
16
17# 複製原始碼
18COPY . .
19
20# 建立應用
21RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
22 -ldflags='-w -s -extldflags "-static"' \
23 -a \
24 -o /app/server \
25 ./cmd/server
26
27# ===== 生產階段 =====
28FROM scratch
29
30# 從 builder 複製必要檔案
31COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
32COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
33COPY --from=builder /app/server /server
34
35# 設定時區
36ENV TZ=Asia/Taipei
37
38# 暴露埠
39EXPOSE 8080
40
41# 非 root 使用者
42USER 65534:65534
43
44# 啟動應用
45ENTRYPOINT ["/server"]
映像大小對照:
- 單階段建立:~800 MB
- 多階段建立:~10 MB
- 減少:98.75%
Java Spring Boot 多階段範例
1# ===== 建立階段 =====
2FROM maven:3.9-eclipse-temurin-17 AS build
3
4WORKDIR /build
5
6# 複製 pom.xml 並下載依賴(快取優化)
7COPY pom.xml .
8RUN mvn dependency:go-offline -B
9
10# 複製原始碼並建立
11COPY src ./src
12RUN mvn clean package -DskipTests && \
13 java -Djarmode=layertools -jar target/*.jar extract
14
15# ===== 生產階段 =====
16FROM eclipse-temurin:17-jre-alpine
17
18# 設定標籤
19LABEL maintainer="devops@example.com"
20
21# 安裝工具
22RUN apk add --no-cache curl
23
24# 創建應用使用者
25RUN addgroup -g 1000 spring && \
26 adduser -D -u 1000 -G spring spring
27
28WORKDIR /app
29
30# 從建立階段複製分層
31COPY --from=build /build/dependencies/ ./
32COPY --from=build /build/spring-boot-loader/ ./
33COPY --from=build /build/snapshot-dependencies/ ./
34COPY --from=build /build/application/ ./
35
36# 設定擁有者
37RUN chown -R spring:spring /app
38
39USER spring
40
41EXPOSE 8080
42
43# 健康檢查
44HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
45 CMD curl -f http://localhost:8080/actuator/health || exit 1
46
47# 啟動應用
48ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
🎭 Docker Compose 深入應用
Docker Compose 檔案結構
1services: # 服務定義
2 service-name:
3 build: # 建立配置
4 image: # 映像名稱
5 ports: # 埠映射
6 volumes: # 資料卷掛載
7 environment: # 環境變數
8 depends_on: # 依賴關係
9 networks: # 網路配置
10 deploy: # 部署配置
11 healthcheck: # 健康檢查
12
13volumes: # 資料卷定義
14networks: # 網路定義
15configs: # 配置定義
16secrets: # 密鑰定義
完整的生產級 Compose 範例
1# ========== 服務定義 ==========
2services:
3
4 # Nginx 反向代理
5 nginx:
6 image: nginx:alpine
7 container_name: nginx-proxy
8 restart: unless-stopped
9 ports:
10 - "80:80"
11 - "443:443"
12 volumes:
13 - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
14 - ./nginx/conf.d:/etc/nginx/conf.d:ro
15 - ./nginx/ssl:/etc/nginx/ssl:ro
16 - nginx-cache:/var/cache/nginx
17 - nginx-logs:/var/log/nginx
18 networks:
19 - frontend
20 depends_on:
21 - web
22 healthcheck:
23 test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
24 interval: 30s
25 timeout: 10s
26 retries: 3
27 start_period: 40s
28 labels:
29 - "com.example.description=Nginx reverse proxy"
30 - "com.example.department=ops"
31 - "com.example.environment=production"
32
33 # Web 應用
34 web:
35 build:
36 context: ./app
37 dockerfile: Dockerfile
38 target: production
39 args:
40 - NODE_ENV=production
41 - BUILD_DATE=${BUILD_DATE}
42 - VERSION=${VERSION}
43 image: myapp:${VERSION:-latest}
44 container_name: web-app
45 restart: unless-stopped
46 ports:
47 - "3000:3000"
48 environment:
49 - NODE_ENV=production
50 - PORT=3000
51 - DATABASE_URL=postgres://postgres:${DB_PASSWORD}@postgres:5432/myapp
52 - REDIS_URL=redis://redis:6379
53 - LOG_LEVEL=${LOG_LEVEL:-info}
54 env_file:
55 - .env
56 volumes:
57 - app-logs:/app/logs
58 - app-uploads:/app/uploads
59 networks:
60 - frontend
61 - backend
62 depends_on:
63 postgres:
64 condition: service_healthy
65 redis:
66 condition: service_started
67 healthcheck:
68 test: ["CMD", "node", "healthcheck.js"]
69 interval: 30s
70 timeout: 5s
71 retries: 3
72 start_period: 60s
73 deploy:
74 resources:
75 limits:
76 cpus: '2.0'
77 memory: 1G
78 reservations:
79 cpus: '0.5'
80 memory: 512M
81 logging:
82 driver: "json-file"
83 options:
84 max-size: "10m"
85 max-file: "3"
86
87 # PostgreSQL 資料庫
88 postgres:
89 image: postgres:15-alpine
90 container_name: postgres-db
91 restart: unless-stopped
92 ports:
93 - "5432:5432"
94 environment:
95 - POSTGRES_USER=postgres
96 - POSTGRES_PASSWORD=${DB_PASSWORD}
97 - POSTGRES_DB=myapp
98 - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
99 - PGDATA=/var/lib/postgresql/data/pgdata
100 volumes:
101 - postgres-data:/var/lib/postgresql/data
102 - ./database/init:/docker-entrypoint-initdb.d:ro
103 - ./database/backup:/backup
104 networks:
105 - backend
106 healthcheck:
107 test: ["CMD-SHELL", "pg_isready -U postgres"]
108 interval: 10s
109 timeout: 5s
110 retries: 5
111 start_period: 30s
112 deploy:
113 resources:
114 limits:
115 memory: 2G
116 command:
117 - "postgres"
118 - "-c"
119 - "max_connections=200"
120 - "-c"
121 - "shared_buffers=256MB"
122 - "-c"
123 - "effective_cache_size=1GB"
124
125 # Redis 快取
126 redis:
127 image: redis:7-alpine
128 container_name: redis-cache
129 restart: unless-stopped
130 ports:
131 - "6379:6379"
132 volumes:
133 - redis-data:/data
134 - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
135 networks:
136 - backend
137 healthcheck:
138 test: ["CMD", "redis-cli", "ping"]
139 interval: 10s
140 timeout: 3s
141 retries: 5
142 command: redis-server /usr/local/etc/redis/redis.conf
143 deploy:
144 resources:
145 limits:
146 memory: 512M
147
148 # 背景工作器
149 worker:
150 build:
151 context: ./app
152 dockerfile: Dockerfile
153 target: production
154 image: myapp:${VERSION:-latest}
155 container_name: app-worker
156 restart: unless-stopped
157 environment:
158 - NODE_ENV=production
159 - WORKER_MODE=true
160 - DATABASE_URL=postgres://postgres:${DB_PASSWORD}@postgres:5432/myapp
161 - REDIS_URL=redis://redis:6379
162 volumes:
163 - app-logs:/app/logs
164 networks:
165 - backend
166 depends_on:
167 - postgres
168 - redis
169 command: ["node", "worker.js"]
170 deploy:
171 replicas: 2
172 resources:
173 limits:
174 cpus: '1.0'
175 memory: 512M
176
177 # 監控 - Prometheus
178 prometheus:
179 image: prom/prometheus:latest
180 container_name: prometheus
181 restart: unless-stopped
182 ports:
183 - "9090:9090"
184 volumes:
185 - ./monitoring/prometheus:/etc/prometheus:ro
186 - prometheus-data:/prometheus
187 networks:
188 - monitoring
189 command:
190 - '--config.file=/etc/prometheus/prometheus.yml'
191 - '--storage.tsdb.path=/prometheus'
192 - '--storage.tsdb.retention.time=30d'
193
194 # 監控 - Grafana
195 grafana:
196 image: grafana/grafana:latest
197 container_name: grafana
198 restart: unless-stopped
199 ports:
200 - "3001:3000"
201 environment:
202 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
203 - GF_USERS_ALLOW_SIGN_UP=false
204 volumes:
205 - grafana-data:/var/lib/grafana
206 - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
207 networks:
208 - monitoring
209 depends_on:
210 - prometheus
211
212# ========== 資料卷定義 ==========
213volumes:
214 postgres-data:
215 driver: local
216 driver_opts:
217 type: none
218 o: bind
219 device: ./data/postgres
220 redis-data:
221 app-logs:
222 app-uploads:
223 nginx-cache:
224 nginx-logs:
225 prometheus-data:
226 grafana-data:
227
228# ========== 網路定義 ==========
229networks:
230 frontend:
231 driver: bridge
232 ipam:
233 config:
234 - subnet: 172.20.0.0/24
235 backend:
236 driver: bridge
237 internal: true
238 ipam:
239 config:
240 - subnet: 172.21.0.0/24
241 monitoring:
242 driver: bridge
Compose 環境變數管理
.env 檔案範例:
1# 應用版本
2VERSION=1.0.0
3BUILD_DATE=2023-12-01
4
5# 資料庫設定
6DB_PASSWORD=your_secure_password_here
7POSTGRES_VERSION=15
8
9# Redis 設定
10REDIS_PASSWORD=your_redis_password
11
12# 應用設定
13NODE_ENV=production
14LOG_LEVEL=info
15JWT_SECRET=your_jwt_secret
16
17# Grafana 設定
18GRAFANA_PASSWORD=admin_password
19
20# 其他設定
21TZ=Asia/Taipei
Compose 實用指令
1# 啟動所有服務(背景運行)
2docker compose up -d
3
4# 查看服務狀態
5docker compose ps
6
7# 查看日誌(實時)
8docker compose logs -f
9
10# 查看特定服務日誌
11docker compose logs -f web
12
13# 進入服務容器
14docker compose exec web bash
15
16# 擴展服務
17docker compose up -d --scale worker=3
18
19# 重新建立並啟動
20docker compose up -d --build
21
22# 停止並刪除所有資源
23docker compose down
24
25# 停止並刪除(包含資料卷)
26docker compose down -v
27
28# 驗證配置檔
29docker compose config
30
31# 只建立映像
32docker compose build
33
34# 拉取所有映像
35docker compose pull
36
37# 重啟特定服務
38docker compose restart web
39
40# 查看資源使用
41docker compose top
🔐 容器安全性最佳實踐
安全性檢查清單
| 類別 | 檢查項目 | 實施方法 |
|---|---|---|
| 基礎映像 | 使用最小化映像 | Alpine, Distroless |
| 漏洞掃描 | 定期掃描映像 | docker scan, Trivy |
| 非 root 使用者 | 不使用 root 運行 | USER 指令 |
| 密鑰管理 | 不在映像中存儲密鑰 | Docker Secrets, 環境變數 |
| 網路隔離 | 最小權限網路 | 自訂網路, 防火牆規則 |
| 資源限制 | 限制CPU和記憶體 | deploy.resources |
| 唯讀檔案系統 | 盡可能使用唯讀 | –read-only |
| 能力限制 | 移除不必要的能力 | –cap-drop |
1. 使用非 root 使用者
1# ❌ 不推薦:使用 root 使用者
2FROM nginx:alpine
3COPY app /usr/share/nginx/html
4
5# ✅ 推薦:創建並使用非 root 使用者
6FROM nginx:alpine
7
8# 創建使用者和群組
9RUN addgroup -g 1001 -S appgroup && \
10 adduser -S appuser -u 1001 -G appgroup
11
12# 設定檔案權限
13COPY --chown=appuser:appgroup app /app
14
15# 切換使用者
16USER appuser
17
18# 使用非特權埠
19EXPOSE 8080
2. 最小化映像攻擊面
1# 使用最小化基礎映像
2FROM gcr.io/distroless/nodejs18-debian11
3
4# 或使用 Alpine
5FROM node:18-alpine
6
7# 移除不必要的套件
8RUN apk del apk-tools && \
9 rm -rf /var/cache/apk/*
10
11# 唯讀檔案系統
12# docker run --read-only --tmpfs /tmp myapp
3. 密鑰管理
1# 使用 Docker Secrets(Swarm 模式)
2echo "my_secret_password" | docker secret create db_password -
3
4# 在 Compose 中使用
5services:
6 app:
7 image: myapp
8 secrets:
9 - db_password
10secrets:
11 db_password:
12 external: true
13
14# 在應用中讀取
15# cat /run/secrets/db_password
4. 映像掃描
1# 使用 Docker Scan
2docker scan myapp:latest
3
4# 使用 Trivy
5docker run --rm \
6 -v /var/run/docker.sock:/var/run/docker.sock \
7 aquasec/trivy:latest \
8 image myapp:latest
9
10# 使用 Clair
11docker run -p 6060:6060 -d --name clair-db postgres:latest
12docker run -p 6061:6061 --link clair-db:postgres -d quay.io/coreos/clair:latest
5. 容器運行時安全
1# 限制容器能力
2docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
3
4# 使用安全選項
5docker run \
6 --security-opt=no-new-privileges:true \
7 --security-opt=apparmor=docker-default \
8 myapp
9
10# 唯讀根檔案系統
11docker run --read-only --tmpfs /tmp --tmpfs /run myapp
12
13# 資源限制
14docker run \
15 --memory="512m" \
16 --cpus="1.0" \
17 --pids-limit=100 \
18 myapp
⚡ 效能優化策略
映像大小優化
graph TB
A[映像優化策略] --> B[選擇精簡基礎映像]
A --> C[多階段建立]
A --> D[合併 RUN 指令]
A --> E[清理快取與臨時檔案]
A --> F[使用 .dockerignore]
B --> B1[Alpine: ~5-10MB<br/>Distroless: ~20-50MB]
C --> C1[只保留執行時檔案<br/>減少 80-95%]
D --> D1[減少層數<br/>優化快取]
E --> E1[減少 10-30%]
F --> F1[加快建立速度<br/>減少內容]
style A fill:#4ecdc4
優化前後對照:
| 項目 | 優化前 | 優化後 | 改善 |
|---|---|---|---|
| 映像大小 | 1.5 GB | 150 MB | 90% |
| 建立時間 | 10 分鐘 | 2 分鐘 | 80% |
| 層數 | 25 層 | 8 層 | 68% |
| 啟動時間 | 30 秒 | 3 秒 | 90% |
建立優化技巧
1# 1. 使用 BuildKit(Docker Engine 23.0 起已是預設建置器,無須再設定)
2# 舊版 Docker 才需要:export DOCKER_BUILDKIT=1
3
4# 2. 使用快取掛載(BuildKit)
5FROM golang:1.21-alpine
6RUN --mount=type=cache,target=/go/pkg/mod \
7 --mount=type=cache,target=/root/.cache/go-build \
8 go build -o app
9
10# 3. 使用秘密掛載
11RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
12 npm install
13
14# 4. 並行化建立步驟
15FROM base AS deps-stage1
16RUN npm install package1
17
18FROM base AS deps-stage2
19RUN npm install package2
20
21FROM base AS final
22COPY --from=deps-stage1 /app/node_modules ./
23COPY --from=deps-stage2 /app/node_modules ./
容器運行時優化
1# docker-compose.yml 效能配置
2services:
3 app:
4 image: myapp:latest
5 deploy:
6 resources:
7 limits:
8 cpus: '2.0'
9 memory: 2G
10 reservations:
11 cpus: '1.0'
12 memory: 1G
13 # 使用主機網路(效能最佳)
14 network_mode: "host"
15 # 或使用自訂網路
16 networks:
17 - app-network
18 # IPC 模式
19 ipc: "shareable"
20 # 日誌配置
21 logging:
22 driver: "json-file"
23 options:
24 max-size: "10m"
25 max-file: "3"
26 compress: "true"
網路效能優化
1# 1. 使用主機網路(最佳效能)
2docker run --network host myapp
3
4# 2. 自訂 MTU
5docker network create --opt com.docker.network.driver.mtu=9000 mynetwork
6
7# 3. 禁用 iptables(內部網路)
8docker network create --internal mynetwork
9
10# 4. 使用 overlay 網路(Swarm)
11docker network create --driver overlay --attachable mynetwork
🚀 生產環境部署策略
部署架構
graph TB
subgraph "負載均衡層"
LB[Load Balancer<br/>Nginx/HAProxy]
end
subgraph "應用層"
APP1[App Container 1]
APP2[App Container 2]
APP3[App Container 3]
end
subgraph "資料層"
DB[(PostgreSQL<br/>Primary)]
REDIS[(Redis<br/>Cache)]
DB2[(PostgreSQL<br/>Replica)]
end
subgraph "監控層"
PROM[Prometheus]
GRAF[Grafana]
ALERT[AlertManager]
end
LB --> APP1
LB --> APP2
LB --> APP3
APP1 --> DB
APP2 --> DB
APP3 --> DB
APP1 --> REDIS
APP2 --> REDIS
APP3 --> REDIS
DB --> DB2
PROM --> APP1
PROM --> APP2
PROM --> APP3
PROM --> DB
PROM --> REDIS
GRAF --> PROM
ALERT --> PROM
style LB fill:#4ecdc4
style DB fill:#ff6b6b
style PROM fill:#feca57
高可用性配置
1services:
2 # 應用服務(多實例)
3 app:
4 image: myapp:${VERSION}
5 deploy:
6 replicas: 3
7 update_config:
8 parallelism: 1
9 delay: 10s
10 failure_action: rollback
11 order: start-first
12 rollback_config:
13 parallelism: 1
14 delay: 5s
15 restart_policy:
16 condition: on-failure
17 delay: 5s
18 max_attempts: 3
19 window: 120s
20 placement:
21 constraints:
22 - node.role == worker
23 preferences:
24 - spread: node.labels.zone
25 healthcheck:
26 test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
27 interval: 30s
28 timeout: 5s
29 retries: 3
30 start_period: 60s
31
32 # 資料庫(主從複製)
33 postgres-primary:
34 image: postgres:15
35 environment:
36 - POSTGRES_REPLICATION_MODE=master
37 volumes:
38 - postgres-primary-data:/var/lib/postgresql/data
39 deploy:
40 placement:
41 constraints:
42 - node.labels.database == primary
43
44 postgres-replica:
45 image: postgres:15
46 environment:
47 - POSTGRES_REPLICATION_MODE=slave
48 - POSTGRES_MASTER_HOST=postgres-primary
49 volumes:
50 - postgres-replica-data:/var/lib/postgresql/data
51 deploy:
52 replicas: 2
53 placement:
54 constraints:
55 - node.labels.database == replica
滾動更新策略
1# Docker Swarm 滾動更新
2docker service update \
3 --image myapp:v2.0 \
4 --update-parallelism 1 \
5 --update-delay 10s \
6 --update-failure-action rollback \
7 myapp
8
9# 使用 Compose
10docker compose up -d --no-deps --build app
11
12# 藍綠部署
13# 1. 部署綠色環境
14docker compose -f docker-compose.green.yml up -d
15
16# 2. 測試綠色環境
17curl http://green.example.com/health
18
19# 3. 切換流量(更新 Nginx 配置)
20docker exec nginx nginx -s reload
21
22# 4. 停止藍色環境
23docker compose -f docker-compose.blue.yml down
監控與日誌
1# Prometheus 配置
2services:
3 prometheus:
4 image: prom/prometheus:latest
5 volumes:
6 - ./prometheus.yml:/etc/prometheus/prometheus.yml
7 command:
8 - '--config.file=/etc/prometheus/prometheus.yml'
9 - '--storage.tsdb.retention.time=30d'
10 ports:
11 - "9090:9090"
12
13 # 應用暴露 metrics
14 app:
15 image: myapp:latest
16 environment:
17 - METRICS_ENABLED=true
18 ports:
19 - "3000:3000"
20 - "9100:9100" # Metrics 埠
21
22 # Node Exporter
23 node-exporter:
24 image: prom/node-exporter:latest
25 volumes:
26 - /proc:/host/proc:ro
27 - /sys:/host/sys:ro
28 - /:/rootfs:ro
29 command:
30 - '--path.procfs=/host/proc'
31 - '--path.sysfs=/host/sys'
32 - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
33
34 # cAdvisor(容器監控)
35 cadvisor:
36 image: gcr.io/cadvisor/cadvisor:latest
37 volumes:
38 - /:/rootfs:ro
39 - /var/run:/var/run:ro
40 - /sys:/sys:ro
41 - /var/lib/docker/:/var/lib/docker:ro
42 ports:
43 - "8080:8080"
🎯 CI/CD 整合實踐
GitLab CI 範例
1# .gitlab-ci.yml
2stages:
3 - test
4 - build
5 - deploy
6
7variables:
8 DOCKER_DRIVER: overlay2
9 DOCKER_TLS_CERTDIR: "/certs"
10 IMAGE_NAME: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
11
12# 測試階段
13test:
14 stage: test
15 image: node:18-alpine
16 script:
17 - npm ci
18 - npm run test
19 - npm run lint
20 coverage: '/Statements\s+:\s+(\d+\.\d+)%/'
21 artifacts:
22 reports:
23 coverage_report:
24 coverage_format: cobertura
25 path: coverage/cobertura-coverage.xml
26
27# 建立映像
28build:
29 stage: build
30 image: docker:24
31 services:
32 - docker:24-dind
33 before_script:
34 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
35 script:
36 - docker build --pull -t $IMAGE_NAME .
37 - docker tag $IMAGE_NAME ${CI_REGISTRY_IMAGE}:latest
38 - docker push $IMAGE_NAME
39 - docker push ${CI_REGISTRY_IMAGE}:latest
40 only:
41 - main
42 - develop
43
44# 部署到開發環境
45deploy-dev:
46 stage: deploy
47 image: docker/compose:latest
48 before_script:
49 - apk add --no-cache openssh-client
50 - eval $(ssh-agent -s)
51 - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
52 - mkdir -p ~/.ssh
53 - chmod 700 ~/.ssh
54 - ssh-keyscan $DEV_SERVER_IP >> ~/.ssh/known_hosts
55 script:
56 - ssh $DEV_USER@$DEV_SERVER_IP "
57 cd /app &&
58 docker compose pull app &&
59 docker compose up -d app
60 "
61 environment:
62 name: development
63 url: https://dev.example.com
64 only:
65 - develop
66
67# 部署到生產環境
68deploy-prod:
69 stage: deploy
70 image: docker/compose:latest
71 before_script:
72 - apk add --no-cache openssh-client
73 - eval $(ssh-agent -s)
74 - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
75 script:
76 - ssh $PROD_USER@$PROD_SERVER_IP "
77 cd /app &&
78 docker compose pull app &&
79 docker compose up -d --no-deps app
80 "
81 environment:
82 name: production
83 url: https://example.com
84 when: manual
85 only:
86 - main
GitHub Actions 範例
1# .github/workflows/docker-build.yml
2name: Docker Build and Push
3
4on:
5 push:
6 branches: [main, develop]
7 pull_request:
8 branches: [main]
9
10env:
11 REGISTRY: ghcr.io
12 IMAGE_NAME: ${{ github.repository }}
13
14jobs:
15 test:
16 runs-on: ubuntu-latest
17 steps:
18 - uses: actions/checkout@v3
19
20 - name: Setup Node.js
21 uses: actions/setup-node@v3
22 with:
23 node-version: '18'
24 cache: 'npm'
25
26 - name: Install dependencies
27 run: npm ci
28
29 - name: Run tests
30 run: npm test
31
32 - name: Run linter
33 run: npm run lint
34
35 build-and-push:
36 needs: test
37 runs-on: ubuntu-latest
38 permissions:
39 contents: read
40 packages: write
41
42 steps:
43 - uses: actions/checkout@v3
44
45 - name: Set up Docker Buildx
46 uses: docker/setup-buildx-action@v2
47
48 - name: Log in to Container Registry
49 uses: docker/login-action@v2
50 with:
51 registry: ${{ env.REGISTRY }}
52 username: ${{ github.actor }}
53 password: ${{ secrets.GITHUB_TOKEN }}
54
55 - name: Extract metadata
56 id: meta
57 uses: docker/metadata-action@v4
58 with:
59 images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
60 tags: |
61 type=ref,event=branch
62 type=ref,event=pr
63 type=semver,pattern={{version}}
64 type=semver,pattern={{major}}.{{minor}}
65 type=sha,prefix={{branch}}-
66
67 - name: Build and push
68 uses: docker/build-push-action@v4
69 with:
70 context: .
71 push: true
72 tags: ${{ steps.meta.outputs.tags }}
73 labels: ${{ steps.meta.outputs.labels }}
74 cache-from: type=gha
75 cache-to: type=gha,mode=max
76
77 deploy:
78 needs: build-and-push
79 runs-on: ubuntu-latest
80 if: github.ref == 'refs/heads/main'
81
82 steps:
83 - name: Deploy to production
84 uses: appleboy/ssh-action@master
85 with:
86 host: ${{ secrets.PROD_HOST }}
87 username: ${{ secrets.PROD_USER }}
88 key: ${{ secrets.SSH_PRIVATE_KEY }}
89 script: |
90 cd /app
91 docker compose pull
92 docker compose up -d --no-deps app
93 docker system prune -f
📊 總結與最佳實踐清單
核心知識回顧
本系列文章涵蓋了 Docker 從入門到實戰的完整內容:
第一篇:基礎概念
- Docker 架構與核心元件
- 容器 vs 虛擬機
- 安裝與配置
第二篇:指令操作
- 容器生命週期管理
- 映像操作技巧
- 網路與儲存配置
第三篇:進階實踐(本篇)
- Dockerfile 優化
- 多階段建立
- Docker Compose 編排
- 安全性與效能
- 生產環境部署
Docker 最佳實踐檢查清單
📝 開發階段
- 使用 .dockerignore 排除不必要的檔案
- 利用建立快取優化建立速度
- 使用 Bind Mount 實現程式碼熱重載
- 為映像添加明確的標籤版本
- 使用 Docker Compose 管理多容器應用
🏗️ 映像建立
- 選擇最小化的基礎映像(Alpine, Distroless)
- 使用多階段建立減少映像大小
- 合併 RUN 指令減少層數
- 在每個 RUN 末尾清理快取和臨時檔案
- 先複製依賴檔案,再複製原始碼(快取優化)
- 使用非 root 使用者運行容器
- 添加健康檢查(HEALTHCHECK)
- 使用 LABEL 添加元資料
🔐 安全性
- 定期掃描映像漏洞
- 不在映像中存儲敏感資訊
- 使用 Docker Secrets 管理密鑰
- 限制容器能力(–cap-drop)
- 使用唯讀檔案系統
- 設定資源限制(CPU、記憶體)
- 使用官方或可信的基礎映像
- 定期更新基礎映像
🌐 網路配置
- 使用自訂網路替代預設 bridge
- 為服務設定有意義的網路別名
- 後端服務使用內部網路(internal)
- 最小化暴露的埠
- 使用反向代理(Nginx, Traefik)
💾 資料管理
- 使用 Volume 而非 Bind Mount 持久化資料
- 為 Volume 使用有意義的命名
- 定期備份重要資料
- 避免在容器內存儲狀態
- 使用外部儲存服務(S3, NFS)
🚀 生產部署
- 設定容器重啟策略(restart: unless-stopped)
- 實作健康檢查和就緒探測
- 配置日誌輪替避免磁碟填滿
- 使用滾動更新策略
- 準備回滾方案
- 實作監控和告警(Prometheus, Grafana)
- 設定資源限制和預留
- 文件化部署流程
📊 監控與維護
- 收集並分析容器日誌
- 監控容器資源使用(CPU、記憶體、網路)
- 設定關鍵指標告警
- 定期清理未使用的映像和容器
- 追蹤映像大小變化
- 測試災難恢復流程
常見錯誤與解決方案
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 映像太大 | 包含不必要的檔案和工具 | 使用多階段建立、Alpine 映像 |
| 建立緩慢 | 沒有優化快取層 | 調整 Dockerfile 指令順序 |
| 容器無法啟動 | 權限或依賴問題 | 檢查日誌、使用健康檢查 |
| 資料遺失 | 未使用 Volume | 使用 Volume 持久化資料 |
| 網路連接失敗 | 網路配置錯誤 | 檢查網路設定和防火牆 |
| 效能不佳 | 資源限制不當 | 調整 CPU、記憶體限制 |
| 安全漏洞 | 使用過時映像 | 定期更新和掃描映像 |
學習資源推薦
官方文件
進階學習
- Docker 認證考試(DCA)
- Kubernetes(容器編排)
- Docker Swarm(集群管理)
- Helm(Kubernetes 套件管理)
社群與工具
- Docker Community Forums
- GitHub Docker 範例專案
- Play with Docker(線上實驗環境)
- Portainer(容器管理 UI)
🎉 結語
Docker 容器技術已經成為現代軟體開發和部署的標準工具。透過本系列三篇文章的學習,您已經掌握了:
- 基礎知識:理解容器化的概念和 Docker 架構
- 實務操作:熟練使用 Docker CLI 管理容器和映像
- 進階技能:掌握 Dockerfile 優化、Compose 編排和生產部署
下一步建議
- 實踐專案:將現有專案容器化
- 學習編排:深入學習 Kubernetes 或 Docker Swarm
- 持續優化:關注效能和安全性
- 社群參與:貢獻開源專案,分享經驗
Docker 的學習是一個持續的過程,隨著實踐經驗的累積,您將能夠構建更高效、更安全、更可靠的容器化應用。
祝您在容器化技術的道路上不斷進步!🚀
