## 21.7 实战案例:Go/Rust/数据库/微服务 本节通过实际项目案例演示如何为不同类型的应用构建最优化的 Docker 镜像,以及如何使用 Docker Compose 构建完整的开发和生产环境。 ### 21.7.1 Go 应用的最小化镜像构建 Go 语言因其编译为静态二进制和快速启动而特别适合容器化。以下展示如何构建极小的 Go 应用镜像。 #### 超小 Go Web 服务 **应用代码(main.go):** ```go package main import ( "fmt" "log" "net/http" "os" ) func healthHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"healthy","version":"1.0.0"}`) } func helloHandler(w http.ResponseWriter, r *http.Request) { hostname, _ := os.Hostname() w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"message":"Hello from %s","version":"1.0.0"}`, hostname) } func main() { http.HandleFunc("/health", healthHandler) http.HandleFunc("/hello", helloHandler) http.HandleFunc("/", helloHandler) port := ":8080" log.Printf("Server starting on %s", port) if err := http.ListenAndServe(port, nil); err != nil { log.Fatalf("Server failed: %v", err) } } ``` **多阶段 Dockerfile:** ```dockerfile # Stage 1: 构建阶段 FROM golang:1.20-alpine AS builder WORKDIR /build # 安装构建依赖 RUN apk add --no-cache git ca-certificates tzdata # 复制模块文件(利用缓存) COPY go.mod go.sum ./ RUN go mod download # 复制源代码 COPY . . # 构建静态二进制 # -ldflags="-w -s" 去除调试符号减小体积 RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -a -installsuffix cgo \ -ldflags="-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ -o app . # Stage 2: 运行阶段(scratch 镜像) FROM scratch # 复制 CA 证书(用于 HTTPS) COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # 复制时区数据(用于时间处理) COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo # 复制应用二进制 COPY --from=builder /build/app /app EXPOSE 8080 # 使用绝对路径作为 ENTRYPOINT ENTRYPOINT ["/app"] ``` **构建和测试:** ```bash # 构建镜像 docker build -t go-app:latest . # 检查镜像大小 docker images go-app # 运行容器 docker run -d -p 8080:8080 --name go-demo go-app:latest # 测试应用 curl http://localhost:8080/health | jq . # 进入容器验证 docker exec go-demo ls -la / # 只包含 /app 和系统必要文件 # 镜像大小通常 < 10MB(相比 golang:1.20-alpine 的 ~1GB) docker history go-app:latest ``` **go.mod 和 go.sum 示例:** ``` module github.com/example/go-app go 1.20 require ( // 如果需要依赖 ) ``` #### 带依赖的 Go 应用 **应用代码(使用 Gin 框架):** ```go package main import ( "github.com/gin-gonic/gin" "log" ) func main() { router := gin.Default() router.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{ "status": "ok", }) }) router.GET("/api/users", func(c *gin.Context) { c.JSON(200, gin.H{ "users": []string{"alice", "bob"}, }) }) log.Fatal(router.Run(":8080")) } ``` **优化的 Dockerfile:** ```dockerfile FROM golang:1.20-alpine AS builder WORKDIR /src RUN apk add --no-cache git ca-certificates tzdata COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -installsuffix cgo \ -ldflags="-w -s" \ -o app . # 最终镜像 FROM alpine:3.17 RUN apk add --no-cache ca-certificates tzdata WORKDIR /root/ COPY --from=builder /src/app . EXPOSE 8080 CMD ["./app"] ``` ### 21.7.2 Rust 应用的最小化镜像构建 Rust 因其性能和安全性在系统级应用中备受青睐。 **应用代码(main.rs):** ```rust use actix_web::{web, App, HttpServer, HttpResponse}; use std::sync::Mutex; #[actix_web::main] async fn main() -> std::io::Result<()> { println!("Starting server on 0.0.0.0:8080"); HttpServer::new(|| { App::new() .route("/health", web::get().to(health)) .route("/hello", web::get().to(hello)) }) .bind("0.0.0.0:8080")? .run() .await } async fn health() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({ "status": "healthy" })) } async fn hello() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({ "message": "Hello from Rust" })) } ``` **Cargo.toml:** ```toml [package] name = "rust-app" version = "0.1.0" edition = "2021" [[bin]] name = "rust-app" path = "src/main.rs" [dependencies] actix-web = "4.4" tokio = { version = "1.35", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" ``` **多阶段构建 Dockerfile:** ```dockerfile # Stage 1: 编译 FROM rust:1.75-alpine AS builder RUN apk add --no-cache musl-dev WORKDIR /src COPY Cargo.* ./ COPY src ./src # 构建优化的发布版本 RUN cargo build --release # Stage 2: 运行镜像 FROM alpine:3.17 RUN apk add --no-cache ca-certificates COPY --from=builder /src/target/release/rust-app /app EXPOSE 8080 CMD ["/app"] ``` **构建和验证:** ```bash docker build -t rust-app:latest . docker run -d -p 8080:8080 rust-app:latest curl http://localhost:8080/health | jq . # Rust 应用通常比 Go 更小:5-20MB(取决于依赖) docker images rust-app ``` ### 21.7.3 数据库容器化最佳实践 #### PostgreSQL 生产部署 **自定义 PostgreSQL 镜像:** ```dockerfile FROM postgres:16-alpine # 安装额外工具 RUN apk add --no-cache \ postgresql-contrib \ pg-stat-monitor \ curl # 复制初始化脚本 COPY init-db.sql /docker-entrypoint-initdb.d/ COPY health-check.sh / RUN chmod +x /health-check.sh HEALTHCHECK --interval=10s --timeout=5s --start-period=40s --retries=3 \ CMD /health-check.sh EXPOSE 5432 ``` **初始化脚本(init-db.sql):** ```sql -- 创建自定义用户 CREATE USER appuser WITH PASSWORD 'secure_password'; -- 创建数据库 CREATE DATABASE myappdb OWNER appuser; -- 创建扩展 \c myappdb CREATE EXTENSION IF NOT EXISTS uuid-ossp; CREATE EXTENSION IF NOT EXISTS hstore; CREATE EXTENSION IF NOT EXISTS pg_trgm; -- 创建表 CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 创建索引 CREATE INDEX idx_users_username ON users (username); CREATE INDEX idx_users_email ON users (email); -- 授予权限 GRANT CONNECT ON DATABASE myappdb TO appuser; GRANT USAGE ON SCHEMA public TO appuser; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO appuser; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO appuser; ``` **健康检查脚本(health-check.sh):** ```bash #!/bin/bash PGPASSWORD=$POSTGRES_PASSWORD pg_isready \ -h localhost \ -U $POSTGRES_USER \ -d $POSTGRES_DB \ -p 5432 > /dev/null 2>&1 exit $? ``` **Docker Compose 配置:** ```yaml version: '3.9' services: postgres: build: context: . dockerfile: Dockerfile.postgres container_name: postgres-db environment: POSTGRES_DB: myappdb POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres_password POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8" volumes: - postgres_data:/var/lib/postgresql/data - ./backups:/backups ports: - "5432:5432" networks: - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" # 备份服务 backup: image: postgres:16-alpine depends_on: - postgres environment: PGPASSWORD: postgres_password volumes: - ./backups:/backups command: | sh -c 'while true; do pg_dump -h postgres -U postgres -d myappdb > /backups/backup_$$(date +%Y%m%d_%H%M%S).sql echo "Backup completed at $$(date)" sleep 86400 done' networks: - backend volumes: postgres_data: driver: local networks: backend: driver: bridge ``` **性能优化配置:** ```yaml version: '3.9' services: postgres: image: postgres:16-alpine environment: POSTGRES_DB: myappdb command: - "postgres" - "-c" - "max_connections=200" - "-c" - "shared_buffers=256MB" - "-c" - "effective_cache_size=1GB" - "-c" - "maintenance_work_mem=64MB" - "-c" - "checkpoint_completion_target=0.9" - "-c" - "wal_buffers=16MB" - "-c" - "default_statistics_target=100" - "-c" - "random_page_cost=1.1" - "-c" - "effective_io_concurrency=200" - "-c" - "work_mem=1310kB" - "-c" - "min_wal_size=1GB" - "-c" - "max_wal_size=4GB" - "-c" - "max_worker_processes=4" - "-c" - "max_parallel_workers_per_gather=2" - "-c" - "max_parallel_workers=4" - "-c" - "max_parallel_maintenance_workers=2" volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" volumes: postgres_data: ``` #### MySQL/MariaDB 部署 ```dockerfile FROM mariadb:11 # 复制自定义配置 COPY my.cnf /etc/mysql/conf.d/custom.cnf # 初始化脚本 COPY init.sql /docker-entrypoint-initdb.d/ EXPOSE 3306 HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD mariadb-admin ping -h localhost || exit 1 ``` **自定义 my.cnf:** ```ini [mysqld] # 性能优化 max_connections = 200 default_storage_engine = InnoDB innodb_buffer_pool_size = 1GB innodb_log_file_size = 256MB query_cache_type = 0 query_cache_size = 0 # 日志配置 log_error = /var/log/mysql/error.log slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 # 复制配置 server_id = 1 log_bin = mysql-bin binlog_format = ROW ``` #### Redis 缓存部署 ```dockerfile FROM redis:7-alpine # 复制 Redis 配置 COPY redis.conf /usr/local/etc/redis/redis.conf # 使用配置文件启动 CMD ["redis-server", "/usr/local/etc/redis/redis.conf"] EXPOSE 6379 HEALTHCHECK --interval=5s --timeout=3s --retries=5 \ CMD redis-cli ping || exit 1 ``` **redis.conf 配置:** ```conf # 绑定地址 bind 0.0.0.0 # 端口 port 6379 # 密码保护 requirepass your_secure_password # 内存管理 maxmemory 512mb maxmemory-policy allkeys-lru # 持久化 save 900 1 save 300 10 save 60 10000 # AOF 持久化 appendonly yes appendfsync everysec # 日志 loglevel notice logfile "" # 客户端输出缓冲限制 client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 ``` ### 21.7.4 微服务架构的 Docker Compose 编排 **三层微服务架构示例:** ```yaml version: '3.9' services: # 前端服务 frontend: build: context: ./frontend dockerfile: Dockerfile container_name: frontend ports: - "3000:3000" environment: REACT_APP_API_URL: http://localhost:8000 NODE_ENV: production depends_on: - api networks: - frontend-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000"] interval: 30s timeout: 10s retries: 3 # API 服务 api: build: context: ./api dockerfile: Dockerfile container_name: api ports: - "8000:8000" environment: DATABASE_URL: postgresql://appuser:password@postgres:5432/myappdb REDIS_URL: redis://redis:6379 LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - frontend-network - backend-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 deploy: resources: limits: cpus: '1' memory: 512M reservations: cpus: '0.5' memory: 256M # PostgreSQL 数据库 postgres: image: postgres:16-alpine container_name: postgres environment: POSTGRES_DB: myappdb POSTGRES_USER: appuser POSTGRES_PASSWORD: password volumes: - postgres_data:/var/lib/postgresql/data - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql networks: - backend-network restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U appuser -d myappdb"] interval: 10s timeout: 5s retries: 5 # Redis 缓存 redis: image: redis:7-alpine container_name: redis command: redis-server --appendonly yes --requirepass redispass volumes: - redis_data:/data networks: - backend-network restart: unless-stopped healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 10s timeout: 5s retries: 5 # Nginx 反向代理 nginx: image: nginx:alpine container_name: nginx ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - frontend - api networks: - frontend-network restart: unless-stopped healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 volumes: postgres_data: driver: local redis_data: driver: local networks: frontend-network: driver: bridge backend-network: driver: bridge ``` **nginx.conf 配置:** ```nginx upstream frontend { server frontend:3000; } upstream api { server api:8000; } server { listen 80; server_name localhost; client_max_body_size 100M; # 健康检查端点 location /health { access_log off; return 200 "OK\n"; add_header Content-Type text/plain; } # 前端应用 location / { proxy_pass http://frontend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # API 接口 location /api/ { proxy_pass http://api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } # 静态资源缓存 location ~* ^.+\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { proxy_pass http://frontend; expires 30d; add_header Cache-Control "public, immutable"; } } ``` ### 21.7.5 使用 VS Code Dev Containers Dev Containers 让整个开发环境容器化,提升团队一致性。 **.devcontainer/devcontainer.json:** ```json { "name": "Python Dev Environment", "image": "mcr.microsoft.com/devcontainers/python:3.11", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {} }, "customizations": { "vscode": { "extensions": [ "ms-python.python", "ms-python.vscode-pylance", "ms-python.pylint", "charliermarsh.ruff", "ms-vscode-remote.remote-containers" ], "settings": { "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.formatting.provider": "black", "[python]": { "editor.formatOnSave": true, "editor.defaultFormatter": "ms-python.python" } } } }, "postCreateCommand": "pip install -r requirements.txt && pip install pytest black pylint", "forwardPorts": [8000, 5432, 6379], "portsAttributes": { "8000": { "label": "Application", "onAutoForward": "notify" }, "5432": { "label": "PostgreSQL", "onAutoForward": "ignore" }, "6379": { "label": "Redis", "onAutoForward": "ignore" } }, "mounts": [ "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,readonly" ], "remoteUser": "vscode" } ``` **.devcontainer/Dockerfile:** ```dockerfile FROM mcr.microsoft.com/devcontainers/python:3.11 # 安装额外工具 RUN apt-get update && apt-get install -y \ postgresql-client \ redis-tools \ curl \ git \ && rm -rf /var/lib/apt/lists/* # 创建虚拟环境 RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" WORKDIR /workspace ``` **docker-compose 用于 Dev Containers:** ```yaml # .devcontainer/docker-compose.yml version: '3.9' services: app: build: context: . dockerfile: Dockerfile environment: DATABASE_URL: postgresql://dev:dev@postgres:5432/myapp REDIS_URL: redis://redis:6379 volumes: - ..:/workspace:cached ports: - "8000:8000" depends_on: - postgres - redis postgres: image: postgres:16-alpine environment: POSTGRES_USER: dev POSTGRES_PASSWORD: dev POSTGRES_DB: myapp volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: postgres_data: ```