容器里的临时盘:tmpfs 挂载选项要写的权限与上限

一、四处要回答的问题,配置里却常常只有路径
在容器运行时的挂载声明里,临时文件系统通常是写得最短的一行:给一个挂载点,选项要么不写,要么照抄别处的示例。它被忽略的理由也很直白:反正是临时的,容器一走就没了。
问题恰恰在于落在里面的东西不临时。密钥缓存、令牌、会话状态、解密后的中间结果、批处理的分片文件、推理服务加载权重前的暂存目录,多数都落在这块盘上。它们的存在时间短,但敏感等级不低,占用也不小。把临时文件系统当作一个有敏感等级、有容量上限、有生命周期的资源来管,就要在配置里回答四个问题:谁能读、最多占多少、能不能被执行、什么时候消失。这四问都有明确答案的临时盘,才是被管理过的;只有路径没有选项的临时盘,等于把责任交给了默认值。
二、写错的两种后果,第二种更麻烦
选项的位置和分隔方式是很死的规则:挂载点之后用冒号引出选项,多个选项之间用逗号分隔。漏掉分隔符、写成空格或者少一个逗号,解析就会失败。这类错误的好处是它会立刻暴露,容器起不来,现场就在眼前,改完即走。
另一类后果要隐蔽得多:选项被解析器接受但没有生效,或者整段选项被忽略,运行时按自己的默认值把盘挂上。表现是完全正常的启动、正常的读写、监控上一切平静,而实际状态是三件事同时落空:权限位落空,默认值通常偏向可用性,倾向于让所有人都能写;容量上限落空,这块盘不再受约束;安全开关落空,禁止执行这一类约束根本没有装上。这类错误不会给出告警,它只会在某一天被审计或者事件复盘时翻出来,而此时已经运行了很久。
所以核对方式必须写进流程,不能靠看配置。判据只有一个:在容器内实际读回挂载信息,确认选项字符串里每一项都在,再确认权限位与属主与预期一致。配置写了不等于生效,这一条在所有运行时上都成立。
三、权限三元组:权限位、属主属组与容器内的运行用户
临时盘上最常见的故障不是权限太松,而是三元组不匹配。挂载时指定的属主与属组,用的是宿主侧的数字标识;容器里的进程可能以另一个用户身份运行,甚至由镜像里的构建阶段决定了它落地成哪个用户。两边数字对不上,会出现两种截然相反的现象:写入被拒绝,或者目录人人可读写。前者容易发现,后者只会留在审计报告里。
| 选项类别 | 要回答的问题 | 漏写或写错的典型表现 | 核对方式 |
|---|---|---|---|
| 读写模式 | 这块盘允许写吗 | 只读场景被默认挂成可写,写入悄悄成功 | 读回挂载选项,试着写一次并确许可 |
| 容量上限 | 最多占多少 | 未设上限,占用不受约束地增长 | 按上限写满一次,观察失败方式 |
| 权限位 | 谁能读、谁能写 | 密钥目录被挂成全局可读 | 在容器内查看实际权限,而非比对配置 |
| 属主与属组 | 归属与容器内运行用户是否一致 | 写入被拒绝,或换成任意用户皆可写 | 确认实际运行用户身份与目录归属一致 |
| 禁止执行 | 落在里面的文件能否被当作程序运行 | 缓存目录变成可执行落点 | 读回选项确认该开关在列 |
| 禁止设置属主、禁止设备 | 特殊位与设备文件是否被解释 | 特殊位被沿用,设备文件被当成真实设备 | 读回选项,逐项确认 |
权限位按八进制解释,写的时候要注意运行时的表示习惯,同一串数字在不同写法下含义可能不同,这一点也必须靠读回验证而不是靠经验。用途上的取值原则很简单:只有单一进程需要读写的目录给到最紧的一档,例如密钥缓存这类目录适合用只有属主可进出的 700;确实需要多人共用的临时目录,用带粘滞位的 1777。这两个数值只是示意,具体档位要按自己的读写主体数量核定。
粘滞位挡的是删除与改名:目录里任何人都能放文件,但只有文件属主和特权账号能删掉或改名别人的文件,因此共用暂存区不会变成互相清理的现场。它挡不住读写:目录既然全局可写,别人写进来的内容你依然能读,你写的东西别人也照样能读。把粘滞位当作隔离手段是使用这类目录最常见的误解,共用区里要放的是可以被同组人看到的东西,敏感内容不该进来。
四、容量上限的真实代价是内存
临时盘不占磁盘,它占的是内存。这意味着它的上限不是一个磁盘问题,而是工作负载预算的一部分。上限设小了,写入在某个时刻直接失败,业务表现为批处理中断、会话创建失败、权重加载到一半报错,这类失败通常带着明确的写入错误,日志能看出来。上限没设,故事就换了一条线:这块盘继续吃内存,节点内存被压满之后先是换页与延迟上升,然后是整个节点上的其他负载被拖慢,最后可能触发节点压力驱逐,把不相干的容器一起带走。同一次配置疏漏,有上限时坏在一个业务上,没上限时坏在一片节点上。
因此上限的定法不能只看这块盘自己。可用的口径是三项相加:正常峰值占用、异常场景下的最大残留(比如任务失败没清理)、再加一个明确的余量,得出上限;同时把这项数字与该容器的内存请求和限制放在一起看,两者之和不能超过节点留给它的份额。上限值本身要按自己的负载核定,抄别人的数字没有意义,因为决定它的是单次写入的规模与失败时的残留行为。清理策略也要配套:谁负责删、多久删一次、删不掉时怎么告警,这三问没有答案的临时盘,最后都会撞到上限。
五、该默认打开的开关,以及密钥缓存的四条约束
禁止执行、禁止设置属主、禁止设备这三类开关不需要为每个目录都开,但有几类场景应当默认打开:只用于存数据而从不运行程序的缓存目录;对外可达的服务写入的上传暂存区;多租户共用宿主机时的所有临时挂载点。理由不是它们一定会被滥用,而是这些目录没有执行需求,开了执行只是平白多出一条路径。需要执行脚本的作业,应当另给一块明确标注用途的目录,而不是放松公共目录的约束。
密钥与令牌缓存放在临时盘上,比落盘更安全,但只有四条约束同时成立时才是。第一条是权限,只有属主可进出,其他一律不给。第二条是生命周期,随容器销毁即消失,不留持久化副本;这里要提醒一句,停止与重启之间的保留行为在不同运行时上并不一致,不要按经验假设,要用自己的运行时实测。第三条是宿主侧不得再有第二份,同一份凭据既进临时盘又写进宿主目录或者日志文件,等于把两处泄露面同时打开。第四条是日志里不能出现内容,包括路径拼接后的调试输出与异常堆栈里的字段值,这类残留往往比文件本身更难清理。
还有一件事要在产品层面约定,而不是留给运维:容器重建即丢数据。凡是丢了就无法继续的状态,必须外置到真正的存储里;凡是可以重算的中间结果,才允许留在临时盘。这个边界要写在设计文档里,并且明确哪些数据被认为可丢、由谁签字,否则早晚会出现把会话当持久化用的系统。
六、验收清单与声明位置的差别
上线前的验收按四项做,每项都要留下可核对的结果。
- 挂载选项生效核对:在容器内读回实际挂载信息,逐项确认读写模式、容量上限、权限位、属主属组与三类禁止开关都在列,与声明一致。
- 按上限写满一次:用数据把这块盘写到上限,确认失败表现为受控的写入错误,而不是节点内存压力或者无响应;同时确认失败后已有数据仍可读、任务能清理。
- 销毁后确认残留:容器停止并销毁后,在宿主侧检查该路径是否还有残留文件;重启场景单独实测,把结论写进运维说明。
- 密钥是否出现在别处:检查镜像层、宿主目录、日志与备份里是否存在同一份凭据,任何一处存在都判定为不合格。
声明位置也值得单独比一句。写在启动命令里的选项是一次性的,改不动历史、进不了评审、换个人重启就可能漂移;写在编排文件里的声明随版本发布,能被评审、被回滚、被自动化校验覆盖,也可以按环境分别覆盖。两者的差别不在便利性,在于配置能不能被追溯。灰度时的注意事项是新旧声明并存的那段时间:同一份盘在滚动更新过程中可能出现两套选项,敏感目录要安排在低峰一次换完并当场核对,不要放任两版长期共存。
这类配置的收益很慢,坏在没人会为一个从来没有出过事的挂载选项付预算。我们的做法是把它并进容器上线前的固定检查项,洹拓科技交付云端环境时,这四项验收结果直接附在交付记录里,作为一次可核对的结论而不是口头承诺。