thundersnap v0.01:一个可以撤销所有操作的按钮
摘要
Thundersnap 是 Tailscale 开发的一个实验性分布式文件系统,它利用 btrfs 快照和 Tailscale 网络来提供类似撤销的功能,并在计算机之间高效复制。目前它只是一个实验品,尚未达到生产就绪状态。
查看缓存全文
缓存时间: 2026/07/04 18:44
tailscale/thundersnap 源代码:https://github.com/tailscale/thundersnap
THUNDERSNAP
Thundersnap 是一个分布式文件系统设计中的新旧想法集合。它的命名源自这个早期版本可能失败的两种主要方式:惊群效应(其网格复制协议)和灭霸响指(很可能导致大约一半的数据丢失)。总之,现在不要在生产环境中使用它!它只是一个玩具。但作为玩具,它给了我们发挥创造力的机会。
想象一下,如果我们去掉生产级分布式系统的所有要求:不必无限扩展,不必有完全可预测的性能,不必严格将持久化存储与运行时分离,不必在执行前构建一个幂等的容器。你可以做有趣的事情,而不是谨慎的事情。
如果你的整个电脑有一个“撤销”按钮会怎样?如果你在一台电脑上运行着完整的 Debian 系统和容器,然后用几条命令就能把它迁移到另一台电脑上会怎样?如果你有一个类似 Nix 的、内容可寻址的操作系统,但不需要 Nix,因为任何内容都可以被内容寻址?这就是我们追求的目标。它可能没用,但很有趣。
快速开始
要开始使用 Thundersnap,安装其中一个发行版包或从源代码构建。(TODO:将发行版包放到某个地方。)
注意:你需要一个基于 btrfs 的根文件系统,或者给 thundersnapd 提供一个挂载了 btrfs 的 --data-dir 参数。也许有一天我们不再依赖 btrfs,但今天依然依赖它。
然后你需要通过登录 Tailscale 来激活。Thundersnap 从根本上需要 Tailscale 才能工作,这样它的认证和网格复制功能才是安全的。你可以使用 Headscale(开源控制服务器),或者使用 Tailscale 慷慨的免费计划(https://tailscale.com/blog/free-plan)。
总之,一旦 thundersnapd 在后台运行,使用 thundersnapd --activate 并打开链接登录。完成后,你可以使用 SSH 访问你的第一个空容器:ssh root@@thundersnap(是的,双 @ 符号表示容器名为空,给你一个默认的空空间来玩耍)。
持久化快照
Thundersnap 系统的主要属性是持久性——数据库“ACID”术语中的 D。Thundersnap 非常努力地使你的数据持久化。它并不特别追求原子性、一致性或隔离性。在这个版本中,它可能也不是超级持久,但这不是架构的限制。
Thundersnap 的基本论点是,现代分布式存储需要依赖快照而不是实时同步。没有其他东西能给你有用的多用户语义(你其实不想看到其他用户未完成的工作!)。而且,因为真正的全球分布式系统(包括你笔记本电脑或手机上的虚拟机)存在延迟和可靠性问题,始终与所有副本保持连接本身也是一种幻觉。
简而言之,Thundersnap 对文件系统的假设类似于 git 对源代码树的假设。这不是巧合,因为它的结构受到我十多年前的 bup 项目(https://apenwarr.ca/log/20100104)的启发,而 bup 又受 git 启发。
你可以在一次性或低质量的计算机上运行 Thundersnap。与其试图让这些计算机(或它们的磁盘)更高质量或更不易丢弃,我们让创建内容可寻址的快照变得容易,然后有效地将这些快照复制到其他计算机。如果你在足够多的地方有足够多的计算机,你可能就不会丢失数据。
Thundersnap 目前使用 btrfs 来制作瞬间快照,但不使用 btrfs 复制,因为那不使用内容可寻址格式,因此有长期的架构限制。因此,Thundersnap 的实现目前依赖于 btrfs,但它的架构和协议并不依赖。也许将来会有更好的快照文件系统,我们可以透明地切换过去。
创建快照使用 ts snap。
轻量级执行帧
Thundersnap 默认使用 Linux 命名空间(“容器”)而不是完整虚拟机。其理念是,现代云架构建议将你做的每件事都变成自己独立的微型 VM。虽然这非常安全,但也非常慢,而且不知怎的我们已经习惯了。在 Unix 中,你可以在毫秒内启动和停止一个小程序。Thundersnap 的目标是启动容器时达到相同的速度(虽然目前还没那么快,但已经非常快了)。而且这些容器比 Unix 进程隔离得更好。它并不完美,但速度快。
💡 注意:还有部分成型的基于 VM 的隔离支持;这个想法是,你可以将一个或多个容器放入一个微 VM 中,它们仍然共享同一个底层 btrfs 文件系统。VM 添加了一个抽象层,但仍然非常快。这方面还有更多工作要做,但你现在就可以尝试。
一个执行帧实际上由三个独立的快照组成:
- root (
/):操作系统(你可以从 Docker 容器中提取一个来开始,如果你喜欢的话) /home:包含你个人偏好和工具的 /home 目录/work:存放你正在工作的项目的 /work 目录
你可能会惊讶 /home 和 /work 是分开的。我们这样做是因为你可能想独立地替换这三个部分中的每一个。例如,如果你在 Debian 容器中工作,并想在你的程序在 Red Hat 构建环境中测试,你会替换 root。如果你想把你的玩具给朋友继续开发,他们可以保留 root 和 /work,但使用他们自己的 /home,这样他们所有的个人点文件都完好无损。
要创建一个新的执行帧,使用 ts frame <root-snap>:<home-snap>:<work-snap>。这些字符串中的任何一个都可以为空,表示“保留我已有的那个”。或者它们可以是 nil,表示该组件最初为空。
你可以用 ts go 跳入另一个帧。例如,ts go nil:nil:nil 创建一个新的空帧并在其中启动一个交互式 shell;退出该 shell 会让你回到父帧。ts go redhat:: 创建一个新帧,它是当前帧的完美副本,但根文件系统是 redhat,然后跳进去。
ts go 的一个有点奇怪(目前)的变体是 ts undo。它从你上一个快照创建一个新帧,然后进入该帧,使你回到过去。然后你可以再次 ts undo 回到更早的快照,以此类推。这的语义并不是我想要的(难道不应该替换当前帧或类似操作吗?),但它是一个很好的演示。
引用(Refs):命名帧
帧通常以 uuid 命名,因为我们创建了很多帧,而且它们通常是用于一次性临时目的。每次你想做一个实验,你都可以从当前环境的副本中启动一个帧,做实验,然后扔掉它。这类似于 git 中的“提交”,每个都有自己的乱码名称。(快照在 git 中类似于“树”。)
引用(Refs)也类似于 git 引用(即分支和标签)。你可以将 ref 指向任何你想要的帧 uuid。一旦这样做,你就可以通过 ref 来引用那个确切的帧。多个 ref 可以指向同一个 uuid,但每个 ref 只指向一个 uuid。你可以使用 ts ref 命令或在使用 ts frame 时的 --ref 标志来操作 ref。
Refs 还关联着私有数据,这些数据不会被快照。这旨在用于秘密身份和密钥文件,这些文件应该只存在于一个帧中。例如,Tailscale 的 tsnet(https://tailscale.com/docs/features/tsnet)库维护着唯一标识一个节点的状态信息。给定 ref 的状态位于该 ref 当前指向的帧内的 /id/<refname>/ 目录中。如果你将 ref 移动到另一个帧,该目录也会随之移动(并且旧帧内的所有进程都会被终止,这样就不会有残留的密钥材料在 RAM 中浮动)。
这让你可以实现蓝绿部署,例如:
ts frame --ref blue <root>:<home>:<work>ts go blueapp --state-dir=/id/blue/ --login# 执行 Tailscale 登录流程- …让应用正常工作…
ts frame --ref=green ::# 复制我的帧ts go green# 复制我的帧app --state-dir=/id/green --login# 执行 Tailscale 登录流程- …修改应用并在 green 模式下测试…
ts ref move blue $(ts frame)
总之这部分测试得不多,但你明白了。
轻量级持久化应用
说到应用,有一种方式可以在你本人没有连接到帧时自动运行它们,这很好。我们没有在每个 VM 或容器内运行 /sbin/init,而是只提供一个文件系统,看起来像你想要的,以及一个简单的 ts autorun 命令,让你确保一个给定程序始终在一个容器内运行。
想法是你可以 SSH 进入容器,实时编辑程序,杀死它,然后它会重启。你这样做会感到内疚。这就像 1990 年代在生产服务器上编辑 PHP 网站。但请记住,Thundersnap 不是为生产环境准备的,它是为玩具准备的。你的玩具停机会有什么大不了的吗?如果你搞砸了,ts snap 会根据你的需要经常为旧版本创建快照。
将 Docker 容器转换为快照
众所周知,Docker 容器具有可以更新的“名称”。所以当你下载一个特定的容器时,你可能不完全确定它是否和你昨天下载的那个一样。如果你想拉取最新的安全修复,这很“棒”,但这也意味着你拉入了任意的其他更改,这些更改可能更好或更坏,但至少会让人意外。
Thundersnap 的工作方式不同。如果你使用例如 ts download-docker debian:latest,它会下载名为 debian:latest 的最新 Docker 容器,但随后它会打印出它所创建快照的内容哈希;每次你使用那个内容哈希,你都会得到完全预期的内容。
例如,ts frame --ref=deb (ts download-docker debian:latest):: 会给你一个名为 deb 的新帧,其中包含 debian:latest 操作系统以及你当前的 home 和 work 目录树。然后你可以用 ts go deb 进入它,或者退出你的 SSH 会话并重新进入:ssh root@deb@thundersnap。
网格复制
这部分仍然有 bug,但核心框架已经存在。要启用网格模式,你需要在启动 thundersnapd 时传递 --mesh 参数。启用网格模式后,thundersnapd 会定期 ping 你 tailnet 上所有具有相同身份(默认是你的用户名)的机器。这有点 hacky,我们应该修复它,但这是一个开始的好方法。
所以如果你有多台电脑,并在每台上安装了带 --mesh 的 thundersnapd,它们会互相发现。如果你愿意,可以通过访问 http://thundersnap:7575/ 查看它们的网格状态和对等节点列表。(将 “thundersnap” 替换为你分配的主机名。)
一旦你有对等节点,你就可以复制快照。目前唯一的相关命令是 ts download-snap <snapid> —— 只要你知道在一台机器上通过 ts snap 生成的 snapid,你就可以将它下载到另一台机器,然后从中构建帧。
这为未来的自动化留下了很大的空间。例如,我想在本地机器上每次运行 ts snap 时自动将快照复制到另一台机器,自动剪除旧快照,实现帧的一键迁移等等。你可以将这些全部构建为基于 Thundersnap 现有原语的工具。
Thundersnap 与 Tailscale 的关系是什么?
Tailscale 拥有版权,但目前这只是我的个人项目(Avery Pennarun,apenwarr)。它目前并未在 Tailscale 内部用于任何实际用途,也不应该——它是我用于自己一些东西的玩具。Tailscale 还有其他四五个用于启动 VM 或容器的工具,其中一些是开源的,一些很快会开源。其中一些用于生产环境,有些使用频率很高。Thundersnap 不是。
我认为 Thundersnap 是最好玩的一个……但我有偏见。Tailscale 现在是一家“真正的公司”,官方 Tailscale 产品需要有一定质量水平,以便我们能为那些将其用于实际工作的人提供实际支持。但是,为了在家庭实验室中获得真正的乐趣,有时我们必须把边界推得更远。
Thundersnap 将许多不寻常的假设结合到一个包中。它需要一段时间才能成熟。但我很想听听你的想法!我的邮箱是 [email protected],或者在 Bluesky 上叫 @apenwarr.ca,我听说你也可以通过一个叫 bridgy 的工具从 Fediverse 上联系到我。
相似文章
lazy-tmux:延迟恢复 tmux 会话,保留滚动缓冲区
lazy-tmux 是一个 CLI 工具,它能对 tmux 会话(包括正在运行的进程和滚动缓冲区)进行快照,并在从 TUI 选择器中选中时延迟恢复它们。
Rift:Git Worktrees 的更优替代方案
Rift 是一个命令行工具,提供比 Git worktrees 更好的替代方案,通过写时复制快照在 Linux 的 btrfs 和 macOS 的 APFS 上实现快速创建工作区。
Frood:一个基于Alpine Initramfs的NAS
描述了一种完全从Alpine Linux initramfs运行NAS的方法,支持干净启动、A/B部署、声明式git跟踪配置,并相比Alpine的无盘模式降低了复杂性。
GhostSnap
GhostSnap 是一款工具,可以让您拍摄多个截图并自动压缩,以供人工智能使用。
TailMux
TailMux 允许用户同时连接多个 Tailscale 网络,无需切换或使用虚拟机。