Featured image of post JDK 自建镜像实战:从 JDK 8 到 JDK 21 的三大流派

JDK 自建镜像实战:从 JDK 8 到 JDK 21 的三大流派

在 Debian / Ubuntu / Alpine 三大基础镜像上构建 JDK 8u381 / 8u401 / 21 基础镜像,解决时区、中文乱码、字体、wait 启动等典型坑

在企业里跑 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FROM debian:11.8
LABEL maintainer="ops@example.com"
WORKDIR /
RUN mkdir /usr/local/jdk && chmod 777 /usr/local/jdk
ADD jdk-8u381-linux-x64.tar.gz /usr/local/jdk
ENV JAVA_HOME /usr/local/jdk/jdk1.8.0_381
ENV CLASSPATH=$JAVA_HOME/bin:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
ENV PATH=.:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
ENV LANG C.UTF-8
ENV TZ=Asia/Shanghai DEBIAN_FRONTEND=noninteractive
RUN ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime \
    && echo ${TZ} > /etc/timezone \
    && dpkg-reconfigure --frontend noninteractive tzdata \
    && rm -rf /var/lib/apt/lists/*

关键点

  • LABEL maintainer 用部门邮箱而非个人邮箱,避免人员流动留下旧地址
  • ENV LANG C.UTF-8 解决Java 应用中文乱码最常见原因
  • dpkg-reconfigure tzdatajava.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+ dmidecodearthas / 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 不够。完整做法:

1
2
3
4
5
6
# 1. 软链时区文件
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 2. 写 timezone 文件(apt 工具会读这个)
echo "Asia/Shanghai" > /etc/timezone
# 3. 让 glibc 知道
dpkg-reconfigure --frontend noninteractive tzdata

第 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):

1
2
3
4
docker run -d --name xjAuth \
  --network_mode: "host" \
  <your-public-ip>:13001/base/lwd/jdk:8u381 \
  /wait.sh localhost:8848 -t 0 -- java -jar /jar/xjrsoft-auth.jar

wait.sh 会先阻塞直到 localhost:8848 可达,再 exec 真正启动命令。-t 0 表示无限等待(生产推荐,避免临时网络抖动让容器直接退出)。

实战日志:

1
2
wait.sh: waiting for localhost:8848 without a timeout
wait.sh: localhost:8848 is available after 23 seconds

三、Ubuntu 流派:JDK 8u333 / 8u351

Ubuntu 适合老系统延续——ubuntu:22.10 / 22.04 都有官方仓库支持。

1
2
3
4
5
6
7
8
FROM ubuntu:22.10
MAINTAINER ops@example.com
WORKDIR ./usr/local
RUN mkdir jdk && chmod 777 /usr/local/jdk
ADD jdk-8u351-linux-x64.tar.gz /usr/local/jdk
ENV JAVA_HOME /usr/local/jdk/jdk1.8.0_351
ENV CLASSPATH=$JAVA_HOME/bin:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
ENV PATH=.:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH

注意: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 包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FROM alpine:v3.19.1
LABEL author="ops"
WORKDIR /opt
# 1. 换 Alpine 源到清华
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
RUN apk update && apk upgrade
# 2. 装 ca-certificates
RUN apk --no-cache --upgrade add ca-certificates
# 3. 装 glibc(关键!)
ADD sgerrand.rsa.pub /etc/apk/keys/sgerrand.rsa.pub
COPY glibc-2.35-r1.apk glibc-bin-2.35-r1.apk glibc-dev-2.35-r1.apk glibc-i18n-2.35-r1.apk /opt/
RUN apk add --no-cache --force-overwrite glibc-2.35-r1.apk \
    glibc-bin-2.35-r1.apk glibc-dev-2.35-r1.apk glibc-i18n-2.35-r1.apk \
 && rm -f glibc-*.apk
# 4. 装时区数据
RUN apk add --no-cache tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 5. 装中文字体(alpine 3.15+ 必需,否则图形验证码 OOM)
RUN apk add --no-cache fontconfig
# 6. 解压 JDK
ADD jdk-8u401-linux-x64.tar.gz /opt
# 7. 环境变量
ENV JAVA_HOME=/opt/jdk1.8.0_401
ENV JRE_HOME=/opt/jdk1.8.0_401/jre
ENV CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
ENV PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
# 8. 软链 ld-linux 解决库文件报错
RUN ln -sf /usr/glibc-compat/lib/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
CMD ["java", "-version"]

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 两种打包,省心:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
FROM debian:12.12
LABEL maintainer="ops@example.com"
LABEL description="Debian 12.12 + BellSoft Liberica JDK 21"

RUN mkdir /usr/local/jdk && chmod 755 /usr/local/jdk
ADD bellsoft-jdk21.0.5+11-linux-amd64-full.tar.gz /usr/local/jdk
ENV JAVA_HOME /usr/local/jdk/jdk-21.0.5-full
ENV PATH=$JAVA_HOME/bin:$PATH:.
ENV LANG C.UTF-8
ENV TZ=Asia/Shanghai DEBIAN_FRONTEND=noninteractive

RUN sed -i 's|URIs: http://deb.debian.org/debian|URIs: http://mirrors.ustc.edu.cn/debian|g' /etc/apt/sources.list.d/debian.sources \
    && apt clean \
    && apt update \
    && apt install -y --no-install-recommends fontconfig \
    && ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \
    && echo ${TZ} > /etc/timezone \
    && fc-cache -fv \
    && rm -rf /var/lib/apt/lists/* \
    && java -version && javac -version

WORKDIR /app

5.2 JDK 21 的 Virtual Thread

JDK 21 的最大亮点是 Virtual Thread(虚拟线程,JEP 444)——轻量级线程,百万级并发不再是梦想:

1
2
3
4
5
6
7
// 旧写法:平台线程
Thread.startVirtualThread(() -> {
    handleRequest(request);
});

// Spring Boot 3.2+ 自动集成
spring.threads.virtual.enabled=true

实战效果:同等 QPS 下,线程数从 200 降到 50,P99 延迟下降 30%。这是 JDK 21 值得升级的核心动力。

六、典型坑位

6.1 中文乱码三连击

症状:Java 应用读 CSV 出现 ???、写 MySQL 中文 ?、导出 PDF

排查顺序

1
2
3
4
5
6
7
8
# 1. 看 locale
docker exec -it app locale
# 期望:C C.UTF-8 POSIX en_US.utf8
# 2. 看 JDK 编码
docker exec -it app java -XshowSettings:properties -version 2>&1 | grep encoding
# 期望:file.encoding = UTF-8
# 3. 看 MySQL 客户端字符集
docker exec -it app locale -a | grep -i utf

根治方案

  • 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 被禁用。

修复

1
2
3
4
5
6
vim $JAVA_HOME/jre/lib/security/java.security
# 找到 jdk.tls.disabledAlgorithms
# 删掉 TLSv1, TLSv1.1
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, \
    DH keySize < 1024, EC keySize < 224, anon, NULL, \
    include jdk.disabled.namedCurves

注意:TLS 1.0/1.1 已不安全,只在必须对接老客户时开。新项目应该让客户升级到 TLS 1.2+。

6.3 容器内 ping / ip 找不到

Debian 镜像默认最小化,连 ping 都没有:

1
RUN apt install -y iputils-ping iproute2 dnsutils curl wget

七、Jenkins Agent 镜像

CI/CD 场景需要 JNLP 客户端连 Jenkins Master:

1
2
3
4
5
FROM dockerhub.example.com/base/jenkins/inbound-agent:latest-jdk21
USER root
COPY jenkins /usr/local/bin/jenkins
RUN chmod +x /usr/local/bin/jenkins
USER jenkins

配套 Jenkinsfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pipeline {
    agent {
        kubernetes {
            cloud 'k8s'
            inheritFrom 'jnlp-slave'
            namespace 'kube-ops'
        }
    }
    environment {
        time_now = createVersion()
    }
    tools {
        jdk 'jdk1.8'
        maven 'maven3.9.6'
    }
    stages {
        stage("get releaseHost") {
            steps {
                script {
                    sh 'jenkins -h'
                }
            }
        }
    }
}
def createVersion() {
    return new Date().format("yyyy-MM-dd'T'HH-mm-ss", TimeZone.getTimeZone("Asia/Shanghai")) + "_${env.BUILD_ID}"
}

八、镜像分发:自建 vs Docker Hub

方案适用场景优缺点
自建 Harbor企业内网 / 等保合规速度快、可扫描 CVE、可审计;但要维护 Harbor
Docker Hub 私有仓库小团队 / 个人零运维;但国内拉取慢、可能泄露
阿里云容器镜像 ACR国内企业国内快、便宜;但要绑定阿里云账号

生产推荐:Harbor + 跨地域复制。构建在 北方节点,自动同步到 华东 / 华南 / 华北 三个 Harbor。

九、构建与分发完整命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. 构建
docker build -f Dockerfile -t dockerhub.example.com/base/lwd/jdk:8u381-2 .

# 2. 验证
docker run --rm dockerhub.example.com/base/lwd/jdk:8u381-2 java -version
docker run --rm dockerhub.example.com/base/lwd/jdk:8u381-2 date

# 3. 推 Harbor
docker login -u admin -p {{REDACTED}} dockerhub.example.com
docker push dockerhub.example.com/base/lwd/jdk:8u381-2

# 4. 离线分发(内网或保密环境)
docker save dockerhub.example.com/base/lwd/jdk:8u381-2 > jdk8u381-2.tar
docker load < jdk8u381-2.tar

十、最佳实践清单

  • 版本号带具体 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 / 252024-2026 容器首选——多平台 + LTS 支持 + 商业保障
  • BellSoft Liberica JDK 21+Alpine / glibc / CRaC 三种打包——云原生首选
  • GraalVM JDK 21+AOT 编译 + Native Image + Truffle 多语言——Serverless 首选
  • Azul Zulu Prime 17 / 21C4 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 ImageCRaC
  • 金融 / 电信级延迟Azul Zulu Prime 21
  • AWS 环境Amazon Corretto 21
  • 多架构docker buildx linux/amd64 + linux/arm64

下一步

使用 Hugo 构建
主题 StackJimmy 设计