如何为Distroless容器添加字体解决Jasper Reports字体缺失报错
我正在开发一个基于Spring Boot的应用,使用Jasper Reports生成PDF报表,但在Distroless容器中遇到了字体缺失问题。Distroless容器默认不包含任何字体,而Jasper Reports运行依赖系统中存在可用字体。
此前查阅的方案均针对带有包管理器的Docker镜像(如Alpine),无法直接应用在无包管理器、无Shell的Distroless环境中,包括:
- 《Docker and Java - FontConfiguration issue》:针对Alpine发行版的方案
- 《Docker and Spring boot giving "sun.awt.FontConfiguration.head" is null》:需要执行
RUN apk add命令,无法在Distroless中运行 - 某Stack Overflow回答:非推荐方案
报错信息
无可用字体时会抛出以下异常:
java.lang.NullPointerException: Cannot load from short array because "sun.awt.FontConfiguration.head" is null at java.desktop/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264) at java.desktop/sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:224)
或
java.lang.RuntimeException: Fontconfig head is null, check your fonts or fonts configuration at java.desktop/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1271)
已尝试的方法
我尝试从带有字体的Ubuntu构建镜像中复制字体到Distroless镜像,Dockerfile内容如下:
FROM myregistry:5000/xyz/builder-base:mytag as BUILDER # ubuntu image with fonts in it # do some stuff here, build artifacts FROM myregistry:5000/xyz/mydistroless:mytag # Copy app.jar COPY --from=BUILDER /build/target/myapp.jar /opt/iamboss/app.jar COPY --from=BUILDER /usr/share/fonts /usr/local/share/fonts ## Run my app ENTRYPOINT ["java", "-jar","app.jar"]
但问题仍然存在。由于Distroless镜像没有包管理器和Shell,无法执行fc-cache更新字体缓存,根据Debian字体相关规范,字体放在/usr/local/share/fonts且权限为644(-rw-r--r--)时无需执行该操作,但依旧无效。
可行解决方案
方法1:复制字体到自定义目录并指定JVM参数
Java可通过系统属性指定字体目录,无需依赖系统字体缓存。步骤如下:
- 在构建阶段(BUILDER镜像)准备好需要的字体文件(比如从Ubuntu镜像复制,或直接下载开源字体如Noto Sans)
- 将字体复制到Distroless镜像的自定义目录,比如
/opt/fonts - 在启动命令中添加JVM参数,指定字体路径并禁用系统字体配置检查:
FROM myregistry:5000/xyz/builder-base:mytag as BUILDER # 示例:安装Noto字体并复制到构建镜像的/fonts目录 RUN apt-get update && apt-get install -y fonts-noto && cp -r /usr/share/fonts/truetype/noto /fonts FROM myregistry:5000/xyz/mydistroless:mytag COPY --from=BUILDER /build/target/myapp.jar /opt/iamboss/app.jar COPY --from=BUILDER /fonts /opt/fonts # 添加JVM参数指定字体目录,同时禁用fontconfig检查 ENTRYPOINT ["java", "-Djava.awt.headless=true", "-Dsun.awt.fontconfig=/dev/null", "-Dfont.dir=/opt/fonts", "-jar","/opt/iamboss/app.jar"]
说明:
-Dsun.awt.fontconfig=/dev/null会让Java跳过系统字体配置文件的读取,直接使用指定目录下的字体;-Djava.awt.headless=true确保Spring Boot在无图形环境的容器中正常工作。
方法2:在Jasper Reports模板中嵌入字体
直接在Jasper Reports的报表模板中嵌入字体,无需依赖系统字体:
- 将字体文件(如
.ttf)添加到项目资源目录(比如src/main/resources/fonts) - 在Jasper Reports的字体配置文件(
font.xml)中定义该字体:
<fontFamilies> <fontFamily name="Noto Sans"> <normal>fonts/NotoSans-Regular.ttf</normal> <bold>fonts/NotoSans-Bold.ttf</bold> <italic>fonts/NotoSans-Italic.ttf</italic> <boldItalic>fonts/NotoSans-BoldItalic.ttf</boldItalic> <pdfEncoding>Identity-H</pdfEncoding> <pdfEmbedded>true</pdfEmbedded> <exportFonts> <export key="pdf">Noto Sans</export> </exportFonts> </fontFamily> </fontFamilies>
- 在报表模板中引用该字体,编译报表时Jasper会将字体嵌入到PDF中,彻底脱离对系统字体的依赖。
方法3:构建预生成字体缓存的自定义Distroless基础镜像
如果需要多个应用共用字体,可提前在有包管理器的镜像中生成字体缓存,再复制到Distroless镜像:
# 第一步:在Ubuntu镜像中安装字体并生成缓存 FROM ubuntu:latest as FONT_BUILDER RUN apt-get update && apt-get install -y fonts-noto fontconfig RUN fc-cache -fv # 第二步:构建自定义Distroless镜像 FROM myregistry:5000/xyz/mydistroless:mytag COPY --from=FONT_BUILDER /usr/share/fonts /usr/share/fonts COPY --from=FONT_BUILDER /var/cache/fontconfig /var/cache/fontconfig COPY --from=FONT_BUILDER /root/.cache/fontconfig /root/.cache/fontconfig
后续应用镜像基于这个自定义Distroless镜像构建,即可直接使用系统字体。
内容的提问来源于stack exchange,提问作者Rajendra

