🦀

RustでAWS公式のsdkを使ってMinioを操作する.md

2024/10/03に公開

1. 結論

今回の開発では、RustでAWS SDKを使用してMinioに接続し、S3互換のバケット操作を行うことに成功しました。resolver を利用してMinioのエンドポイントを指定し、非同期プログラミングのためにTokioを使用しています。

以下は、Minioに接続してバケットを作成する際の重要なコードです。

use aws_sdk_s3::{Client, Credentials, Region};
use aws_sdk_s3::config::Builder;
use aws_sdk_s3::endpoint::{Endpoint, ResolveEndpoint, EndpointFuture, Params};

#[derive(Debug)]
struct DefaultResolver {
    endpoint: String,
}

impl ResolveEndpoint for DefaultResolver {
    fn resolve_endpoint(&self, _params: &Params) -> EndpointFuture<'static> {
        let endpoint = Endpoint::immutable(self.endpoint.parse().unwrap());
        EndpointFuture::ready(Ok(endpoint))
    }
}

#[tokio::main]
async fn main() {
    let endpoint = "http://minio:9000".to_string();
    let access_key = "XLKtI4YgqXJA11JlqyB7";
    let secret_key = "qSlpgFhm4uNX01dUgfRvuCmc36zpxPpHQgTKOBcO";

    let resolver = DefaultResolver { endpoint };

    let config = Builder::new()
        .endpoint_resolver(resolver)
        .region(Region::new("us-east-1"))
        .credentials_provider(Credentials::new(access_key, secret_key, None, None, "static"))
        .build();

    let client = Client::from_conf(config);

    let bucket_name = "test-bucket";
    let result = client.create_bucket().bucket(bucket_name).send().await;

    match result {
        Ok(_) => println!("Bucket created successfully"),
        Err(e) => eprintln!("Failed to create bucket: {:?}", e),
    }
}

このコードは、Minioに接続して指定されたバケット名でバケットを作成するシンプルな実装です。resolver を使うことで、Minioのカスタムエンドポイントを指定して接続できるようにしています。

最終的なコードは、以下のリンクにまとめています。

最終的なコード結果はこちら

2. 環境構築とプロジェクト設定

今回のプロジェクトでは、RustでMinioに接続し、S3互換のオブジェクトストレージを操作できる環境を構築しました。Rust環境は既に整っている前提で進め、Minioのセットアップやプロジェクトの依存関係について解説します。

2.1 Docker ComposeでMinioをセットアップ

まず、ローカルでMinioを動作させるために、Docker Composeを使用しました。以下の設定を使って、Minioを簡単に起動できます。

version: "3"
services:
  minio:
    image: minio/minio:RELEASE.2024-08-17T01-24-54Z
    environment:
      MINIO_ACCESS_KEY: XLKtI4YgqXJA11JlqyB7
      MINIO_SECRET_KEY: qSlpgFhm4uNX01dUgfRvuCmc36zpxPpHQgTKOBcO
    command: server /data --address :9000 --console-address :9001
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - minio-data:/data
volumes:
  minio-data:

この設定では、Minioを9000ポートで公開し、管理コンソールを9001ポートでアクセス可能にしています。

次に、以下のコマンドでMinioを起動します。

docker-compose up -d

これで、ローカル環境にMinioが起動し、S3互換のオブジェクトストレージをテストする準備が整います。管理コンソールは http://localhost:9001 から確認できます。

2.2 Cargoプロジェクトの設定

次に、RustでS3に接続するために、Cargo.toml を設定します。依存関係には、aws-sdk-s3aws-config を指定し、S3操作やMinioへの接続を可能にします。

[package]
name = "core"
version = "0.1.0"
edition = "2021"

[dependencies]
aws-sdk-s3 = { version = "1.52.0", features = ["behavior-version-latest"] }
aws-config = { version = "1.0.1", features = ["behavior-version-latest"] }
tokio = { version = "1.40.0", features = ["full"] }
aws-smithy-runtime = { version = "1.7.1" }
aws-smithy-runtime-api = { version = "1.7.2" }
aws-smithy-types = { version = "1.2.7" }

[dependencies.uuid]
version = "1.10.0"
features = [
    "v4",                # Lets you generate random UUIDs
    "fast-rng",          # Use a faster (but still sufficiently random) RNG
    "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
]

この設定により、非同期でS3操作を行うためのtokioや、UUID生成に必要なuuidクレートも追加しています。これで、S3互換のバケット操作やMinioへの接続準備が整います。

3. Minioとの接続とエラー解決の流れ

RustでMinioに接続する際、いくつかのエラーに遭遇しましたが、これらを順に解決することで、最終的にS3互換のストレージにアクセスできる環境を構築しました。この章では、そのエラーと解決方法を紹介します。

3.1 Invalid client configuration: A behavior major version must be set エラー

Minioに接続する際、以下のエラーが発生しました。

Invalid client configuration: A behavior major version must be set when sending a request or constructing a client. You must set it during client construction or by enabling the `behavior-version-latest` cargo feature.

このエラーは、S3クライアントの作成時に正しい設定が不足していたことが原因です。behavior-version-latest の機能が必要だったため、Cargo.tomlでこの機能を有効化しました。

aws-sdk-s3 = { version = "1.52.0", features = ["behavior-version-latest"] }

3.1 名前解決エラーとカスタムエンドポイントの設定

Minioにバケットを作成しようとした際に、以下のような名前解決エラーが発生しました。

dns error: failed to lookup address information: Name or service not known

このエラーの原因は、DefaultResolverを設定していなかったことにありました。Minioのエンドポイントを正しく解決するためには、カスタムエンドポイントリゾルバを設定する必要がありました。

そこで、endpoint_resolverの公式ドキュメントに次の説明がありました。

Sets the endpoint resolver to use when making requests.

When unset, the client will use a generated endpoint resolver based on the endpoint resolution rules for aws_sdk_s3.

Note: setting an endpoint resolver will replace any endpoint URL that has been set. This method accepts an endpoint resolver specific to this service. If you want to provide a shared endpoint resolver, use Self::set_endpoint_resolver.

この説明によれば、カスタムエンドポイントを設定しない場合は自動生成されたものが使用されますが、特定のサービス向けにエンドポイントリゾルバを設定することで、カスタムエンドポイントが利用できることが分かりました。

そこで、以下のようにDefaultResolverを実装し、カスタムエンドポイントを設定することで、この名前解決エラーを解決しました。

#[derive(Debug)]
struct DefaultResolver {
    endpoint: String,
}

impl ResolveEndpoint for DefaultResolver {
    fn resolve_endpoint(&self, _params: &Params) -> EndpointFuture<'static> {
        let endpoint = Endpoint::immutable(self.endpoint.parse().unwrap());
        EndpointFuture::ready(Ok(endpoint))
    }
}

let config = aws_sdk_s3::config::Builder::new()
    .endpoint_resolver(DefaultResolver { endpoint: "http://minio:9000".to_string() })
    .region(Region::new("us-east-1"))
    .credentials_provider(Credentials::new(access_key, secret_key, None, None, "static"))
    .build();
let client = Client::from_conf(config);

DefaultResolverを使うことで、Minioのカスタムエンドポイントを正しく設定でき、名前解決のエラーが解消されました。

3.3 InvalidAccessKeyId エラー

Minioにバケットを作成しようとすると、次に以下のエラーに遭遇しました。

InvalidAccessKeyId: The Access Key Id you provided does not exist in our records.

このエラーの原因は、Minioに設定されたアクセスキーとシークレットキーが正しくないか、Docker環境で適切に設定されていなかったことにあります。docker-compose.ymlファイルを修正し、Minioに正しいアクセスキーとシークレットキーを渡すことで、このエラーを解決しました。

environment:
  MINIO_ACCESS_KEY: XLKtI4YgqXJA11JlqyB7
  MINIO_SECRET_KEY: qSlpgFhm4uNX01dUgfRvuCmc36zpxPpHQgTKOBcO

4. Minioに対するテストの実行

Minioへの接続とバケットの作成に成功した後、次に実施したのはテストによる確認です。Rustでは非同期処理をサポートするため、tokioを利用して非同期テストを行いました。ここでは、Minioにバケットを作成し、正しく動作するかどうかをテストする流れについて説明します。

4.1 非同期テストの設定

Rustの標準的なテスト機能は非同期関数に対応していないため、非同期テストを行う場合は#[tokio::test]アトリビュートを使用します。このアトリビュートを使うことで、非同期関数をテストすることが可能です。

以下は、Minioにバケットを作成するテストの例です。

#[cfg(test)]
mod tests {
    use super::*;
    use uuid::Uuid;

    #[tokio::test]
    async fn test_minio_create_bucket() {
        let is_minio = true;
        let prefix: String = Uuid::new_v4()
            .as_bytes()
            .iter()
            .map(|b| format!("{:02x}", b))
            .collect();
        let test_bucket_name = format!("test-bucket-{prefix}", prefix = prefix);
        let endpoint = format!(
            "http://minio:9000/{bucket_name}",
            bucket_name = test_bucket_name
        );
        let client = S3::new(is_minio, Some(endpoint)).await;

        let result = client.create_bucket(&test_bucket_name).await;

        match result {
            Ok(Some(output)) => {
                println!("Bucket created successfully: {:?}", output);
            }
            Ok(None) => {
                println!("Bucket already exists or no output was returned.");
            }
            Err(e) => {
                eprintln!("Failed to create bucket: {}", e);
                panic!("Test failed due to bucket creation error.");
            }
        };
    }
}

4.2 バケット作成のテスト

上記のテストでは、ランダムな名前のバケットを生成し、そのバケットをMinioに作成できるかどうかを確認しています。テストが成功した場合、Bucket created successfully というメッセージが表示されます。すでにバケットが存在する場合は Bucket already exists と表示され、何もエラーが返されなければテストは成功と見なされます。

test_minio_create_bucket テストの流れは次の通りです。

  1. UUIDを生成して、一意なバケット名を作成します。
  2. Minioのエンドポイントを設定し、S3クライアントを初期化します。
  3. create_bucket 関数を使用してバケットの作成を試みます。
  4. 成功すれば結果を表示し、すでにバケットが存在する場合でも問題なく進行します。

4.3 テスト結果の確認

テストは cargo test を使って実行できます。cargo testを実行すると、以下のような出力が表示され、テストが成功したかどうかを確認できます。

running 1 test
test s3::tests::test_minio_create_bucket ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s

この結果、Minioへのバケット作成が正常に動作することが確認できました。

5. まとめと振り返り

今回の開発では、RustでAWS SDKを利用してMinioに接続し、S3互換のバケット操作を実行することに成功しました。以下に、重要なポイントを振り返ります。

5.1 Minioとの接続

まず、Minioをローカル環境で動作させるためにDocker Composeを使い、簡単にS3互換のストレージを構築しました。RustでMinioに接続する際、AWS SDKを利用し、非同期プログラミングを可能にするためにtokioを導入しました。

最初の段階でエンドポイントの設定に課題がありましたが、DefaultResolverを使うことでMinioのカスタムエンドポイントを解決し、名前解決の問題を克服しました。

5.2 エラー解決のプロセス

Minioに接続する際、さまざまなエラーに直面しましたが、これらを順番に解決することで最終的に正常に動作させることができました。特に、behavior-version-latestの指定や、InvalidAccessKeyIdエラーに対処するための適切なアクセスキーの設定がポイントでした。

最も大きな問題は、Minioのエンドポイントに正しく接続するための設定であり、DefaultResolverを使うことで正しいエンドポイント解決が可能になりました。

5.3 テストの実行

Rustの非同期テスト機能を活用し、Minioにバケットを作成するテストを実行しました。cargo testによってテストが正常に動作し、Minioに対するS3互換の操作が正しく行われることを確認できました。

5.4 今後の展望

今回の開発を通して、RustでMinioのようなS3互換のストレージを扱う際の基礎的な方法を理解することができました。今後は、オブジェクトのアップロードや削除などの操作もテストに含め、より複雑なS3操作に対応するコードを実装していくことが目標です。また、エラー処理をさらに最適化し、リトライ戦略やログ出力の改善なども進めていく余地があります。


今回のプロジェクトは、Minioへの接続から始まり、さまざまなエラーを乗り越えながらRustでのS3互換操作を成功させるものでした。このプロセスが、今後Rustで同様の問題に直面した際の参考になれば幸いです。

参考一覧

1. AWS SDK for Rust の公式サンプル

  • 概要: AWSが公式に提供しているRust向けのS3操作サンプル。
  • 用途: Cargo.tomlの依存関係設定を参考にして、Minioに接続するために必要なライブラリのバージョンや機能を確認しました。

2. AWS SDK for Rust のサンプルコード (バケット操作)

  • 概要: RustでS3にアクセスするサンプルコード。バケットの作成やオブジェクト操作に関する記述が含まれています。
  • 用途: Minioへの接続とバケット作成を実装する際、このコードをベースにしてRustでのS3操作を理解しました。

3. RustでMinioに接続するためのエラー解決に関するフォーラムスレッド

  • 概要: RustでAWS SDKを使用してMinioに接続する際のトラブルや解決策が議論されているフォーラムスレッド。

4. AWSのS3バケット作成APIのドキュメント

  • 概要: AWS S3のAPIドキュメント。バケット作成に必要なパラメータやリクエストの詳細が記載されています。
  • 用途: Minioに対してバケット作成リクエストを送る際に、このドキュメントを参考にしてリクエストフォーマットを確認しました。

5. AWS SDKのドキュメント (AWS Lambda向けS3アクセス)

  • 概要: RustでAWS LambdaからS3にアクセスするための公式ドキュメント。

Discussion