MinIO on Docker Compose を試してみた
ストレージとしてS3を選択した際のローカル開発環境のストレージをどうするか?の一つの解決手段として、MinIO on Docker Composeを試してみた。
- LocalStackを使う
- 実際にAWS上のS3にアクセスする
等の手段もあると思うが、Goで実装されていると聞いて単純に使ってみたくなったという経緯である。
環境
- macOS Monterey 12.3.1
- Docker 20.10.10
- Docker Compose 2.1.1
- go1.18
コンテナ起動
https://raw.githubusercontent.com/minio/minio/master/docs/orchestration/docker-compose/docker-compose.yaml を参考にしてdocker-compose.ymlにMinIOの定義をする。
version: '3.9'
services:
minio:
image: quay.io/minio/minio:latest
container_name: example-minio
environment:
MINIO_ROOT_USER: root
MINIO_ROOT_PASSWORD: password
command: server --console-address ":9001" /data
ports:
- 9000:9000
- 9001:9001
command
ではコンソールのエンドポイントを設定している。
MINIO_ROOT_USER
とMINIO_ROOT_PASSWORD
はログイン時、API実行時に使う。
起動してコンソールにアクセスしてみる。
$ docker compose up -d
localhost:9001
にアクセスするとログイン画面が表示される。
docker-compose.yml
にて定義したMINIO_ROOT_USER
とMINIO_ROOT_PASSWORD
をそれぞれ入力してログインする。
シンプルで良い感じのUIだ。
バケット作成
aws-sdk-goのS3のAPIを使ってバケットを作成してみる。
せっかくなので、指定のバケットが既に存在していれば一度削除して再作成するプログラムとする。
package main
import (
"fmt"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
func main() {
sess := session.Must(
session.NewSession(
&aws.Config{
Credentials: credentials.NewStaticCredentials("root", "password", ""),
Endpoint: aws.String("http://localhost:9000"),
Region: aws.String("ap-northeast-1"),
S3ForcePathStyle: aws.Bool(true),
}))
svc := s3.New(sess)
bucket := "example"
exists, err := existsBucket(svc, bucket)
if err != nil {
fmt.Printf("failed to exists bucket: %s\n", err)
os.Exit(1)
}
if exists {
if err := deleteBucket(svc, bucket); err != nil {
fmt.Printf("failed to delete bucket: %s\n", err)
os.Exit(1)
}
}
if err := createBucket(svc, bucket); err != nil {
fmt.Printf("failed to create bucket: %s\n", err)
os.Exit(1)
}
}
func existsBucket(svc *s3.S3, bucket string) (bool, error) {
_, err := svc.HeadBucket(&s3.HeadBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case "NotFound":
return false, nil
default:
return false, err
}
} else {
return false, err
}
}
return true, nil
}
func deleteBucket(svc *s3.S3, bucket string) error {
_, err := svc.DeleteBucket(&s3.DeleteBucketInput{
Bucket: aws.String(bucket),
})
return err
}
func createBucket(svc *s3.S3, bucket string) error {
_, err := svc.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
return err
}
少しだけコードの内容を解説する。
sess := session.Must(
session.NewSession(
&aws.Config{
Credentials: credentials.NewStaticCredentials("root", "password", ""),
Endpoint: aws.String("http://localhost:9000"),
Region: aws.String("ap-northeast-1"),
S3ForcePathStyle: aws.Bool(true),
}))
svc := s3.New(sess)
credentials.NewStaticCredentials
の第一引数がアクセスキーID、第二引数がシークレットアクセスキーとなるが、ここはdocker-compose.yml
にて定義したMINIO_ROOT_USER
とMINIO_ROOT_PASSWORD
を渡す。
Region
はいくつか適当に試してみたところ、別になんでもいいっぽいけど指定がないとエラーになる。
S3ForcePathStyle
はtrueにしておかないと、http://{バケット名}.localhost:9000
みたいなエンドポイントにアクセスしにいってしまう。
_, err := svc.HeadBucket(&s3.HeadBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case "NotFound":
return false, nil
default:
return false, err
}
} else {
return false, err
}
}
return true, nil
MinIOは関係ない?けどHeadBucket
実行時にバケットがないと、
&s3err.RequestFailure{
RequestFailure: &awserr.requestError{
awsError: &awserr.baseError{
code: "NotFound",
message: "Not Found",
errs: []error{},
},
statusCode: 404,
requestID: "16ED174B7BB404D8",
bytes: []uint8{},
},
hostID: "",
}
みたいなエラーが返ってくる。
aws-sdk-goのドキュメントのExampleが
svc := s3.New(session.New())
input := &s3.HeadBucketInput{
Bucket: aws.String("acl1"),
}
result, err := svc.HeadBucket(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case s3.ErrCodeNoSuchBucket:
fmt.Println(s3.ErrCodeNoSuchBucket, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
になっていたので、最初はcase "NotFound":
の部分をcase s3.ErrCodeNoSuchBucket
で実装していた。
バケットがない時に全部defaultに入ってしまって上手くいかなかったので、プリントデバッグしてエラーの実体を確認しcase "NotFound":
に書き換えた。
(NotFound
は定数化されていなかった。。。)
プログラムを実行してみる。
$ go run main.go
無事バケットができていた。
まとめ
ドキュメントがしっかりしていてあまり躓くことなく試せた。
これから個人開発で使っていってもいいなと思った。
Discussion