老Docker构建Bookworm报NO_PUBKEY与内存错误
老版本 Docker 构建 Debian Bookworm 镜像报 NO_PUBKEY 与 lzma 内存错误
一句话结论:不是缺 key,也不是真的内存不足,而是 Jenkins Agent 上的 Docker 太旧,其默认 seccomp 白名单没有放行 Debian 12(Bookworm)新版 glibc 依赖的
clone3/faccessat2等 syscall,导致 apt/gpg 验证签名和 xz 解包被”误杀”,报出两个极具迷惑性的假错误。
1. 现象
在 Jenkins Agent 上 docker build 一个基于 python:3.11(Bookworm)的基础镜像时失败,本地机器(Docker 新)一切正常,Jenkins Agent(Docker 旧)必挂。
失败出现在 apt-get update / apt-get install 阶段,报错有两副面孔:
- 签名类:
NO_PUBKEY .../The following signatures couldn't be verified/InRelease is not signed - 解包类:
lzma error: Cannot allocate memory+tar: This does not look like a tar archive
2. 弯路:把 apt 命令”修对”没用
一开始怀疑是 apt 命令写错(多条 apt-get 子命令被拼到同一行,导致 --allow-insecure-repositories 被当成 apt-get clean 的参数)。命令确实写错了,但即使把命令改对,错误依旧——因为根因在更底层:syscall 被 seccomp 拦截,apt 这次不是”命令语法”问题,而是”运行时被掐断”。
这也解释了为什么各种 --allow-unauthenticated / trusted=yes 的 workaround 治标不治本。
3. 真正根因
| 项 | 说明 |
|---|---|
| 直接原因 | 老 Docker(疑似 < 20.10.10)的默认 seccomp profile 未知 clone3(435)、faccessat2(439)、rseq、close_range 等新 syscall,默认动作 SCMP_ACT_ERRNO 直接返回 EPERM |
| 间接原因 | 宿主 libseccomp2 版本太旧(如 2.3.3),根本”不认识”这些 syscall,seccomp 规则里加了也没用 |
| 触发条件 | 基础镜像从 Debian 11(Bullseye)升级到 Debian 12(Bookworm,2023-06 起 python 官方镜像默认切换),其 glibc ≥ 2.34 会主动调用 clone3 / faccessat2 |
| 环境差异 | 本地 Docker 新(profile 已含这些 syscall)→ 正常;Jenkins Agent Docker 旧 → 拦截 |
4. 两个”假错误”的机制
Bookworm 的 apt/gpg 验证签名
-> 调用 faccessat2 / clone3
-> 被老 seccomp 拦截,返回 EPERM
-> 签名验证流程被中断
=> 抛出 "NO_PUBKEY / InRelease is not signed"(误导:让人去折腾 key)
新版 .deb 用 xz 压缩,解包
-> 依赖被拦的新 syscall;clone3 建线程失败
-> xz 解压器崩溃 / 内存分配失败
=> 抛出 "lzma error: Cannot allocate memory"(误导:让人去加内存)
两个错误都是同一根因的不同表象,加 key、加内存、改 apt 命令都救不了。
5. 社区印证(不止我们踩坑)
- Airflow 官方讨论:明确指出 CI 里的
NO_PUBKEY/invalid signature基本是 red herring,真凶是 runner 上 Docker/libseccomp 太旧,不支持 Bookworm 的faccessat2/clone3;用seccomp=unconfined或升级 Docker 20.10.10+ 即解。(Airflow) - UCS Bug 55360:glibc ≥ 2.34 用
clone3+faccessat2,旧 Docker(19.03.8) 默认 seccomp 不放行,libseccomp2 2.3.3 不认识这些 syscall;最终修复是升级 libseccomp2 ≥ 2.5 并在 seccomp 规则加clone3/rseq/faccessat2。(UCS) - crazylion 博客:Docker ≤ 20.10.9 跑新版 glibc,apt 安装包报
lzma error: Cannot allocate memory/tar: This does not look like a tar archive——与本问题 100% 吻合,根因是clone3被 seccomp 拦截。(crazylion) - Alpine 3.14 发布说明:
scmp_sys_resolver faccessat2返回 439 = 支持,-1 = 不支持,可快速验证宿主 libseccomp 是否认得该 syscall。(Alpine) - devopsaitoolkit:多篇文章确认
clone3被拦会表现为pthread_create failed: Operation not permitted/ apt 签名失败,并强调seccomp=unconfined只是临时诊断手段。(devops)
6. 解决方案
方法 1:构建时关闭 seccomp(最快,临时止血)
改 CI 里的 docker build:
docker build --security-opt seccomp=unconfined \
--memory=2g --memory-swap=2g \
-t reg.qschou.com/qsevidence/$app_id:${env}_${BranchOrTag} .
⚠️
seccomp=unconfined会完全关闭 syscall 过滤、削弱容器隔离,只应作为临时诊断/止血,不要进生产。配合的--memory/--memory-swap对”假内存错误”也对症。
方法 1 增强:自定义 seccomp profile(推荐止血方案)
比 unconfined 更安全——只放行缺失的 syscall,保留其余隔离:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86"],
"syscalls": [
{
"names": ["clone3", "faccessat2", "rseq", "close_range"],
"action": "SCMP_ACT_ALLOW"
}
]
}
保存为 seccomp-bookworm.json,然后:
docker build --security-opt seccomp=seccomp-bookworm.json \
-t reg.qschou.com/qsevidence/$app_id:${env}_${BranchOrTag} .
方法 2:Dockerfile 强制 trusted 源(仅辅助,非根因修复)
FROM reg.qschou.com/base/python:3.11-tectonic
RUN find /etc/apt -type f \( -name "*.list" -o -name "*.sources" \) -exec \
sed -i -e 's/^deb /deb [trusted=yes] /' \
-e 's/^Types: deb/Types: deb\nTrusted: yes/' {} + || true
RUN apt-get update -o Acquire::AllowInsecureRepositories=true \
-o APT::Get::AllowUnauthenticated=true && \
apt-get install -y --no-install-recommends --allow-unauthenticated \
ca-certificates fonts-noto-cjk fontconfig && \
fc-cache -fv && \
rm -rf /var/lib/apt/lists/*
这一步让 apt 不再校验签名,“假 NO_PUBKEY”不报了,但底层 syscall 被拦没解决,涉及 xz 解包/其他依赖新 syscall 的操作可能换个错继续挂。只当配合手段。
方法 3:升级 Jenkins Agent 的 Docker(根本解决)
要求运维把 Docker 升到 24+ 或至少 20.10.10 以上,并同步升级 runc / containerd / libseccomp2(≥ 2.5)。这才是根因修复。
# 在 Agent 上验证/升级
docker version --format '{{.Server.Version}}'
sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io libseccomp2
sudo systemctl restart docker
7. 先确认”是不是 seccomp”的诊断命令
在 Jenkins Agent 上跑(不改任何东西):
# 看 Docker / runc / libseccomp 版本
docker version --format '{{.Server.Version}}'; runc --version
# 验证宿主 libseccomp 是否认得 faccessat2(439=支持, -1=不支持)
scmp_sys_resolver faccessat2
# 关键对照实验:unconfined 能跑通就坐实是 seccomp 拦截
docker run --rm --security-opt seccomp=unconfined <你那个失败的构建步骤>
第 3 步在
unconfined下能跑通 → 100% 坐实 seccomp 拦截,方法 1 / 增强方案可放心用。
8. 结论与处置顺序
- 先试方法 1(或方法 1 增强) 验证并止血,把新日志确认通过;
- 同时把方法 2 当辅助,保证 apt 侧不再因签名校验卡住;
- 推动运维走方法 3 升级 Docker / libseccomp2,这是唯一的根因修复,避免长期依赖
unconfined或trusted=yes削弱安全性。
当前结论
Jenkins Agent 上 Docker 过旧,其默认 seccomp 白名单未放行 Debian 12(Bookworm)新版 glibc 依赖的
clone3/faccessat2等 syscall,导致 apt/gpg 签名验证与 xz 解包被拦截,分别伪装成NO_PUBKEY公钥错误和lzma: Cannot allocate memory内存错误。本地 Docker 新故正常。
建议处理
- 临时:方法 1 增强(自定义 seccomp profile 放行缺失 syscall)止血,避免直接用
unconfined; - 辅助:方法 2 Dockerfile 强制 trusted 源,确保 apt 侧不卡签名;
- 根本:方法 3 升级 Agent 的 Docker ≥ 20.10.10(建议 24+)并升级 libseccomp2 ≥ 2.5。