معرفی و استفاده از Minio

متاسفانه سرویس‌های ابری آمازون برای ما ایرانی‌ها تحریم هست ( البته از طریق‌هایی می‌توان از آمازون سرویس گرفت ! ) به هر حال استفاده از چنین سرویس‌هایی واسه ماها تقریباً رویاست، پس خیلی سخته شما کسب‌وکار خودتان را برپایه سرویس ابری آمازون بنا کنید، یکی از سرویس‌های معروف آن Amazon S3، یک Object Storage کارآمدی هست که کل دنیا ازش استفاده می‌کنند. حالا اصلاً Object Storage چی هست ؟ به زبان ساده میشه گفت به جای اینکه شما به روش ساده فایل‌هایی مثل تصاویر، صوت و غیره رو ذخیره کنید، فایل را به عنوان یک Object در نظر می‌گیرید که در این صورت می‌توانید یک سری Metadata با آن فایل ذخیره نمایید، همچنین در این روش ذخیره‌سازی، می‌توان با استفاده از روش‌هایی عمل خواندن و نوشتن را به صورت توزیع شده پیاده‌سازی کرد، پس میشه گفت کاری پیچیده‌ای هست ! حالا این Metadata چی هست و به چه استفاده‌ای دارد ؟ در واقع Metadata یک Key/Value هست که می‌توانید به صورت string مقادیری را به همراه فایل در قالب یک Object ذخیره کنید، به عنوان مثال هنگامی که کاربری تصویری را آپلود می‌کند، می‌توان userID کاربر را داخل metadata گذاشت و بعد فایل را در قالب Object ذخیره کرد، در این حالت هنگامی که کاربر درخواست حذف فایل را داشته باشد، قبل از حذف می‌توان چک کرد که آیا این کاربر اجازه‌ی چنین کاری را دارد یا نه !؟ پس یکی از مشکلات همیشگی حل شد، حالا شما فکر کنید چنین قابلیتی چه کاربردهایی می‌تواند داشته باشد !

شرکت‌های زیادی در دنیا از Cloud Storage هایی نظیر Amazon S3، Google Cloud Storage بهره می‌برند، یا خودشان از پلتفرم‌های متن‌باز نظیر OpenStack Swift استفاده می‌کنند.

استفاده از سرویس‌های ابری موجود که واسه ما ایرانی‌ها مقدور نیست، پس باید از طریق ابزارهای متن‌باز چنین امکانی را برای خومان فرآهم کنیم، راه‌اندازی OpenStack Swift هم دردسرهای فراوانی دارد، اینجاست که Minio.io به کمک ما میاد.

هدف توسعه‌دهنده Minio این بوده که شما به راحتی بتوانید یک Object Storage مثل Amazon S3 با همان استاندارد API داشته باشید، همچنین می‌خواسته فوق‌العاده سریع و جمع و جور باشه، و بتوان به راحتی راه‌ اندازی کرد، او با استفاده از زبان Go چنین ابزاری را ساخته و به صورت متن‌باز منتشر کرده. واسه اطلاعات بیشتر می‌توانید این ویدیو را ببینید.

راه‌اندازی minio فوق‌العاده راحت هست، کل آن یک باینری فایل هست و شما می‌توانید روی پلتفرم‌های مختلف اجرایش کنید. مثلاً برای اجرای آن روی مک کافیه شما از طریق brew آن را نصب و بعد یه دایرکتوری برایش در نظر بگیرید:

brew install minio
minio server ~/Photos

همچنین می‌توانید از طریق داکر minio را اجرا کنید:

docker pull minio/minio
docker run -p 9000:9000 minio/minio server /export

بعد از اجرا به صورت تصادفی یک AccessKey و SecretKey اختصاص می‌دهد.

شما می‌توانید از طریق کامندلاین به minio دسترسی داشته باشید.

go get -u github.com/minio/mc
mc config host add minio http://127.0.0.1 AccessKey SecretKey Region

و یا از طریق مرورگر می‌توانید به آن دسترسی داشته باشید، Bucket بسازید، فایل آپلود کنید و یا برای Bucket ها یک سری Policy تعریف کنید. ( جالبه بدانید کلاینت وب آن با ReactJS نوشته شده، دلیل حجم بالای باینری minio هم همین فایل‌های کلاینت وب‌ آن است، که از طریق ابزار go-bindata به صورت واحد در یک باینری فایل bind شده.)

در این پست می‌خواهیم یک وب‌سرور ساده با زبان Go بنویسیم که یک فرم دریافت فایل داشته باشد، تصویر آپلود شده را گرفته و بعد در minio ذخیره نماید. دقیقاً کاری که شما می‌توانید با Amazon S3 انجام دهید، چرا که minio یک API مشابه Amazon S3 برای شما فرآهم کرده است !

در مثال زیر با استفاده از یک وب سرور ساده یک Handler نوشته شده که دو متد POST و GET را هندل کند.

package main

import (
        "fmt"
        "log"
        "net/http"

        "github.com/minio/minio-go"
)

func main() {

        endpoint := "127.0.0.1:9000"
        accessKeyID := "PH1Z8A1VR1IVHC93QAMT"
        secretAccessKey := "6pp2bahfp/1t2V+mASiOb+OEpHxhxgRLa2pHreA/"
        useSSL := false

        // Initialize minio client object.
        mClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL)
        if err != nil {
                log.Fatalln(err)
        }
        // initial MinioTest type
        m := MinioTest{
                MinioClient:   mClient,
                PublicBaseURL: fmt.Sprintf("http://%s", endpoint),
        }
        // handle upload file at "/" route
        http.HandleFunc("/", m.UploadFile)
        http.ListenAndServe(":8080", nil)
}

// MinioTest type with MinioClient
type MinioTest struct {
        MinioClient   *minio.Client
        PublicBaseURL string
}

// UploadFile upload file
func (m *MinioTest) UploadFile(w http.ResponseWriter, req *http.Request) {
        // we can use from html template !
        s := `<form action="/" method="post" enctype="multipart/form-data">
        upload a file<br>
        <input type="file" name="file"><br>
        <input type="submit">
        </form>
        <br>
        <br>`
        // set text/html header as contentType to see it in browser
        w.Header().Set("CONTENT-TYPE", "text/html; charset=UTF-8")

        // check the request method
        if req.Method == http.MethodPost {
                // get file from form
                f, _, err := req.FormFile("file")
                if err != nil {
                        log.Println(err)
                        http.Error(w, "error uploading file", http.StatusInternalServerError)
                        return
                }
                defer f.Close()

                // NOTE: already create bucket named 'images' via browser :) and set ReadOnly policy for this bucket

                objectName := "newfile.jpeg"
                bucketName := "images"

                // NOTE: since we have ReadOnly policy with '*' prefix in images bucket can serve image via public url
                publicURL := fmt.Sprintf("%s/%s/%s", m.PublicBaseURL, bucketName, objectName)

                // File have implement Read(p []byte) (n int, err error) method so we can use it as io.Reader
                // method signature PutObject(bucketName, objectName string, reader io.Reader, contentType string)(n int64, err error)
                n, err := m.MinioClient.PutObject(bucketName, objectName, f, "image/jpeg")
                if err != nil {
                        log.Fatalln(err)
                        http.Error(w, "error on put object to minio", http.StatusInternalServerError)
                }
                // NOTE: to save metadata with object should use from PutObjectWithMetadata
                // PutObjectWithMetadata(bucketName, objectName string, reader io.Reader, metaData map[string][]string, progress io.Reader) (n int64, err error)

                log.Printf("successfully uploaded - size %d", n)
                // put it into response as image
                fmt.Fprintf(w, s+`
                        <img src="%s" />
                `, publicURL)
                return
        }
        // write s to ResponseWriter in GET method
        fmt.Fprintf(w, s)
}

*داکیونت فوق‌العاده خوبی داره.

پس از طریق minio می‌توانید به راحتی یک Object Storage مثل Amazon S3 روی سیستم شخصی و یا سرور خودتان داشته باشد. نکته‌ی آخر اینکه سرویسی وطنی به نام مکعب توسط بچه‌های ایرانی توسعه داده شده، تازه اول راه هستند و قابلیت‌های یک Object Storage کامل رو نداره، ولی روند رشد آن امیدوار کنندست، حتی در حال حاضر یک API ساده برای آن توسعه داده‌اند.