--- title: "持久卷" description: "将块存储挂载到 Deno 沙盒中,以在会话之间保持状态" --- 持久卷让你可以将区域块存储附加到沙箱中,从而使数据在进程重启和新连接之间得以保留。它们非常适合包缓存、构建产物、SQLite 数据库,或任何需要少量耐久存储但不需要将代码提升为完整 Deno Deploy 应用的工作流。 ### 预配存储 持久卷目前处于私有测试阶段。如需访问此功能,请联系 [support](mailto:support@deno.com) ::: ## 使用 `Client.volumes` 预配置存储 使用管理 Deploy 应用的相同 `Client` 类。 ```tsx import { Client } from "@deno/sandbox"; const client = new Client(); const volume = await client.volumes.create({ slug: "training-cache", region: "ord", // ord(芝加哥)或 ams(阿姆斯特丹) capacity: "2GB", // 接受字节数或 "1GB"/"512MB" 等样式的字符串 }); console.log(volume); // { // id: "8a0f...", // slug: "training-cache", // region: "ord", // capacity: 2147483648, // used: 0 // } ``` ```py from deno_sandbox import DenoDeploy sdk = DenoDeploy() volume = sdk.volumes.create( slug="training-cache", region="ord", # ord (芝加哥) 或 ams (阿姆斯特丹) capacity="2GB" # 接受字节数或 "1GB"/"512MB" 样式的字符串 ) print(volume) # { # "id": "8a0f...", # "slug": "training-cache", # "region": "ord", # "capacity": 2147483648, # "used": 0 # } ``` ```py from deno_sandbox import AsyncDenoDeploy sdk = AsyncDenoDeploy() volume = await sdk.volumes.create( slug="training-cache", region="ord", # ord (芝加哥) 或 ams (阿姆斯特丹) capacity="2GB" # 接受字节数或 "1GB"/"512MB" 样式的字符串 ) print(volume) # { # "id": "8a0f...", # "slug": "training-cache", # "region": "ord", # "capacity": 2147483648, # "used": 0 # } ``` | 字段 | 是否必需 | 详情 | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------- | | `slug` | ✅ | 在组织内唯一。Slug 会成为挂载元数据的一部分,请选择描述性强的名称。 | | `region` | ✅ | 必须匹配可用的沙箱区域(目前为 `"ord"` 或 `"ams"`)。只有同一区域的沙箱才能挂载该卷。 | | `capacity` | ✅ | 介于 300 MB 和 20 GB 之间。支持字节数,或带有 `GB`/`MB`/`KB`(十进制)或 `GiB`/`MiB`/`KiB`(二进制)单位的字符串。 | ## 检查和搜索卷 `client.volumes.list()` 返回分页结果和辅助迭代器,`client.volumes.get()` 可按 slug 或 UUID 获取单个记录。 ```tsx const page = await client.volumes.list({ search: "training" }); for (const vol of page.items) { console.log(vol.slug, vol.estimatedFlattenedSize, vol.capacity); } const vol = await client.volumes.get("training-cache"); ``` ```py page = sdk.volumes.list(search="training") for vol in page.items: print(f"{vol['slug']} {vol['estimatedFlattenedSize']} {vol['capacity']}") vol = sdk.volumes.get("training-cache") ``` ```py page = await sdk.volumes.list(search="training") async for vol in page: print(f"{vol['slug']} {vol['estimatedFlattenedSize']} {vol['capacity']}") vol = await sdk.volumes.get("training-cache") ``` `used` 字段报告控制平面从底层集群获得的最新估算值。该值可能会比实际情况滞后几分钟,因此创建卷时请预留足够的容量。 ## 在沙箱内挂载卷 调用 `Sandbox.create()` 时传入 `volumes` 映射。键为挂载路径,值为卷的 slug 或 ID。沙箱和卷**必须位于同一区域**。 :::note `Sandbox.create()` 和 `client.sandboxes.create()` 功能相同——根据你的代码风格选择使用即可。 ::: ```tsx import { Client, Sandbox } from "@deno/sandbox"; const client = new Client(); const volume = await client.volumes.create({ slug: "dataset", region: "ord", capacity: "1GB", }); // 第一次运行时向卷中写入文件 { await using sandbox = await Sandbox.create({ region: "ord", volumes: { "/data/dataset": volume.slug, }, labels: { job: "prepare" }, }); await sandbox.fs.writeTextFile("/data/dataset/hello.txt", "Persist me!\n"); } // 新沙箱 —— 可能在数小时之后启动 —— 可以读取同一个文件 { await using sandbox = await Sandbox.create({ region: "ord", volumes: { "/data/dataset": volume.id, // ID 也可用 }, }); const contents = await sandbox.fs.readTextFile("/data/dataset/hello.txt"); console.log(contents); // "Persist me!" } ``` ```py from deno_sandbox import DenoDeploy sdk = DenoDeploy() volume = sdk.volumes.create( slug="dataset", region="ord", capacity="1GB" ) # 第一次运行时向卷中写入文件 with sdk.sandbox.create( region="ord", volumes={ "/data/dataset": volume["slug"], }, labels={"job": "prepare"} ) as sandbox: sandbox.fs.write_text_file("/data/dataset/hello.txt", "Persist me!\n") # 新沙箱 —— 可能数小时后启动 —— 可以读取同一个文件 with sdk.sandbox.create( region="ord", volumes={ "/data/dataset": volume["id"], # ID 也可以用 } ) as sandbox: contents = sandbox.fs.read_text_file("/data/dataset/hello.txt") print(contents) # "Persist me!" ``` ```py from deno_sandbox import AsyncDenoDeploy sdk = AsyncDenoDeploy() volume = await sdk.volumes.create( slug="dataset", region="ord", capacity="1GB" ) # 第一次运行时向卷中写入文件 async with sdk.sandbox.create( region="ord", volumes={ "/data/dataset": volume["slug"], }, labels={"job": "prepare"} ) as sandbox: await sandbox.fs.write_text_file("/data/dataset/hello.txt", "Persist me!\n") # 新沙箱 —— 可能数小时后启动 —— 可以读取同一个文件 async with sdk.sandbox.create( region="ord", volumes={ "/data/dataset": volume["id"], # ID 也可以用 } ) as sandbox: contents = await sandbox.fs.read_text_file("/data/dataset/hello.txt") print(contents) # "Persist me!" ``` 挂载点表现得像普通目录。你可以创建子文件夹、写入二进制文件,或直接从卷中执行程序。 ## 安全删除卷 ```tsx await client.volumes.delete("training-cache"); ``` ```py sdk.volumes.delete("training-cache") ``` ```py await sdk.volumes.delete("training-cache") ``` 删除是一个两步过程: 1. API 会立即将卷标记为删除状态,阻止新沙箱请求挂载它,并释放该 slug 以供将来使用。 2. 后台任务将在 24 小时后从集群中移除底层块存储。此宽限期允许你在意外删除卷时联系支持。 宽限期内无法挂载或读取该卷。 ## 快照 快照是从卷创建的只读镜像。当沙箱以快照作为根目录启动时,整个文件系统被快照内容替换。你可以运行一次 `apt-get install` 或 `npm install`,创建快照,然后所有未来的沙箱启动时都会立即拥有预安装的软件。 ### 创建快照的工作流程 1. 从基础镜像创建一个**可启动卷** 2. 使用该卷作为 `root`(可写)启动沙箱 3. 安装软件 4. 对该卷进行快照 从快照启动的沙箱会立即启动并拥有所有预配置的软件。 当卷用 `from` 选项创建时即为**可启动**。目前唯一可用的基础镜像是 `builtin:debian-13`。 ```tsx import { Client } from "@deno/sandbox"; const client = new Client(); // 1. 创建一个可启动卷 const volume = await client.volumes.create({ region: "ord", slug: "my-toolchain", capacity: "10GiB", from: "builtin:debian-13", }); // 2. 启动沙箱,使用该卷作为根目录(可写) await using sandbox = await client.sandboxes.create({ region: "ord", root: volume.slug, }); // 3. 安装软件 await sandbox.sh`apt-get update && apt-get install -y nodejs npm`; await sandbox.sh`npm install -g typescript`; // 4. 对卷进行快照 const snapshot = await client.volumes.snapshot(volume.id, { slug: "my-toolchain-snapshot", }); ``` ```bash # 从卷创建快照 deno sandbox snapshots create my-toolchain my-toolchain-snapshot ``` ### 从快照启动 拿到快照后,可用其作为创建新沙箱时的 `root`。沙箱必须和快照位于同一区域: ```tsx await using sandbox = await client.sandboxes.create({ region: "ord", root: "my-toolchain-snapshot", // 快照的 slug 或 ID }); // TypeScript 和 Node.js 已经安装好 await sandbox.sh`tsc --version`; await sandbox.sh`node --version`; ``` 沙箱启动时,快照的文件系统作为根目录。会话期间所有写操作都是临时性的,它们不会修改快照。 ### 列出快照 ```tsx const page = await client.snapshots.list(); for (const snap of page.items) { console.log(snap.slug, snap.region, snap.bootable); } ``` ```bash $ deno sandbox snapshots list ID SLUG REGION ALLOCATED BOOTABLE snp_ord_spmbe47dysccpy277ma6 my-toolchain-snapshot ord 217.05 MiB TRUE ``` ### 从快照创建卷 从快照创建一个新的可写卷: ```tsx const volume = await client.volumes.create({ region: "ord", slug: "my-toolchain-fork", capacity: "10GiB", from: "my-toolchain-snapshot", }); ``` 新卷包含快照内容且完全可写。你可以用它来修改快照内容然后重新创建快照。 ### 删除快照 ```tsx await client.snapshots.delete("my-toolchain-snapshot"); ``` ```bash deno sandbox snapshots delete my-toolchain-snapshot ``` ### 卷 和 快照对比 | 特性 | 卷(Volumes) | 快照(Snapshots) | | ------------ | --------------------------------- | --------------------------------- | | 访问权限 | 读写 | 只读 | | 挂载点 | 任意路径,启动卷可用作根目录 | 仅根文件系统 | | 使用场景 | 缓存、数据库、安装软件 | 预安装的软件、工具链 | | 并发使用 | 同时只能一个沙箱挂载 | 多个沙箱可以同时使用 | | 区域 | 必须与沙箱区域匹配 | 必须与沙箱区域匹配 |