一、Java 项目容器化的核心思路¶
Java 服务镜像制作通常可以拆成两部分:
- 先在构建环境里执行 Maven 打包
- 再把生成的 jar 放进运行镜像
这也是最典型的“构建环境”和“运行环境”分离思路。
二、准备源码、缓存目录和构建镜像¶
原始示例使用的源码地址:
https://gitee.com/dukuan/spring-boot-project.git
构建镜像:
registry.cn-hangzhou.aliyuncs.com/abroad_images/maven:3.5.3
构建命令:
mvn clean install -DskipTests
为了让 Maven 依赖缓存能够复用,先在宿主机准备缓存目录:
mkdir -p /root/java/m2
三、为什么推荐用临时容器做 Maven 构建¶
示例里使用临时容器完成构建:
docker run --ulimit nofile=1024 -it --rm \
-v `pwd`/m2:/root/.m2 \
-v `pwd`/spring-boot-project:/mnt/ \
registry.cn-hangzhou.aliyuncs.com/abroad_images/maven:3.5.3 bash
进入容器后执行:
cd /mnt
mvn clean install -DskipTests
这种方式的好处是:
- 宿主机不用直接安装 Maven 和 JDK
- 团队构建环境统一
- Maven 缓存可通过挂载目录重复利用
只要输出中出现 BUILD SUCCESS,就说明打包已经成功。
四、如何编写 Java 运行镜像的 Dockerfile¶
打包成功后,宿主机目录下会出现 target 目录和对应的 jar 包。运行镜像可以写得非常简洁:
FROM registry.cn-hangzhou.aliyuncs.com/abroad_images/jre:8u211-data
COPY target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar ./app.jar
ENV JAVA_OPTS="-XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=70.0"
CMD java -jar app.jar
这里有几个关键点:
- 基础镜像只保留运行 JRE,不再保留构建工具
- 把实际业务 jar 复制为统一名称
app.jar - 用环境变量控制 JVM 堆内存占比
五、构建和启动 Java 镜像¶
构建镜像:
docker build -t registry.cn-hangzhou.aliyuncs.com/abroad_images/java:v1 .
启动容器前,还要先确认应用本身监听的端口。原始示例中 application.yml 显示端口为 8761。
因此启动命令可以写成:
docker run -d -p 18761:8761 -m 1g --ulimit nofile=65535:65535 \
registry.cn-hangzhou.aliyuncs.com/abroad_images/java:v1
运行后可以用 curl 10.0.0.12:18761 访问验证。

六、为什么 Java 容器经常要额外关注资源参数¶
Java 在容器环境里比很多轻量语言更依赖资源边界设置,因为:
- JVM 会根据可见资源进行内存和线程相关决策
- 文件描述符限制不合理时,构建和运行都可能出问题
- 某些旧版本 JRE/JDK 在容器里对资源探测并不总是理想
因此 Java 镜像不仅要会做,还要会调运行参数。
七、常见问题一:Maven 构建时报文件描述符或内存异常¶
原始示例在执行 mvn clean install -DskipTests 时,遇到了类似下面的错误:
library initialization failed - unable to allocate file descriptor table - out of memory
解决思路是给构建容器增加合适的 ulimit 控制,例如:
docker run --ulimit nofile=1024 -it --rm ...
这说明问题不一定是 Maven 本身,而可能是容器可用文件描述符数量太小。
八、常见问题二:容器启动后 Exited(127) 或 JVM 崩溃¶
原始笔记还记录了一类比较典型的运行异常:
- 容器状态为
Exited (127) - 日志中出现 JVM fatal error、
SIGSEGV - 同时伴随
unable to allocate file descriptor table - out of memory
这种情况通常需要同时从两个方向处理:
8.1 限制容器内存边界¶
-m 1g
8.2 调整文件描述符限制¶
--ulimit nofile=65535:65535
原始验证结果表明,这两个参数单独增加其中一个,在实际环境中都可能帮助解决问题。
九、Java 镜像实战里最值得记住的经验¶
结合 Day004 的 Java 部分,可以沉淀出几条很有价值的经验:
- Maven 构建和运行镜像尽量分离
- 构建缓存目录最好挂载到宿主机,避免重复下载依赖
- 启动前先确认应用真实监听端口
- Java 镜像要重点关注内存和
ulimit - 遇到
Exited (127)不要只盯着 Docker 命令,要先看日志
Java 容器化最难的通常不是写 Dockerfile,而是处理“资源限制、启动参数和运行故障”这几个细节。把这些细节打磨好,Java 服务的镜像交付会稳定很多。