更快地部署 Rust 代码
在技术之路上,我走过了一段漫长的旅程,从打理裸机服务器到探索云计算的世界。起初,一切似乎都很简单——启动一台服务器,部署一个容器,就大功告成了。但随着我深入钻研,我发现基础设施的“易用”并不像表面看起来那么简单。
云服务商提供了琳琅满目的工具,每一种都有自己的学习曲线:
- Google Cloud / AWS
- Kubernetes
- Helm
- Docker
- Terraform
- GitHub Actions
如果你够爱折腾,甚至可能一头扎进 EKS 或 GKE 这类托管 Kubernetes 服务。它们确实诱人——只需几次点击,你的应用就能跑起来。但当你开始同时应付监控、日志、安全、扩缩容等等问题时,现实就会给你当头一棒。
很快,你会发现自己不知不觉间在领导一个 DevOps 团队,而不是专注于自己的产品。你雇了更多员工来管理基础设施,而你的竞争对手却在不断发布新功能、扩大用户群。
我的挫败感
云曾承诺让基础设施变得简单,但工具和服务的数量之多令人应接不暇。即使你不全部使用它们,也必须了解它们的存在并掌握基本知识。结果呢?你对产品的专注度被削弱了。
我很乐意与基础设施打交道,但我同样热爱交付产品。遗憾的是,许多公司都在基础设施上浪费宝贵的时间和金钱,重复着同样的错误。
如果有一种方法能彻底消除对基础设施的担忧呢?
Serverless 的诱惑
Serverless 架构看似前景光明——没有服务器,没有容器,只有纯粹的业务逻辑。然而它并非没有挑战:
- 冷启动时间
- Lambda 大小限制
- 内存问题
- 长时间运行的进程
- 调试复杂
- 缺乏本地测试
Serverless 在某些场景下确有优势,但对于较大的应用,你可能仍然需要一些服务器。
平台即服务(PaaS)
Heroku 和 Netlify 等平台引入了第三种选择——由托管服务替你处理所有基础设施。再也不用操心基础设施;你只需推送代码,它就会自动部署。这类解决方案的妙处在于它们与特定编程语言生态系统的深度集成。
我当时在寻找一个专为 Rust 开发者打造的平台,希望它能提供一流的开发者体验。我想要与 Rust 生态(serde、sqlx、axum……)的深度集成。
不久前,我在想办法让 Rust 开发流程更顺畅一些时,发现了 Shuttle。这个工具几乎可以无缝融入现有的 Rust 生态,让你照常使用 cargo,只是把一部分繁重的基础设施工作从你肩上卸下。当然,它不是解决所有问题的魔法棒,但我欣赏 Shuttle 的地方在于它的简单。你不会被扔进一个学习曲线陡峭的全新环境。相反,你继续专注于 Rust 代码,Shuttle 则在后台默默运转,帮你处理一些服务端的复杂性。所以本质上,这就是坚持你所熟悉的东西,同时在部署和服务器管理方面让生活轻松一点。它并不是要彻底改变你的编码方式,而是对那些有时让人头疼的后台事务的管理方式做一点微妙的调整。
我使用 Shuttle 的体验
到目前为止,我用 Shuttle 构建了两个较小的 Rust 服务:Zerocal 和 Readable。
Shuttle 拿到你的 Rust 代码后,只需极少的注解就能部署到云端。考虑到资源供给和部署通常是构建服务中最痛苦的部分,这样的开发者体验已经相当接近理想状态。
相反,只需添加几行代码即可。你自己看看吧。样板代码就这样消失了,剩下的全是业务逻辑。
Zerocal —— 无状态的日历魔法
Zerocal 是我在 Shuttle 上部署的第一个项目。它的原理非常简单却颇具创意:把日历数据直接编码进 URL。这意味着创建一个事件就像这样简单:
curl https://zerocal.shuttleapp.rs?start=2023-11-04+20:00&duration=3h&title=Birthday&description=paaarty它会返回一个 iCal 文件,你可以把它添加到日历中。下面是在浏览器中创建事件的方式:

当时 Shuttle 还在修复一些问题、不时调整 API,我就尝试在这个平台上构建了这个项目。即便有这些小问题,体验依然不错。短短几分钟内,我的应用就已经上线运行了。
以下是启动该服务的代码,包括 axum 路由:
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {
// just normal axum routes
let router = Router::new()
.route("/", get(calendar))
.route("/", post(calendar));
Ok(router.into())
}我自己已经不再需要 Zerocal 了,所以希望有人愿意接手它。我认为它在 GitHub 或 Discord 这类平台上分享邀请会非常有用。如果你想进一步了解 Zerocal,可以阅读这篇详细解析。
我还想提一下,有人受 Zerocal 启发构建了一个类似的项目:kiwi,作者是 Mahesh Sundaram(马赫什·孙达拉姆),用 Deno 编写。这是一个非常酷的结果。
为我的电子书阅读器打造的阅读模式
我对 Firefox 阅读视图的喜爱催生了一个 Reader Mode Proxy(阅读模式代理)项目,旨在提供一种极简、无 JavaScript 的网页阅读体验,尤其适合电子书阅读器。其初衷是把冗长繁琐的网站转换成更易消化的格式,实现无干扰阅读。
这个项目深深体现了我的个人偏好,因为我喜欢那种解决实际问题的简单应用。只需少量注解,我的代码就顺利适配了 Shuttle 的环境。起初我有自己的本地模式,可以在本机上运行应用进行测试,但后来发现没必要维护它,因为 Shuttle 自带的本地模式效果一样好。
在开发这个应用的过程中也遇到了一些波折。服务停机需要对代码做一些改造。不过 Shuttle 的演进简化了我的部分流程,尤其是它引入了原生静态文件处理之后。
以前是这样的:
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {
let router = Router::new()
// Previously, I needed to manually serve static files
.route(
"/static/Crimson.woff2",
get(|| async {
static_content(
include_bytes!("../static/fonts/Crimson.woff2",),
HeaderValue::from_static("text/woff2"),
)
}),
)
.route(
"/static/JetBrainsMono.woff2",
get(|| async {
static_content(
include_bytes!("../static/fonts/JetBrainsMono.woff2",),
HeaderValue::from_static("font/woff2"),
)
}),
)
.fallback(readable);
Ok(router.into())
}现在只需:
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {
let router = Router::new()
.nest_service("/static", ServeDir::new(PathBuf::from("static")))
.fallback(readable);
Ok(router.into())
}想深入了解这个项目的细节,这里有更全面的介绍。
掌控力与安全感
起初,我曾担心为了基础设施而在代码中添加注解会导致厂商锁定。我希望对自己的项目保持完全掌控。想迁移走?Shuttle 的宏帮你省去了样板代码,所以我只需删掉添加的那两个注解,就能还原出原始代码。Shuttle 的代码也是开源的,因此我甚至可以搭建自托管实例——尽管我并不打算这么做。
DIY 基础设施的真正代价
基础设施表面上看似容易,但维护它涉及各种复杂性和成本。更新、部署、可用性——这些可能让人不堪重负。花在这些任务上的每一小时都既有直接成本,也有机会成本。
基础设施可能是一座迷宫,而对于使用 Rust 的人来说,Shuttle 似乎是个不错的选择。既然我已经比较清楚 Shuttle 能做什么、不能做什么,我打算很快在一个更大的项目上试用它。如果你也在考虑尝试一下,明智的做法是先查看他们的定价,确保它符合你的需求。
请留意基础设施的真实成本!
正如我之前所说,这不仅仅是服务器费用,还远不止于此。最大的因素很可能是用于维护和调试基础设施的人力成本,而这非常昂贵。如果我采用 Infrastructure as Code(基础设施即代码),我将花费大量时间搭建基础设施,还要花更多时间去维护它——以如今的薪资水平来看,这可能相当昂贵。
哪怕只是一个业余爱好项目,对我来说也不值得费这份劲。我宁愿把精力放在功能开发上,而不是支撑这一切运行的代码上。
随机一篇博客