在企业里跑 Java 容器,最常见的一条路是 docker pull openjdk:8 然后把 jar 塞进去。但稍微严肃一点的场景——生产环境要可控、要带字体、要带时区、要 wait 启动——都会走向自建 JDK 基础镜像这条路。这篇把 5 年间从 JDK 8u333 一直到 JDK 21 的演进整理一遍,把踩过的坑一次说清楚。
阅读对象:需要维护 Java 应用基础镜像的运维 / 后端工程师 覆盖范围:JDK 8u333 / 8u351 / 8u371 / 8u381 / 8u401(Alpine)/ JDK 21 + Debian / Ubuntu / Alpine 三大基础镜像流派 + wait-for-it 启动 + 中文乱码 + 字体 + 时区 + TLS 1.0/1.1 兼容
一、为什么需要自建 JDK 基础镜像
官方 openjdk 镜像有两个"够用但不舒服"的地方:
- 时区不是上海:容器里
date永远差 8 小时,日志时间戳对不上排查窗口特别痛苦 - 没有字体:用到图形验证码、PDF 导出、POI 操作 Excel 的应用,直接 OOM 崩溃
- 没有
wait.sh:微服务启动顺序错乱,A 服务没等 B 服务的注册中心起来就启动 - 没有
tzdata/curl/iputils-ping等常用工具:调试时ping/nslookup找不到,只能 exec 进去手动装
自建基础镜像的本质是:把这些"跑生产迟早要的东西"预先 bake 进去,业务镜像只关心应用本身。
When to use:
- 一个团队有 ≥ 3 个 Java 微服务需要部署 → 必须自建 JDK 基础镜像
- 公司内部有"安全合规"要求,必须用某特定 JDK 版本 / 特定 OS → 自建
- 单个临时 demo → 没必要,
openjdk:8-jre-slim凑合用
二、Debian 流派:JDK 8u371 / 8u381
Debian 11 是 JDK 8 / 11 时代最稳的基础镜像——包管理 apt 完善、镜像体积可控、对 glibc 应用兼容好。
2.1 基础 Dockerfile
| |
关键点:
LABEL maintainer用部门邮箱而非个人邮箱,避免人员流动留下旧地址ENV LANG C.UTF-8解决Java 应用中文乱码最常见原因dpkg-reconfigure tzdata让java.util.Date也走上海时区(不是只改了/etc/localtime)- 末尾
rm -rf /var/lib/apt/lists/*必须保留,否则镜像会大 100MB+
2.2 镜像版本演进
实战中一个 JDK 8u381 基础镜像可能要经过 5~6 次迭代:
| Tag | 增加的能力 | 触发场景 |
|---|---|---|
8u381 | 基础 JDK + 时区 | 第一次发版 |
8u381-2 | + 中文字体(fontconfig) | 应用导出 PDF 报错 |
8u381-3 | + dmidecode | arthas / sigar 读硬件信息 |
8u381-4 | + TLS 1.0 / 1.1 启用 | 接入老客户的 HTTPS 服务 |
8u381-5 | 合并 -3 和 -4 全部能力 | 版本归一 |
8u381-6 | + skywalking agent + arthas-boot.jar | 接入分布式追踪 |
8u381-7 | + 第三方 SDK(硬盘录像机 .so) | 视频监控项目 |
8u381-8 | + mysqldump / mysql 客户端 | 容器内做 DB 备份 |
经验法则:每加一个能力就 +1 版本号,永远不要覆盖旧 tag——历史容器用的就是旧镜像,回头调试时还能拉下来。
2.3 时区必须做两件事
只改 /etc/localtime 不够。完整做法:
| |
第 3 步会更新 /etc/localtime 并设置内部状态。不执行第 3 步时 date 命令对,new Date() 错——Java 用的是 glibc 内部状态。
2.4 wait-for-it:解决启动顺序
微服务 xjAuth 依赖 nacos 先启动,直接 depends_on 没用——Docker 只控制启动顺序,不等待端口。
引入 wait.sh(基于 wait-for-it.sh):
| |
wait.sh 会先阻塞直到 localhost:8848 可达,再 exec 真正启动命令。-t 0 表示无限等待(生产推荐,避免临时网络抖动让容器直接退出)。
实战日志:
| |
三、Ubuntu 流派:JDK 8u333 / 8u351
Ubuntu 适合老系统延续——ubuntu:22.10 / 22.04 都有官方仓库支持。
| |
注意:Ubuntu 基础镜像默认没有 ca-certificates——HTTPS 请求会报
x509: certificate signed by unknown authority。要么apt install -y ca-certificates,要么用wget --no-check-certificate(不推荐)。
四、Alpine 流派:JDK 8u401(极致小)
Alpine 用 musl libc 而非 glibc,镜像只有 5MB 级别——但 JDK 跑在 Alpine 上要绕开 glibc 依赖。
4.1 坑:Alpine 自带 musl,Java 跑不动
解决方案:装 sgerrand/alpine-pkg-glibc 包。
| |
4.2 镜像大小对比
| 基础镜像 | JDK 8u401 后大小 |
|---|---|
debian:11.8 + JDK 8u381 | ~ 800 MB |
ubuntu:22.10 + JDK 8u351 | ~ 750 MB |
alpine:v3.19.1 + JDK 8u401 + glibc | ~ 350 MB |
Alpine 省了一半体积,冷启动时间和拉取速度都更友好——但 apk add 包要慎重(musl 与 glibc 二进制不兼容)。
五、JDK 21:新时代的开端
JDK 21 是 LTS(2023-09 发布,支持到 2031 年),如果新项目可以直接上 21。
5.1 Liberica JDK 21 Dockerfile
Liberica 是 BellSoft 出品的 OpenJDK 发行版,支持 alpine 和 glibc 两种打包,省心:
| |
5.2 JDK 21 的 Virtual Thread
JDK 21 的最大亮点是 Virtual Thread(虚拟线程,JEP 444)——轻量级线程,百万级并发不再是梦想:
| |
实战效果:同等 QPS 下,线程数从 200 降到 50,P99 延迟下降 30%。这是 JDK 21 值得升级的核心动力。
六、典型坑位
6.1 中文乱码三连击
症状:Java 应用读 CSV 出现 ???、写 MySQL 中文 ?、导出 PDF □。
排查顺序:
| |
根治方案:
- Dockerfile 加
ENV LANG C.UTF-8 - JVM 参数加
-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 - MySQL 连接串加
useUnicode=true&characterEncoding=UTF-8
6.2 TLS 1.0 / 1.1 兼容
症状:对接老客户的 HTTPS 接口报 SSLHandshakeException。
原因:JDK 8u381 默认 jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, ...,TLS 1.0/1.1 被禁用。
修复:
| |
注意:TLS 1.0/1.1 已不安全,只在必须对接老客户时开。新项目应该让客户升级到 TLS 1.2+。
6.3 容器内 ping / ip 找不到
Debian 镜像默认最小化,连 ping 都没有:
| |
七、Jenkins Agent 镜像
CI/CD 场景需要 JNLP 客户端连 Jenkins Master:
| |
配套 Jenkinsfile:
| |
八、镜像分发:自建 vs Docker Hub
| 方案 | 适用场景 | 优缺点 |
|---|---|---|
| 自建 Harbor | 企业内网 / 等保合规 | 速度快、可扫描 CVE、可审计;但要维护 Harbor |
| Docker Hub 私有仓库 | 小团队 / 个人 | 零运维;但国内拉取慢、可能泄露 |
| 阿里云容器镜像 ACR | 国内企业 | 国内快、便宜;但要绑定阿里云账号 |
生产推荐:Harbor + 跨地域复制。构建在 北方节点,自动同步到 华东 / 华南 / 华北 三个 Harbor。
九、构建与分发完整命令
| |
十、最佳实践清单
- 版本号带具体 patch 号:
8u381而非8,便于回溯 - 基础镜像锁版本:
debian:11.8而非debian:latest,避免某天突然构建失败 - 时区三步走:软链 + timezone 文件 + dpkg-reconfigure
- wait 脚本必装:微服务架构 90% 的"启动失败"都是顺序问题
- 字体必装:Alpine 3.15+、Debian 12+ 都需要
apt install fontconfig - TLS 兼容性单独 tag:避免一个基础镜像同时承担新客户和老客户的兼容要求
- MySQL 客户端单独 tag:不是所有 Java 应用都需要它,按需装
2024+ 视角补充
本文写于 2024-12,2025-2026 期间 JDK 自建镜像关键演进:
- JDK 21 / 25 主流化:JDK 25(2025-09 发布)已成为新 LTS(支持到 2033+)——2025+ 新项目强烈推荐 JDK 25
- JDK 8u421+ / 8u431+ 维护更新:Oracle 仍给付费用户提供 JDK 8 季度更新;开源社区转向 Eclipse Temurin 8u432+(2025+ 仍维护)
- Eclipse Temurin 17 / 21 / 25:2024-2026 容器首选——多平台 + LTS 支持 + 商业保障
- BellSoft Liberica JDK 21+:Alpine / glibc / CRaC 三种打包——云原生首选
- GraalVM JDK 21+:AOT 编译 + Native Image + Truffle 多语言——Serverless 首选
- Azul Zulu Prime 17 / 21:C4 Pauseless GC(亚毫秒 GC)——金融 / 电信级延迟敏感
- Amazon Corretto 21+:AWS 维护,生产级 LTS——AWS 用户首选
- **多架构构建(ARM64 + AMD64)**已成 2024+ 标准
- CRaC(Coordinated Restore at Checkpoint):JDK 17 / 21 实验特性,JVM 启动 < 10ms
- JLink 定制 JRE:Spring Boot 3.x + JLink 50MB 级别镜像
实战建议(2025-2026 视角):
- 新项目 → JDK 21 LTS / 25 LTS + Eclipse Temurin 或 BellSoft Liberica
- Serverless / 冷启动 → GraalVM Native Image 或 CRaC
- 金融 / 电信级延迟 → Azul Zulu Prime 21
- AWS 环境 → Amazon Corretto 21
- 多架构 → docker buildx linux/amd64 + linux/arm64
下一步
- 想看 Nginx 自建镜像(前端静态 / 反向代理 / WebSocket / TCP UDP)→ Nginx 自建镜像实战
- 想看 Go 自建最小镜像(scratch 5MB 级别)→ Go 自建最小镜像
- 想看 Java 应用 Docker 化(Tomcat / Spring Boot 打包 + HTTPS 证书)→ Java 应用 Docker 化
