Garage S3 Bucket 安裝與配置 (With Java)

Garage S3 Bucket 安裝與配置 (With Java)

本次介紹 Garage S3 與安裝方法, 並使用 Spring boot 專案進行圖片檔案管理。


前言

今年休息的時間嘗試把 colorly 的核心功能剝出來直接讓朋友使用 (Colorly-Lite 上線了, 可以點這裡去試試看), 在開新的 MinIO 服務時發現 Web UI, 的功能好像跟以前長得不太一樣?!

原來有的各種功能

仔細一看根本不是少一些, 整個 administrator 的功能在 UI 上全被拔掉了, 包含 Monitoring, IAM 等, 雖然 Cli 還是可以操作, 但功能也在慢性閹割中.

怎麼剩個 Create Bucket?

上網了解了一下, MinIO 2025 年 2 月由共同創辦人 Harshavardhana 在 GitHub 提交「AGPL Object Browser 簡化 console」的 commit,等於把 Community Edition 裡原本的管理主控台整包拆掉,只剩下最陽春的 Object Browser.

隨後在 2025-05-24 釋出的版本中, Monitoring、IAM、User / Policy 管理等通通不見,想要圖形化管理就只能付費用他們新的 Enterprise 商業版, 直接引發大眾憤怒。

dani 兄好猛

另一方面, MinIO 之前在自家 AGPL 合規頁面上也有過匪夷所思的超譯, 我個人也不太喜歡這種隨時可能被噁心的感覺, 因此這幾週開始尋找替代方案.看鄉民們很推薦 Garage, 研究完就順手把 MinIO 改掉了.


Garage 介紹, Node, Layout, Bucket, Key

Garage 是由 Deuxfleurs 公司開發的開源 S3 儲存專案, 用 Rust 開發且採用 MIT License. 雖然目前看下來還沒有大型企業再使用, 且 MinIO 在高企業場景仍略有優勢,但很多功能已經變成 Enterprise 才提供, Garage 等替代方案在近期應該會越發被大家重視。

在開始搭建之前, 還是介紹一下 Garage 的一些基板概念。

Garage 核心

Node

Node 是 Garage 叢集的最小物理儲存單位, 每個 node:

  • 擁有一個唯一 node_id
  • 擁有自己的 storage capacity
  • 屬於某個 zone(用於容災)
  • 用來存放部分的分片(shards)

在 Docker 裡跑一個 Garage container 就是 1 個 node.

Layout(儲存佈局藍圖)

Garage 用來描述叢集中資料如何分散的規則, 它包含:

  • 哪些 node 在叢集中
  • 每個 node 的容量
  • node 的 zone
  • 資料分片如何分布

Bucket

與 AWS S3 的 Bucket 完全相同, bucket 內可以有 prefix, 每個 bucket 可以套 IAM policy.

Key(Access Key / Secret)

Garage 的 Key 可以有多個, 可以套用不同 policy, 如 read, write, owner 等。

Garage 與 Web UI 安裝 (with Docker Compose)

這次一樣使用 docker compose 啟動, 我開了一台 VM 直接在 home 目錄下做, 之後正式版本基本上都放在 service user 內.

1
2
3
# 先建立 dir
mkdir -p /home/william/meta
mkdir -p /home/william/data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 建立 toml 配置
cat > /home/william/garage.toml <<EOF
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"

replication_factor = 1

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "garage:3901"
rpc_secret = "$(openssl rand -hex 32)"

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"

[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.garage.localhost"
index = "index.html"

[k2v_api]
api_bind_addr = "[::]:3904"

[admin]
api_bind_addr = "[::]:3903"
admin_token = "$(openssl rand -base64 32)"
metrics_token = "$(openssl rand -base64 32)"
EOF
1
2
3
4
# 調整權限
chmod 755 /home/william/meta
chmod 755 /home/william/data
chmod 644 /home/william/garage.toml

官方的鏡像是不含 Web UI 的, 這裡整合 khairul169 的 Web UI 鏡像, 方便後續配置與操作.

另外要注意的是這個 UI 不提供 bucket/prefix 做 namespace 級的 key / policy 設定, 具體來說就是只能建立 myBucket 綁 A key, 無法建立 prefix myBucket/NEW 綁 B Key, myBucket/OLD 綁 C Key 這樣, Prefix 級的 IAM 還是得用 Garage CLI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
cat > /home/william/docker-compose.yml <<EOF
services:
garage:
image: dxflrs/garage:v2.1.0
container_name: garage
volumes:
- ./garage.toml:/etc/garage.toml
- ./meta:/var/lib/garage/meta
- ./data:/var/lib/garage/data
restart: unless-stopped
ports:
- "3900:3900"
- "3901:3901"
- "3902:3902"
- "3903:3903"
- "3904:3904"

webui:
image: khairul169/garage-webui:latest
container_name: garage-webui
restart: unless-stopped
volumes:
- ./garage.toml:/etc/garage.toml:ro
ports:
- "3909:3909"
environment:
API_BASE_URL: "http://garage:3903"
S3_ENDPOINT_URL: "http://garage:3900"
depends_on:
- garage
EOF

建立好後進行單機的配置.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
## 0. 啟動 container
docker-compose up -d

## 1. 查看 garage 啟動情況, 並記住 node_id
sudo docker exec -it garage /garage status

## 1.1
2025-12-06T14:15:54.403189Z INFO garage_net::netapp: Connected to 172.19.0.2:3901, negotiating handshake...
2025-12-06T14:15:54.447229Z INFO garage_net::netapp: Connection established to b7f541a516e7e422
==== HEALTHY NODES ====
ID Hostname Address Tags Zone Capacity DataAvail Version
b7f541a516e7e422 c5a9e9af56ad 172.19.0.2:3901 NO ROLE ASSIGNED v2.1.0

## 將 node_id 分派到 layout 內, 設定 dc, node 的容量等, 這裡給 10 GB
# sudo docker exec -it garage /garage layout assign -z dc1 -c 20G <node_id>
sudo docker exec -it garage /garage layout assign -z dc1 -c 10G b7f541a516e7e422

## 啟用 layout
sudo docker exec -it garage /garage layout apply --version 1

## 開放 vm 對外給 garage 使用的 port
sudo ufw allow 3900/tcp
sudo ufw allow 3901/tcp
sudo ufw allow 3902/tcp
sudo ufw allow 3903/tcp
sudo ufw allow 3904/tcp
sudo ufw allow 3909/tcp

此時, 應該可以在 http://vm.ip:3909/ 訪問

Garage 核心

Web UI 生成與配置 Bucket, Key

我們在 Key 側欄下建立一個 key, 並儲存他的 Key ID & Secret Key, 這個將用於 spring boot 的連線參數。

再來, 在 Bucket 中建立一個 bucket, 並點選該 bucket 的 manage 按鈕, 再選擇 Permissions, 點選 +Allow Key 加入剛才建立的 key, 並給予 Read Write 權限。

Note: Garage 有 User 的概念, 但是他的 User 其實是 IAM user, 就把它當作一個資料夾的感覺就好, 在等等與 spring boot 連線的時候還是使用 Key。

Spring Boot 對接

Garage 使用 S3 的 api, 若之前專案使用 minio 可以無痛接續。

1
2
3
4
5
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.17</version>
</dependency>

其實應該要改名成 Garage Config, 另外要特別注意 Config 內要加上 .region("garage"), 這是之前 .toml 內的 s3_region = "garage".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class MinioConfig {

@Value("${minio.endpoint}")
private String endpoint;

@Value("${minio.access-key}")
private String accessKey;

@Value("${minio.secret-key}")
private String secretKey;

@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
// 這裡要特別注意!
.region("garage")
.build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@AllArgsConstructor
@Slf4j
@Service
public class MinioObjectStorageAdapter {

private final MinioClient client;

private final String COLORLY_BUCKET = "colorly";

public void putObject(String objectKey, String contentType, long contentLength, InputStream stream) {
try {
client.putObject(
PutObjectArgs.builder()
.bucket(COLORLY_BUCKET)
.object(objectKey)
.contentType(contentType)
.stream(stream, contentLength, -1)
.build()
);
} catch (Exception e) {
log.error("MinIO putObject failed: " + objectKey, e);
throw new ImageServiceException(ImageServiceException.ImageServiceErrorType.MINIO_FILE_UPLOAD_FAILED);
}
}
}
1
2
3
4
5
minio:
endpoint: http://ip:3900
# UI 內生成的 Key
access-key: access-key
secret-key: secret-key

後續上傳檔案後, 即可在 Web UI 裡面即時瀏覽, 另外 Garage 不像 Minio 會把檔案直接儲存到 server 路徑的 bucket 資料夾內, 而是切成分片檔案, 所以在 server 上會找不到可以瀏覽的檔案。

Garage Cluster 版本

待補充, 又要開兩台 vm QQ。

結語

你說 MinIO 的資料怎麼 migrate 到 Garage 內? 等有空再研究…, 預計測試 Garage Cluster 後在更新一次。