跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

S3Mount API

最后更新 查看 MarkdownAgent 设置

S3Mount 将兼容 S3 的存储桶(或其中的前缀)挂载到正在运行的容器中的某个路径。容器运行 s3fs,由它向 Worker 中的 S3Gateway 发送存储请求。S3Gateway 检查每个请求,并用容器永远收不到的凭据为其签名。S3Mount 不会启动或停止容器。

import { S3Mount } from "@cloudflare/sandbox";
import { DurableObject } from "cloudflare:workers";

export { S3Gateway } from "@cloudflare/sandbox";

export class MyContainer extends DurableObject {
	mounts;

	constructor(ctx, env) {
		super(ctx, env);
		if (!ctx.container) {
			throw new Error("No container is configured");
		}
		// ctx.container stays the same object while the Durable Object runs.
		this.mounts = new S3Mount(ctx.container, ctx.exports.S3Gateway);
	}

	// The container must already be running.
	async mountModels() {
		await this.mounts.mount({
			mountPath: "/mnt/models",
			source: {
				type: "s3",
				endpoint: `https://${this.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
				region: "auto",
				bucket: "models",
				credentials: {
					type: "static",
					accessKeyId: this.env.R2_ACCESS_KEY_ID,
					secretAccessKey: this.env.R2_SECRET_ACCESS_KEY,
				},
			},
			keyPrefix: "production",
			access: "read-only",
		});
	}
}
import { S3Mount } from "@cloudflare/sandbox";
import { DurableObject } from "cloudflare:workers";

export { S3Gateway } from "@cloudflare/sandbox";

export class MyContainer extends DurableObject<Env> {
    private readonly mounts: S3Mount;

    constructor(ctx: DurableObjectState, env: Env) {
        super(ctx, env);
        if (!ctx.container) {
            throw new Error("No container is configured");
        }
        // ctx.container stays the same object while the Durable Object runs.
        this.mounts = new S3Mount(ctx.container, ctx.exports.S3Gateway);
    }

    // The container must already be running.
    async mountModels() {
        await this.mounts.mount({
            mountPath: "/mnt/models",
            source: {
                type: "s3",
                endpoint: `https://${this.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
                region: "auto",
                bucket: "models",
                credentials: {
                    type: "static",
                    accessKeyId: this.env.R2_ACCESS_KEY_ID,
                    secretAccessKey: this.env.R2_SECRET_ACCESS_KEY,
                },
            },
            keyPrefix: "production",
            access: "read-only",
        });
    }
}

完整 Worker 请参阅 挂载 R2 存储桶。

要求

除了包要求外,S3Mount 还有这些要求:

  • 容器镜像包含 FUSE 3 和 s3fs。在 Debian 和 Ubuntu 镜像中,安装 fuse3 和 s3fs 软件包。

  • Worker 的主模块重新导出 S3Gateway,以便存在 this.ctx.exports.S3Gateway:

    export { S3Gateway } from "@cloudflare/sandbox";

    不要将 HTTP 请求路由到 S3Gateway。

S3Mount

new S3Mount(container: Pick<Container, "exec" | "interceptOutboundHttp">, gateway: S3GatewayBinding)
  • container — 来自 this.ctx.container 的容器。
  • gateway — this.ctx.exports.S3Gateway.

方法

mount

mount(request: S3MountRequest, options?: S3MountOperationOptions): Promise<void>

将源挂载到 request.mountPath。结果取决于该路径上已有的内容:

  • 没有任何内容:mount() 创建目录并挂载源。
  • 设置相同的挂载:mount() 复用它。
  • 来自失败或已取消、且设置相同的 mount() 的残留状态:mount() 会修复它。
  • 另一个文件系统,或设置不同的挂载:mount() 会抛出代码为 S3_MOUNT_CONFLICT 的 SandboxS3MountError。要更改设置,请先调用 unmount()。

每个新挂载都会向容器添加一个出站拦截目标。重复使用已有挂载不会新增目标。unmount() 不会移除目标。挂载会与自己的代码注册的拦截器共同占用容器的拦截限制。容器达到限制后,使用新设置的挂载会失败。

如果 Durable Object 还会调用 interceptAllOutboundHttp(),请在启动容器后立刻、在调用它之前调用 mount()。否则 catch-all 会接收来自容器的存储请求,mount() 会失败并出现 cannot verify the new FUSE connection。先注册了 catch-all 的容器会继续将存储请求发送给它。请重启容器后再挂载。

S3Mount 不会重试失败的操作。重试前请调用 inspect()。

inspect

inspect(mountPath: string, options?: S3MountOperationOptions): Promise<S3MountInspection>

报告路径状态,但不改变它。inspect() 会等待同一路径上正在进行的 mount() 或 unmount()。然后读取容器中的挂载状态,并通过 S3Gateway 对该前缀发送一次 list 请求。

读取挂载状态时,如果文件系统无响应,可能会阻塞。inspect() 没有超时。传入 signal 来设置超时。

能够读取对象但不能列出前缀的凭据会产生 rejected 的 upstream 状态。

unmount

unmount(mountPath: string, options?: S3MountOperationOptions): Promise<void>

拒绝该挂载的后续存储请求,然后卸载该路径。路径上没有挂载时,unmount() 会成功且不做更改。unmount() 从不强制卸载。

  • 当有进程正在使用该路径时,unmount() 会抛出 S3_MOUNT_BUSY。请求会保持被拒绝。停止该进程后再次调用 unmount(),或使用相同请求调用 mount() 以恢复访问。
  • 拒绝之后,该挂载的新存储请求会收到 HTTP 403。已在进行中的请求可以完成。
  • 拒绝之后,仍在使用该路径的进程可以读取空文件,并且写入会丢失而不报错。列出目录会失败。
  • 进程在拒绝前打开并读取过的文件,可以通过该已打开的文件继续读取。
  • 取消 unmount() 或 mount() 可能使文件系统仍处于挂载状态,但请求已被拒绝。请使用相同请求调用 mount(),或再次调用 unmount()。

类型

S3MountRequest

  • mountPath string — 要挂载到的绝对、规范化路径。不能是 /,也不能与 /proc/self/mountinfo、/run/sandbox/s3-mounts 或 /usr/local/bin/sandbox-shim 重叠。
  • source object:
    • type "s3" — 源类型。
    • endpoint string — S3 API 的 HTTP 或 HTTPS origin,不含凭据、路径、查询或 fragment。对于 R2,使用 https://<ACCOUNT_ID>.r2.cloudflarestorage.com。
    • region string — 用于签名请求的区域。对于 R2,使用 auto。
    • bucket string — 存储桶名称。不能以 - 开头,也不能包含 / 或 :。
    • credentials object — 静态凭据或凭据提供器。请参阅 凭据。
  • keyPrefix stringoptional — 要挂载的对象键前缀。挂载只显示该前缀下的键。前缀不能以 / 开头。S3Mount 会添加尾部 /。省略它即可挂载整个存储桶。
  • access "read-only" | "read-write" — 容器中的进程是否可以创建、更改和删除对象。
  • s3fsOptions Readonly<Record<string, string | number | boolean>>optional — 额外的 s3fs 选项。选项请参阅 s3fs 手册 ↗。
    • true 值会将该选项添加为标志,false 会省略它,字符串或数字会添加 name=value。
    • 设置端点、凭据、访问模式、代理或文件系统身份的选项会抛出 TypeError。
    • S3Mount 始终设置 nomixupload。传入 nomixupload 会抛出 TypeError。
    • 使用 nomixupload 时,更改大文件一部分的命令会按一种分片大小上传整个文件,这是 R2 所要求的。
    • S3Mount 不设置缓存、重试或超时选项。适用你镜像中 s3fs 版本的默认值。

S3Mount 不接受 R2 绑定。请使用 R2 S3 API 端点配合 R2 API 凭据。

凭据

静态凭据:

  • type "static"
  • accessKeyId string
  • secretAccessKey string
  • sessionToken stringoptional

提供器凭据:

  • type "provider"
  • fetcher Pick<Fetcher, "fetch"> — 对 S3Gateway 允许的每个存储请求调用。

S3Gateway 向提供器发送 GET https://credentials.sandbox.internal/,并带有 Accept: application/json。响应必须是不超过 16 KiB 的 JSON:

{
    "accessKeyId": "<ACCESS_KEY_ID>",
    "secretAccessKey": "<SECRET_ACCESS_KEY>",
    "sessionToken": "<SESSION_TOKEN>",
    "expiresAt": 1790000000000
}

sessionToken 是可选的。expiresAt 是以毫秒计的 Unix 时间,必须位于未来。S3Gateway 不缓存凭据,也不会重试失败的提供器请求。

S3MountOperationOptions

  • signal AbortSignaloptional — 取消操作。操作会抛出 abort reason。

S3MountInspection

每次检查都有 mountPath 和 attachment.status:

attachment.status 其他字段 含义
absent 无 该路径上没有挂载
unmanaged attachment.filesystemType 该路径上挂载了另一个文件系统
incompatible 无 该路径具有不受支持版本留下的状态
stale attachment.configuration, gateway 挂载设置仍在,但文件系统已消失
managed attachment.configuration, fuse, gateway 挂载处于活动状态
  • configuration — 挂载的端点、区域、存储桶、键前缀、访问模式和 s3fs 选项。
  • fuse.status "connected" | "disconnected" | "indeterminate"
  • gateway.status "unreachable" | "error" | "reachable"
  • gateway.upstream.status "usable" | "unavailable" | "rejected" — 当 gateway.status 为 reachable 时存在。

S3Gateway

S3Gateway 是处理来自容器的存储请求的 WorkerEntrypoint。S3Mount 为每个挂载连接单独的 S3Gateway。

  • 容器收到占位凭据,因为 s3fs 需要一对密钥。
  • S3Gateway 不转发容器的 Authorization 标头。它使用该挂载的凭据为每个允许的请求签名。
  • S3Gateway 只允许 s3fs 使用的对象和 list 操作,且仅限于该挂载的存储桶和前缀。其他请求会被拒绝。
  • 只读挂载不允许写入。
  • 容器中任何能到达挂载路径的进程,都可以对该前缀执行每一项被允许的操作。请将凭据也限制到该存储桶和前缀。

已挂载文件的行为

已挂载目录将文件映射到存储桶中的对象。它的行为不像本地磁盘:

  • 重命名文件会复制对象并删除原对象。
  • 文件锁、硬链接、所有权、权限和原子替换的工作方式与本地文件系统不同。
  • 目录来自对象键前缀。
  • s3fs 默认缓存文件元数据(包括文件不存在这一事实)900 秒。
  • 在缓存过期前,另一个客户端添加到存储桶的文件,在容器中可能仍显示为缺失。
  • 在缓存过期前,另一个客户端更改的文件可能仍保持旧大小,读取可能返回被截断到该大小的新内容。
  • 要缩短等待时间,请在 s3fsOptions 中设置更低的 stat_cache_expire。要停止缓存缺失文件,请设置 disable_noobj_cache: true。
  • 中断的写入可能在存储桶中留下不完整的上传。
  • 快照 不包含已挂载目录。

错误

SandboxS3MountError

挂载操作失败。

字段 类型 描述
name "SandboxS3MountError" 错误名称
code SandboxS3MountErrorCode 错误代码
operation S3MountOperation 失败的方法:mount、inspect 或 unmount
path string 传给该方法的路径
detail string 来自 helper 的错误描述

code 是以下值之一:

代码 含义
S3_MOUNT_CONFLICT 另一个文件系统,或设置不同的挂载占用了该路径
S3_MOUNT_BUSY 有进程正在使用该路径
S3_MOUNT_FAILED s3fs、FUSE 或操作的另一步失败
S3_MOUNT_INCOMPATIBLE 该路径具有不受支持版本留下的状态

该包导出 SandboxS3MountErrorCode 和 S3MountOperation 类型,它们列出了这些代码和方法。

SandboxS3MountError 不是类。请使用 SandboxS3MountError.is(error),而不是 instanceof。它可以识别同一 Worker 中抛出的错误,以及通过 Durable Object RPC 返回的错误。

SandboxProtocolError

S3Mount 无法完成与 helper 二进制的交换。请参阅 SandboxProtocolError。

其他错误

S3Mount 不包装来自运行时或其输入的错误。

条件 错误
无效的请求或路径 TypeError
容器未在运行 来自 exec() 的 Error
signal 已中止 中止原因
无法添加 intercept 来自 interceptOutboundHttp() 的 Error

相关资源

这篇文档对您有帮助吗?