docker 镜像缺运行依赖速查:精简镜像的时区证书与字体补齐
更新时间:2026-10-24。FAQ 速查页,面向「镜像换成 alpine / slim / distroless 之后,应用莫名不对了」的同学——日志时间差 8 小时、HTTPS 请求报证书错误、导出的 PDF 全是方框、entrypoint 里的 shell 脚本直接跑不起来。docker 镜像缺运行依赖的本质一句话:精简镜像把「操作系统该有的常识」裁掉了——tzdata、ca-certificates、字体、glibc 生态、shell 工具链,这些在完整发行版里默认存在的东西,在精简镜像里都要自己点名补装,缺一样就是一类诡异故障。病灶收敛在六根轴上:时区数据、CA 证书、字体、shell 与基础工具、glibc 兼容、动态库未打包。动态库缺失导致的启动即崩看 docker 容器缺动态库速查;路径类缺失看 docker 路径不存在速查。
本文要回答的问题
- 容器里时间差 8 小时,TZ 环境变量设了为什么没用?
- Go 程序在 scratch 镜像里请求 HTTPS 报
x509: certificate signed by unknown authority,差什么? - alpine 镜像里为什么
bash、ss、ps全都没有? - 应用能跑但导出的图片/PDF 中文全变方框,缺的是哪一层?
- 本地编译的二进制拷进 alpine 就
not found,文件明明存在?
一、报错原文与典型现象
缺依赖的报错分散在各应用层,但都有共同指纹——「在完整系统上不可能出现」(路径以 /proj/demo 占位,镜像源用 registry.example.com 占位):
# 指纹一:时区 —— 设了 TZ 但时区数据库不存在,回落到 UTC
$ docker run --rm -e TZ=Asia/Shanghai registry.example.com/base/alpine date
Sat Oct 24 02:15:07 UTC 2026 # 期望 +08,实际还是 UTC# 指纹二:CA 证书 —— 静态编译的 Go 程序在 scratch 里验不了证书
$ docker run --rm registry.example.com/team/api:1.0
Get "https://registry.example.com/health": tls: failed to verify certificate:
x509: certificate signed by unknown authority# 指纹三:shell 不存在 —— entrypoint 脚本连解释器都没有
$ docker run --rm registry.example.com/base/distroless /proj/demo/start.sh
docker: Error response from daemon: failed to create task for container:
exec: "/proj/demo/start.sh": stat /bin/sh: no such file or directory.# 指纹四:glibc —— 二进制在 alpine 里「存在但 not found」
$ docker run --rm registry.example.com/base/alpine /proj/demo/agent
/bin/sh: /proj/demo/agent: not found # 文件明明在,缺的是 glibc 动态链接器读现象抓三条判据:故障发生在「完整发行版默认有」的领域(时间、证书、字体、shell),第一反应就该怀疑基础镜像太精简;alpine 上的 not found 十有八九不是文件不在,而是 musl 与 glibc 的动态链接器路径不同(用 ldd 或 file 命令验证);证书与时间类问题往往不影响进程存活,容器一直 Up 但行为不对,最容易被漏检。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 容器时间恒为 UTC | 镜像无 tzdata | 容器内 ls /usr/share/zoneinfo | 装 tzdata 或挂载 zoneinfo |
| HTTPS 报 x509 证书错误 | 镜像无 ca-certificates | 容器内 ls /etc/ssl/certs | 补 ca-certificates 包 |
| 导出图片/PDF 中文方框 | 镜像无中文字体 | fc-list 为空或无中文字体 | 装 fontconfig 与中文字体包 |
| start.sh 报 /bin/sh 不存在 | distroless/scratch 无 shell | docker run 进镜像 ls /bin | 改用直接 exec 或换有 shell 基础镜像 |
| alpine 里 ps/ss/curl 没有 | busybox 裁剪未装对应 applet | which 逐个验证 | apk add 按需补装 |
| 二进制 not found 但文件在 | musl 与 glibc 不兼容 | file 命令看动态链接器 | 换 glibc 镜像或静态编译 |
| Java 容器字符集错乱 | 缺 locales/编码配置 | locale 命令验证 | 装 locales 或设 LANG |
| 应用能跑但功能半残 | 某依赖库未随镜像打包 | 对照依赖清单逐项核 | Dockerfile 显式 COPY/安装 |
三、排查决策图
这张图是标准排查路线:红是精简镜像导致的现象与根因,蓝是排查动作,绿是验证通过,橙是修复,紫是防复发沉淀。
读这张图抓住关键一步:「归类」比「修复」值钱——时区、证书、字体、工具、glibc 五个领域的补包各不相同,但验证动作是同构的(容器内直接看对应资源在不在)。先在容器里确认「缺的是哪一类」,再去 Dockerfile 补对应那一行,避免「换镜像试试」式的瞎蒙。
四、逐条排查展开
时区缺失是最普及的一个。 alpine、distroless、scratch 都不带 tzdata;TZ 环境变量只是「选哪个时区」,时区数据库文件本身不存在时变量形同虚设。alpine 的补法:
FROM registry.example.com/base/alpine:3.20
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghaidebian-slim 对应 apt-get install -y tzdata(注意 slim 镜像装包前要 apt-get update,且交互问题用 DEBIAN_FRONTEND=noninteractive 压住)。distroless 没有包管理器,只能从构建阶段 COPY --from= 把 /usr/share/zoneinfo 拷进来。验证:docker run --rm <镜像> date 看时区缩写是不是 CST。
CA 证书缺失坑所有出站的 HTTPS。 Go 静态编译的程序不依赖 libc,但读系统 CA 证书,scratch/distroless 里 /etc/ssl/certs 是空的,于是所有对外 HTTPS 调用全部 x509 失败。补法同样两种:有包管理器的 apk add ca-certificates 或 apt-get install ca-certificates;没有包管理器的从构建阶段拷 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/。注意自签或内部 CA 的场景还要把企业根证书一并拷入并 update-ca-certificates。
字体缺失只在「生成图形内容」时暴露。 应用跑 Web 服务毫无症状,一旦导出 PDF、渲染图表、生成验证码图片,中文全部变方框(豆腐块)。验证与补法:
docker run --rm <镜像> fc-list | grep -i cjk # 空 = 没中文字体RUN apk add --no-cache fontconfig ttf-dejavu font-noto-cjk # alpine
# debian-slim: apt-get install -y fontconfig fonts-noto-cjkJava 应用还要注意:JDK 的字体检索走 fontconfig,补完字体要确认 fc-cache 已刷新(多数包安装时自动做)。
shell 与工具缺失在 distroless/scratch 上是设计使然。 这类镜像的卖点就是没有 shell、没有包管理器、攻击面最小——entrypoint 必须直接 exec 二进制,任何 sh -c 形式的启动脚本都不可用。排查期没有 sh 可进怎么办:用 docker run --rm -it --entrypoint ... 无效时,换同应用的 debug 变体镜像(distroless 官方提供 :debug 标签,带 busybox),或 docker export 出文件系统在宿主机里翻。alpine 场景温和些:busybox 自带 sh,但 ps、ss、curl、vim 都要 apk add 补。
glibc 与 musl 的鸿沟是 alpine 独有的坑。 alpine 用 musl libc,动态链接器在 /lib/ld-musl-x86_64.so.1;而常规发行版编出的二进制找的是 /lib64/ld-linux-x86-64.so.2——文件存在、权限正确,exec 时找不到解释器,shell 只能报一句误导性的 not found。三条出路按优先级:在 alpine 里重新编译(CI 加 musl 目标);程序支持的话静态编译(Go 的 CGO_ENABLED=0);实在要跑现成 glibc 二进制就 apk add gcompat 或干脆换 debian-slim 基础镜像。Node 官方镜像的 -alpine 变体同样是 musl,原生模块(node-gyp 产物)有同样问题。
「依赖没随镜像打包」要在 Dockerfile 评审期拦住。 多阶段构建只 COPY 了二进制,漏了它运行期读的配置模板、字典文件、licence 校验文件——容器能起、核心功能能跑、某个边角功能才炸。治理靠两张清单:构建期的「运行期依赖清单」(二进制 + 配置 + 数据文件逐项列)与容器内的「冒烟检查脚本」(启动后自检关键文件存在性),后者挂进健康检查,缺失即不健康。
五、规范做法与预防
- 基础镜像选型先评审:团队镜像模板定型(如 debian-slim 为默认、alpine 限网络工具类、distroless 限静态二进制服务),换底要走评审。
- 时区证书进基础层:自建基础镜像统一预装 tzdata 与 ca-certificates,业务镜像不再各自补。
- 字体按需专项补:有渲染/导出需求的服务单独评估字体包,不全局强加(中文字体包通常上百 MB)。
- CI 跑容器内冒烟:镜像构建后在 CI 里跑一遍
date、HTTPS 拨测、fc-list等断言,缺依赖当场拦下。 - 静态编译优先 CGO_ENABLED=0:Go 服务默认静态编译,从根上绕开 libc 兼容问题。
- 运行期依赖清单进评审:多阶段构建的 COPY 行对照清单逐项核对,漏打包在评审期发现。
这一篇在知识体系里的位置
本篇是 docker 排错速查簇的镜像内容完整性篇,定位「容器活着但行为不对」:启动期即崩的动态库缺失看 docker 容器缺动态库速查 与 docker 路径不存在速查;镜像体积与构建层面的语法问题看 docker 构建语法速查;字符集乱码的另一条线看 docker 中文乱码速查;同批的命名端口冲突看 docker 容器名与端口冲突速查、跨架构问题看 docker 镜像架构不匹配速查。基础镜像选型、按需补包、CI 冒烟,是精简镜像用得住的前提。
一句话总结
docker 精简镜像缺依赖的规律是「完整发行版默认有的,这里都要点名补」:时间错乱补 tzdata、HTTPS 报 x509 补 ca-certificates、中文方框补 fontconfig 加 CJK 字体、脚本起不来是 distroless 没 shell(换直接 exec 或 debug 变体排障)、alpine 里二进制 not found 多半是 musl 与 glibc 的链接器鸿沟(重编译、静态编译或 gcompat 三选一)。排查的通式是容器内直接验证对应资源——zoneinfo、certs、fc-list、ldd 各管一类。治本靠基础镜像选型评审、公共依赖进基础层、CI 容器内冒烟断言,把「缺依赖」从线上故障变成构建期拦截。