本次介紹 Garage S3 與安裝方法, 並使用 Spring boot 專案進行圖片檔案管理。
前言 今年休息的時間嘗試把 colorly 的核心功能剝出來直接讓朋友使用 (Colorly-Lite 上線了, 可以點這裡 去試試看), 在開新的 MinIO 服務時發現 Web UI, 的功能好像跟以前長得不太一樣?!
仔細一看根本不是少一些, 整個 administrator 的功能在 UI 上全被拔掉了, 包含 Monitoring, IAM 等, 雖然 Cli 還是可以操作, 但功能也在慢性閹割中.
上網了解了一下, MinIO 2025 年 2 月由共同創辦人 Harshavardhana 在 GitHub 提交「AGPL Object Browser 簡化 console」的 commit,等於把 Community Edition 裡原本的管理主控台整包拆掉,只剩下最陽春的 Object Browser.
隨後在 2025-05-24 釋出的版本中, Monitoring、IAM、User / Policy 管理等通通不見,想要圖形化管理就只能付費用他們新的 Enterprise 商業版, 直接引發大眾憤怒。
另一方面, MinIO 之前在自家 AGPL 合規頁面上也有過匪夷所思的超譯, 我個人也不太喜歡這種隨時可能被噁心的感覺, 因此這幾週開始尋找替代方案.看鄉民們很推薦 Garage, 研究完就順手把 MinIO 改掉了.
Garage 介紹, Node, Layout, Bucket, Key Garage 是由 Deuxfleurs 公司開發的開源 S3 儲存專案, 用 Rust 開發且採用 MIT License . 雖然目前看下來還沒有大型企業再使用, 且 MinIO 在高企業場景仍略有優勢,但很多功能已經變成 Enterprise 才提供, 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 mkdir -p /home/william/metamkdir -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 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/metachmod 755 /home/william/datachmod 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 docker-compose up -d sudo docker exec -it garage /garage status2025-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 sudo docker exec -it garage /garage layout assign -z dc1 -c 10G b7f541a516e7e422sudo docker exec -it garage /garage layout apply --version 1sudo ufw allow 3900/tcpsudo ufw allow 3901/tcpsudo ufw allow 3902/tcpsudo ufw allow 3903/tcpsudo ufw allow 3904/tcpsudo ufw allow 3909/tcp
此時, 應該可以在 http://vm.ip:3909/ 訪問
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 access-key: access-key secret-key: secret-key
後續上傳檔案後, 即可在 Web UI 裡面即時瀏覽, 另外 Garage 不像 Minio 會把檔案直接儲存到 server 路徑的 bucket 資料夾內, 而是切成分片檔案, 所以在 server 上會找不到可以瀏覽的檔案。
Garage Cluster 版本 待補充, 又要開兩台 vm QQ。
結語 你說 MinIO 的資料怎麼 migrate 到 Garage 內? 等有空再研究…, 預計測試 Garage Cluster 後在更新一次。