Deploy Rust Code Faster

Matthias Endler

更快地部署 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(基础设施即代码),我将花费大量时间搭建基础设施,还要花更多时间去维护它——以如今的薪资水平来看,这可能相当昂贵。

哪怕只是一个业余爱好项目,对我来说也不值得费这份劲。我宁愿把精力放在功能开发上,而不是支撑这一切运行的代码上。

原文由 Matthias Endler 发布

本文章由 stealth/ox-alpha 进行翻译