Java Docker 部署
把 Java 服务装进容器:镜像分层、内存参数与健康检查基本实践。
#howto
#growing
#dev
#java
[!info] related notes
Java Docker 部署
目标
- 多阶段构建产出运行镜像
- 以非 root 运行
- 正确处理容器内存与退出信号
- 提供健康检查端点配合编排
这个场景为什么出现
部署单元从“机器上的 jar”变成镜像。JVM 与 cgroup 内存、信号、只读层需要额外纪律。
[!abstract] 一句话理解 构建阶段产出 jar,运行阶段用精简 JRE 镜像启动;配置由环境注入,并确保 SIGTERM 触发优雅关闭。
项目适配模板
场景:部署已有 Maven 服务
前提:项目包含可执行的 mvnw 和 .mvn 配置;package 产出名确为 target/app.jar 的可执行 JAR(含正确 Main-Class 与运行依赖)。普通 Maven JAR 不自动满足此条件。本例假设测试已在 CI 通过;-DskipTests 仅用于镜像打包阶段。
# 概念示例(按项目调整)
FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY . .
RUN ./mvnw -q -DskipTests package
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/app.jar /app/app.jar
USER 10001:10001
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0"
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]
完成上述项目适配后,本地构建与验证:
docker build -t shop-order:dev .
docker run --rm --name shop-order-review --memory=512m -p 127.0.x.x:8080:8080 -e APP_PORT=8080 shop-order:dev
# 另开终端,按项目实际健康路径检查;退出时:docker stop --time 10 shop-order-review
应用还必须实际读取 APP_PORT,并监听容器内可访问的接口(通常 0.0.x.x);仅设置变量或 EXPOSE 不会修改应用。前面的 JDK HTTP 学习示例绑定回环地址,不能原样作为此端口映射模板的网络服务。
用 .dockerignore 排除 .git、target、本机缓存和密钥文件;构建期凭据用专门的 secret 挂载,多阶段构建不会自动消除构建上下文/缓存中的秘密。UID 10001 不需要镜像内有同名账号,但挂载卷和可写目录须授予相应权限。
结合场景再看三个关注点
- JDK 构建 / JRE 运行分层。
- MaxRAMPercentage 只影响堆上限的计算,不是 JVM 总内存上限;线程栈、元空间、直接缓冲区及本地内存也计入容器限制。75% 只是示意预算,需按负载测量;没有容器限额时基数也不同。
- PID1/信号:确保 java 能收到 SIGTERM(注意 shell 包装)。
核心步骤与准确模型
- 可复现构建
- 最小运行镜像
- 配置外置
- 健康端点由应用提供,Docker HEALTHCHECK 或编排探针需另行配置;EXPOSE 只是端口元数据,不会开放主机端口或创建探针。
- 资源 requests/limits
工程实践
- 固定基础镜像摘要。
- 扫描 CVE。
- 使用只读根文件系统前,确认 JVM/应用需要的临时与数据目录,并显式挂载可写路径。
- 与优雅关闭联调。
本节总结
- 多阶段镜像
- 容器内存感知
- 信号与配置
自测题
- 为什么运行镜像不必含 jdk 编译器?
- shell 形式 ENTRYPOINT 的信号风险?
参考答案
- 运行已编译应用通常不需要 javac;若运行时还要动态编译、诊断工具或特定模块,应按实际需求保留。
- 信号可能被 shell 吞/不转发,影响优雅关闭。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Eclipse Temurin images | 镜像 | 基础镜像 |
| Container support in Java | 官方博客线索 | 容器感知 |