Deploy Rust Code Faster

Matthias Endler

Rustコードをより速くデプロイする

原文は Matthias Endler により に公開されました。 このブログを購読する

私のテックジャーニーは、ベアメタルサーバーを扱うところから始まり、クラウドコンピューティングの世界を探求するまで、長い道のりでした。当初はとてもシンプルに思えました。サーバーを立ち上げ、コンテナをデプロイすれば完了、と。しかしいざ深く掘り下げてみると、インフラの手軽さは見かけほど単純ではないことに気づいたのです。

クラウドプロバイダーは多数のツールを提供しており、それぞれに学習コストがかかります。

  • Google Cloud / AWS
  • Kubernetes
  • Helm
  • Docker
  • Terraform
  • GitHub Actions

冒険心があれば、EKSやGKEのようなマネージドKubernetesサービスに手を出すこともあるでしょう。数クリックでアプリケーションが動き出すのは魅力的です。しかし、モニタリング、ロギング、セキュリティ、スケーリングなどを同時にこなさなければならなくなったとき、現実に直面することになります。

気づけばプロダクトに集中するどころか、意図せずDevOpsチームを率いることになります。インフラを管理するために人員を増やしている間に、競合は次々と機能をリリースし、ユーザーベースを拡大していくのです。

フラストレーション

クラウドはインフラを簡単にすると約束してくれましたが、ツールやサービスの数は圧倒されるほどです。すべてを使うわけではなくても、その存在を把握し、基礎を学ばなければなりません。結果として、プロダクトへの集中力は削がれていきます。

私はインフラを扱うこと自体は好きですが、プロダクトを届けることも同じくらい好きです。残念ながら、多くの企業が同じ過ちを繰り返し、貴重な時間とお金をインフラに費やしています。

もし、インフラの悩みを丸ごと消し去る方法があったらどうでしょう。

サーバーレスの魅力

サーバーレスアーキテクチャは有望に見えます。サーバーもコンテナもなく、あるのは純粋なビジネスロジックだけ。しかし、課題がないわけではありません。

  • コールドスタートの遅延
  • Lambdaのサイズ制限
  • メモリの問題
  • 長時間実行されるプロセス
  • デバッグの複雑さ
  • ローカルテストの困難さ

サーバーレスは特定のユースケースでは優れていますが、大規模なアプリケーションでは、やはりある程度サーバーが必要になることもあります。

Platform-As-A-Service (PaaS)

HerokuやNetlifyのようなプラットフォームは、第三の選択肢をもたらしました。すべてのインフラを代行してくれるマネージドサービスです。もうインフラを気にする必要はありません。コードをプッシュするだけでデプロイが完了します。こうしたソリューションの素晴らしい点は、特定のプログラミング言語のエコシステムと深く統合されていることです。

私はRust開発者向けに作られ、一流の開発者体験を提供してくれるプラットフォームを探していました。Rustエコシステム(serde、sqlx、axumなど)との深い統合を求めていたのです。

少し前、Rustの開発ワークフローをもう少しスムーズにする方法を探していたときにShuttleに出会いました。これは既存のRustエコシステムにすっと馴染むようなツールで、いつも通りにcargoを使いながら、インフラ周りの面倒な作業を取り除いてくれます。もちろん、すべての問題を解決する魔法の杖というわけではありませんが、Shuttleで気に入っているのはそのシンプルさです。急な学習曲線を伴うまったく新しい環境に放り込まれることはありません。あくまで自分のRustコードに集中すればよく、Shuttleがバックグラウンドでサーバー側の複雑さを管理してくれるのです。つまり、本質的には自分が慣れ親しんだやり方をそのままに、デプロイやサーバー管理を少しだけ楽にしてくれるものなのです。コーディングの方法を根本から変えるような革命的なものではなく、ときに頭を悩ませるバックグラウンドのプロセス管理を、少しだけ肩代わりしてくれるような存在です。

これまでのShuttle体験

これまでに、Shuttleで2つの小さな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 by Mahesh Sundaramで、Denoで書かれています。これは本当に素晴らしい成果です。

E-リーダー用のリーダーモード

Firefoxのリーダービューへの愛着が、ミニマルでJavaScriptを使わないウェブ読書体験、特にE-リーダー向けに最適化されたReader Mode Proxyを作るきっかけになりました。目的は、情報過多なウェブサイトを、集中を妨げない読みやすい形式に変換することでした。

このプロジェクトは、問題を解決するシンプルなアプリが好きという私の個人的な好みを色濃く反映しています。ほんの少しアノテーションを加えるだけで、私のコードは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のマクロはボイラープレートを取り除いてくれるので、追加した2つのアノテーションを削除すれば元のコードに戻せます。Shuttleのコード自体もオープンソースなので、セルフホストのインスタンスを立てることさえ可能です。もっとも、私はそうしたいとは思いませんが。

自前インフラの本当のコスト

インフラは一見簡単に見えるかもしれませんが、その維持にはさまざまな複雑さとコストが伴います。アップデート、デプロイ、可用性の確保など、負担は大きくなりがちです。これらの作業に費やす1時間1時間には、直接的なコストと機会費用の両方がかかります。

インフラは迷路のようなものであり、Rustで開発する人にとってShuttleはうまくフィットするように思えます。Shuttleに何ができて何ができないか、ある程度理解できたので、近いうちにもっと大きなプロジェクトをShuttleで試してみようと考えています。試してみようと思っているなら、ニーズに合っているか確認するために料金プランをチェックしておくとよいでしょう。

インフラの本当のコストに目を向けましょう!

前にも述べたように、コストはサーバー代だけではありません。それ以上のものがかかります。最大の要因は、インフラの保守やデバッグにかかる人的労働でしょうが、これは高くつきます。Infrastructure as Codeを使うとしても、インフラのセットアップに何時間も費やし、さらにその維持にも多くの時間を要することになり、今の給与水準を考えれば高額になりかねません。

たとえ趣味のプロジェクトであっても、私にはその手間をかける価値はないと感じます。すべてを動かすためのコードよりも、むしろ機能の開発に取り組みたいのです。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント