🎯 前言
經過前兩篇文章的學習,我們已經掌握了 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 檔案結構
1version: "3.8" # Compose 檔案版本
2
3services: # 服務定義
4 service-name:
5 build: # 建立配置
6 image: # 映像名稱
7 ports: # 埠映射
8 volumes: # 資料卷掛載
9 environment: # 環境變數
10 depends_on: # 依賴關係
11 networks: # 網路配置
12 deploy: # 部署配置
13 healthcheck: # 健康檢查
14
15volumes: # 資料卷定義
16networks: # 網路定義
17configs: # 配置定義
18secrets: # 密鑰定義
完整的生產級 Compose 範例
1version: "3.8"
2
3# ========== 服務定義 ==========
4services:
5
6 # Nginx 反向代理
7 nginx:
8 image: nginx:alpine
9 container_name: nginx-proxy
10 restart: unless-stopped
11 ports:
12 - "80:80"
13 - "443:443"
14 volumes:
15 - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
16 - ./nginx/conf.d:/etc/nginx/conf.d:ro
17 - ./nginx/ssl:/etc/nginx/ssl:ro
18 - nginx-cache:/var/cache/nginx
19 - nginx-logs:/var/log/nginx
20 networks:
21 - frontend
22 depends_on:
23 - web
24 healthcheck:
25 test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
26 interval: 30s
27 timeout: 10s
28 retries: 3
29 start_period: 40s
30 labels:
31 - "com.example.description=Nginx reverse proxy"
32 - "com.example.department=ops"
33 - "com.example.environment=production"
34
35 # Web 應用
36 web:
37 build:
38 context: ./app
39 dockerfile: Dockerfile
40 target: production
41 args:
42 - NODE_ENV=production
43 - BUILD_DATE=${BUILD_DATE}
44 - VERSION=${VERSION}
45 image: myapp:${VERSION:-latest}
46 container_name: web-app
47 restart: unless-stopped
48 ports:
49 - "3000:3000"
50 environment:
51 - NODE_ENV=production
52 - PORT=3000
53 - DATABASE_URL=postgres://postgres:${DB_PASSWORD}@postgres:5432/myapp
54 - REDIS_URL=redis://redis:6379
55 - LOG_LEVEL=${LOG_LEVEL:-info}
56 env_file:
57 - .env
58 volumes:
59 - app-logs:/app/logs
60 - app-uploads:/app/uploads
61 networks:
62 - frontend
63 - backend
64 depends_on:
65 postgres:
66 condition: service_healthy
67 redis:
68 condition: service_started
69 healthcheck:
70 test: ["CMD", "node", "healthcheck.js"]
71 interval: 30s
72 timeout: 5s
73 retries: 3
74 start_period: 60s
75 deploy:
76 resources:
77 limits:
78 cpus: '2.0'
79 memory: 1G
80 reservations:
81 cpus: '0.5'
82 memory: 512M
83 logging:
84 driver: "json-file"
85 options:
86 max-size: "10m"
87 max-file: "3"
88
89 # PostgreSQL 資料庫
90 postgres:
91 image: postgres:15-alpine
92 container_name: postgres-db
93 restart: unless-stopped
94 ports:
95 - "5432:5432"
96 environment:
97 - POSTGRES_USER=postgres
98 - POSTGRES_PASSWORD=${DB_PASSWORD}
99 - POSTGRES_DB=myapp
100 - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
101 - PGDATA=/var/lib/postgresql/data/pgdata
102 volumes:
103 - postgres-data:/var/lib/postgresql/data
104 - ./database/init:/docker-entrypoint-initdb.d:ro
105 - ./database/backup:/backup
106 networks:
107 - backend
108 healthcheck:
109 test: ["CMD-SHELL", "pg_isready -U postgres"]
110 interval: 10s
111 timeout: 5s
112 retries: 5
113 start_period: 30s
114 deploy:
115 resources:
116 limits:
117 memory: 2G
118 command:
119 - "postgres"
120 - "-c"
121 - "max_connections=200"
122 - "-c"
123 - "shared_buffers=256MB"
124 - "-c"
125 - "effective_cache_size=1GB"
126
127 # Redis 快取
128 redis:
129 image: redis:7-alpine
130 container_name: redis-cache
131 restart: unless-stopped
132 ports:
133 - "6379:6379"
134 volumes:
135 - redis-data:/data
136 - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
137 networks:
138 - backend
139 healthcheck:
140 test: ["CMD", "redis-cli", "ping"]
141 interval: 10s
142 timeout: 3s
143 retries: 5
144 command: redis-server /usr/local/etc/redis/redis.conf
145 deploy:
146 resources:
147 limits:
148 memory: 512M
149
150 # 背景工作器
151 worker:
152 build:
153 context: ./app
154 dockerfile: Dockerfile
155 target: production
156 image: myapp:${VERSION:-latest}
157 container_name: app-worker
158 restart: unless-stopped
159 environment:
160 - NODE_ENV=production
161 - WORKER_MODE=true
162 - DATABASE_URL=postgres://postgres:${DB_PASSWORD}@postgres:5432/myapp
163 - REDIS_URL=redis://redis:6379
164 volumes:
165 - app-logs:/app/logs
166 networks:
167 - backend
168 depends_on:
169 - postgres
170 - redis
171 command: ["node", "worker.js"]
172 deploy:
173 replicas: 2
174 resources:
175 limits:
176 cpus: '1.0'
177 memory: 512M
178
179 # 監控 - Prometheus
180 prometheus:
181 image: prom/prometheus:latest
182 container_name: prometheus
183 restart: unless-stopped
184 ports:
185 - "9090:9090"
186 volumes:
187 - ./monitoring/prometheus:/etc/prometheus:ro
188 - prometheus-data:/prometheus
189 networks:
190 - monitoring
191 command:
192 - '--config.file=/etc/prometheus/prometheus.yml'
193 - '--storage.tsdb.path=/prometheus'
194 - '--storage.tsdb.retention.time=30d'
195
196 # 監控 - Grafana
197 grafana:
198 image: grafana/grafana:latest
199 container_name: grafana
200 restart: unless-stopped
201 ports:
202 - "3001:3000"
203 environment:
204 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
205 - GF_USERS_ALLOW_SIGN_UP=false
206 volumes:
207 - grafana-data:/var/lib/grafana
208 - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
209 networks:
210 - monitoring
211 depends_on:
212 - prometheus
213
214# ========== 資料卷定義 ==========
215volumes:
216 postgres-data:
217 driver: local
218 driver_opts:
219 type: none
220 o: bind
221 device: ./data/postgres
222 redis-data:
223 app-logs:
224 app-uploads:
225 nginx-cache:
226 nginx-logs:
227 prometheus-data:
228 grafana-data:
229
230# ========== 網路定義 ==========
231networks:
232 frontend:
233 driver: bridge
234 ipam:
235 config:
236 - subnet: 172.20.0.0/24
237 backend:
238 driver: bridge
239 internal: true
240 ipam:
241 config:
242 - subnet: 172.21.0.0/24
243 monitoring:
244 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 中使用
5version: "3.8"
6services:
7 app:
8 image: myapp
9 secrets:
10 - db_password
11secrets:
12 db_password:
13 external: true
14
15# 在應用中讀取
16# 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 18.09+)
2# 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 效能配置
2version: "3.8"
3
4services:
5 app:
6 image: myapp:latest
7 deploy:
8 resources:
9 limits:
10 cpus: '2.0'
11 memory: 2G
12 reservations:
13 cpus: '1.0'
14 memory: 1G
15 # 使用主機網路(效能最佳)
16 network_mode: "host"
17 # 或使用自訂網路
18 networks:
19 - app-network
20 # IPC 模式
21 ipc: "shareable"
22 # 日誌配置
23 logging:
24 driver: "json-file"
25 options:
26 max-size: "10m"
27 max-file: "3"
28 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
高可用性配置
1version: "3.8"
2
3services:
4 # 應用服務(多實例)
5 app:
6 image: myapp:${VERSION}
7 deploy:
8 replicas: 3
9 update_config:
10 parallelism: 1
11 delay: 10s
12 failure_action: rollback
13 order: start-first
14 rollback_config:
15 parallelism: 1
16 delay: 5s
17 restart_policy:
18 condition: on-failure
19 delay: 5s
20 max_attempts: 3
21 window: 120s
22 placement:
23 constraints:
24 - node.role == worker
25 preferences:
26 - spread: node.labels.zone
27 healthcheck:
28 test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
29 interval: 30s
30 timeout: 5s
31 retries: 3
32 start_period: 60s
33
34 # 資料庫(主從複製)
35 postgres-primary:
36 image: postgres:15
37 environment:
38 - POSTGRES_REPLICATION_MODE=master
39 volumes:
40 - postgres-primary-data:/var/lib/postgresql/data
41 deploy:
42 placement:
43 constraints:
44 - node.labels.database == primary
45
46 postgres-replica:
47 image: postgres:15
48 environment:
49 - POSTGRES_REPLICATION_MODE=slave
50 - POSTGRES_MASTER_HOST=postgres-primary
51 volumes:
52 - postgres-replica-data:/var/lib/postgresql/data
53 deploy:
54 replicas: 2
55 placement:
56 constraints:
57 - 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 配置
2version: "3.8"
3
4services:
5 prometheus:
6 image: prom/prometheus:latest
7 volumes:
8 - ./prometheus.yml:/etc/prometheus/prometheus.yml
9 command:
10 - '--config.file=/etc/prometheus/prometheus.yml'
11 - '--storage.tsdb.retention.time=30d'
12 ports:
13 - "9090:9090"
14
15 # 應用暴露 metrics
16 app:
17 image: myapp:latest
18 environment:
19 - METRICS_ENABLED=true
20 ports:
21 - "3000:3000"
22 - "9100:9100" # Metrics 埠
23
24 # Node Exporter
25 node-exporter:
26 image: prom/node-exporter:latest
27 volumes:
28 - /proc:/host/proc:ro
29 - /sys:/host/sys:ro
30 - /:/rootfs:ro
31 command:
32 - '--path.procfs=/host/proc'
33 - '--path.sysfs=/host/sys'
34 - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
35
36 # cAdvisor(容器監控)
37 cadvisor:
38 image: gcr.io/cadvisor/cadvisor:latest
39 volumes:
40 - /:/rootfs:ro
41 - /var/run:/var/run:ro
42 - /sys:/sys:ro
43 - /var/lib/docker/:/var/lib/docker:ro
44 ports:
45 - "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 的學習是一個持續的過程,隨著實踐經驗的累積,您將能夠構建更高效、更安全、更可靠的容器化應用。
祝您在容器化技術的道路上不斷進步!🚀