EmotionTechテックブログ

株式会社エモーションテックのProduct Teamのメンバーが、日々の取り組みや技術的なことを発信していくブログです。

Rust から Google Cloud の API を呼び出す

はじめに

こんにちは、バックエンドエンジニアのおおたわらです。

何度かこのブログでも紹介しましたが、新サービスの開発ではマイクロサービスの言語の 1 つとして Rust を採用しており、クラウドサービスは Google Cloud をメインに使っています。

現状、Google Cloud の 公式 Rust クライアントライブラリはありません*1。そのため、サードパーティーのクレートを使って開発していますが、使いたいサービスに対応したものがなかったり期待した作りではない場合があります。

クレートの内部でやっていることは基本的に Google Cloud API の呼び出しなので、それを自前で実装できればクレートがなくてもやりたいことはできる場合があります。ということで、Rust から Google Cloud の API を呼び出す実装方法を紹介してみます。

この記事はエモーションテック Advent Calendar 2024の 8 日目の記事です。

弊社の Google Cloud 関連クレート選定方針

望んだクレートがないことがある、と冒頭で述べましたが、そもそものクレートの選定方針についても紹介しておきます。

クレートは大きく独自実装型(例:gcp-bigquery-client)、コード自動生成型(例:google-cloud-rs、メンテナンスモードになっていますが google-apis-rs)があります。

以下のような観点で比較し、コード自動生成型のデメリットが大きいと考え、基本的には独自実装型の中から選定しています。

  • バグ修正の容易さ:公式クライアントライブラリではない以上、何かバグがあった場合に自己解決が求められることもあります。独自実装型は Google Cloud API を呼び出す薄いラッパーのような作りになっていることが多く、内部実装の把握が容易で修正しやすい印象です。一方、自動生成型は修正難易度が高い印象です。
  • 対応範囲:コード自動生成型は対応範囲が広く、API 仕様の変更への追従がしやすいと考えられます。独自実装型はそもそも対応しているサービスが少なめです。
  • 開発者体験:コード自動生成型では、Rust 以外の仕組みが必要な場合もあります。例えば、google-apis-rs であれば mako に依存しているため、開発環境に Python のインストールが必要になってしまうことがネックで見送りました。

ちなみに、コード自動生成型は本格的には使ったことがないので、実際には印象と違う部分もあるかもしれないと考えています。コード自動生成型をお使いの方がいらっしゃれば、ぜひ所感を聞いてみたいです。

実装の紹介

プロダクト開発の中で、Cloud Run Jobs の実行に関して選定条件を満たすクレートがなく、直接 APIjobs.run API メソッド)を呼び出すことにしました。以下では、この際に行った実装を例として紹介します。例としてのわかりやすさのため、エラーハンドリングは省略してすべて unwrap しています。

認証

前提として、Google Cloud API の認証は OAuth 2.0 に基づいています。

Google APIOAuth 2.0 フレームワークを実装して拡張します。

Google での認証方法 | Authentication | Google Cloud

アプリケーションから Google Cloud を使用する際には、アプリケーションのデフォルト認証情報(以下略して「ADC」と記載)の仕組みを使うのが推奨されています。これをサポートするクレートとしては、google-cloud-authyup-auth があります。

今回は、作者が公開している別のクレートを使っていて馴染みがあったこと、メンテナンスも行われていることから google-cloud-auth を使用しました。これによる トークンの発行方法の設定の実装は以下のようになります。

let config = Config {
    audience: None,
    scopes: Some(&["https://www.googleapis.com/auth/cloud-platform"]),
    sub: None,
};
let token_source_provider = DefaultTokenSourceProvider::new(config)
    .await
    .unwrap();
let token_source: Arc<dyn TokenSource> = token_source_provider.token_source();
let token: String = token_source.token().await.unwrap();

スコープについては公式ドキュメントに以下のように記載があります。

API メソッドで受け入れられるスコープのリストは、プロダクト ドキュメントの API リファレンス ページで確認できます。

Google での認証方法 | Authentication | Google Cloud

よって、API リファレンスを参考に scopes を指定します。今回の例ですと、こちらに記載されています。

DefaultTokenSourceProvider というのは ADC により認証を行い、OAuth 2.0 のアクセストークンを生成する設定です。

TokenSource トレイトの実装でトークン生成処理が定義されています。アクセストークンには有効期限がありますが、DefaultTokenSourceProvider から得られる実装は ReuseTokenSource という更新を行いつつトークンを取得するものになっています。そのため、アプリケーション内でシングルトンで持つとすれば TokenSource にして、使うタイミングで token メソッドでトークンを取得するのが良いでしょう。

クライアント

クライアントはニーズに沿ったものを使えば良いのですが、今回はこれまでのプロダクト開発でも使い慣れている reqwest を使いました。

Authorization ヘッダーにトークンを指定し、あとは送りたいリクエストを記述すれば OK です。ちょっとした注意点として、token メソッドで得られる値は Bearer {token} という形式になっているので、Bearer を自分で記載する必要はありませんでした。

let client = reqwest::Client::builder()
    .build()
    .unwrap();
let response = client
    .post(&format!(
      "https://run.googleapis.com/v2/projects/{project_id}/locations/{location}/jobs/{job_name}:run",
      project_id = "project_id",
      location = "asia-northeast1",
      job_name =  "hoge"),
    )
    .header("Content-Type", "application/json")
    .header("Authorization", token)
    .json(&json!({
      "overrides": {
        "containerOverrides": [
          {
            "args": cloud_run_jobs_args
          }
        ],
      }
    }))
    .send().await.unwrap();

おわりに

Rust から Google Cloud の API を呼び出す方法についてまとめてみました。実際にやってみると難しい実装ではなかったですが、あまり情報がなくてハードルを感じていました。同じようなことをしたい方の参考になれば幸いです。

エモーションテックでは顧客体験、従業員体験の改善をサポートし、世の中の体験を変えるプロダクトを開発しています。ご興味のある方、Rust や Google Cloud を使って開発したい方はぜひ採用ページからご応募をお願いいたします。

hrmos.co hrmos.co