更快部署 Rust 程式碼
原文由 Matthias Endler 于 發布,訂閱此部落格
在技術這條路上,我已經走了很長一段,從處理裸機伺服器到探索雲端運算的世界。一開始看起來很單純——開一台伺服器、部署一個容器,就搞定了。但隨著深入研究,我才發現基礎設施的簡單只是表象。
雲端服務商提供了五花八門的工具,每一種都有自己的學習曲線:
- 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,同時幫你扛掉不少基礎設施的繁重工作。當然,它不是能解決所有問題的萬靈丹,但我欣賞的是它的簡單。你不會被丟進一個全新的、學習曲線陡峭的環境。相反地,你只需要專注在 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 的能與不能。如果你也考慮試試看,最好先查看一下他們的定價,確認是否符合你的需求。
務必留意基礎設施的真正成本!
就像我之前提過的,這不只是伺服器費用,還有更多。最大的開銷很可能是維護與除錯基礎設施所需的人力,而這是非常昂貴的。如果我採用 infrastructure as code,我得花上許多小時來建置基礎設施,還要花更多時間去維護,而以現在的薪資水準來看,這會是一筆不小的開銷。
就算只是為了興趣專案,對我來說也不值得這麼大費周章。我寧願把時間花在開發功能上,而不是支撐這一切運行的程式碼上。
隨機一篇部落格
留言
登入後參與討論