SpringBoot Docker镜像构建
Spring Boot 简化了配置,但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。
一、基础Dockerfile
#
1.1 最简单的构建方式
FROM eclipse-temurin:17-jdk VOLUME /tmp COPY target/*.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"]
|
问题分析:
- 使用JDK,镜像体积大
- 单层构建,无缓存优化
- 缺少JVM参数配置
- 无健康检查
#
1.2 使用JRE减小体积
FROM eclipse-temurin:17-jre VOLUME /tmp COPY target/*.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"]
|
JRE比JDK减少约200MB。
二、多阶段构建
#
2.1 构建与运行分离
FROM maven:3.9-eclipse-temurin-17-alpine AS build WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
|
优势:
- 最终镜像不包含Maven和编译工具
- 构建环境独立,不受宿主机影响
- 适合CI/CD流水线
#
2.2 结合分层打包
FROM eclipse-temurin:17-jre AS builder WORKDIR /application ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmode=layertools -jar application.jar extract
FROM eclipse-temurin:17-jre WORKDIR /application
COPY --from=builder /application/dependencies/ ./ COPY --from=builder /application/spring-boot-loader/ ./ COPY --from=builder /application/snapshot-dependencies/ ./ COPY --from=builder /application/application/ ./
EXPOSE 8080 ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
|
三、JVM参数优化
#
3.1 容器感知配置
ENTRYPOINT ["java", \ "-XX:+UseContainerSupport", \ "-XX:MaxRAMPercentage=75.0", \ "-XX:InitialRAMPercentage=50.0", \ "-jar", "app.jar"]
|
-XX:+UseContainerSupport(Java 8u191+ / Java 11+ 默认开启)让JVM识别容器资源限制。
#
3.2 垃圾回收器选择
G1GC(通用场景):
ENTRYPOINT ["java", \ "-XX:+UseG1GC", \ "-XX:MaxGCPauseMillis=200", \ "-XX:+UseContainerSupport", \ "-XX:MaxRAMPercentage=75.0", \ "-jar", "app.jar"]
|
ZGC(低延迟场景,Java 11+):
ENTRYPOINT ["java", \ "-XX:+UseZGC", \ "-XX:+ZGenerational", \ "-XX:+UseContainerSupport", \ "-XX:MaxRAMPercentage=75.0", \ "-jar", "app.jar"]
|
#
3.3 内存配置详解
ENTRYPOINT ["java", \ "-XX:MaxRAMPercentage=75.0", \ "-XX:InitialRAMPercentage=50.0", \ "-XX:MaxMetaspaceSize=256m", \ "-XX:MaxDirectMemorySize=128m", \ "-Xss1m", \ "-XX:+HeapDumpOnOutOfMemoryError", \ "-XX:HeapDumpPath=/tmp/heapdump.hprof", \ "-jar", "app.jar"]
|
四、生产级Dockerfile模板
#
4.1 完整模板
FROM eclipse-temurin:17-jre AS builder WORKDIR /application COPY target/*.jar application.jar RUN java -Djarmode=layertools -jar application.jar extract
FROM eclipse-temurin:17-jre
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /application
COPY --from=builder /application/dependencies/ ./ COPY --from=builder /application/spring-boot-loader/ ./ COPY --from=builder /application/snapshot-dependencies/ ./ COPY --from=builder /application/application/ ./
RUN chown -R appuser:appgroup /application USER appuser
ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV JAVA_OPTS="" ENV JVM_OPTS="-XX:+UseContainerSupport \ -XX:MaxRAMPercentage=75.0 \ -XX:InitialRAMPercentage=50.0 \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/heapdump.hprof \ -Xlog:gc*:file=/tmp/gc.log:time,uptime:filecount=5,filesize=10m"
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
|
#
4.2 使用非Root用户
安全最佳实践:
RUN groupadd -r appgroup && useradd -r -g appgroup appuser USER appuser
|
#
4.3 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health/liveness || exit 1
|
SpringBoot Actuator需配置:
management: endpoints: web: exposure: include: health,info,metrics endpoint: health: probes: enabled: true show-details: always
|
五、构建优化技巧
#
5.1 .dockerignore文件
# Git .git .gitignore
# IDE .idea *.iml .vscode
# Maven target/ !target/*.jar
# 文档 README.md *.md
# 其他 *.log .env
|
#
5.2 利用构建缓存
将不常变更的指令放在前面:
COPY pom.xml . RUN mvn dependency:go-offline
COPY src ./src RUN mvn package -DskipTests
|
#
5.3 选择合适的基础镜像
| 镜像 |
大小 |
适用场景 |
| eclipse-temurin:17-jdk |
~400MB |
需要编译 |
| eclipse-temurin:17-jre |
~250MB |
纯运行 |
| eclipse-temurin:17-jre-alpine |
~180MB |
追求最小体积 |
| distroless-java17 |
~120MB |
极致安全 |
#
5.4 Alpine版本注意事项
FROM eclipse-temurin:17-jre-alpine
RUN apk add --no-cache curl
|
注意:Alpine使用musl libc,某些JNI库可能不兼容。
六、Distroless极简镜像
Google的distroless镜像只包含运行时依赖,无shell、无包管理器。
FROM maven:3.9-eclipse-temurin-17 AS build WORKDIR /app COPY . . RUN mvn clean package -DskipTests
FROM gcr.io/distroless/java17-debian12 WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 USER nonroot:nonroot ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
优势:
- 镜像体积最小
- 攻击面最小(无shell无法exec进入)
- 安全性最高
限制:
七、Docker Compose配置
version: '3.8'
services: app: build: context: . dockerfile: Dockerfile args: - JAR_FILE=target/*.jar image: myapp:latest container_name: myapp ports: - "8080:8080" environment: - JAVA_OPTS=-Dspring.profiles.active=prod - JVM_OPTS=-XX:MaxRAMPercentage=75.0 - TZ=Asia/Shanghai volumes: - ./logs:/tmp healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 3s retries: 3 start_period: 40s deploy: resources: limits: cpus: '2' memory: 1G reservations: cpus: '1' memory: 512M restart: unless-stopped
|
八、Kubernetes部署配置
apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: app image: myapp:latest ports: - containerPort: 8080 env: - name: JAVA_OPTS value: "-Dspring.profiles.active=prod" - name: JVM_OPTS value: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC" resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 30 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 lifecycle: preStop: exec: command: ["sh", "-c", "sleep 10"]
|
九、镜像安全扫描
#
9.1 使用Trivy扫描
brew install trivy
trivy image myapp:latest
trivy image --severity HIGH,CRITICAL myapp:latest
|
#
9.2 使用Docker Scout
docker scout cves myapp:latest
|
十、CI/CD集成示例
#
GitHub Actions工作流
name: Build and Push Docker Image
on: push: branches: [main]
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Build with Maven run: mvn clean package -DskipTests - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max
|
总结
构建高质量的SpringBoot Docker镜像需要关注:
- 体积优化:使用JRE、多阶段构建、分层提取
- 安全加固:非root用户、distroless镜像、安全扫描
- 性能调优:容器感知JVM参数、GC选择
- 可观测性:健康检查、Actuator端点、日志收集
- 构建效率:缓存策略、.dockerignore、并行构建
掌握这些实践,能够打造出既轻量又安全的生产级容器镜像。
核心要点
日志级别设置:根据环境设置合适的级别
日志格式配置:添加 traceId 便于链路追踪
日志输出:控制台输出和文件输出的配置
日志归档:设置滚动策略和保留时间
总结
日志是排查问题的生命线,合理配置日志可以提升排查效率。在实际项目中,结合 ELK 等工具搭建日志系统,可以更好地管理和分析日志。