Tìm kiếm


    Dựng Cloud Storage lưu trữ file với MinIO


    Giới thiệu về MinIO

    MinIO là một dự án mã nguồn mở cung cấp một giải pháp lưu trữ đám mây phi tập trung và mở rộng, được xây dựng dựa trên cơ sở của Amazon S3 (Simple Storage Service).

    Với MinIO, bạn có thể tạo ra một hệ thống lưu trữ dữ liệu mạnh mẽ, linh hoạt và có khả năng mở rộng mà không phải trả phí cho các dịch vụ lưu trữ đám mây thương mại.

    Dưới đây là một số đặc điểm chính của MinIO:

    1. Mã nguồn mở:

    MinIO là một dự án mã nguồn mở được phát triển dành riêng cho việc xây dựng hệ thống lưu trữ đám mây.

    Điều này có nghĩa là bạn có thể tận dụng mã nguồn mở để tùy chỉnh và triển khai MinIO theo nhu cầu của mình mà không cần phải phụ thuộc vào các giải pháp thương mại.

    2. Tương thích với Amazon S3:

    MinIO được xây dựng với mục tiêu tương thích hoàn toàn với giao diện lập trình ứng dụng (API) của Amazon S3.

    Điều này có nghĩa là bạn có thể sử dụng các công cụ, thư viện và ứng dụng hiện có đã được phát triển cho Amazon S3 mà không cần thay đổi.

    3. Độ mở rộng và hiệu suất cao:

    MinIO được thiết kế để có khả năng mở rộng tuyến tính và đạt được hiệu suất cao, giúp bạn xử lý một lượng lớn dữ liệu một cách dễ dàng và hiệu quả. Hệ thống MinIO có thể mở rộng từ một máy chủ đơn giản đến hàng trăm máy chủ, giữ cho hiệu suất ổn định và đáng tin cậy.

    4. Hỗ trợ cho nhiều loại dữ liệu:

    Ngoài việc lưu trữ các tệp tin, MinIO cũng hỗ trợ việc lưu trữ dữ liệu phi cấu trúc như dữ liệu đối tượng và dữ liệu truy cập ngẫu nhiên (NoSQL).

    Điều này mở ra nhiều cơ hội cho việc xây dựng các ứng dụng lưu trữ và xử lý dữ liệu đa dạng.

    MinIO cung cấp một giải pháp lưu trữ dữ liệu đám mây mạnh mẽ và linh hoạt, phù hợp cho cả các doanh nghiệp lớn và các dự án khởi nghiệp.

    Túm cái váy lại thì chúng ta sẽ dụng một Cloud Storage gần giống với Google Cloud, Microsoft Azure Storage để lưu trữ file hoàn toàn Free :D

    Và trong bài viết này, chúng ta sẽ tìm hiểu cách triển khai và sử dụng MinIO bằng Docker, cũng như tích hợp nó vào ứng dụng ASP.NET.

    Kiến thức nên có trước khi cài đặt

    Trước khi đi vào việc Setup thì các bạn nên biết các kiến thức sau:

    2. Docker

    3. Nginx

    Triển khai MinIO bằng Docker

    Bước 1: Cài đặt Docker

    Đầu tiên, bạn cần cài đặt Docker trên hệ thống của mình. Điều này có thể thực hiện thông qua trang web chính thức của Docker.

    Hướng dẫn cài đặt Docker trên Ubuntu hoặc với Windows tại https://docs.docker.com/desktop/install/windows-install/

    Bước 2: Tạo Docker Compose File & Nginx Config File

    Tạo file docker-compose.yml

    version: "3.8"
    
    x-minio-common: &minio-common
      image: quay.io/minio/minio
      command: server --console-address ":9001" http://minio{1...4}/data{1...2}
      expose:
        - "9000"
        - "9001"
      environment:
        MINIO_ROOT_USER: minioadmin
        MINIO_ROOT_PASSWORD: minioadmin
      healthcheck:
        test: ["CMD", "mc", "ready", "local"]
        interval: 5s
        timeout: 5s
        retries: 5
    
    services:
      minio1:
        <<: *minio-common
        hostname: minio1
        volumes:
          - data1-1:/data1
          - data1-2:/data2
    
      minio2:
        <<: *minio-common
        hostname: minio2
        volumes:
          - data2-1:/data1
          - data2-2:/data2
    
      minio3:
        <<: *minio-common
        hostname: minio3
        volumes:
          - data3-1:/data1
          - data3-2:/data2
    
      minio4:
        <<: *minio-common
        hostname: minio4
        volumes:
          - data4-1:/data1
          - data4-2:/data2
    
      nginx:
        image: nginx:1.19.2-alpine
        hostname: nginx
        volumes:
          - ./nginx.conf:/etc/nginx/nginx.conf:ro
        ports:
          - "9010:9000"
          - "9011:9001"
        depends_on:
          - minio1
          - minio2
          - minio3
          - minio4
    
    volumes:
      data1-1:
      data1-2:
      data2-1:
      data2-2:
      data3-1:
      data3-2:
      data4-1:
      data4-2:

    Tạo file nginx.conf

    user  nginx;
    worker_processes  auto;
    
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    
    events {
        worker_connections  4096;
    }
    
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
    
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        access_log  /var/log/nginx/access.log  main;
        sendfile        on;
        keepalive_timeout  65;
    
        # include /etc/nginx/conf.d/*.conf;
    
        upstream minio {
            server minio1:9000;
            server minio2:9000;
            server minio3:9000;
            server minio4:9000;
        }
    
        upstream console {
            ip_hash;
            server minio1:9001;
            server minio2:9001;
            server minio3:9001;
            server minio4:9001;
        }
    
        server {
            listen       9000;
            listen  [::]:9000;
            server_name  localhost;
    
            # To allow special characters in headers
            ignore_invalid_headers off;
            # Allow any size file to be uploaded.
            # Set to a value such as 1000m; to restrict file size to a specific value
            client_max_body_size 0;
            # To disable buffering
            proxy_buffering off;
            proxy_request_buffering off;
    
            location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
    
                proxy_connect_timeout 300;
                # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
                proxy_http_version 1.1;
                proxy_set_header Connection "";
                chunked_transfer_encoding off;
    
                proxy_pass http://minio;
            }
        }
    
        server {
            listen       9001;
            listen  [::]:9001;
            server_name  localhost;
    
            # To allow special characters in headers
            ignore_invalid_headers off;
            # Allow any size file to be uploaded.
            # Set to a value such as 1000m; to restrict file size to a specific value
            client_max_body_size 0;
            # To disable buffering
            proxy_buffering off;
            proxy_request_buffering off;
    
            location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-NginX-Proxy true;
    
                # This is necessary to pass the correct IP to be hashed
                real_ip_header X-Real-IP;
    
                proxy_connect_timeout 300;
                
                # To support websocket
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                
                chunked_transfer_encoding off;
    
                proxy_pass http://console;
            }
        }
    }

    Trong file mình tạo ra 4 cụm server MinIO sau đó sử dụng Nginx để Reverse Proxy và Load Balancing cho server trong trường hợp quá tải. Chi tiết các bạn được kỹ 2 file trên nhé

    Bước 3: Triển khai MinIO

    Mở terminal và chạy lệnh sau trong thư mục chứa docker-compose.yml

    docker-compose up -d

    Sử dụng MinIO trên giao diện người dùng

    Truy cập vào http://localhost:9011 từ trình duyệt của bạn và sử dụng thông tin đăng nhập trong file docker-compose (minioadmin/minioadmin) để truy cập vào giao diện quản lý MinIO.

    Sử dụng API MinIO

    Đối với việc sử dụng API chúng ta sử dụng http://localhost:9010 và cần tạo Access Key + Secret Key trong trang giao diện ở trên (Chi tiết bên dưới bài viết)

    Test hàng MinIO

    Truy cập vào http://localhost:9011 các bạn sẽ thấy giao diện VIP Pro của nó

    Chúng ta sẽ từ Upload 1 file lên xem sao nhé.

    Đầu tiên thì bạn cần tại 1 Bucket nó giống như tạo folder mới đó :D

    Điền tên Bucket và bấm Create

    Upload thử một vài file và nó cứ phải gọi là ngon lành cành đào

    Một vài tùy chọn khác

    Sử dụng MinIO Client SDK cho .NET

    Tiếp theo đây mình sẽ hướng dẫn các bạn sử dụng MinIO Client SDK với ASP.NET

    1. Tạo Access Key và Access Token

    Trước khi sử dụng API thì các bạn cần tạo ra Access KeyAccess Token như sau:

    2. Cài đặt MinIO package từ NuGet

    PM> Install-Package Minio

    Sử dụng với ASP.NET (Website)

    Sử dụng AddMinio để thêm Minio vào ServiceCollection của bạn

    var endpoint = "localhost:9010";
    var accessKey = "RRAw0wUURAxI23mnvPpE";
    var secretKey = "si1qRLfyFvQoBSqeLJGjoUn6RdBJYo3a7SvABuJb";
    var secure = false;
    
    builder.Services.AddMinio(configureClient => configureClient
                .WithEndpoint(endpoint)
                .WithCredentials(accessKey, secretKey)
                .WithSSL(secure)
                .Build());
    
    builder.Services.AddScoped<IMinIOCloudService, MinIOCloudService>();

    Các bạn lưu ý là endpoint ở đây không phải là cổng giao diện của MinIO nhé. Mình đã nói ở trên rồi bạn nào lỡ chưa đọc kỹ thì xem lại bên trên nhé!

    Option WithSSL mình sẽ để false trường hợp api các bạn config yêu cầu ssl thì để true

    Mình sẽ có model và interface như sau:

    Model

     public class MinIOCloudModel
     {
         public string FolderName { get; set; } = string.Empty;
         public string OriginalFileName { get; set; } = string.Empty;
         public string FileName { get; set; } = string.Empty;
         public long FileSize { get; set; }
         public string ContentType { get; set; } = string.Empty;
         public string PublicUrl { get; set; } = string.Empty;
     }

    Interface

    public interface IMinIOCloudService
    {
        Task<List<MinIOCloudModel>> UploadFileAsync(List<IFormFile> files, string bucketName, bool isPublicBucket = false);
        Task<Stream> DownloadFileAsync(string bucketName, string objectName);
        Task<string> GetShareLinkAsync(string bucketName, string objectName, int expireTime);
    }

    Code implement interface:

    public class MinIOCloudService : IMinIOCloudService
    {
        private readonly IMinioClient _minioClient;
        private readonly string _endPoint;
    
        public MinIOCloudService(IMinioClient minioClient)
        {
            _minioClient = minioClient;
            _endPoint = _minioClient.Config.Endpoint;
        }
    
        public async Task<List<MinIOCloudModel>> UploadFileAsync(List<IFormFile> files, string bucketName, bool isPublicBucket = false)
        {
            var fileResponse = new List<MinIOCloudModel>();
    
            try
            {
                if (files.Count <= 0) return fileResponse;
    
                var isExists = await _minioClient.BucketExistsAsync(new BucketExistsArgs().WithBucket(bucketName));
    
                if (!isExists)
                {
                    await _minioClient.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucketName));
                    if (isPublicBucket)
                    {
                        // Define public policy for the bucket
                        string publicReadPolicy = @"
                                                    {
                                                        ""Version"": ""2012-10-17"",
                                                        ""Statement"": [
                                                            {
                                                                ""Effect"": ""Allow"",
                                                                ""Principal"": ""*"",
                                                                ""Action"": ""s3:GetObject"",
                                                                ""Resource"": ""arn:aws:s3:::" + bucketName + @"/*""
                                                            }
                                                        ]
                                                    }";
    
                        var policyArgs = new SetPolicyArgs()
                            .WithPolicy(publicReadPolicy)
                            .WithBucket(bucketName);
                        await _minioClient.SetPolicyAsync(policyArgs);
                    }
                }
    
                foreach (var file in files)
                {
                    using var stream = new MemoryStream();
    
                    await file.CopyToAsync(stream);
    
                    stream.Position = 0;
    
                    var objectName = DateTime.Now.Ticks + "_" + new string(file.FileName.Where(c => !char.IsWhiteSpace(c)).ToArray());
    
                    var putObjectArgs = new PutObjectArgs()
                        .WithBucket(bucketName)
                        .WithObject(objectName)
                        .WithStreamData(stream)
                        .WithObjectSize(stream.Length)
                        .WithContentType(file.ContentType);
    
                    await _minioClient.PutObjectAsync(putObjectArgs);
    
                    var statObject =
                        await _minioClient.StatObjectAsync(
                            new StatObjectArgs()
                                .WithBucket(bucketName)
                                .WithObject(objectName));
    
                    if (!string.IsNullOrEmpty(statObject.ObjectName))
                    {
                        
                        fileResponse.Add(new MinIOCloudModel()
                        {
                            FolderName = bucketName,
                            OriginalFileName = file.FileName,
                            FileName = statObject.ObjectName,
                            FileSize = statObject.Size,
                            ContentType = statObject.ContentType,
                            PublicUrl = isPublicBucket ? $"{_endPoint}/{bucketName}/{statObject.ObjectName}" : string.Empty
                        });
                    }
                }
    
                return fileResponse;
            }
            catch (MinioException e)
            {
                throw new Exception(e.Message);
            }
        }
    
        public async Task<Stream> DownloadFileAsync(string bucketName, string objectName)
        {
            try
            {
                var memoryStream = new MemoryStream();
    
                var arg = new StatObjectArgs()
                    .WithBucket(bucketName)
                    .WithObject(objectName);
    
                var statObject = await _minioClient.StatObjectAsync(arg);
                if (!string.IsNullOrEmpty(statObject.ObjectName))
                {
                    var getObjectArgs = new GetObjectArgs()
                        .WithBucket(bucketName)
                        .WithObject(objectName)
                        .WithCallbackStream((stream) => { stream.CopyTo(memoryStream); });
    
                    await _minioClient.GetObjectAsync(getObjectArgs);
                }
    
                memoryStream.Position = 0;
    
                return memoryStream;
            }
            catch (MinioException e)
            {
                throw new Exception(e.Message);
            }
        }
    
        public async Task<string> GetShareLinkAsync(string bucketName, string objectName, int expireTime)
        {
            try
            {
                var args = new PresignedGetObjectArgs()
                                      .WithBucket(bucketName)
                                      .WithObject(objectName)
                                      .WithExpiry(expireTime * 60);
    
                return await _minioClient.PresignedGetObjectAsync(args);
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
        }
    }

    Kết

    Cảm ơn bạn đã đọc bài viết của mình về việc triển khai MinIO và tích hợp nó vào ứng dụng .NET.

    Nếu bạn thấy nội dung này hữu ích, hãy chia sẻ nó với cộng đồng của bạn để giúp họ khám phá và áp dụng MinIO trong dự án của họ.

    Đồng thời, nếu bạn có bất kỳ câu hỏi hoặc ý kiến, đừng ngần ngại chia sẻ với chúng tôi.

    Cảm ơn bạn và chúc bạn một ngày tốt lành!


    Tiểu sử
    Are you one or zero?


    Bình luận