Skip to content

Commit b619c95

Browse files
authored
Merge pull request #1795 from future-architect/feature
S3エミュレーターrustfs
2 parents f9c64fe + fba6473 commit b619c95

File tree

5 files changed

+143
-0
lines changed

5 files changed

+143
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
title: "S3エミュレーションでrustfsを使ってみたメモとPresigned URLの仕組み"
3+
date: 2026/04/03 00:00:00
4+
postid: a
5+
tag:
6+
- S3
7+
- rustfs
8+
- Docker
9+
- Go
10+
category:
11+
- Infrastructure
12+
thumbnail: /images/2026/20260403a/thumbnail.png
13+
author: 澁川喜規
14+
lede: "ちょっとしたオブジェクトストレージ前提のシステムのローカルテストでApache2ライセンスのrustfsを使ってみました。おおむね簡単だったのですが、認証設定をしたり、presigned URLの発行だけちょっと手間がかかってしまったのでその対応とその過程で学んだことのメモです。"
15+
---
16+
17+
ちょっとしたオブジェクトストレージ前提のシステムのローカルテストでApache 2ライセンスのrustfsを使ってみました。おおむね簡単だったのですが、認証設定をしたり、presigned URLの発行だけちょっと手間がかかってしまったのでその対応とその過程で学んだことのメモです。
18+
19+
このあたり、minioがDockerイメージの配布をやめてメンテナンスモードになったり、LocalStackがユーザー登録必須になってCIで使いにくくなったりでにわかに話題になっていたところですね。
20+
21+
* [さくらんぼの技術備忘録: 手軽に使えるS3互換ストレージを求めて](https://light-of-moe.ddo.jp/~sakura/diary/?p=1931)
22+
* [minioがdockerイメージを配布しなくなったので新しいS3互換ストレージを探す](https://zenn.dev/appleworld/articles/e1bac60a3bd333)
23+
24+
ちょっとしたウェブアプリのバックエンドのストレージとしてオブジェクトストレージが欲しくなったのですが、これまではminioをたまに使ったりしていたものの、別のものを検討するにあたり、docker composeで一緒に起動するという使い方で使いやすいものということで、いろいろ比べてrustfsを選んでみました。
25+
26+
# compose.yamlでの利用方法
27+
28+
rustfsの公式イメージをそのまま使うだけです。一瞬で起動します。
29+
30+
* デフォルトで9000ポートでAPIのエンドポイントを、9001で管理画面(RUSTFS_CONSOLE_ENABLEが必要)を公開します
31+
* 複数ボリュームのレプリケーションとか色々複雑な機能もありますが、テスト用で可用性はいらなかったので1ボリュームにしています
32+
* 起動時にはバケットができて欲しいところなので、amazon/aws-cliイメージを使って起動時にバケットを作るようにします
33+
34+
```yaml compose.yaml
35+
services:
36+
rustfs:
37+
image: rustfs/rustfs:latest
38+
environment:
39+
RUSTFS_CONSOLE_ENABLE: "true"
40+
RUSTFS_ACCESS_KEY: rustfsadmin
41+
RUSTFS_SECRET_KEY: rustfsadmin
42+
RUSTFS_VOLUMES: /data/rustfs0
43+
volumes:
44+
- rustfs-data:/data
45+
- rustfs-logs:/logs
46+
ports:
47+
- "9000:9000"
48+
- "9001:9001"
49+
healthcheck:
50+
test: ["CMD", "sh", "-c", "curl -sS http://localhost:9000/ >/dev/null"]
51+
interval: 1s
52+
timeout: 5s
53+
retries: 20
54+
55+
rustfs-init:
56+
image: amazon/aws-cli:2.31.15
57+
entrypoint: ["/bin/sh", "-c"]
58+
command:
59+
- |
60+
set -eu
61+
until aws --endpoint-url http://rustfs:9000 s3api list-buckets >/dev/null 2>&1; do
62+
sleep 2
63+
done
64+
for bucket in data-bucket log-bucket; do
65+
aws --endpoint-url http://rustfs:9000 s3api create-bucket --bucket "$$bucket" || true
66+
done
67+
environment:
68+
AWS_ACCESS_KEY_ID: rustfsadmin
69+
AWS_SECRET_ACCESS_KEY: rustfsadmin
70+
AWS_REGION: us-east-1
71+
depends_on:
72+
rustfs:
73+
condition: service_healthy
74+
75+
volumes:
76+
rustfs-data:
77+
rustfs-logs:
78+
```
79+
80+
使い方を調べると、`RUSTFS_ADDRESS`などの環境変数でアドレスを定義しているものなどもありますが、なくてもデフォルトで9000番(UIは9001番)ポートで開いたので省略しました。
81+
82+
管理画面は動作も軽快だしなかなか良いですね。今まで触ったことのあるウェブを使ったファイル管理画面の中では一番スピードが速くて体験が良いですね。
83+
84+
<img src="/images/2026/20260403a/screenshot_console.png" alt="" width="1200" height="816" loading="lazy">
85+
86+
# Presigned URL
87+
88+
これでAWS SDKを使ったデータの読み書きは問題ありませんでしたが、Presigned URLの発行で問題が発生しました。rustfsの問題というかDockerを使っているから起きた問題ですが、rustfsでは、Presigned URLで発行されるURLはリクエスト時のホスト情報をもとに作られます。Dockerの中からは`http://rustfs:9000`というドメインでアクセスしますが、外からは`http://localhost:9000`なので、発行されたURLのままではアクセスできないということが起きました。
89+
90+
これは発行時にクライアントを新規で作って、ホストを`http://localhost:9000`に設定してそれで発行し直す必要がありました。
91+
92+
```go goのサンプル
93+
// この環境変数があったらそのホストでURLを発行
94+
endpoint := os.Getenv("RUNTASK_RUSTFS_OBJECT_PUBLIC_ENDPOINT")
95+
if endpoint != "" {
96+
tempOptions := s.options
97+
tempOptions.Endpoint = endpoint
98+
tempClient, err := newS3Client(context.Background(), tempOptions)
99+
if err == nil {
100+
presigner := s3.NewPresignClient(tempClient)
101+
presigned, err := presigner.PresignGetObject(context.Background(), &s3.GetObjectInput{
102+
Bucket: aws.String(s.bucket),
103+
Key: aws.String(key),
104+
}, func(opts *s3.PresignOptions) {
105+
opts.Expires = expiry
106+
})
107+
if err == nil {
108+
return presigned.URL, nil
109+
}
110+
// fallthrough to try using the existing client
111+
}
112+
}
113+
// 設定がない場合は普通に発行
114+
presigner := s3.NewPresignClient(s.client)
115+
presigned, err := presigner.PresignGetObject(context.Background(), &s3.GetObjectInput{
116+
Bucket: aws.String(s.bucket),
117+
Key: aws.String(key),
118+
}, func(opts *s3.PresignOptions) {
119+
opts.Expires = expiry
120+
})
121+
if err != nil {
122+
return "", err
123+
}
124+
return presigned.URL, nil
125+
```
126+
127+
`Endpoint`を上書きしてしまったら逆にバックエンドのサーバーからrustfsに繋がらないからダメなのでは?と思い込んでましたが、このPresigned URLの発行は[S3 APIを実際に叩いているわけではなく、SDKの中で発行している](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_sigv.html)らしい。
128+
129+
使う技術はその名の通り「署名」です。TLSは機密の秘匿化(外から読めない)、完全性保証(改竄検知)、認証(証明書によるサーバーの身元確認)などを行いますが、Presigned URLの場合はこのうちの完全性の保証をベースに、いつ誰が許可したのかの情報が後からわかるようにしています。
130+
131+
サービスにアクセスするのに使うURLに「誰が」というのを明らかにするキーIDと期限が付与されて、シークレットアクセスキーを使って署名されます。署名されているので期限や誰が、といった情報の改ざんは許しません。
132+
133+
<img src="/images/2026/20260403a/screenshot_presigned_url.png" alt="スクリーンショット 2026-03-31 18.26.14.png" width="1200" height="523" loading="lazy">
134+
135+
クライアントはそのURLを使ってS3からファイルをダウンロードしたり、ファイルをアップロードします。S3(ここではrustfs)はその署名をみて、改竄されていないことの確認とともに、誰が署名したのかを確認します。ブラウザ自身はクレデンシャルを持っていなくても、その署名をもとにして認可制御が行われ、読み書きが成功するという流れです。
136+
137+
`Endpoint`を書き換えたクライアントを一時的に作るという方針でも、実際にそのクライアントでS3にリクエストを投げることはなくてURLの発行にしか使わないので問題なく利用できるんですね。てっきり、一時的に利用可能なトークン的なURLとして発行されてサーバー側に情報を持っているのかと思いましたが、そんなことはないんですね。勉強になりました。
138+
139+
# まとめ
140+
141+
S3以外もいろいろ必要となる場合は他のAWSエミュレータ([moto](https://github.com/getmoto/moto)とか[floci](https://github.com/hectorvent/floci)とか)の方が良いかもしれませんが、今回はS3だけが欲しかったのでrustfsを選んでみて使ってみたメモでした。
142+
143+
今まではminioを考えずに使っていましたが、今回別のものを検討してrustfsを使ってみました。seaweedfsとかも良さそうでしたが、filterとかたくさんコンテナが必要そうだったので1つで済むrustfsにしました。コンテナのメモリ消費90MBぐらいですね。動きも軽快なので今後も使ってみようと思いました。
Binary file not shown.
185 KB
Loading
40.2 KB
Loading
29.2 KB
Loading

0 commit comments

Comments
 (0)