批量修改 AWS S3 挂载目录下文件权限

文章目录

    先交代一下背景,在服务器上挂载了一个 S3 类似的对象存储,用于存放网站的图片。
    现在需要把这个挂载目录下的所有文件权限改为 644。但是执行命令:

    find . -maxdepth 1 -type f -print0 | xargs -0 sudo chmod 644
    

    服务器的系统负载很高,而 CPU 和内存占用都很低。这个目录下有 8 万多个文件。

    为何系统负载飙高

    S3 是对象存储,不是真正的硬盘。当执行 chmod 时,挂载工具(如 s3fsgoofys)必须为每个文件发送一个网络请求(通常是 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 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式