通过挂载点创建和读取OSS Symlink

更新时间:
复制 MD 格式

ossfs 2.0.6 及以后版本支持将 POSIX 文件系统的符号链接(symlink,又称软链接)映射到 OSS 的 Symlink 对象。开启该功能后,您可以在挂载点内使用 ln -s 创建符号链接、使用 readlink 读取链接指向,为 Bucket 中的对象建立“别名”,在不复制对象内容的情况下实现数据的多路径访问。本文介绍 symlink 的解析规则、开启方式、支持的形式以及创建方法。

背景信息

在 OSS 中,同一份数据经常需要被多个路径访问,例如为数据集、模型文件或共享配置维护多个“别名”,或在 AI Agent 沙箱中复用同一份数据。对象存储不支持硬链接,复制对象会增加存储成本,多份副本之间也难以保持一致。ossfs 2.0.6 起支持将挂载点内的符号链接映射为 OSS Symlink 对象:链接对象只记录目标对象名,不复制数据,读取链接时直接访问目标对象。

基本原理

相对路径 symlink 与绝对路径 symlink

symlink 的目标路径有两种形式:绝对路径与相对路径。绝对路径形式的 symlink 以文件系统根目录 / 为基准解析,与链接自身的位置无关;相对路径形式的 symlink 则以链接所在的目录为基准解析,而不是以当前工作目录为基准。“相对谁”是理解 symlink 行为的关键,也是最容易产生误解的地方。

以一个例子说明。假设挂载点为 /mnt/ossfs,Bucket 中存在文件 oss://<bucket>/a/c/target,现在要在目录 oss://<bucket>/a/b/ 下创建链接 oss://<bucket>/a/b/link 指向它:

ln -s ../c/target /mnt/ossfs/a/b/link

解析这条链接时,内核从链接所在的本地目录 /mnt/ossfs/a/b/ 出发拼接目标路径:/mnt/ossfs/a/b/ + ../c/target = /mnt/ossfs/a/c/target。这个过程与执行命令时所处的目录无关,无论在哪个工作目录下执行 cat /mnt/ossfs/a/b/link,解析结果都相同。如果误以为 ../c/target 是以工作目录为基准解析的,就会对链接的实际指向产生错误预期。

需要注意的是,readlink 返回的并不是创建时传入的原始字符串,而是规范化后的相对路径,例如上例返回 ../../a/c/target。规范化结果以挂载点根目录为基准重新表达目标位置,跟随链接时得到的对象与创建时一致。

OSS Symlink 对象与控制台兼容

OSS 原生支持 Symlink 对象:通过 PutSymlink 接口创建,目标记录在 x-oss-symlink-target 头中,通过 GetSymlink 接口读取。需要注意的是,PutSymlink 接口本身并不校验目标内容,理论上可以写入任意字符串;“目标必须是 Bucket 内对象名”实际上是 OSS 控制台的解析约定。控制台展示和跳转 Symlink 对象时,会把目标当作 Bucket 内的对象名来解析;若目标被写成其他形式(例如绝对路径),控制台将无法识别和跳转,成为死链接。

也就是说,控制台约定的解析基准是 Bucket 根目录,与 POSIX 相对路径 symlink“以链接所在目录为基准”不同。仍以上文的例子说明,同一条链接 a/b/link 指向 a/c/target,在不同视角下的目标字符串并不相同:

视角

链接路径

目标字符串

解析基准

POSIX 文件系统(创建时传入)

/mnt/ossfs/a/b/link

../c/target

链接所在目录 a/b/

OSS 控制台(Symlink 对象的存储约定)

a/b/link

a/c/target

Bucket 根目录

ossfs 2.0 的 symlink 实现遵循这一约定,并在两套解析基准之间自动完成转换:在挂载点内创建的链接,OSS 存储的 target 为 Bucket 内相对路径(如 a/c/target),控制台可正常识别和跳转;通过 API 或控制台创建、target 为 Bucket 内相对路径的 Symlink 对象,在挂载点内也能正常 readlink 和读取。若 target 写成以 / 开头的绝对路径,挂载点读取该链接会返回 EINVAL。

配置方法

symlink 功能通过 enable_symlink 挂载选项控制,默认关闭,需要在挂载时显式开启。配置项说明如下:

配置项

是否必填

说明

默认值

enable_symlink

否

是否启用 symlink 支持。开启后可在挂载点内使用 ln -s 创建符号链接、使用 readlink 读取链接指向;关闭时无法创建符号链接,也无法按符号链接语义读取已有的 Symlink 对象。

false

挂载命令示例:

ossfs2 mount /mnt/ossfs/ \
  --oss_bucket <your-bucket> \
  --oss_endpoint <your-endpoint> \
  --oss_access_key_id <ak> \
  --oss_access_key_secret <sk> \
  --enable_symlink=true
重要

未开启该选项时,挂载点内使用 ln -s 创建符号链接会返回 ENOTSUP(Operation not supported);读取 Bucket 中已有的 Symlink 对象时,readlink 无法获取目标,读取内容也会被截断。若 Bucket 中已存在 Symlink 对象,必须开启此选项后再挂载。

环境要求

参数

要求

ossfs 版本

2.0.6 及以上。

操作系统

Linux,且支持 FUSE 挂载(ossfs 2.0 依赖 FUSE)。

链接目标

必须是同一 Bucket 内的对象,不支持跨 Bucket、跨账号引用。

功能效果

开启 enable_symlink 前后的行为对比如下:

操作

未开启 enable_symlink

开启 enable_symlink

使用 ln -s 创建符号链接

返回 ENOTSUP

支持

使用 readlink 读取已有 Symlink 对象

失败,无法识别为符号链接

返回规范化后的相对路径

读取已有 Symlink 对象的内容

按普通对象返回,内容可能被截断

返回目标对象的完整内容

绝对路径 target

不支持

不支持,创建和读取返回 EINVAL

悬空链接

不支持

可创建,目标对象上传前读取返回 ENOENT

Symlink 对象只保存目标对象名,不复制目标对象的数据。

ossfs 2.0 支持的 symlink 形式

基于上文与 OSS 控制台的兼容约定,ossfs 2.0 仅接受能换算为 Bucket 内对象名的链接目标,具体支持情况如下:

链接目标形式

是否支持

行为说明

Bucket 内的相对路径(如 target、../c/target、a/b/target,均以链接所在目录为基准解析)

支持

规范化后指向 Bucket 内对象,正常创建。

绝对路径(如 /mnt/ossfs/a/target、/etc/passwd)

不支持

返回 EINVAL。在容器等 bind mount 环境下,同一 Bucket 可能以不同路径挂载到多个位置,ossfs 2.0 无法可靠判断绝对路径对应的 Bucket 内对象名。

折叠后仍逃逸出挂载点的相对路径(如挂载点下一层的链接使用 ../../x)

不支持

返回 EINVAL。

此外,ossfs 2.0 创建链接时不校验目标是否存在,目标不存在时链接也会创建成功(悬空链接,dangling symlink),访问时返回 ENOENT,直到目标对象被上传。链接目标可以是文件,也可以是目录。

通过 API 或控制台创建的 Symlink 对象,其 target 为 Bucket 内相对路径(如 a/c/target)时,在开启 enable_symlink 的挂载点内可正常 readlink 和读取;target 为以 / 开头的绝对路径时,读取返回 EINVAL。因此通过 API 或控制台创建 Symlink 对象时,target 也应使用 Bucket 内相对路径。

重要

不建议在链接目标路径的中间使用 .. 分量。ossfs 2.0 在创建时会对目标路径做规范化(按字面折叠 .、..),而内核是在跟随链接时才逐级解析:若中间分量本身是 symlink,内核会先展开它,.. 回退到展开后目标的父目录,两种解析结果可能不同。

如何创建正确的相对路径 symlink

命令行方式

以挂载点 /mnt/ossfs、在 /mnt/ossfs/a/b/ 下创建指向 /mnt/ossfs/a/c/target 的链接为例,目标需使用相对路径:

cd /mnt/ossfs/a/b
ln -s ../c/target link

# 或者跳过 cd 直接使用
ln -s ../c/target /mnt/ossfs/a/b/link

# 读取链接指向,返回规范化后的相对路径(../../a/c/target)
readlink /mnt/ossfs/a/b/link

# 跟随链接正常读取目标内容
cat /mnt/ossfs/a/b/link

Python 脚本方式

批量创建链接时,可先将目标规范化为“相对于链接所在目录”的相对路径。与 ossfs 2.0 运行时无法判定绝对路径归属不同,脚本由使用者显式传入挂载点,因此可以直接接受本地绝对路径,并在工具侧完成换算:

#!/usr/bin/env python3
"""Create relative symlinks on an ossfs 2.0 mountpoint.

Usage: python3 create_oss_symlink.py <mountpoint> <link_path> <target_path>
All arguments are local paths; <link_path> and <target_path> must be under
<mountpoint>. The script converts the target into a path relative to the
link's parent directory, so that ossfs 2.0 stores it as an in-bucket object
name.
"""
import os
import sys


def create_relative_symlink(mountpoint, link_path, target_path):
    mountpoint = os.path.realpath(mountpoint)

    def to_bucket_rel(p):
        # Lexical conversion, consistent with ossfs 2.0: no symlink
        # expansion, inner ".." folded literally.
        p = os.path.normpath(os.path.abspath(p))
        if not p.startswith(mountpoint + os.sep):
            raise ValueError(f"path escapes the mountpoint: {p}")
        return os.path.relpath(p, mountpoint)

    # Bucket-relative object name of the target (what OSS actually stores).
    bucket_target = to_bucket_rel(target_path)
    link_path = os.path.abspath(link_path)
    if not link_path.startswith(mountpoint + os.sep):
        raise ValueError(f"link path escapes the mountpoint: {link_path}")

    # POSIX readlink semantics: relative to the link's parent directory.
    link_parent_rel = os.path.relpath(os.path.dirname(link_path), mountpoint)
    relative_target = os.path.relpath(bucket_target, link_parent_rel)

    if os.path.lexists(link_path):
        os.remove(link_path)
    os.symlink(relative_target, link_path)
    print(f"{link_path} -> {relative_target}")


if __name__ == "__main__":
    if len(sys.argv) != 4:
        print(__doc__)
        sys.exit(1)
    create_relative_symlink(sys.argv[1], sys.argv[2], sys.argv[3])

使用示例:

python3 create_oss_symlink.py /mnt/ossfs /mnt/ossfs/a/b/link /mnt/ossfs/a/c/target

脚本以挂载点根为基准换算目标。若挂载时配置了 oss_bucket_prefix,OSS 实际存储的对象名为该前缀拼接脚本内部的 bucket 相对路径,换算由 ossfs 2.0 自动完成,不影响脚本的使用方式。

使用建议

适合使用的场景:

  • 为同一对象建立多个访问路径,例如数据集、模型文件或共享配置的“别名”,无需复制数据。

  • 在 AI Agent 沙箱、代码执行环境中用链接组织工作目录,底层对象只保留一份。

  • 需要在挂载点与 OSS 控制台之间互通链接的场景,target 统一使用 Bucket 内相对路径。

需要注意的场景:

  • 需要引用其他 Bucket 或其他账号下的对象:Symlink 只能指向当前 Bucket 内的对象,请改用其他方式。

  • 目标对象尚未上传:悬空链接可以创建,但上传目标对象前读取会返回 ENOENT,建议先上传目标对象。