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。
new S3Mount(container: Pick<Container, "exec" | "interceptOutboundHttp">, gateway: S3GatewayBinding)container— 来自this.ctx.container的容器。gateway—this.ctx.exports.S3Gateway.
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(mountPath: string, options?: S3MountOperationOptions): Promise<S3MountInspection>报告路径状态,但不改变它。inspect() 会等待同一路径上正在进行的 mount() 或 unmount()。然后读取容器中的挂载状态,并通过 S3Gateway 对该前缀发送一次 list 请求。
读取挂载状态时,如果文件系统无响应,可能会阻塞。inspect() 没有超时。传入 signal 来设置超时。
能够读取对象但不能列出前缀的凭据会产生 rejected 的 upstream 状态。
unmount(mountPath: string, options?: S3MountOperationOptions): Promise<void>拒绝该挂载的后续存储请求,然后卸载该路径。路径上没有挂载时,unmount() 会成功且不做更改。unmount() 从不强制卸载。
- 当有进程正在使用该路径时,
unmount()会抛出S3_MOUNT_BUSY。请求会保持被拒绝。停止该进程后再次调用unmount(),或使用相同请求调用mount()以恢复访问。 - 拒绝之后,该挂载的新存储请求会收到 HTTP
403。已在进行中的请求可以完成。 - 拒绝之后,仍在使用该路径的进程可以读取空文件,并且写入会丢失而不报错。列出目录会失败。
- 进程在拒绝前打开并读取过的文件,可以通过该已打开的文件继续读取。
- 取消
unmount()或mount()可能使文件系统仍处于挂载状态,但请求已被拒绝。请使用相同请求调用mount(),或再次调用unmount()。
mountPathstring— 要挂载到的绝对、规范化路径。不能是/,也不能与/proc/self/mountinfo、/run/sandbox/s3-mounts或/usr/local/bin/sandbox-shim重叠。sourceobject:type"s3"— 源类型。endpointstring— S3 API 的 HTTP 或 HTTPS origin,不含凭据、路径、查询或 fragment。对于 R2,使用https://<ACCOUNT_ID>.r2.cloudflarestorage.com。regionstring— 用于签名请求的区域。对于 R2,使用auto。bucketstring— 存储桶名称。不能以-开头,也不能包含/或:。credentialsobject— 静态凭据或凭据提供器。请参阅 凭据。
keyPrefixstringoptional — 要挂载的对象键前缀。挂载只显示该前缀下的键。前缀不能以/开头。S3Mount会添加尾部/。省略它即可挂载整个存储桶。access"read-only" | "read-write"— 容器中的进程是否可以创建、更改和删除对象。s3fsOptionsReadonly<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"accessKeyIdstringsecretAccessKeystringsessionTokenstringoptional
提供器凭据:
type"provider"fetcherPick<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 不缓存凭据,也不会重试失败的提供器请求。
signalAbortSignaloptional — 取消操作。操作会抛出 abort reason。
每次检查都有 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 是处理来自容器的存储请求的 WorkerEntrypoint。S3Mount 为每个挂载连接单独的 S3Gateway。
- 容器收到占位凭据,因为
s3fs需要一对密钥。 S3Gateway不转发容器的Authorization标头。它使用该挂载的凭据为每个允许的请求签名。S3Gateway只允许s3fs使用的对象和 list 操作,且仅限于该挂载的存储桶和前缀。其他请求会被拒绝。- 只读挂载不允许写入。
- 容器中任何能到达挂载路径的进程,都可以对该前缀执行每一项被允许的操作。请将凭据也限制到该存储桶和前缀。
已挂载目录将文件映射到存储桶中的对象。它的行为不像本地磁盘:
- 重命名文件会复制对象并删除原对象。
- 文件锁、硬链接、所有权、权限和原子替换的工作方式与本地文件系统不同。
- 目录来自对象键前缀。
s3fs默认缓存文件元数据(包括文件不存在这一事实)900 秒。- 在缓存过期前,另一个客户端添加到存储桶的文件,在容器中可能仍显示为缺失。
- 在缓存过期前,另一个客户端更改的文件可能仍保持旧大小,读取可能返回被截断到该大小的新内容。
- 要缩短等待时间,请在
s3fsOptions中设置更低的stat_cache_expire。要停止缓存缺失文件,请设置disable_noobj_cache: true。 - 中断的写入可能在存储桶中留下不完整的上传。
- 快照 不包含已挂载目录。
挂载操作失败。
| 字段 | 类型 | 描述 |
|---|---|---|
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 返回的错误。
S3Mount 无法完成与 helper 二进制的交换。请参阅 SandboxProtocolError。
S3Mount 不包装来自运行时或其输入的错误。
| 条件 | 错误 |
|---|---|
| 无效的请求或路径 | TypeError |
| 容器未在运行 | 来自 exec() 的 Error |
signal 已中止 |
中止原因 |
| 无法添加 intercept | 来自 interceptOutboundHttp() 的 Error |