这是一个在 Java 工程化领域争论已久的话题,但站在 2024 年的视角来看,答案其实已经非常清晰:除非你有极特殊的底层定制需求,否则强烈建议直接使用包含 JRE/JDK 的官方基础镜像(如 eclipse-temurin、amazon-corretto 或 Oracle 官方镜像),而不是从纯净的 Linux 系统镜像(如 ubuntu:22.04 或 centos:7)开始手动安装。
下面我从实际生产经验、运维成本、安全性以及云原生最佳实践几个维度,把为什么“选现成镜像”是更优解讲清楚。
1. 维护成本的巨大差异:你是在写业务代码,不是在造轮子
如果你选择从 ubuntu 或 centos 开始搭建,你需要处理以下一系列琐事:
- 依赖包管理:安装
apt-get install openjdk-xx-jre,还要担心版本冲突。 - 时区与编码:必须显式设置
TZ=Asia/Shanghai和LANG=en_US.UTF-8,否则日志里的时间可能乱跳,中文文件名可能变问号。 - 用户权限:Linux 安全规范严禁以 root 运行应用,你得创建非特权用户(non-root user),配置 sudoers,处理文件读写权限。
- 字体库:如果你的项目涉及 PDF 生成、验证码或图表渲染,你还得额外安装
fonts-liberation等字体包,这在纯净镜像里是不存在的。
而使用 eclipse-temurin:17-jre-alpine 或 amazoncorretto:17-alpine,这些坑基本都被上游厂商填平了。你只需要关注你的 application.yml 和代码逻辑。
2. 镜像体积与启动速度:Alpine vs Debian/Ubuntu
很多人有个误区,觉得“系统镜像小所以快”。实际上,现代 Java 部署推崇的是 JRE + Alpine Linux 的组合。
- 全量 JDK 镜像:通常 400MB+,包含了开发工具链、源码、调试符号等大量无用文件。
- JRE 镜像:通常 150MB-200MB,只包含运行时核心。
- Alpine 基础:基于 musl libc,镜像体积极其精简。例如
eclipse-temurin:17-jre-alpine大约只有 160MB 左右。 - Debian/Ubuntu 基础:即使只装 JRE,由于 glibc 和基础库的存在,镜像通常在 300MB-500MB。
在 Kubernetes 或云服务器集群中,镜像越小,拉取速度越快,磁盘占用越少,并发扩容时的网络压力也越小。对于高并发场景,这几十 MB 的差异在大规模集群下会被放大成显著的运维瓶颈。
3. 安全合规与漏洞扫描
这是企业级部署最看重的点。
- 手动安装的隐患:当你从 Ubuntu 源安装 OpenJDK 时,你拿到的是发行版打包的版本。这个版本可能滞后于 upstream 的安全补丁,或者引入了不必要的二进制组件。一旦爆出 Log4j 或 Spring4Shell 级别的安全漏洞,你需要重新构建镜像、打补丁、验证、发布,流程长且容易出错。
- 官方维护的 JRE 镜像:像 Eclipse Temurin、Amazon Corretto、Azul Zulu 这些发行商,会持续提供经过安全加固的 JRE 镜像。更重要的是,它们提供了清晰的 CVE 追踪。你可以直接基于最新的稳定标签构建,确保基础镜像本身是干净的。
此外,主流云厂商(阿里云、腾讯云、华为云)的容器镜像服务(ACR/TCR/HCR)都内置了漏洞扫描功能。使用标准的基础镜像,扫描报告更准确;而你自己拼凑的系统镜像,往往因为缺少某些元数据或版本标识,导致扫描结果不准确,增加排查难度。
4. 可复现性与确定性(Reproducibility)
这是 DevOps 的核心原则之一。
- 场景 A(手动搭建):你在本地用
apt-get install openjdk-17-jre-headless,同事用yum install java-17-openjdk,测试环境用 Docker Hub 上的某个第三方镜像。这三个环境的 JVM 参数默认值、GC 行为、甚至内部类加载器都可能存在细微差异。这种“在我机器上是好的”问题,是线上故障的温床。 - 场景 B(使用标准镜像):所有人统一使用
openjdk:17-jre-slim或temurin:17-jre-alpine。JVM 的行为完全一致,环境变量预置相同。你可以通过 Pin 住具体版本号(如temurin:17.0.9_9-jre-alpine)来实现绝对的构建可复现性。
5. 什么情况下才考虑从 Linux 系统镜像开始?
虽然绝大多数情况推荐用 JRE 镜像,但确实有少数例外:
- 极简嵌入式场景:你的应用不是传统 Spring Boot,而是 Quarkus 或 Micronaut,并且你希望极致压缩镜像到 50MB 以内,可能需要自己裁剪 Alpine 的基础库。但这需要极高的 Linux 功底。
- 特殊内核模块或硬件驱动:如果应用需要访问特定的 GPU 驱动、FPGA 卡或自定义的内核模块,而这些不在标准 JRE 镜像中,你可能需要从特定版本的 CentOS 或 Ubuntu 开始,再安装 JRE。
- 遗留系统迁移:某些老项目强依赖
/etc/init.d/脚本、SysVinit 或特定的系统服务管理器,强行切换到容器化 JRE 镜像改造成本过高。
但对于 95% 以上的互联网 Java 项目(Spring Cloud, Dubbo, 微服务等),这些都不是理由。
实操建议:Dockerfile 最佳实践
一个标准的、生产就绪的 Java 项目 Dockerfile 应该长这样:
# 阶段一:构建(可选,如果使用 Maven Wrapper 或 Gradle)
FROM maven:3.8.6-jdk-17 AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# 阶段二:运行(关键部分)
# 使用 Eclipse Temurin JRE Alpine 镜像,体积小、安全、无头版
FROM eclipse-temurin:17-jre-alpine
# 设置工作目录
WORKDIR /app
# 复制构建好的 jar 包
COPY --from=builder /app/target/*.jar app.jar
# 创建非 root 用户,提升安全性
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
# 暴露端口(根据实际调整)
EXPOSE 8080
# 设置 JVM 参数,适配容器内存限制
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
总结
- 不要从
ubuntu:22.04或centos:7开始手动安装 JRE。那是 2015 年前的做法。 - 要使用
eclipse-temurin,amazon-corretto, 或azul-zulu提供的 JRE 镜像。 - 优先选择
-alpine或-slim后缀的版本,以减小镜像体积,加快部署速度。 - 永远不要以 root 身份运行 Java 进程。
在云计算和容器化时代,基础设施即代码(IaC)的理念要求我们尽可能复用经过验证的标准组件。JRE 就是一个被广泛验证的标准组件,没必要重复发明轮子。
云技术博客