Java / Spring Boot 容器镜像制作¶
运维制作镜像的目标是:制品可追溯、运行环境固定、非 root 运行、配置外置、日志可收集,并能在受限资源下优雅启动和停止。
基础运行镜像¶
FROM eclipse-temurin:21-jre
RUN groupadd --system app \
&& useradd --system --gid app --home-dir /app app
WORKDIR /app
COPY --chown=app:app app.jar /app/app.jar
USER app:app
EXPOSE 8080
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70 -XX:+ExitOnOutOfMemoryError -Dfile.encoding=UTF-8"
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
21 只是示例,必须与应用声明的 Java 版本一致。生产中进一步固定经过验证的镜像标签或 digest,避免同一 Dockerfile 在不同时间拉到不同基础镜像。
构建与运行¶
docker build -t registry.example.com/team/app:1.4.0 .
docker run --rm \
--name app \
--memory 1g \
--cpus 1.5 \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-v /srv/app/config:/app/config:ro \
registry.example.com/team/app:1.4.0 \
--spring.config.additional-location=file:/app/config/
镜像内只放 JAR 和固定运行依赖;环境配置、密码、证书和可增长日志不烘焙进镜像。
镜像检查清单¶
| 项目 | 要求 |
|---|---|
| 基础镜像 | 与 Java 版本和 CPU 架构一致,固定版本/digest,定期扫描漏洞 |
| 运行用户 | 使用非 root 用户,数据与临时目录提前授权 |
| 制品 | JAR 版本、Git SHA 和镜像标签可对应,CI 中校验来源 |
| 配置 | 通过只读挂载和环境变量注入,Secret 不进入 Layer |
| 日志 | 默认写 stdout/stderr,由容器平台收集 |
| 资源 | 设置容器 CPU/内存,并为非 Heap 内存留余量 |
| 停止 | 应用正确响应 SIGTERM,编排平台给予合理优雅退出时间 |
| 健康检查 | 区分进程存活、应用就绪和外部依赖状态 |
分层镜像为什么有用¶
Spring Boot Fat JAR 中依赖库变化通常比业务代码少。使用 Spring Boot 的分层 JAR/工具模式把 dependencies、loader 和 application 分成不同镜像层,可以在只修改业务代码时复用依赖层缓存,减少构建和推送量。
是否采用分层取决于开发构建产物是否支持。运维不要自行解压未知 JAR 改内容,应让 Maven/Gradle 构建和 Dockerfile 形成一套经过验证的流程。
健康检查¶
如果应用启用了 Actuator:
健康端点应限制暴露内容和访问范围。不要把“数据库短暂不可用”不加区分地当成 liveness 失败,否则容器会被反复重启,放大依赖故障。
常见问题¶
| 现象 | 优先检查 |
|---|---|
UnsupportedClassVersionError |
JAR 构建版本高于运行镜像 Java 版本 |
| 容器立即退出 | docker logs、入口命令、JAR 路径、文件权限、配置缺失 |
| 写日志/临时文件报权限错误 | 非 root 用户对挂载目录和 java.io.tmpdir 是否有权限 |
| 容器 OOMKilled 但无 Java OOM | 容器总内存不足,Heap 外内存或进程被内核直接杀死 |
| 镜像很大 | 是否误用完整构建镜像、复制 Maven 缓存或未使用 .dockerignore |