Skip to content
第 13 章 ⏱ 12 分钟阅读

第 13 章:部署上线 ​

学习目标 ​

  • 编写生产级 Dockerfile
  • 用 Docker Compose 一键启动全套依赖
  • 部署到 K8s 完成上线

一、生产 Dockerfile ​

多阶段构建 + 非 root 用户 + 健康检查:

dockerfile
# ============ 阶段 1:构建 jar ============
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B         # 先下载依赖,利用 Docker 缓存

COPY src ./src
RUN mvn clean package -DskipTests -B     # 编译打 jar

# ============ 阶段 2:运行时镜像 ============
FROM eclipse-temurin:17-jre-alpine

LABEL maintainer="taskflow@example.com"

# ① 时区
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata curl tini && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

# ② 非 root 用户
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

WORKDIR /app

# ③ 拷贝 jar
COPY --from=builder /build/target/taskflow-application-*.jar /app/app.jar

# ④ JVM 调优
ENV JAVA_OPTS="\
    -XX:+UseG1GC \
    -XX:MaxRAMPercentage=75.0 \
    -XX:+PrintGCDetails \
    -XX:+HeapDumpOnOutOfMemoryError \
    -XX:HeapDumpPath=/tmp/heap.hprof \
    -Djava.security.egd=file:/dev/./urandom"

EXPOSE 8080

# ⑤ 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
    CMD curl -fs http://localhost:8080/actuator/health/liveness || exit 1

# ⑥ 用 tini 接管 PID 1(优雅回收僵尸进程)
ENTRYPOINT ["/sbin/tini", "--", "sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"]

⚠️ 坑 1:生产必须用 -jre 镜像,不是 -jdk。JDK 多 300MB,运行时有 JRE 就够了。

⚠️ 坑 2:non-root 用户运行容器。Docker 默认 root 用户跑进程,任何容器逃逸都是 root 权限。加上 USER spring:spring 至少降低风险等级。

⚠️ 坑 3:JAVA_OPTS 里 必须 有 -XX:MaxRAMPercentage=75.0。JVM 默认按物理机内存算堆,在 1GB 内存的容器里堆默认按 25% 算容易 OOMKilled。让 JVM 按容器限制自适应才行。

二、.dockerignore ​

gitignore
target/
.git/
.idea/
.vscode/
*.iml
*.log
.mvn/
mvnw.cmd
README.md
docker-compose.yml

三、Docker Compose 一键启动 ​

yaml
# docker-compose.yml
version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: taskflow-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
      MYSQL_DATABASE: taskflow
      TZ: Asia/Shanghai
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    volumes:
      - mysql-data:/var/lib/mysql
      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - taskflow-net

  redis:
    image: redis:7-alpine
    container_name: taskflow-redis
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redis123}
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    networks:
      - taskflow-net

  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: taskflow:1.0.0
    container_name: taskflow-app
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      SPRING_PROFILES_ACTIVE: prod
      DB_HOST: mysql
      DB_PORT: 3306
      DB_USER: root
      DB_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
      DB_NAME: taskflow
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
      JWT_SECRET: ${JWT_SECRET:-taskflow-secret-key-must-be-at-least-32-bytes}
    ports:
      - "8080:8080"
    networks:
      - taskflow-net

volumes:
  mysql-data:
  redis-data:

networks:
  taskflow-net:
    driver: bridge
bash
docker compose up -d --build
docker compose logs -f app

四、生产 application 配置 ​

yaml
# application-prod.yml
spring:
  profiles:
    active: prod
  datasource:
    hikari:
      maximum-pool-size: 50         # 生产加大连接池
      minimum-idle: 10
      leak-detection-threshold: 30000

  data:
    redis:
      host: ${REDIS_HOST}
      password: ${REDIS_PASSWORD}

server:
  port: 8080
  compression:
    enabled: true
  tomcat:
    threads:
      max: 200

# 关掉开发期功能
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false

# 调高日志级别
logging:
  level:
    root: WARN
    com.taskflow: INFO

⚠️ 坑 4:生产环境必须关闭 Swagger UI。它会把所有接口暴露给公网,等于把数据库结构白送。enabled: false 后,/swagger-ui.html 返回 404。

五、Nginx 反向代理 ​

nginx
# /etc/nginx/nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream taskflow {
        server app:8080;
        keepalive 32;
    }

    server {
        listen 80;
        server_name taskflow.example.com;

        # gzip
        gzip on;
        gzip_types text/plain application/json text/css application/javascript;

        # 限流
        limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;

        # 前端
        location / {
            root /usr/share/nginx/html;
            try_files $uri $uri/ /index.html;
        }

        # API
        location /api/ {
            limit_req zone=api burst=50 nodelay;
            proxy_pass http://taskflow;
            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_read_timeout 60s;
        }

        # Actuator 仅内网
        location /actuator/ {
            allow 10.0.0.0/8;
            deny all;
            proxy_pass http://taskflow;
        }
    }
}

六、K8s 部署清单 ​

Deployment ​

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: taskflow
  namespace: taskflow
spec:
  replicas: 3
  selector:
    matchLabels:
      app: taskflow
  template:
    metadata:
      labels:
        app: taskflow
    spec:
      containers:
        - name: app
          image: registry.example.com/taskflow:1.0.0
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: k8s
            - name: DB_HOST
              value: mysql
          envFrom:
            - secretRef:
                name: taskflow-secrets
          resources:
            requests:
              memory: "512Mi"
              cpu: "500m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 5

Service + Ingress ​

yaml
apiVersion: v1
kind: Service
metadata:
  name: taskflow
  namespace: taskflow
spec:
  selector:
    app: taskflow
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: taskflow
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
  ingressClassName: nginx
  rules:
    - host: taskflow.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: taskflow
                port:
                  number: 80
bash
kubectl apply -f k8s/
kubectl rollout status deployment/taskflow -n taskflow

七、滚动更新策略 ​

yaml
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%         # 最多比期望多 25%
      maxUnavailable: 0      # 零停机

发布流程:

bash
# 1. 打新 tag
docker build -t registry.example.com/taskflow:1.1.0 .

# 2. 推仓库
docker push registry.example.com/taskflow:1.1.0

# 3. 滚动更新
kubectl set image deployment/taskflow \
    app=registry.example.com/taskflow:1.1.0 \
    -n taskflow

# 4. 观察进度
kubectl rollout status deployment/taskflow -n taskflow

# 5. 出问题回滚
kubectl rollout undo deployment/taskflow -n taskflow

八、生产检查清单 ​

类别项目
应用配置profiles.active=prod、Swagger 关闭、Actuator 最小化
数据库慢 SQL 监控、连接池调优、每日全量备份
缓存Redis 持久化(AOF)、大 key 监控
监控Prometheus + Grafana,关键告警(QPS/错误率/响应时间)
性能G1 GC、MaxRAMPercentage=75、P99 < 500ms
安全OWASP 扫描、密钥 K8s Secret 管理、HTTPS 强制

九、本章小结 ​

要点关键
多阶段 Dockerfilebuilder + runtime 分层,镜像小 70%
JVM 容器化-XX:MaxRAMPercentage=75.0
ComposeMySQL + Redis + App 一键起
生产配置关闭 Swagger + 调高日志
Nginx反向代理 + gzip + 限流
K8sDeployment + Service + Ingress
探针Liveness / Readiness / Startup
滚动更新maxSurge + maxUnavailable 控制

动手练习 ​

  1. 构建镜像:为本项目写多阶段 Dockerfile,本地 docker build 通过
  2. Compose 启动:写 docker-compose.yml,一键起 MySQL + Redis + App
  3. K8s 部署:把本地镜像推到测试集群,Deployment + Service + Ingress 三件套跑通

实战章节全部完成。到这里你已经掌握了从需求分析到部署上线的完整 RBAC 项目开发能力,可以独立交付一个企业级权限管理系统。

本站基于 VitePress 构建 · 由 StackHub 团队维护