先交代一下背景,在服务器上挂载了一个 S3 类似的对象存储,用于存放网站的图片。 现在需要把这个挂载目录下的所有文件权限改为 644。但是执行命令:
find . -maxdepth 1 -type f -print0 | xargs -0 sudo chmod 644
服务器的系统负载很高,而 CPU 和内存占用都很低。这个目录下有 8 万多个文件。
为何系统负载飙高
S3 是对象存储,不是真正的硬盘。当执行 chmod 时,挂载工具(如 s3fs 或 goofys)必须为每个文件发送一个网络请求(通常是 COPY 请求以更新元数据)。8 万个文件意味着 8 万次网络往返。
CPU 在等待网络响应,所以负载(Load Average)很高,但并没在运算。
解决方案
最佳的解决方案是修改挂载参数。即,修改 /etc/fstab 中的挂载选项,添加 umask=022,并指定 uid 和 gid,这样所有文件在 Linux 看来都是 644。
由于,我还在 docker 中映射了这个目录,所以需要先停掉相关的 docker 容器,然后卸载目录,修改挂载参数,重新挂载,最后再启动 docker 容器。
停掉相关 Docker
docker compose stop xxx
卸载挂载的目录
umount /var/www/some_directory
确认服务器用户的 uid gid
例如,如果是 www 用户:
> id www
uid=1000(www) gid=1000(www) groups=1000(www),27(sudo),100(users)
修改挂载参数
由于使用的是类似 S3 存储的 Linode Object Storage,所以挂载参数如下:
xxx.sunzhongwei.com /var/www/some_directory fuse.s3fs _netdev,allow_other,umask=022,uid=1000,gid=1000,use_path_request_style,nonempty,url=https://us-east-1.linodeobjects.com/ 0 0
umask 的全称是 User File-Creation Mask(用户文件创建掩码)。它的作用是:规定“不允许”出现哪些权限。
在 Linux 中,权限是通过从“满权限”中减去 umask 的值来计算的。
执行重新挂载
mount -a
参考:s3fs-fuse 将 Linode Object Storage 挂载到 Ubuntu Server 本地文件系统
启动相关 Docker
docker compose start xxx
确认
> ls -lah /var/www/some_directory
total 543K
drwxr-xr-x 1 www www 0 Jan 16 06:08 .
drwxr-xr-x 1 www www 0 Dec 13 2024 ..
-rwxr-xr-x 1 www www 330K Jan 16 06:11 a.jpg
-rwxr-xr-x 1 www www 214K Jan 17 03:04 b.jpg
到此搞定。但是,我还是有一些其他疑问的,所以继续下面的整理。
新建文件的权限
设置了 umask=022 后,如果是 root 用户在这个 s3 目录下新建一个文件,文件的所有人是否会改变。
umask 只管“权限数字”,不管“所有人(Owner)”。 但是,在 S3 挂载(FUSE)的环境下,新建文件的所有人到底是谁,取决于你挂载时的参数设置,而不是 umask。
当挂载时指定了固定的 uid 和 gid,即在挂载命令或 /etc/fstab 中设置了 -o uid=1000,gid=1000。
无论你用 root 还是普通用户新建文件,在这个目录下看到的文件的所有人永远是 UID 1000 的用户。
原因: FUSE 驱动会“劫持”所有权信息。即使 root 写入了对象,挂载工具也会在显示时把文件强行显示为 UID 1000。
修改挂载参数对 Docker 容器的影响
修改挂载参数后重新挂载, 如果这个目录也挂载到了某个 docker 容器中,重新挂载是否会影响 docker 容器的运行?
重新挂载(Unmount & Remount)宿主机的目录,一定会影响正在运行的 Docker 容器。
如果不重启容器,容器内部通常会看到一个空目录或者遇到 Stale file handle(失效的文件句柄) 错误。
Docker 的容器卷挂载(Bind Mount)在容器启动时,是基于宿主机路径的 Inode(索引节点) 或当时的挂载点建立的连接。
断开连接: 当在宿主机执行 umount 时,宿主机内核会撤销该路径的挂载。虽然容器还在运行,但它原本引用的文件系统指针已经“断了”。
无法自动跟随: 当在宿主机执行新的 mount 后,虽然路径没变,但宿主机内核为它分配了新的挂载 ID。已经运行的容器不会自动同步这个新挂载,它会继续盯着那个已经变成空目录的原始挂载点。
对于 s3 的文件,在服务器本地执行 chmod 后,权限信息是保存在哪里?
在 S3 挂载的目录下执行 chmod,权限信息并不存在于一个类似 Linux Inode 的本地索引中,而是直接保存在 S3 对象的元数据(Metadata) 里。
如果使用的是最常用的 s3fs,权限信息被存储在 S3 对象的 HTTP 自定义元数据头中。
存储位置: AWS S3 对象的 Metadata 部分。
具体的 Key:
- x-amz-meta-mode: 存储权限位(如 33188 代表 644)。
- x-amz-meta-uid: 存储所有者的 UID。
- x-amz-meta-gid: 存储组的 GID。
如果你登录 AWS 控制台,随便找一个文件点击“属性(Properties)”,在“元数据”一栏就能看到这些以 x-amz-meta- 开头的参数。
关于作者 🌱
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式