Rust と Cloud Run でサーバレスかつストリーミング対応の gRPC サーバを構築する方法

[前回の記事](https://qiita.com/K-Kachi/items/67c871a09f22d839eeff)を公開してから半年近く経過しましたが、最近 Cloud Run がストリーミングの gRPC に対応したので（[公式ブログ記事](https://cloud.google.com/blog/products/serverless/cloud-run-now-supports-http-grpc-server-streaming)）、早速試してみます。
前回と同様、tonic の公式サンプルを　Cloud Run にデプロイしてみます。

## 準備

サンプルコードの取得

```shell-session
$ git clone https://github.com/hyperium/tonic.git
$ cp -r tonic/examples tonic-examples
$ cd tonic-examples
```

`Cargo.toml` の書き換え

```diff
diff ../tonic/examples/Cargo.toml ./Cargo.toml
142c142
< tonic = { path = "../tonic", features = ["tls"] }
---
> tonic = { version = "0.3", features = ["tls", "tls-roots"] }
166c166
< tonic-health = { path = "../tonic-health" }
---
> tonic-health = { version = "0.2" }
170c170
< tonic-build = { path = "../tonic-build", features = ["prost"] }
---
> tonic-build = { version = "0.3", features = ["prost"] }
```

ディレクトリ構成変更の対応

```shell-session
$ fd --extension rs | xargs -I @ sed -i 's,examples/,,g' @
```

## サンプルコードの動作確認

```shell-session
$ cargo run --bin routeguide-server
# 別のターミナルウィンドウで
$ cargo run --bin routeguide-client
```

## Cloud Run へのデプロイ

IPアドレスおよびポート番号の変更

```diff
diff ../tonic/examples/src/routeguide/data.rs src/routeguide/data.rs
18c18
<     let file = File::open("examples/data/route_guide_db.json").expect("failed to open data file");
---
>     let file = File::open("data/route_guide_db.json").expect("failed to open data file");
diff ../tonic/examples/src/routeguide/server.rs src/routeguide/server.rs
137c137,138
<     let addr = "[::1]:10000".parse().unwrap();
---
>     let port = std::env::var("PORT").unwrap_or("8080".into());
>     let addr = format!("0.0.0.0:{}", port).parse().unwrap();
```

`Dockerfile` を用意

```dockerfile
FROM rust as build-env
WORKDIR /app
ADD . .
RUN rustup component add rustfmt && cargo build --bin routeguide-server

FROM gcr.io/distroless/cc
COPY --from=build-env /app/data/route_guide_db.json /data/route_guide_db.json
COPY --from=build-env /app/target/debug/routeguide-server /
CMD ["./routeguide-server"]
```

ビルド

```shell-session
$ echo 'target/' > .gcloudignore
$ gcloud builds submit --machine-type=n1-highcpu-32 --tag gcr.io/$PROJECT_ID/routeguide
```

デプロイ

```shell-session
$ gcloud config set run/region asia-northeast1
$ gcloud run deploy routeguide \
  --image=gcr.io/$PROJECT_ID/routeguide \
  --allow-unauthenticated \
  --platform managed
```

## デプロイされたサービスの動作確認

デプロイされたサービスに接続するよう変更。
双方向ストリーミングは記事執筆時点で対応していないためコメントアウトした。

```diff
diff ../tonic/examples/src/routeguide/client.rs src/routeguide/client.rs
8c8
< use tonic::transport::Channel;
---
> use tonic::transport::{Channel, ClientTlsConfig};
94c94,97
<     let mut client = RouteGuideClient::connect("http://[::1]:10000").await?;
---
>     let tls = ClientTlsConfig::new().domain_name("デプロイ先");
>     let channel = Channel::from_static("https://デプロイ先")
>         .tls_config(tls)?.connect().await?;
>     let mut client = RouteGuideClient::new(channel);
111,112c114,115
<     println!("\n*** BIDIRECTIONAL STREAMING ***");
<     run_route_chat(&mut client).await?;
---
>     // println!("\n*** BIDIRECTIONAL STREAMING ***");
>     // run_route_chat(&mut client).await?;
```

動作確認

```shell-session
$ cargo run --bin routeguide-client
```

## まとめ

Cloud Run は Docker コンテナさえ用意できればデプロイできるため自由度の高いサービスである。
今回 Cloud Run がストリーミングに対応したことで、クライアントに大きなサイズのデータを送信したり、長期プロセスの途中経過を通知したりすることが可能となり、更に活用の幅が広がった。