分布式对象存储minio

news2024/11/15 15:48:00

本教程minio 版本:RELEASE.2021-07-*及以上

1. 分布式文件系统应用场景

互联网海量非结构化数据的存储需求

  • 电商网站:海量商品图片
  • 视频网站:海量视频文件
  • 网盘 : 海量文件
  • 社交网站:海量图片

1.1 Minio介绍

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

官网:https://min.io/ http://www.minio.org.cn/

对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。

对于中小型企业,如果不选择存储上云,那么 Minio 是个不错的选择,麻雀虽小,五脏俱全。当然Minio 除了直接作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3、MicroSoft Azure。

在中国:阿里巴巴、腾讯、百度、中国联通、华为、中国移动等等9000多家企业也都在使用MinIO产品。

Minio优点

  • 部署简单: 一个single二进制文件即是一切,还可支持各种平台。
  • minio支持海量存储,可按zone扩展(原zone不受任何影响),支持单个对象最大5TB;
  • 兼容Amazon S3接口,充分考虑开发人员的需求和体验;
  • 低冗余且磁盘损坏高容忍,标准且最高的数据冗余系数为2(即存储一个1M的数据对象,实际占用磁盘空间为2M)。但在任意n/2块disk损坏的情况下依然可以读出数据(n为一个纠删码集合(Erasure Coding Set)中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的。
  • 读写性能优异

1.2 MinIO的基础概念

  • Object:存储到 Minio 的基本对象,如文件、字节流,Anything...
  • Bucket:用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
  • Drive:即存储数据的磁盘,在 MinIO 启动时,以参数的方式传入。Minio 中所有的对象数据都会存储在 Drive 里。
  • Set :即一组 Drive 的集合,分布式部署根据集群规模自动划分一个或多个 Set ,每个 Set 中的Drive 分布在不同位置。一个对象存储在一个 Set 上。(For example: {1...64} is divided into 4 sets each of size 16.)
    • 一个对象存储在一个Set上
    • 一个集群划分为多个Set
    • 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
    • 一个SET中的Drive尽可能分布在不同的节点上

1.3 纠删码EC(Erasure Code)

MinIO 使用纠删码机制来保证高可靠性,使用 highwayhash 来处理数据损坏( Bit Rot Protection )。

关于纠删码,简单来说就是可以通过数学计算,把丢失的数据进行还原,它可以将n份原始数据,增加m份数据,并能通过n+m份中的任意n份数据,还原为原始数据。即如果有任意小于等于m份的数据失效,仍然能通过剩下的数据还原出来。

1.4 存储形式

文件对象上传到 MinIO ,会在对应的数据存储磁盘中,以 Bucket 名称为目录,文件名称为下一级目录,文件名下是 part.1 和 xl.meta(老版本,最新版本如下图),前者是编码数据块及检验块,后者是元数据文件。

linux 在指定目录下执行 tree 命令(yum install tree 先安装下)

1.5 存储方案

2. Minio环境搭建

官方文档:https://docs.min.io/docs/

中文文档:http://docs.minio.org.cn/docs/ (没有及时更新,容易被坑)

minio支持多种server启动模式:

2.0 官方推荐

磁盘文件格式

官方文档推荐磁盘文件格式使用 xfs

多磁盘

磁盘需要保持一致

  1. 文件格式一致
  2. 容量一致
  3. 每个区使用偶数数量的磁盘,便于纠删码模式
  4. 每个区的磁盘数量需要一致,所以扩展后新的区和原始区的磁盘的数量、容量、格式都是一致的

扩展

官方文档:Expand a Distributed MinIO Deployment — MinIO Object Storage for Linux

中文文档:扩展一个分布式MinIO部署 — MinIO中文文档 | MinIO Linux中文文档

2.1 单机部署

minio server的standalone模式,即要管理的磁盘都在host本地。该启动模式一般仅用于实验环境、测试环境的验证和学习使用。在standalone模式下,还可以分为non-erasure code mode和erasure code mode。

non-erasure code mode

在此启动模式下,对于每一份对象数据,minio直接在data下面存储这份数据,不会建立副本,也不会启用纠删码机制。因此,这种模式无论是服务实例还是磁盘都是“单点”,无任何高可用保障,磁盘损坏就表示数据丢失。

erasure code mode

此模式为minio server实例传入多个本地磁盘参数。一旦遇到多于一个磁盘参数,minio server会自动启用erasure code mode。erasure code对磁盘的个数是有要求的,如不满足要求,实例启动将失败。 erasure code启用后,要求传给minio server的endpoint(standalone模式下,即本地磁盘上的目录)至少为4个。

基于 centos

操作系统CPU 架构地址
GNU/Linux64-bit Intelhttp://dl.minio.org.cn/server/minio/release/linux-amd64/minio
wget -q http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
chmod +x minio
#启动minio server服务,指定数据存储目录/mnt/data
./minio server /mnt/data

默认用户名密码minioadmin:minioadmin,修改默认用户名密码可以使用:

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678

默认的配置目录是 ${HOME}/.minio (比如root 用户则配置目录在 /root/.minio),可以通过--config-dir命令自定义配置目录:

./minio server --config-dir /mnt/config /mnt/data

控制台监听端口是动态生成的,可以通过--console-address ":port"指定静态端口

./minio server --console-address ":50000" /mnt/data

访问minio控制台: http://192.168.3.14:50000/dashboard

基于docker

docker run -d -p 9000:9000 --name minio \   -v /mnt/data:/data \   -v /mnt/config:/root/.minio \   minio/minio server /data

存在问题: 浏览器无法访问minio控制台,因为没有对外暴露控制台端口

对外暴露minio控制台的端口,通过--console-address ":50000"指定控制台端口为静态端口

docker run -p 9000:9000 -p 50000:50000 --name minio \   -v /mnt/data:/data \   -v /mnt/config:/root/.minio \   minio/minio server --console-address ":50000" /data

MinIO自定义用户名密码

docker run -d -p 9000:9000 -p 50000:50000 --name minio \     -e "MINIO_ROOT_USER=admin" \     -e "MINIO_ROOT_PASSWORD=12345678" \     -v /mnt/data:/data \     -v /mnt/config:/root/.minio \     minio/minio server --console-address ":50000" /data

访问minio控制台: http://192.168.3.14:50000/

基于 docker-compose

docker-compose.yml

version: '3.5'

services:
  minio:
    container_name: minio
    restart: always
    image: minio/minio
    ports:
      - "9000:9000"
      - "50000:50000"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: yh@miniIOtest
    volumes:
      - ./data:/data
      - ./config:/root/.minio
    command: server --console-address ":50000" /data

单机扩容

单机也是可以参考集群的方案来进行扩容,核心就是再添加一个区。

但是单节点扩展后随着数据量的增加对于性能必然时会有影响的(我猜的),毕竟它不像分布式的扩展是由新节点来管理扩展容量的。

如果是那种一开始磁盘不够大导致容量满了,后续倒是可以进行单机扩容的。

docker-compose.yml

version: '3.7'

# Settings and configurations that are common for all containers
x-minio-common: &minio-common
  image: quay.io/minio/minio:RELEASE.2024-03-15T01-07-19Z
  command: server --console-address ":50000" http://minio1/data{1...2}  # http://minio1/data{3...4}
  ports:
    - "9000:9000"
    - "50000:50000"
#  environment:
    # MINIO_ROOT_USER: minioadmin
    # MINIO_ROOT_PASSWORD: minioadmin
  healthcheck:
    test: ["CMD", "mc", "ready", "local"]
    interval: 5s
    timeout: 5s
    retries: 5

# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
  minio1:
    <<: *minio-common
    hostname: minio1
    volumes:
      - ./data-test2-1:/data1
      - ./data-test2-2:/data2
      #- ./data-test2-3:/data3
      #- ./data-test2-4:/data4
      - ./config-test2:/root/.minio

docker-compse 启动容器 -> 访问后台 -> 添加 bucket -> 上传文件

docker-compose down 关闭容器 -> 将 docker-compose.yml 中的 http://minio1/data{3...4} 和 volumes 中的 data3 和 data4 放出来 -> 重新启动容器 -> 访问后台 -> 上传文件

可以看到新加的磁盘 data3和data4(一个区) 中的文件和 data1\data2(另一个区) 是不同的,说明实现了扩容

2.2 minio纠删码模式

Minio使用纠删码 erasure code 和校验和 checksum 来保护数据免受硬件故障和无声数据损坏。 即便您丢失一半数量(N/2)的硬盘,您仍然可以恢复数据。

纠删码是一种恢复丢失和损坏数据的数学算法, Minio采用Reed-Solomon code将对象拆分成N/2数据和N/2 奇偶校验块。 这就意味着如果是12块盘,一个对象会被分成6个数据块、6个奇偶校验块,你可以丢失任意6块盘(不管其是存放的数据块还是奇偶校验块),你仍可以从剩下的盘中的数据进行恢复。

DATA BLOCK:数据块,针对的是对象,1个BLOCK 10M,当对象大小超过 10M 的时候就拆分存放在 DATABLOCK 中

上传文件(小于1M、大于 1M),查看编码文件的情况,可以看到当对象大于 1M 时才会有 part.1 这个编码文件

使用Minio Docker镜像,在8块盘中启动Minio服务:

docker run -d -p 9000:9000 -p 50000:50000 --name minio \
    -v /mnt/data1:/data1 \
    -v /mnt/data2:/data2 \
    -v /mnt/data3:/data3 \
    -v /mnt/data4:/data4 \
    -v /mnt/data5:/data5 \
    -v /mnt/data6:/data6 \
    -v /mnt/data7:/data7 \
    -v /mnt/data8:/data8 \
    minio/minio server /data{1...8} --console-address ":50000"

2.3 分布式集群部署

分布式 Minio 可以让你将多块硬盘(甚至在不同的机器上)组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。

分布式存储可靠性常用方法

分布式存储,很关键的点在于数据的可靠性,即保证数据的完整,不丢失,不损坏。只有在可靠性实现的前提下,才有了追求一致性、高可用、高性能的基础。而对于在存储领域,一般对于保证数据可靠性的方法主要有两类,一类是冗余法,一类是校验法。

冗余

冗余法最简单直接,即对存储的数据进行副本备份,当数据出现丢失,损坏,即可使用备份内容进行恢复,而副本备份的多少,决定了数据可靠性的高低。这其中会有成本的考量,副本数据越多,数据越可靠,但需要的设备就越多,成本就越高。可靠性是允许丢失其中一份数据。当前已有很多分布式系统是采用此种方式实现,如 Hadoop 的文件系统(3个副本),Redis 的集群,MySQL 的主备模式等。

校验

校验法即通过校验码的数学计算的方式,对出现丢失、损坏的数据进行校验、还原。注意,这里有两个作用,一个校验,通过对数据进行校验和( checksum )进行计算,可以检查数据是否完整,有无损坏或更改,在数据传输和保存时经常用到,如 TCP 协议;二是恢复还原,通过对数据结合校验码,通过数学计算,还原丢失或损坏的数据,可以在保证数据可靠的前提下,降低冗余,如单机硬盘存储中的 RAID技术,纠删码(Erasure Code)技术等。MinIO 采用的就是纠删码技术。

分布式Minio优势

数据保护

分布式Minio采用 纠删码来防范多个节点宕机和位衰减 bit rot 。

分布式Minio至少需要4个硬盘,使用分布式Minio自动引入了纠删码功能。

高可用

单机Minio服务存在单点故障,相反,如果是一个有N块硬盘的分布式Minio,只要有N/2硬盘在线,你的数据就是安全()的。不过你需要至少有N/2+1个硬盘来创建(写)新的对象。

例如,一个16节点的Minio集群,每个节点16块硬盘,就算8台服務器宕机,这个集群仍然是可读的,不过你需要9台服務器才能写数据。

一致性

Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。

运行分布式Minio

官方文档:部署 MinIO:多节点多驱动器 — 适用于 Linux 的 MinIO 对象存储

启动一个分布式Minio实例,你只需要把硬盘位置做为参数传给minio server命令即可,然后,你需要在所有其它节点运行同样的命令。

  • 分布式Minio里所有的节点需要有同样的access秘钥和secret秘钥,这样这些节点才能建立联接。为了实现这个,你需要在执行minio server命令之前,先将access秘钥和secret秘钥export成环境变量。新版本使用MINIO_ROOT_USER&MINIO_ROOT_PASSWORD。
  • 分布式Minio使用的磁盘里必须是干净的,里面没有数据。
  • 下面示例里的IP仅供示例参考,你需要改成你真实用到的IP和文件夹路径。
  • 分布式Minio里的节点时间差不能超过3秒,你可以使用 NTP 来保证时间一致。
  • 在Windows下运行分布式Minio处于实验阶段,请悠着点使用。

8个节点,每节点1块盘

启动分布式Minio实例,8个节点,每节点1块盘,需要在8个节点上都运行下面的命令:

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
# http://192.168.1.11/export1 这些是磁盘挂载路径
minio server http://192.168.1.11/export1 http://192.168.1.12/export2 \
                http://192.168.1.13/export3 http://192.168.1.14/export4 \
                http://192.168.1.15/export5 http://192.168.1.16/export6 \
                http://192.168.1.17/export7 http://192.168.1.18/export8

4节点,每节点4块盘

启动分布式Minio实例,4节点,每节点4块盘,需要在4个节点上都运行下面的命令

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
minio server http://192.168.1.11/export1 http://192.168.1.11/export2 \
        http://192.168.1.11/export3 http://192.168.1.11/export4 \
        http://192.168.1.12/export1 http://192.168.1.12/export2 \
        http://192.168.1.12/export3 http://192.168.1.12/export4 \
        http://192.168.1.13/export1 http://192.168.1.13/export2 \
        http://192.168.1.13/export3 http://192.168.1.13/export4 \
        http://192.168.1.14/export1 http://192.168.1.14/export2 \
        http://192.168.1.14/export3 http://192.168.1.14/export4 

通过脚本方式启动

#!/bin/bash
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
MINIO_HOME=/root
MINIO_HOST=192.168.0.105
for i in {01..04}; do
        # --address ":9001" 这个选项指定了MinIO服务器监听的地址和端口(就是 api 的端口 - 原 9000)
        # http://${MINIO_HOST}:9001/mnt/export01 这块是磁盘挂载路径
        nohup ${MINIO_HOME}/minio server --address ":90${i}" --console-address ":500${i}" http://${MINIO_HOST}:9001/mnt/export01 http://${MINIO_HOST}:9002/mnt/export02 > ${MINIO_HOME}/minio-90${i}.log 2>&1 &
done

执行脚本后查看日志和进程

但是我这里因为都是在同一台机器操作的原因,所以启动报错了:Unable to use the drive http://192.168.0.105:9001/mnt/export1: drive is part of root drive, will not be used,原因是:

minio 集群部署被强制性安装在独占的磁盘分区,不能在 root 根盘符下建立目录

docker-compose 部署集群

要在Docker Compose上部署分布式MinIO,请下载 docker-compose.yaml 和 nginx.conf 到你当前的工作目录。

我这里就用了单机来模拟集群部署,多机的话(还是 4节点2硬盘,加入现是2台机器),docker-compose.yml 就得修改成只有2个minio 容器,http://minio{1...4}/data{1...2} 这块也得修改,minio 是推荐使用 hostname 的方式的,添加 hosts 映射,具体还得测试怎么配(我这里就不去测试了)。

实际生产环境看情况到底要不要用 docker-compose 方式吧。

docker-compose.yml

version: '3.7'

# Settings and configurations that are common for all containers
x-minio-common: &minio-common
  image: quay.io/minio/minio:RELEASE.2024-03-30T09-41-56Z
  command: server --console-address ":50000" http://minio{1...4}/data{1...2} # http://minio{5...8}/data{1...2} # minio5~8 用于分布式扩展测试 - 以区的方式进行扩展
  expose:
    - "9000"
    - "50000"
  # environment:
    # MINIO_ROOT_USER: minioadmin
    # MINIO_ROOT_PASSWORD: minioadmin
  healthcheck:
    test: ["CMD", "mc", "ready", "local"]
    interval: 5s
    timeout: 5s
    retries: 5

# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
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
 # minio5 ~ minio8 是为了测试分布式扩展用的           
 #minio5:
  #  <<: *minio-common
  #  hostname: minio5
  #  volumes:
  #    - ./data5-1:/data1
  #    - ./data5-2:/data2
  #
  #minio6:
  #  <<: *minio-common
  #  hostname: minio6
  #  volumes:
  #    - ./data6-1:/data1
  #    - ./data6-2:/data2
  #
  #minio7:
  #  <<: *minio-common
  #  hostname: minio7
  #  volumes:
  #    - ./data7-1:/data1
  #    - ./data7-2:/data2
  #
  #minio8:
  #  <<: *minio-common
  #  hostname: minio8
  #  volumes:
  #    - ./data8-1:/data1
  #    - ./data8-2:/data2

  nginx:
    image: nginx:1.19.2-alpine
    hostname: nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "9000:9000"
      - "50000:50000"
    depends_on:
      - minio1
      - minio2
      - minio3
      - minio4
      # - minio5
      # - minio6
      # - minio7
      # - minio8

## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
# 磁盘挂载
#volumes:
#  data1-1:
#  data1-2:
#  data2-1:
#  data2-2:
#  data3-1:
#  data3-2:
#  data4-1:
#  data4-2:

nginx.conf 基于nginx 对集群进行 loadbalancer

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;
    # server minio5:9000;
        # server minio6:9000;
        # server minio7:9000;
        # server minio8:9000;
    }

    upstream console {
        ip_hash;
        server minio1:50000;
        server minio2:50000;
        server minio3:50000;
        server minio4:50000;
    # server minio5:50000;
        # server minio6:50000;
        # server minio7:50000;
        # server minio8:50000;
    }

    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       50000;
        listen  [::]:50000;
        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;
        }
    }
}

执行 docker-compose 命令输出类似如下:

访问控制台查看 metrics 信息

测试上传

可以看到上传后,集群每个节点数据都是一致的

扩展现有的分布式集群

例如我们是通过区的方式启动MinIO集群,命令行如下:

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
minio server http://host{1...32}/export{1...32}

MinIO支持通过命令,指定新的集群来扩展现有集群(纠删码模式),命令行如下:

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
# 推荐使用 go 的这种扩展方式的写法
# 问题:http://ip:port/export1 这种写死 ip 的方式,扩展时再将新的ip:port/export 加进来能实现扩展吗?? ===> 待测试
minio server http://host{1...32}/export{1...32}
                http://host{33...64}/export{1...32}

现在整个集群就扩展了1024个磁盘,总磁盘变为2048个,新的对象上传请求会自动分配到最少使用的集群上。通过以上扩展策略,您就可以按需扩展您的集群。重新配置后重启集群,会立即在集群中生效,并对现有集群无影响。如上命令中,我们可以把原来的集群看做一个,新增集群看做另一个,新对象按每个区域中的可用空间比例放置在区域中。在每个区域内,基于确定性哈希算法确定位置。

说明: 您添加的每个区域必须具有与原始区域相同的磁盘数量(纠删码集)大小,以便维持相同的数据冗余SLA。 例如,第一个区有8个磁盘,您可以将集群扩展为16个、32个或1024个磁盘的区域,您只需确保部署的SLA是原始区域的倍数即可。

docker-compose 中扩展

将上面的 docker-compose.yml 和 nginx.conf 的 minio5~minio8 放开,重新启动。

访问控制台,查看 metric 信息:

测试上传

我们可以看到,新添加的集群(区)和旧的集群数据是不同的(合起来组成全部数据),新上传的对象会优先往空闲空间较多的集群添加

2.4 minio 客户端使用

MinIO Client (mc)为ls,cat,cp,mirror,diff,find等UNIX命令提供了一种替代方案。它支持文件系统和兼容Amazon S3的云存储服务(AWS Signature v2和v4)。

ls         列出文件和文件夹。
mb         创建一个存储桶或一个文件夹。
cat         显示文件和对象内容。
pipe         将一个STDIN重定向到一个对象或者文件或者STDOUT。
share         生成用于共享的URL。
cp         拷贝文件和对象。
mirror         给存储桶和文件夹做镜像。
find         基于参数查找文件。
diff         对两个文件夹或者存储桶比较差异。
rm         删除文件和对象。
events         管理对象通知。
watch         监视文件和对象的事件。
policy         管理访问策略。
config         管理mc配置文件。
update         检查软件更新。
version     输出版本信息。

部署客户端mc

平台CPU架构URL
GNU/Linux64-bit Intelhttp://dl.minio.org.cn/client/mc/release/linux-amd64/mc
wget http://dl.minio.org.cn/client/mc/release/linux-amd64/mc 
chmod +x mc 
./mc --help 
mv mc /usr/local/sbin/ 
平台CPU架构URL
MicrosoftWindows64-bit Intelhttp://dl.minio.org.cn/client/mc/release/windows-amd64/mc.exe

配置mc

mc 将所有的配置信息都存储在 ~/.mc/config.json 文件中

# 查询mc host配置
mc config host ls
# 添加minio服务
# 上面的集群模式由 nginx 进行负载均衡,所以这里的 url 是要用 nginx 的 url
mc config host add minio-server http://192.168.0.105:9000 admin 12345678
# 删除host
mc config host remove minio-server

mc命令使用

ls - 列出存储桶和对象mb - 创建存储桶cat - 合并对象
cp - 拷贝对象rm - 删除对象pipe - Pipe到一个对象
share - 共享mirror - 存储桶镜像find - 查找文件和对象
diff - 比较存储桶差异policy - 给存储桶或前缀设置访问策略(已改为 anonymous)
config - 管理配置文件(官方文档没找到 mc config --help 查看使用)watch - 事件监听event - 管理存储桶事件
update - 管理软件更新version - 显示版本信息

上传下载

# 查询minio服务上的所有buckets(文件和文件夹)
mc ls minio-server
# 下载文件
mc cp minio-server/tulingmall/fox/fox.jpg /tmp/
#删除文件
mc rm minio-server/tulingmall/fox/fox.jpg
#上传文件
mc cp zookeeper.out minio-server/tulingmall/
# 批量上传(目录下的所有子目录和文件都会上传上去)
mc cp --recursive /your_dir/ minio-server-name/bucketName
 

Bucket管理

# 创建bucket
mc mb minio-server/bucket01
# 删除bucket
mc rb minio-server/bucket02
# bucket不为空,可以强制删除 慎用
mc rb --force minio-server/bucket01
# 查询bucket03磁盘使用情况
mc du minio-server/bucket03

mc admin使用

MinIO Client(mc)提供了“ admin”子命令来对您的MinIO部署执行管理任务。

service        服务重启并停止所有MinIO服务器
update        更新更新所有MinIO服务器
info        信息显示MinIO服务器信息
user        用户管理用户
group        小组管理小组
policy        MinIO服务器中定义的策略管理策略
config        配置管理MinIO服务器配置
heal        修复MinIO服务器上的磁盘,存储桶和对象
profile        概要文件生成概要文件数据以进行调试
top        顶部提供MinIO的顶部统计信息
trace        跟踪显示MinIO服务器的http跟踪
console        控制台显示MinIO服务器的控制台日志
prometheus    Prometheus管理Prometheus配置
kms        kms执行KMS管理操作

 
mc admin user --help
#新建用户
mc admin user add minio-server fox
mc admin user add minio-server fox02 12345678
#查看用户
mc admin user list minio-server
#禁用用户
mc admin user disable minio-server fox02
#启用用户
mc admin user disable minio-server fox02
#查看用户信息
mc admin user info minio-server fox
#删除用户
mc admin user remove minio-server fox02

策略管理

policy命令,用于添加,删除,列出策略,获取有关策略的信息并为MinIO服务器上的用户设置策略。

mc admin policy --help
#列出MinIO上的所有固定策略
mc admin policy list minio-server
# 查看plicy信息
mc admin policy info minio-server readwrite

控制台查看每个 bucket 的访问策略

查看每个策略的规则

添加新的策略

如果这个策略的 json 不知道怎么写的话让 ChatGpt 来写

编写策略文件:/root/tulingmall.json

{
    "Version": "2012-10-17",
    "Statement": [
    {
        "Effect": "Allow",
        "Action": [
            "s3:GetBucketLocation",
            "s3:GetObject"
        ],
        "Resource": [
            "arn:aws:s3:::tulingmall"
        ]
    },
    {
        "Effect": "Allow",
            "Action": [
            "s3:*"
        ],
        "Resource": [
            "arn:aws:s3:::tulingmall/*"
        ]
    }
    ]
}
 

将 tulingmall.json 添加到策略数据库

# 添加新的策略
# add 已过期  mc admin policy add minio-server tulingmall-admin /root/tulingmall.json
mc admin policy create minio-server tulingmall-admin /root/tulingmall.json
mc admin policy list minio-server

新建用户并赋予 tulingmall-admin 策略

mc admin user add minio-server world 12345678
# 设置用户的访问策略
# set 命令已过期 --> attach  mc admin policy set minio-server tulingmall-admin user=fox03
# 指定 user 也过期了 -> --user
# 最新命令可以去官网查看
mc admin policy attach minio-server tulingmall-admin --user world 

测试:world/12345678 登录minio控制台http://192.168.3.14:50000/,只能操作tulingmall的bucket

3. springboot 整合 minio

MinIO Java Client SDK提供简单的API来访问任何与Amazon S3兼容的对象存储服务。

官方demo: https://github.com/minio/minio-java

官方文档:https://docs.min.io/docs/java-client-api-reference.htm

3.1 引入依赖

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.3.0</version>
</dependency>
<dependency>
    <groupId>me.tongfei</groupId>
    <artifactId>progressbar</artifactId>
    <version>0.5.3</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.8.1</version>
</dependency>

3.1.1 MinIO依赖冲突问题

8.3.x 会有依赖冲突的问题

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    io.minio.S3Base.<clinit>(S3Base.java:98)

The following method did not exist:

    okhttp3.RequestBody.create([BLokhttp3/MediaType;)Lokhttp3/RequestBody;

The calling method's class, io.minio.S3Base, was loaded from the following location:

    jar:file:/D:/Soft/apache-maven-3.8.4/repository/io/minio/minio/8.3.7/minio-8.3.7.jar!/io/minio/S3Base.class

The called method's class, okhttp3.RequestBody, is available from the following locations:

    jar:file:/D:/Soft/apache-maven-3.8.4/repository/com/squareup/okhttp3/okhttp/3.14.9/okhttp-3.14.9.jar!/okhttp3/RequestBody.class

原因

spring-boot 默认引入了 okhttp3依赖,两者版本不一致导致有些方法的缺失或不一致

解决

1. 在父工程pom.xml里的properties中指定 okhttp3 版本号 (不知道为啥我这样写了没用)

<!--父pom-->
<properties>
    <!-- 覆盖SpringBoot中okhttp3的旧版本声明,解决MinIO 8.3.x的依赖冲突 -->
    <okhttp3.version>4.8.1</okhttp3.version>
</properties

2. 可以的话修改 spring-boot 版本(不推荐)

3. 修改 minio 版本为 8.2.x (方案1 不行的话就用这个)

3.2 minio 配置

3.2.1 配置类

@Configuration
@EnableConfigurationProperties(MinioProperties.class)
@ConditionalOnProperty(value = "oss.name", havingValue = "minio")
public class MinioConfiguration {
 
    @Autowired
    private MinioProperties ossProperties;
 
    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(ossProperties.getEndpoint())
                .credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey())
                // 当 minio server 使用了 https,那么将该代码放出来,不然会报 ssl trust 相关的错误哦
                // .httpClient(getUnsafeOkHttpClient())
                .build();
    }
    
    public static OkHttpClient getUnsafeOkHttpClient() {
        return new OkHttpClient.Builder()
                .readTimeout(60, TimeUnit.SECONDS)
                .connectTimeout(60, TimeUnit.SECONDS)
                .sslSocketFactory(getSSLSocketFactory(), getX509TrustManager())
                .hostnameVerifier(getHostnameVerifier())
                .build();
    }
    
    
    public static SSLSocketFactory getSSLSocketFactory() {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, getTrustManager(), new java.security.SecureRandom());
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static TrustManager[] getTrustManager() {
        return new TrustManager[]{
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) {
                }
                
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) {
                }
                
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[]{};
                }
            }
        };
    }
    public static X509TrustManager getX509TrustManager() {
        X509TrustManager trustManager = null;
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
            }
            trustManager = (X509TrustManager) trustManagers[0];
        } catch (Exception e) {
            e.printStackTrace();
        }

        return trustManager;
    }


    public static HostnameVerifier getHostnameVerifier() {
        return (s, sslSession) -> true;
    }
}

3.2.2 参数配置类

@Data
@ConfigurationProperties(prefix = MinioProperties.PREFIX)
public class MinioProperties {
 
    /**
    * 配置前缀
    */
    public static final String PREFIX = "oss";
    
    /**
    * 对象存储名称
    */
    private String name;
    
    /**
    * 对象存储服务的URL
    */
    private String endpoint;
    
    /**
    * Access key 账户ID
    */
    private String accessKey;
    
    /**
    * Secret key 密码
    */
    private String secretKey;
}

3.2.3 yml 配置

oss:
  name: minio
  accessKey: 123456
  secretKey: 123456
  endpoint: http://192.168.0.105:9000

3.3 封装 minio 相关方法

public interface OssTemplate {

    /**
     * 存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return boolean
     */
    boolean bucketExists(String bucketName);


    /**
     * 获取文件信息
     *
     * @param objectName 存储桶对象名称
     * @param bucketName 存储桶名称
     * @return OssResponse
     */
    OssResponse getOssInfo(String bucketName, String objectName);

    /**
     * 上传文件
     *
     * @param folderName 上传的文件夹名称
     * @param file       上传的文件
     * @return OssResponse
     */
    OssResponse upLoadFile(String bucketName, String folderName, MultipartFile file);


    /**
     * 删除文件
     *
     * @param objectName 存储桶对象名称
     * @param bucketName 存储桶名称
     */
    boolean removeFile(String bucketName, String objectName);

    /**
     * 批量删除文件
     *
     * @param objectNames 存储桶对象名称集合
     */
    boolean removeFiles(String bucketName, List<String> objectNames);

    /**
     * @Description: 下载文件
     * @Param response: 响应
     * @Param fileName: 文件名
     * @Param filePath: 文件路径
     * @return: void
     */
    void downloadFile(HttpServletResponse response, String bucketName, String objectName);

    /**
     * 获取文件外链 - 默认过期时间为2小时
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶对象名称
     * @param expires    过期时间,-1 则时默认2小时
     * @return String
     */
    String getPresignedObjectUrl(String bucketName, String objectName, int expires);
}

实现类

@Slf4j
@Component
public class MinioTemplate implements OssTemplate {
    /**
     * MinIO客户端
     */
    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MinioProperties minioProperties;

    @Override
    public boolean bucketExists(String bucketName) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error("minio bucketExists Exception: ", e);
        }
        return false;
    }

    public void makeBucket(String bucketName) {
        try {
            if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                log.info("minio makeBucket success bucketName:{}", bucketName);
            }
        } catch (Exception e) {
            log.error("minio makeBucket Exception: ", e);
        }
    }


    @Override
    public OssResponse getOssInfo(String bucketName, String objectName) {
        try {
            // 获取对象信息
            StatObjectResponse stat = minioClient.statObject(
                    StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            // 获取文件流
            InputStream in = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build());

            Headers headers = stat.headers();
            // headers 转 map
            Map<String, List<String>> headersMap = headers.toMultimap();
            OssObjectResponse ossObjectResponse = new OssObjectResponse();
            ossObjectResponse.setEtag(stat.etag());
            ossObjectResponse.setSize(stat.size());
            ossObjectResponse.setLastModified(stat.lastModified().toInstant().toEpochMilli());
            ossObjectResponse.setContentType(stat.contentType());
            ossObjectResponse.setHeaders(headersMap);
            ossObjectResponse.setBucket(bucketName);
            ossObjectResponse.setRegion(stat.region());
            ossObjectResponse.setObject(objectName);
            ossObjectResponse.setInputStream(in);
            return ossObjectResponse;
        } catch (Exception e) {
            log.error("minio getOssInfo Exception:{}", e.getMessage());
        }
        return null;
    }


    @Override
    public OssResponse upLoadFile(String bucketName, String folderName, MultipartFile multipartFile) {
        // TODO 校验文件大小、文件类型、文件是否为空、bucket、最终文件名
        String fileName = multipartFile.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        // 对象名
        String objectName = folderName + "/" + fileName;
        try {
            //文件上传
            InputStream in = multipartFile.getInputStream();
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(in, multipartFile.getSize(), -1)
                            .contentType(multipartFile.getContentType())
                            .build());
            in.close();
        } catch (Exception e) {
            log.error("minio upLoadFile Exception:{}", e);

        }
        OssUploadResponse response = new OssUploadResponse();
        // TODO 返回自己需要的数据
        response.setObjectName(objectName);
        response.setBucketName(bucketName);
        response.setFileName(fileName);
        response.setUrl(minioProperties.getEndpoint() + "/" + bucketName + "/" + objectName);
        return response;
    }


    @Override
    public boolean removeFile(String bucketName, String objectName) {
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            log.info("minio removeFile success, fileName:{}", objectName);
            return true;
        } catch (Exception e) {
            log.error("minio removeFile fail, fileName:{}, Exception:{}", objectName, e.getMessage());
        }
        return false;
    }


    @Override
    public boolean removeFiles(String bucketName, List<String> objectNames) {
        try {
            List<DeleteObject> list = objectNames.stream().map(DeleteObject::new).collect(Collectors.toList());
            /**
             * lazy -  不是立即删除,而是异步删除
             */
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName)
                    .objects(list).build());
            log.info("minio removeFiles success, fileNames:{}", objectNames);
            return true;
        } catch (Exception e) {
            log.error("minio removeFiles fail, fileNames:{}, Exception:{}", objectNames, e.getMessage());
        }
        return false;
    }

    @Override
    public void downloadFile(HttpServletResponse response, String bucketName, String objectName) {
        InputStream in = null;
        try {
            OssObjectResponse downloadObjectResponse = (OssObjectResponse) getOssInfo(bucketName, objectName);
            Assert.notNull(downloadObjectResponse, "文件下载获取文件流失败");

            in = downloadObjectResponse.getInputStream();
            response.setContentType(downloadObjectResponse.getContentType());
            response.setHeader("Content-Disposition", "attachment;filename=" +
                    URLEncoder.encode(objectName, "UTF-8"));
            IOUtils.copy(in, response.getOutputStream());
        } catch (Exception e) {
            log.error("文件下载失败" + e.getMessage(), e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error("文件下载文件流关闭失败" + e.getMessage());
                }
            }
        }
    }

    @Override
    public String getPresignedObjectUrl(String bucketName, String objectName, int expires) {
        expires = expires == -1 ? 2 * 60 * 60 : expires;
        try {
            GetPresignedObjectUrlArgs objectArgs = GetPresignedObjectUrlArgs.builder().object(objectName)
                    .bucket(bucketName)
                    .expiry(expires).method(Method.GET).build();
            String url = minioClient.getPresignedObjectUrl(objectArgs);
            return URLDecoder.decode(url, "UTF-8");
        } catch (Exception e) {
            log.info("文件路径获取失败" + e.getMessage());
        }
        return null;
    }
}

controller

@Api(tags = "文件oss服务")
@RestController
@RequestMapping("/oss")
@Slf4j
public class OssController {
    @Autowired
    private OssTemplate ossTemplate;

    @PostMapping("/upload")
    @ApiOperation("上传文件")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "file", value = "文件", required = true, dataType = "MultipartFile"),
            @ApiImplicitParam(name = "bizDir", value = "业务目录", required = false, dataType = "String"),
            @ApiImplicitParam(name = "bucketName", value = "bucket名称", required = true, dataType = "String"),
    })
    public R<OssUploadResponse> upload(MultipartFile file,
                                       @RequestParam(required = false) String bizDir,
                                       @RequestParam(required = true) String bucketName) {
        OssUploadResponse uploadResponse = (OssUploadResponse) ossTemplate.upLoadFile(bucketName, bizDir, file);
        return R.ok(uploadResponse);
    }

    @GetMapping("/download")
    @ApiOperation("下载文件")
    public void download(HttpServletResponse response, String bucketName, String objectName) {
        ossTemplate.downloadFile(response, bucketName, objectName);
    }

    @GetMapping("/view/{bucketName}/**")
    @ApiOperation("预览 - 主要用于桶,需要带token")
    public void view(HttpServletResponse response, HttpServletRequest request, @PathVariable String bucketName) {
        InputStream in = null;
        String objectName = RequestUtils.extractPathFromPattern(request);
        try {
            OssObjectResponse objectResponse =
                    (OssObjectResponse) ossTemplate.getOssInfo(bucketName, objectName);
            Assert.notNull(objectResponse, "获取文件流失败");

            in = objectResponse.getInputStream();
            response.setContentType(objectResponse.getContentType());
            // cache - 图片浏览器缓存
            response.setDateHeader(LAST_MODIFIED, objectResponse.getLastModified());
            response.setHeader(ETAG, objectResponse.getEtag());

            IOUtils.copy(in, response.getOutputStream());
        } catch (Exception e) {
            log.error("文件流获取失败" + e.getMessage(), e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error("文件流关闭失败" + e.getMessage());
                }
            }
        }
    }

    @DeleteMapping("/removeObject")
    @ApiOperation("删除文件")
    public R removeObject(String bucketName, String objectName) {
        ossTemplate.removeFile(bucketName, objectName);
        return R.ok();
    }

    @DeleteMapping("/removeBatch")
    @ApiOperation("批量删除文件")
    public R removeBatch(String bucketName, String[] objectNames) {
        List<String> objectNameList = Arrays.asList(objectNames);
        ossTemplate.removeFiles(bucketName, objectNameList);
        return R.ok();
    }

    @GetMapping("/getPresignedObjectUrl")
    @ApiOperation("获取文件外链")
    public R<String> getPresignedObjectUrl(String bucketName, String objectName) {
        String url = ossTemplate.getPresignedObjectUrl(bucketName, objectName, -1);
        return R.ok(url);
    }
}

4. 拓展

4.0 minio 接入 Prometheus 监控

Prometheus官网下载地址:Download | Prometheus

4.0.0 官方文档参考

中文文档:使用Prometheus进行监控和报警 — MinIO中文文档 | MinIO Linux中文文档

官方文档:Monitoring and Alerting using Prometheus — MinIO Object Storage for Linux

4.0.1 安装 Prometheus

1. 下载适合自己的版本

2. 将下载好的安装包放到服务器上解压,进入目录修改配置文件 prometheus.yml

# scrape_configs 下增加如下配置
- job_name: "minio-job"
    metrics_path: /minio/v2/metrics/cluster
    scheme: http
    static_configs:
      - targets: ['192.168.0.105:9000'] #更改为自己的minio地址

配置不知道怎么写可以使用如下命令

mc admin prometheus generate minio-server

输出类似如下:

scrape_configs:
- job_name: minio-job
  # bearer_token 如果不配置的话
  # 就要修改 minio 的docker-compose.yml 添加环境变量  MINIO_PROMETHEUS_AUTH_TYPE: public     
  bearer_token: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwcm9tZXRoZXVzIiwic3ViIjoibWluaW9hZG1pbiIsImV4cCI6NDg2NjU5ODU1MH0.I5nYSNEUiHaUPRm6OeF6VBXFh-PckHDzIzV2OaTiy46Lf5BkjdNrfRWjIIREyNeIJhMUMZZj8q967BYF3w904Q
  metrics_path: /minio/v2/metrics/cluster
  scheme: http
  static_configs:
  - targets: ['192.168.0.105:9000']

注意:bearer_token 如果不配置的话就要修改 minio 的docker-compose.yml,添加环境变量 MINIO_PROMETHEUS_AUTH_TYPE: public

3. 注册系统管理服务

vim /etc/systemd/system/prometheus.service

# 添加如下内容
# ExecStart 这块改成自己的路径
[Unit]
Description=Prometheus Monitoring
Wants=network-online.target
After=network-online.target


[Service]
User=root
Group=root
Type=simple
ExecStart=/usr/local/prometheus-2.45.4/prometheus \
    --config.file=/usr/local/prometheus-2.45.4/prometheus.yml \
    --storage.tsdb.path=/usr/local/prometheus-2.45.4/data \
    --web.console.templates=/usr/local/prometheus-2.45.4/consoles \
    --web.console.libraries=/usr/local/prometheus-2.45.4/console_libraries
Restart=always

[Install]
WantedBy=multi-user.target

4. 启动并设置服务开机自动启动

systemctl enable prometheus.service --now
# 查看状态
systemctl status prometheus.service 

5. 开放服务器端口,访问 Prometheus http://123.60.150.23:9090

如下图:minio-job 就是我创建 minio 的监控 job

4.0.2 minio docker-compose.yml 配置文件修改

主要是添加了几个环境变量 environment

  • MINIO_PROMETHEUS_AUTH_TYPE

开放 PROMETHEUS 访问,配置后 prometheus.yml 中 bearer_token 就可以不配置

  • MINIO_PROMETHEUS_URL

PROMETHEUS 的地址

注意:http 不要漏写了,不然控制台访问 metric 页面的时候会报错:Prometheus URL is unreachable

  • MINIO_PROMETHEUS_JOB_ID

prometheus.yml 中配置的 job_name

version: '3.7'

# Settings and configurations that are common for all containers
x-minio-common: &minio-common
  image: quay.io/minio/minio:RELEASE.2024-03-15T01-07-19Z
  command: server --console-address ":50000" http://minio1/data{1...2}  http://minio1/data{3...4}
  ports:
    - "9000:9000"
    - "50000:50000"
  ############ 在这里添加了  
  # MINIO_PROMETHEUS_AUTH_TYPE 开放 PROMETHEUS 访问
  # MINIO_PROMETHEUS_URL 是 PROMETHEUS 的地址 - http 一定要写不要会报错
  environment:
    MINIO_PROMETHEUS_AUTH_TYPE: public
    MINIO_PROMETHEUS_URL: http://192.168.0.105:9090
    MINIO_PROMETHEUS_JOB_ID: minio-job
    # MINIO_ROOT_USER: minioadmin
    # MINIO_ROOT_PASSWORD: minioadmin
  healthcheck:
    test: ["CMD", "mc", "ready", "local"]
    interval: 5s
    timeout: 5s
    retries: 5

# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
  minio1:
    <<: *minio-common
    hostname: minio1
    volumes:
      - ./data-test2-1:/data1
      - ./data-test2-2:/data2
      - ./data-test2-3:/data3
      - ./data-test2-4:/data4
      - ./config-test2:/root/.minio

重新启动 minio 容器访问控制台,就能查看 metric 监控数据了

4.1 使用备份数据恢复 minio 服务

测试:config 和 data 复制到另外一个目录下,然后修改 docker-compose.yml 的挂载路径,重启 minio,数据能正常访问

4.2 数据迁移

4.2.1 本地文件迁移到 minio

1. 使用 minio 的 mc client 工具

文件批量迁移上传到 minio

# --recursive 表示会将指定目录下(不包括 yourDir)下的所有资源(包括目录)都复制到 minio 对应的 bucket 下
mc cp --recursive ./yourDir minio-server-name/bucketName
# 将 2024 目录下的所有资源都复制到 minio 的 bucket - service-wrapper 的historyTrack 目录下
mc cp  --recursive ./2024  minio-server-name/service-wrapper/historyTrack

# 我的文件没有后缀名所以上传后的content-type 默认是application/octet-stream
# 使用 getObject 没法读取数据流,只能下载 -> 所以需要加上 content-type 
# 如果需要指定 content-type 就增加 --attr="content-type=text/plain" (当然:content-type 根据自己的实际情况进行修改)
mc cp --attr="content-type=text/plain" --recursive ./yourDir minio-server-name/bucketName

文件迁移从 minio 迁移回本地

mc cp minio-server-name/bucketName  本地目录

2. 使用 rclone 工具 - 自行百度吧

4.3 bucket 公桶\私桶

公桶:所有用户都能访问资源

私桶:获取外链(有时间限制,url 会带验签信息)-> 过期就需要重新生成外链

4.4 访问策略问题

参考上面 2.4 minio 客户端使用 -> 策略管理

说白了就是抄 minio 自带的,有额外的需求的话就问 chatgpt阿里云等云存储中的文件迁移到 minio

4.5 子网掩码

/32

举例:192.168.0.100

/32 表示这是一个单个的IP地址,没有子网。换句话说,这个CIDR表示法指定了一个单一的IP地址,即 192.168.0.100,没有包含任何其他IP地址

/24

  • /24 表示子网掩码的前24位是网络部分,后8位是主机部分。
  • 转换为传统的子网掩码表示法,它等于 255.255.255.0。
  • 在这个子网中,有 256 个可能的IP地址(从 192.168.0.0 到 192.168.0.255),但其中两个(192.168.0.0 和 192.168.0.255)通常分别作为网络地址和广播地址,不可用于分配给主机。
  • 因此,实际可用于主机的IP地址有 254 个(从 192.168.0.1 到 192.168.0.254)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring解决循环依赖:三级缓存

1.什么是循环依赖 通俗来讲&#xff0c;循环依赖指的是一个实例或多个实例存在相互依赖的关系&#xff08;类之间循环嵌套引用&#xff09;。 2.Spring如何解决循环依赖 首先&#xff0c;先介绍Spring是如何创建Bean的。 &#xff08;1&#xff09;createBeanInstance&…

【LoadRunner】博客笔记项目 性能测试报告

文章目录 前言一、博客笔记项目性能测试介绍二、编写性能测试脚本&#xff08;VUG&#xff09; 2.1 测试脚本编写步骤 2.2 脚本总代码和结果分析三、创建测试场景&#xff08;Controller&#xff09; 3.1 测试场景创建实现步骤四、生成测试报告&#xff08;Anal…

集合相关知识

string final&#xff0c;不能追加&#xff0c;需要重新new一个 stringbuild&#xff0c;内容 可变&#xff0c;可以重新赋能&#xff0c;能够追加&#xff0c;空间不足创造一个更大的&#xff0c;然后复制过去 stringbufferbuild 线程安全 javac编译&#xff0c;字符串加号…

SpringBoot介绍以及第一个SpringBoot程序

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 2.第一个SpringBoot程序2.1Spring Boot介绍2.2使用idea创建Spring Boot程序2.2.1 社区版idea2.2.2专业版idea2.2.3创建SpringBoot项目2.2.4项目代码和目录介绍目录介绍pom文件 2.3Web…

Linux 上 TTY 的起源

注&#xff1a;机翻&#xff0c;未校对。 What is a TTY on Linux? (and How to Use the tty Command) What does the tty command do? It prints the name of the terminal you’re using. TTY stands for “teletypewriter.” What’s the story behind the name of the co…

【边缘计算网关教程】6.松下 Mewtocol TCP 协议

前景回顾&#xff1a;【边缘计算网关教程】5.三菱FX3U编程口通讯-CSDN博客 松下 Mewtocol TCP 协议 适配PLC&#xff1a;松下FP0H 松下XHC60ET 1. 硬件连接 Mewtocol TCP协议采用网口通信的方式&#xff0c;因此&#xff0c;只需要保证网关的LAN口和松下PLC的IP在一个网段即…

STM32怎么把VDD与VSS引脚配置为GPIO?

在 STM32 微控制器中&#xff0c;VDD 和 VSS 引脚是供电引脚&#xff0c;分别用于电源和接地。我收集归类了一份嵌入式学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学、问题视频讲解、毕设800套和语言类教学&#xff0c;敲个…

使用base64通用文件上传

编写一个上传文件的组件 tuku,点击图片上传后使用FileReader异步读取文件的内容&#xff0c;读取完成后获得文件名和base64码&#xff0c;调用后端uploadApi,传入姓名和base64文件信息&#xff0c;后端存入nginx中&#xff0c;用于访问 tuku.ts组件代码&#xff1a; <templa…

变量筛选—特征包含信息量

在变量筛选中,通过衡量特征所包含信息量大小,决定是否删除特征,常用的指标有单一值占比、缺失值占比和方差值大小。单一值或缺失值占比越高,表示特征包含信息量越少,不同公司设置不同阈值,一般单一值、缺失值占比高于95%,建议删除。方差值越小,代表特征包含信息量越小。…

JMeter进行HTTP接口测试的技术要点

参数化 用户定义的变量 用的时候 ${名字} 用户参数 在参数列表中传递 并且也是${} csv数据文件设置 false 不忽略首行 要首行 从第一行读取 true 忽略首行 从第二行开始 请求时的参数设置&#xff1a; 这里的名称是看其接口需要的请求参数的名称 这里的变量名称就是为csv里面…

C语言程序设计实例2

C语言程序设计2 问题2_1代码2_1结果2_1 问题2_2代码2_2结果2_2 问题2_3代码2_3结果2_3 问题2_1 函数 f u n fun fun的功能是&#xff1a;计算如下公式前 n n n项的和&#xff0c;并作为函数值返回。 S 1 3 2 2 3 5 4 2 5 7 6 2 ⋅ ⋅ ⋅ ( 2 n − 1 ) ( 2 n 1 ) …

Linux操作系统——数据库

数据库 sun solaris gnu 1、分类&#xff1a; 大型 中型 小型 ORACLE MYSQL/MSSQL SQLITE DBII powdb 关系型数据库 2、名词&#xff1a; DB 数据库 select update database DBMS 数据…

Sql语句之增删改查(CRUD)

Sql语句的书写也被称之为CRUD&#xff0c;即C&#xff08;Create增加&#xff09;R&#xff08;Retrieve添加&#xff09;U&#xff08;Update更新&#xff09;D&#xff08;Delete删除&#xff09;四个操作的首字母。 我们先来看增、删、改这三个相对较为简单&#xff0c;语法…

string+迭代器

int main(){ string s0; string s1("hello word"); cout<<s1<<endl; //遍历string,下标[] for(size_t i0;i<s1.size();i) { cout<<s1[i]<<""; } cout<<endl; } 注意&#xff1a;这里size_t不算/0 迭代器 int main() {st…

Open3D 生成多个球形点云

一、概述 使用 Open3D 创建一个三角网格的球体&#xff0c;并从中均匀采样点生成点云&#xff0c;同时可以指定球体的半径和中心位置。生成 5 个不同大小和位置的圆球形点云&#xff0c;并将它们合并成一个点云以进行显示。 二、代码实现 import open3d as o3d import numpy …

Django任务管理

1、用django-admin命令创建一个Django项目 django-admin startproject task_manager 2、进入到项目下用命令创建一个应用 cd task_manager python manage.py startapp tasks 3、进入models.py定义数学模型 第2步得到的只是应用的必要空文件&#xff0c;要开始增加各文件实际…

博客都在使用的主题切换使用vue2实现思路

效果展示 步骤 1-变量定义css主题色 2-html初始化主题样式 3-vuex存储主题变量&#xff0c;点击触发修改根元素html的样式 4-method触发方法 mutation使用commit action使用dispatch 5-App组件引入该css文件&#xff0c;使用即可 6-将其加入本地存储&#xff0c;刷新后保持主…

【Git分支管理】分支合并冲突及其解决

目录 0.合并冲突 1.创建和切换dev1 ​2.dev1 bbb on dev branch ​3.master ccc on dev branch 4.dev1和master合并冲突 5.合并冲突解决 ​6.git log查看合并流程图 先提交再合并 0.合并冲突 在使用git进行合并操作的时候&#xff0c;在合并两个分支的时候就有可能出…

鹧鸪云户用业务管理系统:全流程管理+源码部署

在当今数字化转型的浪潮中&#xff0c;企业对于高效、灵活且定制化的业务管理系统需求日益增长。为满足这一市场需求&#xff0c;鹧鸪云户用业务管理系统应运而生&#xff0c;它以“全流程管理源码部署”为核心优势&#xff0c;为企业提供了一套集成化、可扩展且易于维护的解决…

最新版康泰克完整版- Kontakt v7.10.5 for Win和Mac,支持m芯片和intel,有入库工具

一。世界最受欢迎的采样器的新篇章 Native Instruments Kontakt是采样器领域的标准&#xff0c;您将获得高质量的滤波器&#xff0c;在这里您将找到经典的模拟电路和最现代的滤波器。每一个都可以根据您的口味进行定制&#xff0c;并且由于它&#xff0c;您可以获得前所未有的声…