[Rust]Tokio Hyper Axum Ctrl+C 止まらない 止まらない(SIGINT)
皆さんはBashやDockerからAxumのWebサーバーを立ち上げて止まらなくなり、仕方なく別プロセスからkillした経験はありませんか?
他のWebアプリケーションフレームワークではあり得ないAxumのCtrl+Cで止まらない挙動を解決した、"Hello, World!"を返すWebサーバーの作り方を紹介します。
Fly.ioで動かすことを目標としているため、必要最低限のファイルとソースコードでFROM clux/muslrust:stable AS builder
からFROM gcr.io/distroless/static-debian12
のイメージを作っていきます。
Dockerだけあれば動きますので、ローカル環境にGitやRustをインストールする必要はありません。
まずは各ディレクトリ、ファイルを作成します。
$ mkdir -p hello-world hello-world/src
$ cd hello-world
$ touch Dockerfile Cargo.toml src/main.rs
マルチステージビルドでイメージを作成します。
muslrustでcargo buildする際は不要なコードを削ります。
FROM clux/muslrust:stable AS builder
WORKDIR /volume
COPY . .
ENV RUSTFLAGS="-C strip=symbols"
RUN cargo build --release
FROM gcr.io/distroless/static-debian12
WORKDIR /
COPY /volume/target/x86_64-unknown-linux-musl/release/main .
ENTRYPOINT ["/main"]
Cargo.tomlで最新のAxumとTokioを取ってこれるよう設定します。
Axumと一緒に新しめのHyperもついてきます。
[package]
name = "main"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "*"
tokio = { version = "*", features = ["full"] }
AxumとTokioの機能を使い"Hello, World!"を返すWebサーバーを作ります。
use axum::{routing::get, Router};
use tokio::{net::TcpListener, signal};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(async { signal::ctrl_c().await.unwrap() })
.await
.unwrap();
}
Axum 0.7.3から使えるようになったaxum::serve().with_graceful_shutdown()
とTokioのtokio::signal::ctrl_c()
を組み合わせることでAxumのCtrl+Cで止まらない挙動を解決できます。
完成したので実際に動かしてみましょう。
$ docker build -t hello-world .
$ docker run -d --rm -p 8080:8080 --stop-signal=SIGINT hello-world
ドキュメントにも書いてあるとおり、SIGINTを受け取ることで止まるため、Dockerで動かす時は--stop-signal=SIGINT
オプションを付ける必要があります。
これがないとdocker container stopコマンドでSIGTERMを送ってしまいコンテナが正常に停止できなくなり、10秒程経過した後killされて落ちてしまいます。
もし--stop-signal=SIGINT
オプションをつけ忘れた場合は以下のコマンドを試してみましょう。
$ docker container stop -s SIGINT ...
Dockerで動いているStaticバイナリはローカルにコピーできます。
$ docker build -t hello-world .
$ docker run -d --rm -p 8080:8080 --stop-signal=SIGINT hello-world
$ docker container cp "$(docker ps -aq | head -1):/main" main
StaticバイナリからWebサーバーを立ち上げて、停止する時はもちろんCtrl+Cです。
$ ./main
^C$
StaticバイナリもDockerイメージも非常に軽いため簡単に持ち運びができます。
$ docker image save hello-world | gzip > main.tar.gz
$ docker image load < main.tar.gz
バイナリサイズは1.4MB、イメージサイズは3.44MB。圧縮すると1.3MB(2024年5月6日更新)
$ ls -lahR
.:
total 2.7M
drwxr-xr-x 3 gitpod gitpod 84 May 5 20:00 .
drwxr-xr-x 9 gitpod gitpod 126 May 5 20:00 ..
-rw-r--r-- 1 gitpod gitpod 133 May 5 20:00 Cargo.toml
-rw-r--r-- 1 gitpod gitpod 286 May 5 20:00 Dockerfile
-rwxr-xr-x 1 gitpod gitpod 1.4M May 5 20:00 main
-rw-r--r-- 1 gitpod gitpod 1.3M May 5 20:00 main.tar.gz
drwxr-xr-x 2 gitpod gitpod 21 May 5 20:00 src
./src:
total 4.0K
drwxr-xr-x 2 gitpod gitpod 21 May 5 20:00 .
drwxr-xr-x 3 gitpod gitpod 84 May 5 20:00 ..
-rw-r--r-- 1 gitpod gitpod 393 May 5 20:00 main.rs
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest ... About a minute ago 3.44MB
Axumでいつでもどこでも使える"Hello, World!"を返すWebサーバーが完成しました。
もしSIGINTだけではなくSIGTERMも受け取りたい場合は以下の公式Exampleを参考にしてみてください。
今回は以前作って書いたスクラップ記事の内容をより良くすることができたので記事にしてみました。
ありがとうございました。
Discussion