news 2026/9/20 14:24:07

Podman `--read-only-tmpfs` 选项详解:只读容器的可写 tmpfs 挂载机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Podman `--read-only-tmpfs` 选项详解:只读容器的可写 tmpfs 挂载机制

Podman--read-only-tmpfs选项详解:只读容器的可写 tmpfs 挂载机制

【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman

在 Podman 中,--read-only将容器的根文件系统挂载为只读,但进程运行时通常仍需在/tmp/run/dev/shm等目录写入临时数据。--read-only-tmpfs正是用来控制这些目录在只读容器中的读写状态的关键选项。本文基于仓库中该选项的官方文档(docs/source/markdown/options/read-only-tmpfs.md)展开,结合命令行参数注册、specgen 挂载生成与系统测试,完整讲解其默认行为、四种组合场景、底层实现原理、Quadlet 用法与常见实战场景,帮助读者精准设计容器文件系统的读写边界。

一、选项概述与适用命令

--read-only-tmpfs是一个布尔开关,其官方文档定义如下:

当运行--read-only容器时,在/dev/dev/shm/run/tmp/var/tmp上挂载可读写的 tmpfs。默认值为true

该选项由一份共用文档模板生成,同时应用于podman createpodman run两个 CLI 命令,以及 Quadlet 的podman-container.unitsystemd 单元格式(文档头部注释明确标注:This option file is used in: podman podman-container.unit.5.md.in, create, run)。在命令行为 CLI 参数,在 Quadlet 中则写作ReadOnlyTmpfs=true|false

从源码看,该标志在 cmd/podman/common/create.go 中注册:

createFlags.BoolVar( &cf.ReadWriteTmpFS, "read-only-tmpfs", cf.ReadWriteTmpFS, "When running --read-only containers mount read-write tmpfs on /dev, /dev/shm, /run, /tmp and /var/tmp", )

注意其默认值取自cf.ReadWriteTmpFSContainerCreateOptions的初始值),该值最终由containers.conf配置驱动。也就是说,--read-only-tmpfs的默认值可以通过容器配置文件统一调整。

--read-only的配合关系

--read-only-tmpfs单独使用没有意义,它只在与--read-only组合时生效。仓库源码在 pkg/specgenutil/specgen.go 中明确给出了这一组合逻辑:

// Only add ReadWrite tmpfs mounts iff the container is // being run ReadOnly and ReadWriteTmpFS is not disabled, // (user specifying --read-only-tmpfs=false.) localRWTmpfs := c.ReadOnly && c.ReadWriteTmpFS s.ReadWriteTmpfs = &localRWTmpfs

--read-only=false(默认)时,无论--read-only-tmpfs取何值,ReadWriteTmpfs都恒为 false,tmpfs 的读写特性完全由容器镜像决定——即/dev/dev/shm默认可读写,/tmp/run/var/tmp也是镜像中的可读写目录。

二、四种组合的行为对照表

原文档给出了一张非常直观的组合矩阵,本文完整保留并逐行解读:

--read-only--read-only-tmpfs/(根文件系统)/run,/tmp,/var/tmp
truetruer/or/w
truefalser/or/o
falsefalser/wr/w
falsetruer/wr/w

场景一:--read-only=true+--read-only-tmpfs=true(默认组合)

根文件系统只读,同时在/tmp/run/var/tmp三个目录上额外挂载可写 tmpfs。这是 Podman 的默认行为——当你只写--read-only而不显式指定--read-only-tmpfs时,实际生效的就是这个组合。容器既能保证镜像层不可篡改,又能在运行时向这些标准临时目录写入数据,适合大多数“只读根文件系统 + 运行时临时数据”的应用场景。

场景二:--read-only=true+--read-only-tmpfs=false(完全只读)

根文件系统只读,且/dev/dev/shm被标记为只读,/tmp/run/var/tmp上不挂载任何 tmpfs。这些目录直接暴露底层镜像中的内容,默认同样是只读的。此时容器内部不存在任何可写目录,成为一个“完全只读”的容器。若应用需要写入数据,只能通过外部卷(--volume--mount)或外部挂载显式提供可写位置。

场景三、四:--read-only=false

根文件系统可写,/dev/dev/shm可读写,/tmp/run/var/tmp使用镜像中原本的可读写目录。此时--read-only-tmpfs无论传true还是false,行为都完全相同——该选项在非只读容器中不产生任何影响。

三、底层实现原理:从选项到挂载点

--read-only-tmpfs的行为在容器创建链路中被完整实现,涉及三层:参数到 spec 的转换、挂载点生成、OCI 运行时配置的调整。

1. 参数转换为 specgen 字段

ContainerCreateOptions.ReadWriteTmpFS经过 pkg/specgenutil/specgen.go 转换为SpecGenerator.ReadWriteTmpfs指针,该字段在 pkg/specgen/specgen.go 中定义,其 JSON 名为read_write_tmpfs,并明确注释其语义:

ReadWriteTmpfsindicates that when running with a ReadOnlyFilesystem, the tmpfs should be mounted read-write。

随后通过 pkg/specgen/generate/container_create.go 中的libpod.WithReadWriteTmpfs()选项注入容器配置,最终存储于libpod层容器配置的ReadWriteTmpfs bool字段(libpod/container_config.go)。

2. tmpfs 挂载点的生成

真正的挂载点生成位于 pkg/specgen/generate/storage.go 的addReadWriteTmpfsMounts函数:

func addReadWriteTmpfsMounts(mounts map[string]spec.Mount, volumes []*specgen.NamedVolume, runPath string) map[string]spec.Mount { readonlyTmpfs := []string{"/tmp", "/var/tmp", runPath} options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} for _, dest := range readonlyTmpfs { if _, ok := mounts[dest]; ok { continue } for _, m := range volumes { if m.Dest == dest { continue } } mnt := spec.Mount{ Destination: dest, Type: define.TypeTmpfs, Source: define.TypeTmpfs, Options: options, } mounts[dest] = mnt } return mounts }

这段实现揭示了三个值得注意的细节:

  • 挂载点集合/tmp/var/tmp,以及runPath(运行时确定的/run路径),与文档列出的目录一致;
  • 挂载选项rw(可读写)、rprivate(私有传播)、nosuid(禁止 setuid 位)、nodev(禁止设备文件)、tmpcopyup(将上层目录已有内容复制到 tmpfs,保证/tmp等目录中镜像预置的文件仍然可见);
  • 冲突规避:若目标目录已存在挂载或用户显式指定的命名卷目标恰好是这些目录,则跳过自动 tmpfs 挂载,避免与用户配置冲突。

该函数仅在s.ReadWriteTmpfs != nil && *s.ReadWriteTmpfs时被调用(pkg/specgen/generate/storage.go),与文档描述完全吻合。

3./dev/dev/shm的只读化

--read-only=true--read-only-tmpfs=false时,/dev/dev/shm需要被标记为只读。这一逻辑在 OCI spec 生成阶段完成(pkg/specgen/generate/oci_linux.go):

roFS := false if s.ReadOnlyFilesystem != nil { roFS = *s.ReadOnlyFilesystem } rwTmpfs := false if s.ReadWriteTmpfs != nil { rwTmpfs = *s.ReadWriteTmpfs } if roFS && !rwTmpfs { setDevOptsReadOnly(&g) }

setDevOptsReadOnly会将/dev/dev/shm相关挂载的选项调整为只读。而在完全只读模式下,libpod层还会在复制文件到容器(如podman cp)时对/dev/shm做特别判断——container_internal_common.go中的复制逻辑仅在dstPath == "/dev/shm" && ReadWriteTmpfs时才允许写入,否则一律拒绝(libpod/container_internal_common.go 与 libpod/container_internal_common.go),从运行时层面保证“完全只读”的语义。

4. 与用户自定义挂载、卷的优先级

addReadWriteTmpfsMounts的实现可以看出,Podman 遵循“用户显式配置优先”的原则:只要/tmp/var/tmp/run中任意目录已被用户挂载(无论是否 tmpfs)或已绑定命名卷,自动挂载就会跳过该目录。因此在完全只读模式下,用户可以精确地为个别目录补上可写挂载,例如--volume mydata:/data,而不会影响其他目录的只读状态。

四、Quadlet(systemd 单元)中的用法

除 CLI 外,该选项在 Quadlet 的[Container]组中写作ReadOnlyTmpfs=true|false,对应键定义在 pkg/systemd/quadlet/quadlet.go,并作为受支持的容器组键注册(pkg/systemd/quadlet/quadlet.go),在生成 systemd 单元时被翻译为--read-only-tmpfs参数(pkg/systemd/quadlet/quadlet.go)。

仓库的 e2e 测试提供了三个直观的 Quadlet 示例:

readonly.container(默认组合,ReadOnly=yes且不写ReadOnlyTmpfs):

[Container] Image=localhost/imagename ReadOnly=yes

断言生成的 podman 参数为--read-only-tmpfs --read-only,即默认值true被显式带上。

readonly-tmpfs.container(显式开启可写 tmpfs):

[Container] Image=localhost/imagename ReadOnly=yes ReadOnlyTmpfs=yes

readonly-notmpfs.container(完全只读):

[Container] Image=localhost/imagename ReadOnly=yes ReadOnlyTmpfs=no

断言生成的参数为--read-only-tmpfs=false --read-only,对应上文“场景二”的完全只读语义。这三个文件分别位于 test/e2e/quadlet/readonly.container、test/e2e/quadlet/readonly-tmpfs.container 与 test/e2e/quadlet/readonly-notmpfs.container。

五、实战验证与测试佐证

仓库的系统级测试 test/system/030-run.bats 用containers.conf设置read_only=true,然后对/tmp/a/var/tmp/b/dev/c/dev/shm/d/run/e这五个文件逐一执行touch,完整验证了各组合的行为:

  • --read-only(默认 tmpfs=true):touch全部成功——五个目录都是可写 tmpfs;
  • --read-only=false--read-only=false --read-only-tmpfs=truetouch全部成功(非只读模式下选项不生效);
  • --read-only --read-only-tmpfs=falsetouch全部失败,输出touch: /tmp/a: Read-only file system等五条错误——容器完全只读。

这一测试直接印证了文档中四种组合的全部行为,读者可以在本地运行podman run --read-only --read-only-tmpfs=false <image> touch /tmp/a复现“完全只读”的效果。

快速验证命令

# 默认组合:根文件系统只读,/tmp、/run、/var/tmp 可写 podman run --rm --read-only registry.fedoraproject.org/fedora-minimal touch /tmp/hello /var/tmp/world # 完全只读:所有目录只读,写入被拒绝 podman run --rm --read-only --read-only-tmpfs=false registry.fedoraproject.org/fedora-minimal touch /tmp/hello # 输出: touch: /tmp/hello: Read-only file system # 显式关闭只读:一切照旧 podman run --rm --read-only=false --read-only-tmpfs=true registry.fedoraproject.org/fedora-minimal touch /tmp/hello

六、实践建议与注意事项

  1. 安全加固场景优先使用完全只读:对于不需要写临时文件的安全敏感容器(如只读代理、校验器),推荐--read-only --read-only-tmpfs=false,再按需通过--volume--mount挂载唯一的数据写入点,攻击面最小。
  2. 默认组合适合常规只读容器:很多应用(如 JVM、Python 解释器)依赖/tmp写缓存,直接使用默认的--read-only即可获得“镜像只读 + 临时目录可写”的平衡。
  3. 注意/dev/dev/shm的差异:在默认组合下,/dev/dev/shm上挂载的是可写 tmpfs;只有完全只读模式下它们才被标记为只读。若容器依赖共享内存(如--ipc=host下的 IPC),需留意/dev/shm的实际大小与读写状态。
  4. 用户挂载优先级最高:自动 tmpfs 挂载会主动避让用户显式指定的卷与挂载,这一设计保证了--read-only与自定义挂载组合时的可预测性。
  5. Quadlet 中显式声明更稳妥:虽然ReadOnlyTmpfs默认是true,但建议在.container文件中显式写出ReadOnlyTmpfs=yes|no,避免升级或配置变化带来的隐性行为差异。

通过本文的解读可以看到,--read-only-tmpfs虽只是一个布尔选项,但其背后是 Podman 从 CLI 解析、specgen 转换、挂载生成到 OCI 运行时配置的一条完整实现链路。理解这四种组合的语义,是设计安全、可预期、可运维的只读容器的关键一步。

【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 14:22:02

运维工程师3-5年经验:简历写法、核心技术栈与职业转型指南

简介&#xff1a;面向运维工程师岗位、沉淀3-5年工作经验的个人简历样张&#xff0c;适用于互联网领域求职者与HR参考&#xff0c;也可帮助新入行人员理解岗位能力要求&#xff0c;并可作为企业招聘或高校就业指导中的简历范本。简历以docx格式封装&#xff0c;共1个文件&#…

作者头像 李华
网站建设 2026/9/20 14:21:47

RAG技术解析:检索增强生成在企业知识管理中的应用

1. RAG技术全景解读&#xff1a;当检索遇到生成第一次听说RAG这个词时&#xff0c;我正为一个企业知识库项目头疼——客户要求系统既能精准回答专业问题&#xff0c;又要避免传统聊天机器人"一本正经胡说八道"的毛病。直到在NLP顶会论文里发现Retrieval-Augmented Ge…

作者头像 李华
网站建设 2026/9/20 14:20:44

800xA数据采集与处理全链路实战要点

简介&#xff1a;这是面向工业自动化工程师与学习者的ABB System 800xA数据采集与处理技术教程&#xff0c;聚焦过程工业场景下如何利用800xA构建从现场设备到中央数据库的数据采集与分析链路。教程从800xA系统概述与分布式控制架构出发&#xff0c;系统讲解数据采集原理&#…

作者头像 李华
网站建设 2026/9/20 14:20:08

别找临时中转:把 TaoToken 当 Zed 的兼容通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 14:18:31

基于ThinkPHP6+Swoole的视频打赏系统架构拆解与高并发优化实践

简介&#xff1a;最新商业视频打赏系统源码提供完整的前后端实现与运营功能&#xff0c;面向需要快速上线视频打赏、短视频裂变推广业务的站长和开发者&#xff1b;内置多套前端模板、代理后台并已对接支付&#xff0c;可支撑从内容展示、直播讲解到左右滑动式裂变分享的完整链…

作者头像 李华