Deploy Rust Code Faster

Matthias Endler

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

技術者として歩んできた道のりは長く、ベアメタルサーバーを扱うところから、クラウドコンピューティングの世界を探るところまで経験してきました。最初はとても簡単に思えたものです。サーバーを立ち上げ、コンテナをデプロイすれば、それで終わり。ですが、深く掘り下げていくうちに、インフラを手軽に扱えるということは、見た目ほど単純ではないと気づきました。

クラウドプロバイダーは、学習曲線がそれぞれ異なるさまざまなツールを提供しています。

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

冒険してみたいなら、EKSやGKEのようなマネージドKubernetesサービスに踏み込むこともあるでしょう。数回クリックするだけで、アプリケーションをすぐに動かせるのは魅力的です。ですが、監視、ロギング、セキュリティ、スケーリングなどをやりくりし始めると、現実が見えてきます。

気づけば、プロダクトに集中する代わりに、意図せずDevOpsチームを率いる立場になっています。インフラの管理に人を増やしている間にも、競合他社は機能をリリースし、ユーザー基盤を拡大していきます。

私の不満

クラウドはインフラを簡単にしてくれるはずでした。ところが、ツールやサービスの多さに圧倒されることがあります。すべてを使わないとしても、それらが存在することを把握し、基本を学ばなければなりません。その結果、プロダクトへの集中力が落ちてしまいます。

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

インフラに関する懸念を、すべて取り除く方法があったらどうでしょうか。

サーバーレスの魅力

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

  • コールドスタートの時間
  • Lambdaのサイズ制限
  • メモリの問題
  • 長時間実行される処理
  • デバッグの複雑さ
  • ローカルテストの不足

サーバーレスには適したユースケースもありますが、大規模なアプリケーションでは、それでも何らかのサーバーが必要になるかもしれません。

Platform-As-A-Service(PaaS)

HerokuやNetlifyのようなプラットフォームは、3つ目の選択肢を示しました。インフラをすべて管理してくれるマネージドサービスです。もうインフラについて心配する必要はありません。コードをプッシュするだけでデプロイされます。こうしたソリューションのすばらしいところは、特定のプログラミング言語エコシステムと深く統合されていることです。

私は、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に触発されて、別の人が似たプロジェクトを作ったことにも触れておきたいと思います。Mahesh SundaramがDenoで書いたkiwiです。これは本当にすばらしい成果です。

電子書籍リーダー向けのリーダーモード

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のマクロはボイラープレートを取り除いてくれるので、追加した2つのアノテーションを削除するだけで、元のコードに戻せます。Shuttleのコードはオープンソースでもあるため、自分でホストするインスタンスを構築することさえできます。私はそうしたいとは思いませんが。

DIYインフラの本当のコスト

インフラは表面上は簡単に見えても、維持するにはさまざまな複雑さとコストが伴います。アップデート、デプロイ、可用性。考えることは尽きません。こうした作業に費やす1時間には、直接的なコストだけでなく、機会費用も発生します。

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

インフラにかかる本当のコストを忘れないでください。

以前にも書いたように、コストはサーバー代だけではありません。もっと多くのものが関わります。おそらく最大の要因は、インフラの保守やデバッグにかかる人件費です。これは高くつきます。Infrastructure as Codeを使うとしたら、インフラの構築に何時間も費やし、そのうえ維持にもさらに多くの時間を使うことになります。今日の給与水準を考えれば、それは高額になり得ます。

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

原文は Matthias Endler により に公開されました。

この記事は「gpt-5.6-terra」を使用して翻訳されました。