云盘的快照把云盘保存为某一时刻的状态。云盘在写入的同时被复制到所在区域的对象存储中,快照就是它在那里的一个版本:创建快照不会再复制任何数据,使用快照也不会。有了快照,你可以:
- 把云盘原地恢复到它;
- 分叉它:创建一个内容与快照相同的新云盘,可以立即挂载;
- 在云盘所在区域的任意机器上,让任意多个沙箱同时以只读方式挂载它。
快照不随云盘消失。云盘删除后,仍然可以列出它的快照、从中分叉,或以只读方式挂载。
每种客户端都有这些调用:命令行(glac drives snapshots、glac drives fork、glac drives restore、glac drives attach --snapshot),SDK(下面的 Python 和 TypeScript,以及 Go、Rust 和 Java SDK),MCP 服务器(drive_snapshot、drive_fork、drive_restore),以及控制台的 Drives 页面,在那里可以为每个云盘创建、分叉、恢复和删除 Snapshots(快照)。
调用
| 调用 | 作用 |
|---|---|
POST /v1/drives/{id}/snapshots {name?, fs_consistent?} |
立即为云盘创建快照。返回 201 和快照。 |
GET /v1/drives/{id}/snapshots |
云盘的快照,最新的在前;已删除的云盘,按它的 id 列出。 |
GET /v1/drive-snapshots |
项目中所有云盘的快照,最新的在前:已删除云盘的快照也在这里,附带该云盘的 id。?drive_deleted=true 只列出这些,false 只列出其他的(不指定云盘的 glac drives snapshots ls)。 |
GET, DELETE /v1/drives/{id}/snapshots/{snapshot} |
按 id 查看一个快照;删除它。 |
POST /v1/drives/{id}/restore {snapshot} |
把云盘恢复到快照。返回云盘。 |
POST /v1/drives/{id}/fork {name?, from_snapshot?} |
从快照,或从云盘当前的状态,创建一个新云盘。返回 201 和新云盘。 |
POST /v1/sandboxes/{id}/drives {drive, path, mode: "snapshot", snapshot} |
以只读方式把快照挂载到一个已有的沙箱中。返回快照。 |
POST /v1/sandboxes 带上 drives: [{drive, path, mode: "snapshot", snapshot}] |
以只读方式把它挂载到新沙箱中。 |
DELETE /v1/sandboxes/{id}/drives/{snapshot} |
按快照的 id 卸载只读挂载。 |
{id} 是云盘的 id 或名称。列出和读取快照需要带 read 权限范围的密钥;其他调用需要 write。API 参考列出了每个调用的所有字段和响应:创建、列出、读取、删除、恢复、分叉、挂载和卸载。
快照包含 id (dsnap-…)、所属云盘的 drive_id、name、consistency(创建时如何暂停云盘的写入:见下文)、used_bytes(创建时云盘中的数据量)、exclusive_bytes(它单独保留的数据量,也是计费依据)和 exclusive_at(最近一次计算该值的时间)、它对应的云盘 version(number、epoch),以及 created_at。
名称可选:1 到 63 个小写字母、数字和连字符,不能以连字符开头或结尾,也不能以 vol- 或 dsnap- 这类 id 前缀开头 (400 invalid_name)。两个快照可以同名,所以各个调用都用 id 指定快照。
创建快照
示例中,云盘是 data,沙箱 web 把它挂载在 /data。glac drives snapshots create 打印快照的 id,这里保存在 SNAP 中:
SNAP=$(glac drives snapshots create data --name before-upgrade --fs-consistent)无论有没有沙箱持有,云盘都可以创建快照:
- 没有运行中的沙箱持有它(没有挂载,或持有它的沙箱在休眠):快照就是云盘当前的样子 (
consistency: "at_rest")。 - 有运行中的沙箱持有它:快照分两步创建。先在沙箱继续写入的同时,保存调用之前写入的内容作为一个版本;再暂停沙箱的写入,把这期间写入的内容保存为一个版本。写得少的沙箱,暂停不到一秒,最多十秒:超过十秒,快照会被拒绝(503
unavailable,可以重试),沙箱照常运行。还没进入对象存储的数据会先发送,所以刚写入大量数据的云盘需要更久。 - 如何暂停写入:默认暂停沙箱的机器 (
consistency: "paused"):云盘处于相当于断电后的状态,文件系统的日志就是为此准备的。设置fs_consistent: true(--fs-consistent) 时,改由沙箱中的代理程序冻结云盘的文件系统 ("frozen"):文件系统中的内容会先写出,挂载快照时无需重放日志。代理程序无法冻结时,调用会被拒绝 (409not_frozen):这时请不带这个选项创建快照。
列出云盘的快照、读取一个、删除一个:
glac drives snapshots ls data # newest first: how each was taken, what it keeps alone
glac drives snapshots get data $SNAP
glac drives snapshots rm data $SNAP删除的快照立即消失,计费到那一刻为止。从它分叉出的云盘会保留所需的数据,以只读方式挂载它的沙箱会保留挂载,直到卸载或沙箱被删除。
恢复云盘
恢复把云盘带回某个快照:之后它的内容就是创建快照时的内容。恢复会在云盘最新的版本之后写入一个新版本,内容与快照相同;不复制任何数据,云盘的 id 和名称不变。
glac drives detach data web
glac drives restore data $SNAP
glac drives attach data web:/data- 沙箱以读写方式持有的云盘不能恢复 (409
drive_attached):先卸载,恢复后再挂载。 - 中间的版本只要有快照或分叉保留着就仍然存在:云盘的其他快照(包括在这个快照之后创建的)和它的分叉都保持不变,所以恢复到较旧快照的云盘,之后还可以再恢复到较新的快照。
- 恢复之前云盘的内容,只有在快照保留它时才会留下:如果之后可能想回来,请先创建一个快照。
- 恢复之后,云盘的数据块在被读取时从对象存储取来。
分叉云盘
glac drives fork (POST /v1/drives/{id}/fork) 创建一个新云盘,它的第一个版本就是快照的内容(--from-snapshot、from_snapshot),并打印它的 id。不指定快照时,用的是云盘当前的状态:会像创建快照那样为分叉生成一个版本,但不保留它。不复制任何数据:分叉可以立即挂载,数据块在被读取时从对象存储取来。
glac drives fork data --name data-try --from-snapshot $SNAP
glac sandboxes create --name try --drive data-try:/data
glac drives get data-try # with parent_drive_id (the id of data) and root_drive_id- 分叉出的云盘和其他云盘一样:有自己的 id 和名称,同一时间由一个沙箱写入,按云盘计费并受云盘的限制。它的大小与来源云盘相同,也在来源云盘的区域中。
parent_drive_id是它的来源云盘,root_drive_id是这一脉分叉中的第一个云盘。不是分叉出的云盘,两者都为null。- 分叉写入的内容只属于它自己:来源云盘和快照都不会改变。两者都可以删除,分叉会保留它需要的数据。
- 已删除云盘的快照,按该云盘的 id 分叉。
- 分叉的名称不能与项目中其他云盘或存储卷的名称相同 (409
name_taken)。超出套餐云盘数量的分叉会被拒绝 (403drive_limit_reached),和新建云盘一样。
以只读方式挂载快照
只读挂载把快照中的云盘内容交给沙箱,挂载在某个路径上。云盘所在区域任意机器上的任意多个沙箱,都可以同时挂载同一个快照,无论云盘被谁持有:一个沙箱甚至可以同时以读写方式持有云盘、以只读方式挂载它的某个快照。云盘删除后,也可以挂载它的快照。
glac drives attach data web:/then --snapshot $SNAP # in a sandbox that runs
glac sandboxes exec web -- ls /then
glac drives detach $SNAP web # by the snapshot's id
glac sandboxes create --name reader --drive data:/then:dsnap-… # in a new sandbox: the snapshot's id after the path- 给出的云盘(API 中为
drive)必须是快照所属的云盘:它的 id 或名称,或已删除云盘的 id。其他云盘的快照会被拒绝 (400invalid_field)。 - 新沙箱在它的
drives中放入同样的对象来挂载快照(POST /v1/sandboxes;命令行中为--drive DRIVE:/PATH:SNAPSHOT,因为那里的--snapshot是沙箱要从中继续运行的沙箱快照)。只读挂载不决定沙箱创建在哪台机器上,但沙箱会创建在云盘所在的区域:指定了其他区域,或用到两个区域的云盘的创建请求,都会被拒绝 (409not_on_one_machine)。 - 运行在与云盘不同区域的机器上的沙箱,也会被拒绝 (409
not_on_one_machine)。 - 文件系统以只读方式挂载:沙箱不能写入。
- 数据块在沙箱读取时从对象存储取来,并保存在沙箱所在的机器上。读取它们不收费(见下文)。保存下来的数据块计入一个账户在该机器磁盘上可占用的份额:超出时挂载会被拒绝(403
account_share_reached,并说明超出多少)。 - 它计入沙箱挂载的 8 个云盘和存储卷之内 (409
too_many_drives)。 - 它不出现在云盘的
attached_to中,也不会发出drive.attached或drive.detached事件。 - 要按快照的 id 卸载它:用云盘的 id 或名称只会卸载读写挂载 (404
drive_not_attached)。删除沙箱也会卸载它。
沙箱的快照与它的云盘
为以读写方式持有云盘的沙箱创建快照时,每个云盘都会保存为一个版本,不复制任何数据。从这个快照创建的沙箱,会在相同路径上挂载每个云盘的一个分叉,内容是创建快照时云盘中的内容。云盘本身仍属于第一个沙箱,它之后写入的内容不会进入这些分叉。
这些分叉都是项目中的普通云盘:会被列出,parent_drive_id 是它的来源云盘,按 used_bytes 计费,新沙箱被删除后仍会保留,直到你删除它。
用 Python 和 TypeScript
from glac import DriveMount, Glac
glac = Glac() # GLAC_API_KEY and GLAC_API_URL
drive = glac.drives.get("data")
web = glac.sandboxes.get("web") # the sandbox that has it
snap = drive.snapshot(name="before-upgrade", fs_consistent=True)
for s in drive.snapshots():
print(s.id, s.name, s.consistency, s.exclusive_bytes)
# a new Drive made of the snapshot, in a new sandbox
fork = glac.drives.fork(drive, name="data-try", from_snapshot=snap)
trial = glac.sandboxes.create(drives=[DriveMount("/data", fork)])
# the snapshot, read-only: in a new sandbox, and in one that runs
reader = glac.sandboxes.create(drives=[DriveMount("/then", drive, snapshot=snap)])
glac.drives.attach_snapshot(trial, snap, "/then")
print(trial.run("ls /then").stdout)
glac.drives.detach_snapshot(trial, snap)
# the Drive back to the snapshot, while no sandbox has it read-write
glac.drives.detach(web, drive)
glac.drives.restore(drive, snap)
glac.drives.attach(web, drive, "/data")
snap.delete()import { Glac } from "glac";
const glac = new Glac(); // GLAC_API_KEY and GLAC_API_URL
const drive = await glac.drives.get("data");
const web = await glac.sandboxes.get("web"); // the sandbox that has it
const snap = await drive.snapshot({ name: "before-upgrade", fsConsistent: true });
for (const s of await drive.snapshots()) console.log(s.id, s.name, s.consistency, s.exclusiveBytes);
// a new Drive made of the snapshot, in a new sandbox
const fork = await glac.drives.fork(drive, { name: "data-try", fromSnapshot: snap });
const trial = await glac.sandboxes.create({ drives: [{ path: "/data", drive: fork.id }] });
// the snapshot, read-only: in a new sandbox, and in one that runs
const reader = await glac.sandboxes.create({ drives: [{ path: "/then", drive: drive.id, snapshot: snap }] });
await glac.drives.attachSnapshot(trial, snap, "/then");
console.log((await trial.run("ls /then")).stdout);
await glac.drives.detachSnapshot(trial, snap);
// the Drive back to the snapshot, while no sandbox has it read-write
await glac.drives.detach(web, drive);
await glac.drives.restore(drive, snap);
await glac.drives.attach(web, drive, "/data");
await snap.delete();快照在两者中都是 DriveSnapshot,自带 refresh、fork、restore、attach、detach 和 delete。attach 以读写方式挂载云盘:快照用 attach_snapshot (attachSnapshot) 以只读方式挂载,或在新沙箱的 drives 中带上 snapshot。所有调用见 Python 和 TypeScript SDK 页面。
用命令行
| 命令 | 作用 |
|---|---|
glac drives snapshots create DRIVE [--name NAME] [--fs-consistent] |
创建快照并打印它的 id。 |
glac drives snapshots ls DRIVE, get DRIVE SNAPSHOT, rm DRIVE SNAPSHOT… |
云盘的快照,最新的在前;查看一个;删除。已删除的云盘,按它的 id 指定。 |
glac drives restore DRIVE SNAPSHOT |
把云盘恢复到快照。 |
glac drives fork DRIVE [--name NAME] [--from-snapshot SNAPSHOT] |
从快照,或从云盘当前的状态,创建一个新云盘;打印它的 id。 |
glac drives attach DRIVE SANDBOX:/PATH --snapshot SNAPSHOT, glac drives detach SNAPSHOT SANDBOX |
以只读方式把快照挂载到正在运行的沙箱中;卸载它。 |
glac sandboxes create --drive DRIVE:/PATH:SNAPSHOT |
创建一个以只读方式挂载了快照的新沙箱。 |
glac drives get DRIVE |
分叉的 parent_drive_id 和 root_drive_id,以及云盘的其他信息。 |
glac usage --snapshot SNAPSHOT |
单个快照保留了多少。 |
计费内容
- 快照按它单独保留的数据 (
exclusive_bytes) 计费:即它的版本需要、而云盘当前状态、较新的快照和分叉都不需要的数据。两个快照都保留的数据计在较旧的那个上。云盘自那以后没有变化时,快照单独保留的数据为零。云盘删除后,它原有的数据计在它的快照上。 - 它按小时计费,每 GB·月 $0.02,和云盘保存的数据一起计入数据计量项,从第一次算出它单独保留的数据 (
exclusive_at) 起,到它被删除为止。付费版每月包含 20 GB·月 的数据,由快照、云盘和休眠沙箱共用。免费版同时最多保存 15 GB 的数据,其中包括快照:超出时,新建快照会被拒绝 (403storage_quota_reached),直到账户保存的数据减少。参见价格与套餐。 GET /v1/usage?snapshot_id=dsnap-…报告单个快照保留了多少,glac usage --snapshot dsnap-…也一样。- 分叉是云盘,按云盘计费,依据它的
used_bytes:即你读到的全部内容,包括它与来源快照共享的数据。从一个存有 10 GB 数据的云盘的快照分叉出的云盘,从一开始就按 10 GB 的云盘计费,尽管它的大部分数据块在底层是共享的。它的 NVMe 缓存和任何云盘一样计费;写入和读取都不收费。参见云盘的工作方式。 - 只读挂载:为它从分层存储取来的数据块不收费,两个沙箱挂载同一个快照,各自读取也都不收费。它保存在机器上的数据块是快照的缓存,快照按上文计费。
账户欠费或被限制时
- 欠费的账户可以列出、读取和删除云盘的快照,以免欠款继续增加。它不能创建快照、分叉、恢复或挂载 (402
account_in_debt)。如果第一封邮件发出 30 天后仍然欠费,它存储的一切都会被删除,其中包括快照 (drive.snapshot.deleted)。参见欠费的账户。 - 被支出上限限制的项目,或者账户已用完付费版当期包含的 CPU 时间或内存且没有余额时,不能创建快照,也不能分叉(403
spend_limit_reached、balance_used_up);在免费版上,当天 CPU 时间或内存的配额已用完的账户也不能,直到 00:00 UTC (429quota_used_up)。快照保留的数据会继续计量(计量项用完时)。
限制与错误
- 一个云盘可以有任意多个快照;每个快照按它单独保留的数据计费。快照不计入项目的云盘数量。
- 分叉按其大小算作一个新云盘:参见项目能保有多少。
- 只读挂载计入沙箱的 8 个云盘和存储卷之内。
| 响应 | 情形 |
|---|---|
400 invalid_name |
名称不是 1 到 63 个小写字母、数字和连字符,或以 id 前缀开头。 |
400 missing_field |
恢复时没有给出 snapshot;挂载时设置了 mode: "snapshot" 却没有给出 snapshot。 |
400 invalid_field |
snapshot 指定的是另一个云盘的快照,或者给出时没有设置 mode: "snapshot"。 |
402 account_in_debt |
账户欠费期间,除列出、读取和删除之外的任何操作。 |
403 drive_limit_reached |
超出套餐允许项目保有的云盘数量的分叉。 |
403 spend_limit_reached, balance_used_up |
项目被限制期间的快照或分叉。 |
403 storage_quota_reached |
在免费版上,账户保存的数据已达到免费版同时可保存的上限时创建快照。 |
429 quota_used_up |
在免费版上,当天的 CPU 时间或内存配额用完后创建快照或分叉,直到 00:00 UTC。 |
403 account_share_reached |
在账户占用的磁盘份额已达到单个账户上限的机器上进行分叉、恢复或只读挂载。错误信息会说明占用了多少。 |
404 drive_not_found, snapshot_not_found |
没有这个 id 或名称的云盘,或者云盘没有这个 id 的快照。已删除云盘的快照要按云盘的 id 访问。 |
404 drive_not_attached |
用云盘的 id 或名称而不是快照的 id 卸载只读挂载,或者沙箱没有这个挂载。 |
409 drive_attached |
沙箱以读写方式持有云盘时进行恢复。 |
409 not_frozen |
设置了 fs_consistent: true,但沙箱中的代理程序无法冻结文件系统。 |
409 name_taken |
分叉的名称已被另一个云盘或存储卷使用。 |
409 not_on_one_machine |
在与云盘不同区域的沙箱中只读挂载,或新沙箱的区域与它的云盘不同。 |
409 too_many_drives |
沙箱已经有 8 个云盘和存储卷。 |
503 unavailable |
现在无法生成版本:在十秒的暂停时间内没能读完沙箱的写入,或者对象存储中还没有云盘的最新状态。稍后再试。 |
503 drive_in_store |
此刻没有机器保存这个云盘:它正在搬往另一台机器。稍后再试。 |
503 node_unavailable |
无法访问保存这个云盘的机器。 |
事件
drive.snapshot.created 和 drive.snapshot.deleted 会出现在项目的事件和 Webhook 中,带有 snapshot_id、drive_id、name 和 consistency。因账户未付款而被删除的快照也会发出 drive.snapshot.deleted。只读挂载及其卸载不会发出 drive.attached 或 drive.detached。
尚未支持
- 方块的版本只能以读写方式挂载云盘:带
mode: "snapshot"的版本会被拒绝 (400invalid_field)。只读挂载只供沙箱使用。 - 有只读挂载的沙箱不能迁到另一台机器。
- 同一台机器上挂载同一个快照的多个沙箱,各自保存它的数据块:它们之间不共享。
- 从沙箱快照创建的沙箱所得到的分叉,在创建时不检查套餐的云盘限制;从那以后才开始计入。
- 快照只能在创建它的区域中以只读方式挂载(云盘本身可以在其他区域挂载:跨区域)。已删除云盘的快照,平台不知道它属于哪个区域:它挂载在沙箱创建的地方,向其他区域对象存储复制数据的机器会拒绝它。