第 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: bridgebash
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: 5Service + 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: 80bash
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 强制 |
九、本章小结
| 要点 | 关键 |
|---|---|
| 多阶段 Dockerfile | builder + runtime 分层,镜像小 70% |
| JVM 容器化 | -XX:MaxRAMPercentage=75.0 |
| Compose | MySQL + Redis + App 一键起 |
| 生产配置 | 关闭 Swagger + 调高日志 |
| Nginx | 反向代理 + gzip + 限流 |
| K8s | Deployment + Service + Ingress |
| 探针 | Liveness / Readiness / Startup |
| 滚动更新 | maxSurge + maxUnavailable 控制 |
动手练习
- 构建镜像:为本项目写多阶段 Dockerfile,本地
docker build通过 - Compose 启动:写
docker-compose.yml,一键起 MySQL + Redis + App - K8s 部署:把本地镜像推到测试集群,Deployment + Service + Ingress 三件套跑通
实战章节全部完成。到这里你已经掌握了从需求分析到部署上线的完整 RBAC 项目开发能力,可以独立交付一个企业级权限管理系统。