更快地部署 Rust 代码
原文由 Matthias Endler 于 发布,订阅该博客
在技术这条路上,我已经走了很远,从摆弄物理服务器到探索云计算的世界。起初,一切看起来再简单不过——启动一台服务器,部署一个容器,就大功告成了。但随着不断深入,我才意识到,基础设施的所谓“简单”远没有看起来那么容易。
云服务商提供了琳琅满目的工具,而每一项都有各自的学习成本:
- Google Cloud / AWS
- Kubernetes
- Helm
- Docker
- Terraform
- GitHub Actions
如果你喜欢尝鲜,或许还会尝试 EKS 或 GKE 这样的托管 Kubernetes 服务。只需点几下鼠标,应用就能跑起来,听上去很诱人。但当你真正开始处理监控、日志、安全、扩容等一大堆事情时,现实就会给你当头一棒。
很快,你会发现自己不知不觉成了 DevOps 团队的负责人,而不是专注于产品本身。你不得不雇更多人来维护基础设施,而与此同时,竞争对手却在不断发布新功能、扩大用户群。
我的困扰
云曾承诺让基础设施变得简单,但各种工具和服务却让人眼花缭乱。即使你不会全部用到,也必须知道它们的存在,并掌握基本用法。结果呢?你对产品的专注度被不断稀释。
我并不排斥跟基础设施打交道,但我更热爱交付产品。遗憾的是,许多公司在基础设施上浪费了大量宝贵的时间和金钱,还在重复同样的错误。
如果有一种方法能彻底摆脱对基础设施的顾虑,会怎样?
无服务器的诱惑
无服务器架构看似前景美好——没有服务器,没有容器,只有纯粹的业务逻辑。然而,它也并非没有挑战:
- 冷启动耗时
- Lambda 体积限制
- 内存问题
- 长时间运行的任务
- 调试复杂
- 缺乏本地测试
无服务器在某些场景下确实有优势,但对于较大的应用,你可能仍然需要一些服务器。
平台即服务(PaaS)
Heroku 和 Netlify 这类平台提供了第三种选择——为你打理好一切基础设施的托管服务。你不再需要操心基础设施,只需推送代码即可完成部署。这些方案的亮点在于,它们与特定的编程语言生态深度集成。
我一直在寻找一个专为 Rust 开发者量身打造、能提供顶级开发体验的平台。我希望它能与 Rust 生态深度集成(比如 serde、sqlx、axum 等)。
前段时间,为了让自己的 Rust 开发流程更顺畅一些,我偶然发现了 Shuttle。它是一个能自然融入现有 Rust 生态的工具,让你可以像往常一样使用 cargo,同时帮你省去了许多繁重的基础设施工作。当然,它不是能解决一切问题的灵丹妙药,但我欣赏的正是它的简洁。你不会被抛到一个全新、学习曲线陡峭的环境中。相反,你只需专注于 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 启发,用 Deno 做了一个类似的项目:由 Mahesh Sundaram 开发的 kiwi。这是个很棒的成果。
为电子阅读器打造的阅读模式
我对 Firefox 阅读视图的喜爱,催生了阅读模式代理,它提供一种极简、无 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 本身也是开源的,所以我甚至可以自己搭建一个自托管实例——尽管我并不想这么做。
自建基础设施的真正成本
基础设施表面上看起来简单,但维护起来却涉及各种复杂性和成本。更新、部署、可用性保障——足以让人应接不暇。在这些任务上花费的每一小时,都意味着直接成本和机会成本。
基础设施就像一座迷宫,而 Shuttle 对于使用 Rust 的开发者来说似乎是个不错的选择。我正考虑近期在 Shuttle 上尝试一个更大的项目,毕竟现在我对 Shuttle 的能力边界已经有了比较清晰的认识。如果你也想试试,最好先查看一下它的定价,确保符合你的需求。
一定要留意基础设施的真正成本!
正如我之前提到的,这不仅仅是服务器的费用,还有更多。最大的开销很可能是维护和调试基础设施所需的人力,而这非常昂贵。如果我采用基础设施即代码的方式,光是搭建基础设施就要花上很多小时,后续维护更是要投入大量时间,以如今的薪资水平来看,成本相当高。
即便只是一个业余项目,对我来说也不值得如此折腾。我宁愿把精力放在功能开发上,而不是支撑这一切运行的底层代码上。
随机一篇博客
评论
登录后参与讨论