多容器共享目录权限踩坑记
发布时间:2026/10/7 10:07:24 锦皓数字建站

背景项目中有算法服务和后端服务后端服务负责文件上传与保存算法服务负责文件内容解析、报告生成与保存采用docker-compose方式部署两个服务都会读写同一目录/shared挂载自宿主机/data/project/share-data。上线后发现后端服务无法成功上传文件算法服务无法将生成的报告写入目录服务日志提示权限不足的错误本文记录排查过程并给出共享组SGIDumask的解决方案。正文问题现象与环境权限不足的错误PermissionError:[Errno13]Permission denied:/shared/uploadsdocker-compose.yml 挂载配置# ---- 后端服务 ----backend-app: volumes: - /data/project/share-data:/shared# ---- 算法服务 ----ai-app: volumes: - /data/project/share-data:/sharedDockerfile# ---- 算法服务 ----# 创建了 UID 为 10001、名为 appuser 的用户RUNuseradd...--uid10001appuser# 指定后续指令及容器运行时以此用户身份执行USERappuser# ---- 后端服务 ----# 创建了 appuser 的用户和用户组useradd -r appuser 的 uid是系统分配的建议使用固定 uid防止镜像重建时 uid 变更RUNgroupadd-rappuseruseradd-r-gappuser-d/app-s/sbin/nologin appuser# 指定后续指令及容器运行时以此用户身份执行USERappuser排查过程本着 “尽量改配置、不动两个服务 Dockerfile” 的原则开始了排查。以下内容均排除 root 场景。第 1 步查看宿主机的共享目录的权限。$ls-l/data/project/ drwxrwxr-x2ubuntu ubuntu40969月1416:21 share-data $idubuntuuid1000(ubuntu)gid1000(ubuntu)目录的属主和属组都是 ubuntu, 权限 775 属主 rwx属组 rwx其他人 r-x第 2 步对比两个容器内的用户身份。# 后端服务容器内$iduid999(appuser)gid999(appuser)groups999(appuser)# 算法服务容器内$iduid10001(appuser)gid10001(appuser)groups10001(appuser)# 宿主机确认目录属主属组的数字 id$idubuntuuid1000(ubuntu)gid1000(ubuntu)两个容器内的进程 UID 不同且都不是 root也不属于 共享目录的属组gid(1000)。第 3 步定位根因。两个容器里的用户 id 都不等于 共享目录的属主 uid1000 ,也不属于共享目录的属组 gid1000作为共享目录的其他用户其他用户没有 写(w)的权限而在目录里新建文件 需要目录的 wx 权限两个容器都不满足权限需求所以提示Permission denied。根因分析: Linux 是怎么判断文件或目录的读写权限的前提先通过路径解析访问一个文件内核先逐级解析路径每一级目录都需要 x 权限访问/data/project/share-data/a.txt ├─/需要 x ├─/data需要 x ├─/project 需要 x ├─/share-data需要 x └─/a.txt 需要看文件权限任何一级目录缺 x路径解析失败直接拒绝。第 1 步判断进程的 UID 是否是 rootif(进程UID0){// root 跳过权限检查除少数特殊情况如执行位实际情况还受其他条件的影响 允许}注意 root 对文件读写通常无条件允许但对执行仍需至少一个 x 位。第 2 步比较属主权限位属主位只有文件属主本人能用if(进程有效UID文件属主UID){使用文件的“属主权限位”# 如果属主权限位不满足读写条件则拒绝跳到第5步}第 3 步比较属组权限位 属组位文件属组的所有成员都能用if(进程组列表主组 附加组包含 文件属组 GID){使用文件的“属组权限位”# 如果属组权限位不满足读写条件则拒绝跳到第5步}组位是 rw- 还是 r–决定能不能写关键组列表是主组 附加组的并集只要包含文件属组 GID 就算匹配。第 4 步比较其他人权限位否则{使用文件的“其他人权限位”# 如果其他权限位不满足读写条件则拒绝}第 5 步根据操作类型判断权限位拿到对应的权限位属主/属组/其他人之一后看具体操作操作需要的权限位读文件内容cat、read )r写文件内容echo 、writew执行文件./runx列出目录ls目录的 r进入目录cd目录的 x在目录里建/删/重命名目录的 w x只要需要的位在对应的权限段里有就允许否则拒绝。注意linux内核在容器内外不看用户名只看 uid/gid 数字。999 ≠ 10001 ≠ 1000。方案评估与选择方案做法优点缺点结论1.统一 uid / gid两个容器用相同 uid/gid 运行对齐目录属主/属组或修改目录属主/属组与容器 uid/gid 对齐简单直接两个容器服务都要改 Dockerfile / docker-compose / 启动参数还可能影响容器内其他文件权限放弃2.共享组SGID 组可写建共享组目录属组设为共享组容器进程加入附加组并设置 SGID 与组可写权限不动 Dockerfile只加配置需要调整 docker-compose 启动配置group_add、umask采用3.ACLsetfacl分别给 999、10001 授权只针对 uid 授权无需改属组和 umask容器 uid 变化时需同步调整 ACL 命令备选4.放开 other 写权限chmod ow目录或chmod -R 777 /data/project/share-data只改一句其他都不用动a. 安全风险较大b. 新文件的 other 位仍受 umask 限制为r--单独用解决不了互写放弃5.容器以 root 运行两个服务都用 root 用户运行简单省事安全风险极大放弃综合评估选择了方案二只加配置不动镜像共享组SGID 组可写方案落地宿主机侧# 0. 关键目录必须先建好再启动容器否则 Docker 会建一个 root:root 的目录(当前情况下已有该目录)sudomkdir-p/data/project/share-data# 1. 建共享组固定 GIDgroupadd-g1001fileshare# 2. 共享目录属组改为 1001chown1000:fileshare /data/project/share-data# 1000 可替换为当前登录的系统用户的id# 3. 【易漏容易踩坑点】历史文件和目录属组一并迁移SGID 只对新建文件生效chgrp-Rfileshare /data/project/share-data# 历史文件补组写位保留 x 和其他位umask只对新建文件生效find/data/project/share-data-typef-execchmodgw{}# 历史子目录补「组写 SGID 位」find/data/project/share-data-typed-execchmodgw,gs{}# 4. 目录设 SGID 组的读写执行权限 rwx: 2775 2(setgid) 775chmod2775/data/project/share-data# 5. 宿主机侧用户放文件时也要保留组写位umask 022 会导致新文件为 644看具体情况设置echoumask 002~/.profile# 或每次放完后sudo chmod -R gw /data/project/share-data# 6. 确认结果-n 显示数字 uid/gid避免容器内组名解析不出来ls-ldn/data/project/share-data# 期望drwxrwsr-x 1000 1001容器侧(docker-compose.yml) 调整 group_add 和 umask# 后端服务加入共享组group-add 只增加进程的附加组身份不会自动改变目录属组也不会自动让新建文件拥有组写权限。backend-app: group_add: -1001# 设置 umask 0002使新建文件属组可写command:sh-cumask 0002 exec python app.py# 算法服务加入共享组ai-app: group_add: -1001# 设置 umask 0002使新建文件属组可写command:sh-cumask 0002 exec python algorithm.py容器侧(entrypoint/command) 这里和上述 docker-compose command 的配置选其一即可umask0002# 必须在 exec 之前设置exec$# exec 保持信号传递与 PID 1 语义umask 0002 的作用新文件默认权限666 ~002 664保留组写位新目录777 ~002 775。结果验证目录: drwxrwsr-x10001001/data/project/share-data 属组权限 rws 出现了 s说明 sgid生效 文件: -rw-rw-r--9991001a.txt ← 后端服务(uid999)建的文件 文件: -rw-rw-r--100011001b.txt ← 算法服务(uid10001)建的文件在两个容器的 UID文件所有者无法统一的情况下通过 共享组(gid)的方式使两个容器进程拥有了公共的身份设置SGID 目录让目录下的新文件继承目录的属组说明新建的子目录不仅继承属组还会继承 SGID 位所以共享目录的子层级自动继续生效普通文件只继承属组不继承 SGID 位。设置 umask 让新文件组的权限位有了写的权限以上共同作用两个容器才都能写入共享目录操作其中的文件。步骤作用对象机制解决缺失后果共享组进程用户本质上是进程目录group_add chgrp进程有 1001 组身份目录属组 1001组权限不生效目录 rwx目录chmod 775 的组位能在目录里建/删文件、进入目录进不了目录或建不了文件目录 SGID目录chmod gs新文件属组 1001新文件属组 创建者主组对方写不了umask 002进程umask 0002新文件组位有 w新文件组位 r–对方只能读最容易踩坑的几个点删除、新建、重命名文件看父目录权限删除、新建、重命名文件需要父目录的 wx 权限不需要文件本身的权限新建的文件的权限由基本权限和 umask 决定。SGID 继承组不继承完整权限目录设置 SGIDchmod gs 或 2775后在该目录下新建的文件/子目录会继承该目录的所属组而不是创建者的主组。但它不会继承目录的完整权限位。目录里的文件权限还是由新建文件时的默认权限和 umask 综合设置的组是组权限是权限权限是对文件或目录设置的。umask和 SGID 只影响新建对象umask 和SGID只在创建文件/目录时参与计算默认权限对已有对象没有任何作用所以当前问题需要对已有的目录/文件批量补一次。目录必须有 x 权限目录的 x 位表示“能否进入/穿越该目录”是访问目录内任何对象的前提。所以目录新建时的默认权限保留 x基准权限为 777实际默认权限由 umask 决定。只给 r 不给 xls dir 可能显示名字但 ls -l dir、cat dir/file 会失败。$sudochmod444/data/demo/test $ls-altestls: 无法访问test/1.txt:权限不够 ls: 无法访问test/2.txt:权限不够 ls: 无法访问test/..:权限不够 ls: 无法访问test/.:权限不够 总计0d????????? ? ? ? ? ?.d????????? ? ? ? ? ?..-????????? ? ? ? ? ?1.txt -????????? ? ? ? ? ?2.txt $cattest/1.txt cat: test/1.txt: 权限不够文件和目录的 rwx 含义不同位对文件对目录r读取文件内容列出目录中的条目名w修改文件内容在目录中创建/删除/重命名条目x执行文件进入/穿越目录访问目录内对象echo “hello” /shared/new.txt 指令中权限如何判断是文件还是目录的权限执行上述一条命令写了个新文件但实际分为两个独立动作先创建文件再写文件内容动作 1在目录 /shared 里创建一个新目录项 new.txt→ 权限判断对象目录 /shared检查目录有 wx 权限才允许创建动作 2往 new.txt 这个文件里写入内容 “hello”→ 权限判断对象文件 new.txt检查文件有 w权限才允许写入组的作用对象是谁作用对象用户、目录或文件、进程 通过为用户设置共享组、目录或文件设置共享组容器内的进程加入共享组才让容器里的用户或进程操作共享目录共享的是 gid,容器里的属组靠识别数字匹配生效。排错速查清单# 1. 进程到底是谁 —— 容器内执行id# 2. 目录到底归谁 —— 用 -n 看数字容器内组名常常解析不出来ls-ldn/data/project/share-data# 3. 路径每一级的权限 —— 逐级列出定位卡在哪一层namei-l/data/project/share-data/uploads/a.txt# 4. 有没有 ACL 覆盖 —— 传统 rwx 看着对但可能被 ACL 拦下getfacl /data/project/share-data# 5. 挂载选项 —— 只读挂载 / nosuid 会让一切配置失效mount|grepshare-data总结本次的排查过程也是逐渐发现和学习的过程当前本次的解决方案也是建立在一些前提下的如果 docker engine 开启了 userns-remap或者目录位于 NFS 或者设置了只读挂载本次共享组的方案是不生效的本文中的一些内容未详细说明包括linux的权限模型、ACL设置、方案不生效的原因等还需要探究下篇博客将为您继续解答敬请期待
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。