在 Fly.io 上使用 Rust 构建远程开发环境:从 Tokio 到 eBPF

发布时间:2026/6/10 15:02:01
在 Fly.io 上使用 Rust 构建远程开发环境:从 Tokio 到 eBPF 本文是对 Remote development with Rust on fly.io 的整理与翻译(内容结构概览)为什么要构建远程开发环境Fly.io 的底层逻辑Firecracker 微型虚拟机部署测试应用一个极简的 Rust Axum 服务深入虚拟机内部运行 Docker 与 Perf核心挑战如何让机器在闲置时自动休眠解决方案一编写基于 Tokio 与 io-uring 的 TCP 代理解决方案二使用 eBPF (Aya) 无感监听网络流量总结1. 为什么要构建远程开发环境在当今的开发场景下构建一个远程开发环境有诸多不可忽视的理由。首先是性能瓶颈。编译大型的 Rust 项目例如 Rust 编译器本身、rust-analyzer或是多个庞大的私有代码库需要消耗大量的 CPU 资源。与其耗费巨资购买顶配的桌面 CPU不如按需租用高性能的云端机器仅在执行重度任务时开启。其次是跨平台编译的痛点。随着 Apple M1/M2 等 arm64 架构芯片的普及开发者在本地进行开发却往往需要将应用部署到 Linux x86_64 环境。虽然可以通过虚拟机或 Docker 容器进行指令集模拟但这不仅会严重拖慢运行速度还会让笔记本电脑发热严重。此外团队协作也是一个重要因素。远程开发环境使得让分布在全球的开发者快速入职成为可能你无需为他们邮寄昂贵的硬件设备就能提供一个一致、开箱即用的标准环境。就作者个人而言原因非常纯粹炎炎夏日运行一台高功耗、散发大量热量的桌面台式机实在不够环保也会让室内温度急剧上升。通过远程连接到云端的开发环境可以彻底解决算力与物理散热之间的矛盾。2. Fly.io 的底层逻辑Firecracker 微型虚拟机对于云服务大家可能熟悉 Heroku 或者 Google Cloud Run它们主要依赖于常规的容器Docker技术。然而Fly.io 的运作方式略有不同。当你在 Fly.io 上部署代码时它并不是单纯地在 Docker 引擎中运行一个隔离容器而是将你的应用运行在一个Firecracker 微型虚拟机 (microVM)中。这意味着你拥有一个真正的虚拟机环境它具备独立的 Linux 内核没有传统容器环境在内核权限方面的诸多限制。3. 部署测试应用一个极简的 Rust Axum 服务为了演示我们先构建一个最基础的 HTTP 服务器。这里使用了tokio和axum框架useaxum::{response::IntoResponse,routing::get,Router,Server};#[tokio::main]asyncfnmain(){letappRouter::new().route(/,get(index));letaddr[::]:8080.parse().unwrap();println!(Listening on http://{addr});Server::bind(addr).serve(app.into_make_service()).await.unwrap();}asyncfnindex()-implIntoResponse{hello from axum\n}配合该应用我们可以编写一个使用多阶段构建Multi-stage build的 Dockerfile。为了加速 Rust 的编译作者在这里巧妙地利用了 Docker BuildKit 的缓存挂载特性# syntax docker/dockerfile:1.4 FROM rust:1.61.0-slim-bullseye AS builder WORKDIR /app COPY . . RUN --mounttypecache,target/app/target \ --mounttypecache,target/usr/local/cargo/registry \ --mounttypecache,target/usr/local/cargo/git \ --mounttypecache,target/usr/local/rustup \ set -eux; \ rustup install stable; \ cargo build --release; \ objcopy --compress-debug-sections target/release/hello-axum ./hello-axum FROM debian:11.3-slim # 安装基础网络排查工具 RUN apt update apt install --yes bind9-dnsutils iputils-ping iproute2 curl htop WORKDIR app COPY --frombuilder /app/hello-axum ./hello-axum CMD [./hello-axum]使用flyctl生成配置文件fly.toml并进行部署后这个 Rust 应用就会被打包为精简镜像并运行在 Fly.io 离你最近的边缘节点上。4. 深入虚拟机内部运行 Docker 与 Perf部署完成后通过执行fly ssh console命令我们可以直接通过原生 SSH 连入这个实例。连入后通过uname -a查看系统信息你会发现系统运行着一个真实的 Linux 内核文章示例中为 Linux 5.12而且我们的身份是真正的root。因为这是一个由 Firecracker 驱动的虚拟机不仅有独立的用户态还有完整的内核态支持。这意味着我们甚至可以在这个 Fly.io 的虚拟机内部再次安装并运行完整的 Docker 引擎通过向其挂载持久化存储卷Volume你完全可以将其当做一个全功能的 Linux 开发机来使用。更进一步得益于真实内核的存在你可以直接下载当前内核版本的源码编译并安装底层的性能分析工具perf。# 获取并编译安装 perf 的大致流程KERNEL_VERSION$(uname-r|sed-rs/(^[^-]).*/\1/|sed-rs/\.0//g)curl--location[https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-$](https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-$ https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-$){KERNEL_VERSION}.tar.xz|tar-xJmake-Ctools/ perf_installprefix/usr/这让你能在云端直接使用perf top去深入分析 Rust 程序的 CPU 周期消耗这在普通的受限容器托管平台上是绝对做不到的。5. 核心挑战如何让机器在闲置时自动休眠搭建一台高性能云端开发机的关键在于成本控制。Fly.io 的机器默认情况下会一直运行如果不手动执行关机命令就会产生持续的计费。一个理想的远程开发环境应该具备这样的特性当我连接上它并进行开发时它保持唤醒当没有活跃的连接或是网络流量处于长期静默状态后它能够自动关闭自己。为了解决这个自动化生命周期管理的问题作者提出了两种不同层面的 Rust 技术实践方案。6. 解决方案一编写基于 Tokio 与 io-uring 的 TCP 代理第一种思路非常直接我们不让开发者直接连接到虚拟机的标准端口而是自己写一个驻留的 TCP 代理应用监听外部请求。这个代理程序负责将流量无缝转发到后端开发服务端口。它的核心逻辑如下监听对外的公开端口并接收流量。在内存中维护一个原子性的“活跃连接数”计数器。每建立一个连接计数器加一连接断开计数器减一。后台运行一个异步轮询任务如果连接数归零并持续了设定的超时倒计时如几分钟程序就直接调用std::process::exit(0)退出。由于该代理进程被设为微型虚拟机的初始主进程Init 进程一旦主进程退出底层设施就会触发整个虚拟机的安全关闭动作。在具体代码层面为了追求极致的吞吐量并体验前沿技术作者引入了tokio-uring运行时这是一种建立在 Linux 新型异步 I/O 接口io-uring之上的高性能方案。由于tokio-uring采用单线程轮询模型我们可以使用成本极低的Rc引用计数来代替多线程环境下的Arc进行 TCP 连接流的所有权管理。代理任务在完成双向的数据拷贝Ingress 到 Egress 互相直通结束后再清理引用计数并触发全局活动连接数的删减。7. 解决方案二使用 eBPF (Aya) 无感监听网络流量虽然业务层的 TCP 代理方案能完美解决问题但它有一定的侵入性你需要劫持和转发端口流量实质上充当了一个中间人。有没有一种更底层的做法能够直接在操作系统层面“偷听”网络状态的改变而不去干扰和阻断原始的网络链路呢答案是eBPF (Extended Berkeley Packet Filter)。通过使用 Rust 社区强大的 Aya 框架我们可以将编译好的 BPF 字节码直接安全地注入到操作系统的网络栈中。这个纯净方案分为两部分eBPF 探针代码 (Kernel space)一个用#[no_std]约束编写的极简 Rust 程序。我们将其挂载在sock_ops钩子上。它的任务是拦截并洞察系统中所有的套接字行为。当检测到有新的 TCP 连接处于 Established 状态或被 Closed 关闭时它会将这些网络状态变化通过 BPF 的环形缓冲区Ring Buffer安全地传递给用户空间。用户态守护进程 (User space)一个常规的 Rust 守护进程负责将 eBPF 程序加载进内核并持续监听内核发来的事件。它同样维护着网络连接统计一旦发现所有的网络连接均已关闭并且闲置达到预设的超时时间直接下达指令让系统关机休眠。这种 eBPF 方案展现了在 Linux 内核层面实现无缝监控的强大能力。它不需要拦截和转发业务流量程序仅仅是作为系统网络底层的一个旁观者根据真实的 TCP 握手和挥手情况精准、低耗地控制开发机的生命周期。8. 总结这篇博客生动地展现了将现代云原生基础设施以 Fly.io 为代表的微型虚拟机与系统级编程语言Rust结合起来的巨大应用价值。对于饱受本地硬件性能受限、架构兼容性差以及散热噪音困扰的开发者来说构建一个完全属于自己的云端个人工作站已经不再是遥远的梦想。凭借 Rust 在异步网络编程和底层系统级交互领域的深厚积累不管是通过编写一个利用io-uring榨干性能的 TCP 代理还是运用前沿的 eBPF 技术在内核无感监控全局网络连接我们都能以最高效优雅的方式解决机器休眠的自动化控制与云端成本管理难题。