SpringBoot Docker镜像构建

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", \
# OOM时生成dump
"-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

# 创建非root用户
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

# JVM参数
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 利用构建缓存

将不常变更的指令放在前面:

# 先复制pom.xml下载依赖(缓存命中率高)
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

# Alpine使用apk包管理
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

# 运行阶段使用distroless
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扫描

# 安装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镜像需要关注:

  1. 体积优化:使用JRE、多阶段构建、分层提取
  2. 安全加固:非root用户、distroless镜像、安全扫描
  3. 性能调优:容器感知JVM参数、GC选择
  4. 可观测性:健康检查、Actuator端点、日志收集
  5. 构建效率:缓存策略、.dockerignore、并行构建

掌握这些实践,能够打造出既轻量又安全的生产级容器镜像。

核心要点

  1. 日志级别设置:根据环境设置合适的级别

  2. 日志格式配置:添加 traceId 便于链路追踪

  3. 日志输出:控制台输出和文件输出的配置

  4. 日志归档:设置滚动策略和保留时间

总结

日志是排查问题的生命线,合理配置日志可以提升排查效率。在实际项目中,结合 ELK 等工具搭建日志系统,可以更好地管理和分析日志。


   转载规则


《SpringBoot Docker镜像构建》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录