最近Rustの勉強がてらにwebアプリをサンプルで作っているんですが、デプロイ時の構成にここしばらく悩んでいました。
個人用のサンプルなのであまり労力やお金がかからないと嬉しいなー…、と思って探していたら、Webアプリのフレームワークとして利用しているActix-webのドキュメント内に「Shuttle」というプラットフォームへのデプロイ手順が書いてありました。
調べてたらバックエンドのアプリ開発に必要なものがある程度そろっていて、小さなプロジェクトであれば導入も楽そうだったので、試しがてら紹介してみようと思います。
Shuttleのいいところ
Rust利用のWebプロジェクトを、外部リソース含めて楽に開発、デプロイできる
公式で「Rustネイティブのクラウド開発プラットフォーム」と書いてあるように、下記のようなRustのwebフレームワークに数行変更を加えれば導入できるようになっています。
- Axum
- Actix-web
- Rocket
- Warp
- …
また、Webアプリを開発するときに発生するインフラやDB等外部リソースの構成を、マクロなどを利用して開発者が手間をかけることなく構築できるようにしています。
これを、ドキュメント内では「Infrastructure from Code」と呼んでいます。
無料でもある程度使える
筆者は無料プランで試してみてますが、プロジェクトの構築数は3つ、postgresかMongoDBの共用DBは1GBまで利用可能なので、個人で小規模のWebアプリを置いておくには十分なものが揃っている印象です。
やってみた
弊環境構成
- 言語・ライブラリ
- Rust 1.78.0
- Actix-web 4
- 開発環境
- Github Codespace
- CI/CD
- Github Actions
移行手順
shuttleにログイン
公式サイト右上の「Log in」から、Githubのアカウントを利用してログインができます。
ログイン後の画面でAPI_KEYが取得できるので、この後の手順のためにコピーしておきます。
Shuttleのインストール
下記のコマンドで、開発環境にShuttleをインストールします。
(他にも方法はありますが、詳細はこちら)
cargo install cargo-shuttle
インストールが終わったら、下記のコマンドでShuttleにログインします。
認証情報を聞かれると思うので、1つ前の手順で取得したAPI_KEYを入力します。
cargo shuttle login
正常終了すれば、ログイン完了です。
Cargo.tomlへの追記
元々入れていたActix-webに加えて、Shuttle関連のクレートを追加します。
[dependencies]
actix-web = "4"
+ shuttle-actix-web = "0.46.0"
+ shuttle-runtime = "0.46.0"
main.rs、lib.rsの修正
エントリーポイントのマクロと戻り値を、ドキュメントに従って修正します。
弊環境ではmain.rsとlib.rsにファイルを分けて実際の初期化処理はlib.rsに記載していたので、そちら側に書いていたサーバを立てる処理も修正しています。
+ use actix_web::web::ServiceConfig;
+ use shuttle_actix_web::ShuttleActixWeb;
- #[actix_web::main]
- async fn main() -> std::io::Result<()> {
- sample_project::run_server().await
+ #[shuttle_runtime::main]
+ async fn main() -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
+ sample_project::run_server()
}
- use std::net::Ipv4Addr;
- use actix_web::{App, HttpServer};
+ use actix_web::web::ServiceConfig;
+ use shuttle_actix_web::ShuttleActixWeb;
mod controller {
pub mod root;
}
- pub async fn run_server() -> std::io::Result<()> {
- HttpServer::new(|| {
- App::new()
- .service(controller::root::index)
- })
- .bind((Ipv4Addr::UNSPECIFIED, 8080))?
- .run()
- .await
+ pub fn run_server() -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
+ let config = move |cfg: &mut ServiceConfig| {
+ cfg.service(controller::root::index);
+ };
+
+ Ok(config.into())
}
実行
下記のコマンドを実行すると、開発環境が立ち上がります。
cargo shuttle run
デプロイ
まずはローカルからCLIでデプロイ
まず下記のコマンドで、Shuttle内にプロジェクトを作成します。
作成されるプロジェクトは、開いているRustプロジェクトの名称になります。
cargo shuttle project start
プロジェクトが作成されたら、下記のコマンドを実行することでshuttle内の環境にデプロイされます。
cargo shuttle deploy --allow-dirty
Github ActionsでのCI/CD構築
コマンドでデプロイした環境が正常に動いているのを確認したら、CI/CDの構築も試してみます。
Github actionsの中にShuttleへのデプロイ処理が用意されているので、それを利用します。
name: Shuttle Deploy
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: shuttle-hq/deploy-action@main
with:
deploy-key: ${{ secrets.SHUTTLE_API_KEY }}
Shuttleのダッシュボードで取得したAPI_KEYを、リポジトリのシークレットにSHUTTLE_API_KEYとして設定し、ワークフローを実行します。
正常終了すれば成功です。
DBアクセスの追加
続いて、RDBへのアクセスを構成します。
今回はShuttleが共用DBとして用意しているpostgreSQLへの接続を追加します。
Dockerの準備
postgreSQLへの接続構成を追加すると、cargo shuttle run
を実行した際に内部でpostgreSQLのコンテナを追加し接続を構成するので、あらかじめ開発環境にDockerを入れておきます。
弊環境はCodespaceなので、設定ファイルにDockerを追加しています。
{
"name": "Rust",
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
- "features": {}
+ "features": {
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {}
+ }
}
Cargo.tomlへの追記
共用DBアクセス用のクレートを追加します。
接続にはsqlxというライブラリを利用します。
[dependencies]
actix-web = "4"
tera = "1"
serde = { version = "1.0", features = ["derive"] }
shuttle-actix-web = "0.46.0"
shuttle-runtime = "0.46.0"
+ shuttle-shared-db = { version = "0.46.0", features = ["postgres", "sqlx"] }
+ sqlx = "0.7.1"
main.rs、lib.rsの修正
ドキュメントに従って、main.rsとlib.rsにコネクションプールの情報を追記します。
エントリーポイントにマクロ付きで引数を追加すると、実行時やデプロイ時にDBとそこへの接続を勝手に構成してくれます。
use actix_web::web::ServiceConfig;
use shuttle_actix_web::ShuttleActixWeb;
+ use sqlx::PgPool;
#[shuttle_runtime::main]
- async fn main() -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
- htmx_in_rust::run_server()
+ async fn main(
+ #[shuttle_shared_db::Postgres] pool: PgPool,
+ ) -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
+ htmx_in_rust::run_server(pool)
}
- use actix_web::web::ServiceConfig;
+ use actix_web::web::{self, ServiceConfig};
use shuttle_actix_web::ShuttleActixWeb;
+ use sqlx::PgPool;
mod controller {
pub mod root;
}
+ #[derive(Clone)]
+ struct AppState {
+ pool: PgPool,
+ }
- pub fn run_server() -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
+ pub fn run_server(pool: PgPool) -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
let config = move |cfg: &mut ServiceConfig| {
+ let state = web::Data::new(AppState { pool });
cfg.service(controller::root::index);
+ cfg.app_data(state);
};
Ok(config.into())
}
実行、デプロイ
編集後に cargo shuttle run
でローカル実行すると、先述の通りpostgreSQLのコンテナが実行されます。
Shuttleの環境にデプロイすると、該当アプリ内のリソースにpostgresのサービスが追加されます。
参考