一句话结论
是的。 在 Jenkins 容器里把 /var/jenkins_home/workspace 挂成 tmpfs 之后,checkout 出来的文件只存在于 Jenkins 容器内的内存文件系统;你在 宿主机 上打开 jenkins_home/workspace/...,往往是空目录,嵌套的 docker run -v 宿主机路径 也会报 No such file or directory。
这不是 Git 没拉下来,而是 挂载层级被 tmpfs「盖住」了。
背景
典型 homelab 栈:Jenkins 跑在 Docker 里,通过 docker.sock 在 Pipeline 里再启构建容器(Docker-outside-of-Docker)。某次为了「省磁盘 / 加快 IO / 改网络」调整 compose 时,给 workspace 加了 tmpfs——CI 从「全绿」变成「20 秒就挂」,报错类似:
chmod: ./build.sh: No such file or directoryJenkins 日志里 checkout 明明成功,容器内 ls 也能看到文件,唯独宿主机路径和嵌套 docker run 读不到。
架构:三层路径,别混为一谈
宿主机磁盘
└── /srv/stack/jenkins/jenkins_home/workspace/job-name/ ← 你以为文件在这里
│
│ bind mount: jenkins_home → /var/jenkins_home
▼
Jenkins 容器
└── /var/jenkins_home/workspace/job-name/ ← tmpfs 盖在这一层上!
│
│ volumes-from 或 -v 宿主机路径
▼
构建 sidecar 容器 (docker:27-cli)
└── 需要读到 build.sh、Dockerfile…当 compose 写了:
volumes:
- ./jenkins_home:/var/jenkins_home
tmpfs:
- /var/jenkins_home/workspace:rw,exec,size=8192mtmpfs 会在 bind mount 之上再挂一层内存盘,覆盖 workspace 子树。于是:
| 观察位置 | 能否看到 checkout 文件 |
|---|---|
Jenkins 容器内 /var/jenkins_home/workspace/... | ✅ 能 |
宿主机 ./jenkins_home/workspace/... | ❌ 通常不能(目录空或只有占位) |
docker run -v $(pwd):$(pwd)(pwd 在 Jenkins 容器里) | ❌ 映射到宿主机空路径 |
Docker Pipeline agent { docker { volumes-from jenkins } } | ✅ 能(共享 Jenkins 容器挂载命名空间) |
所以:不是「文件消失了」,而是文件在容器内的 tmpfs 里,宿主机 bind mount 那条链路被截断。
怎么自己验证
在 Jenkins 容器里:
# 1. workspace 是否是 tmpfs
docker exec jenkins cat /proc/self/mountinfo | grep workspace
# 典型问题输出含 tmpfs,例如:
# ... /var/jenkins_home/workspace ... tmpfs ...
# 2. 容器内有、宿主机无
docker exec jenkins ls -la /var/jenkins_home/workspace/my-job/build.sh
ls -la /srv/stack/jenkins/jenkins_home/workspace/my-job/build.sh # 宿主机路径若第一条是 tmpfs,第二条「容器有、宿主机无」,就可以确认是 tmpfs 覆盖,而不是权限或 Git 问题。
为什么以前能过、改 compose 后就不行
| 构建方式 | tmpfs workspace 下的行为 |
|---|---|
agent { docker { reuseNode true } } + Docker Pipeline 自动 volumes-from | 往往 仍能工作(sidecar 继承 Jenkins 容器挂载,包括 tmpfs) |
宿主机 sh 里手写 docker run -v "$PWD:$PWD" | 容易失败(PWD 解析到宿主机空目录) |
宿主机 docker run -v /srv/.../workspace/job:$PWD | 失败(宿主机目录里没有 checkout) |
因此:tmpfs 不是绝对不能跑 CI,但一旦 Pipeline 改成「从宿主机路径 bind mount workspace」,或你在宿主机上调试「为什么 workspace 是空的」,就会踩坑。
本次故障里还叠加了:清 Docker 缓存后 sidecar 偶发 process apparently never started(Durable Task 插件 + 嵌套容器),容易误判成「网络 / 代理 / Dockerfile」问题——先用 mountinfo 排除 tmpfs 能省很多时间。
修复建议
1. 去掉 workspace 的 tmpfs(推荐)
tmpfs:
- /var/jenkins_home/caches:rw,exec,size=512m
- /var/jenkins_home/logs:size=128m
# 不要:- /var/jenkins_home/workspace:...caches / logs 用 tmpfs 通常没问题;workspace 应落在 jenkins_home 的 bind mount 磁盘上,方便持久化、宿主机排查、嵌套 docker 挂载。
改完后:
cd /srv/stack/jenkins && docker compose up -d jenkins
ls /srv/stack/jenkins/jenkins_home/workspace/my-job/build.sh # 下一次构建后应存在2. 若坚持 workspace 用 tmpfs
- Pipeline 继续用 Docker Pipeline 的
agent { docker { reuseNode true } },依赖volumes-from,不要手写-v 宿主机workspace。 - 接受 workspace 重启 Jenkins 即丢(需每次重新 checkout)。
3. 其他配套
- Jenkins 容器内要用 BuildKit / buildx 时,挂载宿主机
docker cli-plugins(否则容器里只有docker没有buildx)。 - 清盘时避免
docker system prune -af,以免删掉docker:cli等工具镜像。 - 构建已成功但 post 阶段通知超时,应用
catchError包裹,避免「部署成功、Jenkins 标红」。
小结
| 问题 | 答案 |
|---|---|
| workspace 被 tmpfs 覆盖,宿主机能看到文件吗? | 一般看不到(bind mount 的 workspace 子树被内存盘覆盖) |
| Jenkins 容器内能看到吗? | 能 |
为什么 checkout 日志正常却 build.sh 不存在? | 嵌套容器挂的是宿主机空路径,不是 Jenkins 容器内的 tmpfs |
| 怎么修? | 删除 workspace tmpfs,让 checkout 落盘到 jenkins_home 卷 |
延伸阅读
本文为 homelab CI 实践脱敏整理,IP、域名、路径均为示例或占位,不包含真实生产环境信息。
