1. Environment Preparation
1.1 Preparing Data Disks
1
| lsblk -d -o NAME,SIZE,TYPE | grep nvme
|
1
2
3
4
| nvme0n1 745.2G disk
nvme1n1 745.2G disk
nvme2n1 745.2G disk
nvme3n1 745.2G disk
|
- Prepare the storage directories
1
2
3
| for i in {0..3}; do
mkdir -p /mnt/data${i}
done
|
1
2
3
| for i in {0..3}; do
mkfs.xfs -f /dev/nvme${i}n1
done
|
1
2
3
| for i in {0..3}; do
mount /dev/nvme${i}n1 /mnt/data${i}
done
|
1
2
3
4
| for i in {0..3}; do
rm -rf /mnt/data${i}/*
rm -rf /mnt/data${i}/.minio.sys
done
|
1
2
3
4
| /dev/nvme0n1 745G 5.3G 740G 1% /mnt/data0
/dev/nvme1n1 745G 5.3G 740G 1% /mnt/data1
/dev/nvme2n1 745G 5.3G 740G 1% /mnt/data2
/dev/nvme3n1 745G 5.3G 740G 1% /mnt/data3
|
1.2 Time Synchronization
1
2
| apt install -y chrony
systemctl enable --now chrony
|
2. Deploying MinIO
Each node has 4 disks; we deploy two machines to form a single minio cluster.
2.1 Configuring hosts
1
2
3
4
| cat >> /etc/hosts <<EOF
10.0.0.1 minio1
10.0.0.2 minio2
EOF
|
Configuring hosts pins the node names, which makes operations easier and avoids identification problems during expansion.
2.1 Environment Variables
1
2
3
4
5
6
7
8
9
10
| export CONTAINER_CLI=nerdctl
export IMAGE=minio/minio:RELEASE.2025-04-22T22-12-26Z
export ROOT_USER=minioadmin
export ROOT_PASSWORD=minioadmin
export MINIO_ERASURE_SET_DRIVE_COUNT=4
export MINIO_STORAGE_CLASS_STANDARD=EC:2
export POOL_0="http://minio{1...2}/mnt/data{0...3}"
|
Web consoles in minio versions after RELEASE.2025-04-22T22-12-26Z are extremely bare-bones: you can only create buckets, and you cannot configure policies, keys, and so on. Complex configuration requires the command line.
MINIO_ERASURE_SET_DRIVE_COUNT is the size of an erasure set and cannot exceed 16.
MINIO_STORAGE_CLASS_STANDARD is the redundancy level of the erasure code and cannot exceed erasure set size/2.
A single deployment has just one pool, provided to minio on one line at startup.
2.2 Starting minio
The startup command is identical on every node; here we start two nodes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| $CONTAINER_CLI run -d \
--security-opt apparmor=unconfined \
--security-opt seccomp=unconfined \
--net host \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
--ulimit nofile=1048576:1048576 \
--memory-swappiness=0 \
--name minio \
-v /mnt/data0:/mnt/data0 \
-v /mnt/data1:/mnt/data1 \
-v /mnt/data2:/mnt/data2 \
-v /mnt/data3:/mnt/data3 \
-e "MINIO_ROOT_USER=$ROOT_USER" \
-e "MINIO_ROOT_PASSWORD=$ROOT_PASSWORD" \
-e "MINIO_ERASURE_SET_DRIVE_COUNT=$MINIO_ERASURE_SET_DRIVE_COUNT" \
-e "MINIO_STORAGE_CLASS_STANDARD=$MINIO_STORAGE_CLASS_STANDARD" \
$IMAGE server \
$POOL_0 \
--console-address ":9090" \
--address ":9000"
|
9090 is the MinIO console port and 9000 is the MinIO API port; use port 9000 when accessing data.
After one node starts, it waits for the other nodes to join the cluster.
3. Cluster Management
3.1 Configuring Credentials
1
2
3
| wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
mv mc /usr/bin/
|
1
| mc alias set local http://127.0.0.1:9000 minioadmin minioadmin
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| ● minio1:9000
Uptime: 46 seconds
Version: 2025-04-22T22:12:26Z
Network: 2/2 OK
Drives: 4/4 OK
Pool: 1
● minio2:9000
Uptime: 42 seconds
Version: 2025-04-22T22:12:26Z
Network: 2/2 OK
Drives: 4/4 OK
Pool: 1
┌──────┬───────────────────────┬─────────────────────┬──────────────┐
│ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │
│ 1st │ 0.7% (total: 2.9 TiB) │ 4 │ 2 │
└──────┴───────────────────────┴─────────────────────┴──────────────┘
8 drives online, 0 drives offline, EC:2
|
Erasure stripe size indicates the number of disks per erasure set, and Erasure sets indicates the number of erasure sets. Here we have 4-disk erasure sets, 2 erasure sets, EC:2 with two parity disks, giving a usable capacity ratio of 50%.
3.2 Bucket Management
3.3 Users and Policies
1
| mc admin user add local myuser mypassword
|
1
| mc admin user info local myuser
|
1
| mc admin policy attach local readwrite --user myuser
|
Built-in policies apply to all buckets. The available values are: readwrite, writeonly, consoleAdmin, diagnostics, readonly.
1
| mc admin policy detach local readwrite --user myuser
|
1
| mc admin policy rm local mypolicy
|
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
| cat > test-readwrite.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::test"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::test/*"
]
}
]
}
EOF
|
1
| mc admin policy create local test-readwrite test-readwrite.json
|
1
| mc admin policy attach local test-readwrite --user myuser
|
3.4 File Management
1
| mc cp ./test.txt local/test/test.txt
|
1
| mc cp -r ./ local/test/
|
1
| mc stat local/test/test.txt
|
3.5 rebalance
minio rebalance is only effective on multi-node clusters.
1
| mc admin rebalance start local
|
1
| mc admin rebalance status local
|
1
| mc admin rebalance stop local
|
3.6 Bucket Versioning
1
| mc version enable local/test2
|
1
| mc version info local/test2
|
1
| mc cp -r ./ local/test2/
|
1
| mc ls --versions local/test2/
|
1
2
| 5B STANDARD aa673997-a590-4b04-b3be-80627fa3352d v2 PUT bbbb
5B STANDARD f5bcc176-3544-432f-924c-eb654a317ca0 v1 PUT bbbb
|
- Restore a specific version of a file
1
| mc cp --version-id f5bcc176-3544-432f-924c-eb654a317ca0 local/test2/bbbb ./bbbb-v1
|
3.6 Statistics Details
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| mc stat local/test2
Name : test2
Size : N/A
Type : folder
Properties:
Versioning: Enabled
Location: us-east-1
Anonymous: Disabled
ILM: Disabled
Usage:
Total size: 1002 MiB
Objects count: 1,652
Versions count: 2,967
Object sizes histogram:
509 object(s) BETWEEN_1024B_AND_1_MB
423 object(s) BETWEEN_1024_B_AND_64_KB
9 object(s) BETWEEN_10_MB_AND_64_MB
|
1
| mc stat local/test2/bbbb
|
1
2
3
4
5
6
7
| Name : bbbb
Size : 5 B
ETag : 9448a1bf333fadd2a57965ec38487b89
VersionID : aa673997-a590-4b04-b3be-80627fa3352d
Type : file
Metadata :
Content-Type: application/octet-stream
|
format.json is minio’s format file, recording the cluster’s configuration information. It is recommended to back it up every time you adjust the cluster topology.
1
2
3
4
5
| mkdir -p /backup/miniosys/
for i in {0..3}; do
mkdir -p /backup/miniosys/data${i}
cp /mnt/data${i}/.minio.sys/format.json /backup/miniosys/data${i}/format.json
done
|
View the backup
1
| ls -l /backup/miniosys/
|
1
2
3
4
| drwxr-xr-x 2 root root 4096 Feb 6 19:40 data0
drwxr-xr-x 2 root root 4096 Feb 6 19:40 data1
drwxr-xr-x 2 root root 4096 Feb 6 19:40 data2
drwxr-xr-x 2 root root 4096 Feb 6 19:40 data3
|
4 Backing Up Data
4.1 Full Backup with mc
1
| mc cp -r ./ local/backup/
|
cp uploads everything in full every time rather than incrementally.
1
| mc mirror ./ local/backup/ --remove --disable-multipart --monitoring-address 0.0.0.0:9001
|
mirror uploads incrementally; with remove, files deleted at the source are also deleted from the bucket.
4.2 Incremental Backup with restic
- Set environment variables
1
2
3
4
| export AWS_ACCESS_KEY_ID=minioadmin
export AWS_SECRET_ACCESS_KEY=minioadmin
export RESTIC_REPOSITORY=s3:http://127.0.0.1:9000/backup
export RESTIC_PASSWORD="backuppassword"
|
restic requires a backup password to be set.
restic backs up incrementally.
1
| restic restore <snapshot_ID> --target ./restore
|
1
| restic forget --keep-last 365 --prune
|
4.3 Incremental Backup with rclone
1
2
3
4
5
6
| rclone config create minio s3 \
access_key_id minioadmin \
secret_access_key minioadmin \
endpoint http://127.0.0.1:9000 \
region us-east-1 \
force_path_style true
|
1
| rclone sync ./ minio:backup --progress
|
rclone backs up incrementally.
- Back up overwrites and deletions to a separate directory
1
2
3
| rclone sync ./ minio:backup \
--backup-dir minio:backupold/$(date +%Y%m%d_%H%M%S) \
--progress
|
Now overwritten or deleted files are backed up under the backupold bucket.
1
| rclone copy minio:backup ./backup_restore --progress
|
5. Node Restart
1
| $CONTAINER_CLI stop minio
|
Note that the endpoint configured in local here needs to be switched to a node that is running normally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| ● minio1:9000
Uptime: 2 hours
Version: 2025-04-22T22:12:26Z
Network: 1/2 OK
Drives: 4/4 OK
Pool: 1
● minio2:9000
Uptime: offline
Drives: 0/4 OK
┌──────┬───────────────────────┬─────────────────────┬──────────────┐
│ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │
│ 1st │ 0.8% (total: 1.5 TiB) │ 4 │ 2 │
└──────┴───────────────────────┴─────────────────────┴──────────────┘
1.5 GiB Used, 2 Buckets, 186 Objects
1 node offline, 4 drives online, 4 drives offline, EC:2
|
Data can only be accessed normally if the number of online disks meets the requirement; here one disk is missing, so data cannot be accessed normally.
- Adjust parameters and restart the minio node
The number of data blocks and parity blocks cannot be adjusted; other parameters can.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| ● minio1:9000
Uptime: 2 hours
Version: 2025-04-22T22:12:26Z
Network: 2/2 OK
Drives: 4/4 OK
Pool: 1
● minio2:9000
Uptime: 59 seconds
Version: 2025-04-22T22:12:26Z
Network: 2/2 OK
Drives: 4/4 OK
Pool: 1
┌──────┬───────────────────────┬─────────────────────┬──────────────┐
│ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │
│ 1st │ 0.8% (total: 2.9 TiB) │ 4 │ 2 │
└──────┴───────────────────────┴─────────────────────┴──────────────┘
1.5 GiB Used, 2 Buckets, 186 Objects
8 drives online, 0 drives offline, EC:2
|
1
| mc cp -r local/test/ ./recreate/
|
6. Adding Nodes
The newly added nodes form a new pool, which does not interfere with the original pool.
Do not add just one node; add at least two at a time to guarantee the data reliability of the new pool.
The erasure code parameters of the new nodes must match those of the existing nodes, and the disk count must be an integer multiple of the erasure set size.
During expansion, you need to stop minio and modify the startup parameters of all instances to be consistent.
1
2
3
4
5
6
| cat >> /etc/hosts <<EOF
10.0.0.1 minio1
10.0.0.2 minio2
10.0.0.3 minio3
10.0.0.14 minio4
EOF
|
- Set environment variables
1
2
3
4
5
6
7
8
9
10
11
| export CONTAINER_CLI=nerdctl
export IMAGE=minio/minio:RELEASE.2025-04-22T22-12-26Z
export ROOT_USER=minioadmin
export ROOT_PASSWORD=minioadmin
export MINIO_ERASURE_SET_DRIVE_COUNT=4
export MINIO_STORAGE_CLASS_STANDARD=EC:2
export POOL_0="http://minio{1...2}/mnt/data{0...3}"
export POOL_1="http://minio{3...4}/mnt/data{0...3}"
|
- Delete minio on the old nodes
1
| $CONTAINER_CLI rm -f minio
|
- Start the new nodes and update all the old nodes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| $CONTAINER_CLI run -d \
--security-opt apparmor=unconfined \
--security-opt seccomp=unconfined \
--net host \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
--ulimit nofile=1048576:1048576 \
--memory-swappiness=0 \
--name minio \
-v /mnt/data0:/mnt/data0 \
-v /mnt/data1:/mnt/data1 \
-v /mnt/data2:/mnt/data2 \
-v /mnt/data3:/mnt/data3 \
-e "MINIO_ROOT_USER=$ROOT_USER" \
-e "MINIO_ROOT_PASSWORD=$ROOT_PASSWORD" \
-e "MINIO_ERASURE_SET_DRIVE_COUNT=$MINIO_ERASURE_SET_DRIVE_COUNT" \
-e "MINIO_STORAGE_CLASS_STANDARD=$MINIO_STORAGE_CLASS_STANDARD" \
$IMAGE server \
$POOL_0 \
$POOL_1 \
--console-address ":9090" \
--address ":9000"
|
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
32
33
34
35
36
| ● minio1:9000
Uptime: 1 minute
Version: 2025-04-22T22:12:26Z
Network: 4/4 OK
Drives: 4/4 OK
Pool: 1
● minio2:9000
Uptime: 2 minutes
Version: 2025-04-22T22:12:26Z
Network: 4/4 OK
Drives: 4/4 OK
Pool: 1
● minio3:9000
Uptime: 3 minutes
Version: 2025-04-22T22:12:26Z
Network: 4/4 OK
Drives: 4/4 OK
Pool: 2
● minio4:9000
Uptime: 3 minutes
Version: 2025-04-22T22:12:26Z
Network: 4/4 OK
Drives: 4/4 OK
Pool: 2
┌──────┬────────────────────────┬─────────────────────┬──────────────┐
│ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │
│ 1st │ 0.9% (total: 2.9 TiB) │ 4 │ 2 │
│ 2nd │ 26.2% (total: 3.5 TiB) │ 4 │ 2 │
└──────┴────────────────────────┴─────────────────────┴──────────────┘
3.8 GiB Used, 3 Buckets, 16,022 Objects
16 drives online, 0 drives offline, EC:2
|
At this point you will see two pools: 1st is the previous pool, and 2nd is the newly added pool.
1
2
3
4
5
6
7
| ┌──────┬────────────────────────┬─────────────────────┬──────────────┐
│ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │
│ 1st │ 1.1% (total: 2.9 TiB) │ 4 │ 2 │
│ 2nd │ 26.6% (total: 3.5 TiB) │ 4 │ 2 │
└──────┴────────────────────────┴─────────────────────┴──────────────┘
13 GiB Used, 3 Buckets, 18,692 Objects
|
New data is written more to the new pool, while the old pool also receives some writes.
1
| mc admin rebalance start local
|
1
| mc admin rebalance status local
|
1
2
3
4
5
6
7
8
| Per-pool usage:
┌────────┬────────┐
│ Pool-0 │ Pool-1 │
│ 1.24% │ 26.23% │
└────────┴────────┘
Summary:
Data: 10 GiB (1684 objects, 1684 versions)
Time: 1m3.22950215s (0s to completion)
|
minio balances both within a pool and between pools.
7. Replacing a Single Disk
After replacing a single disk, keep the mount directory unchanged.
1
| $CONTAINER_CLI stop minio
|
Reformat and mount the new disk to the original directory.
1
2
| rm -rf /mnt/data0/*
rm -rf /mnt/data0/.minio.sys
|
1
| $CONTAINER_CLI start minio
|
1
| No active healing is detected for new disks, though 1 offline disk(s) found.
|
1
2
3
4
| mc admin heal local
Objects Healed: 16, 56 KiB (53.4%)
Objects Failed: 0
Heal rate: 363 obj/s, 1.2 MiB/s
|
minio rebuilds the data automatically.
8. Node Rebuild
Four nodes form two pools. If the data of one node is deleted, one of the pools will not have enough replicas, and it can only be read from, not written to.
1
| $CONTAINER_CLI stop minio
|
1
2
3
4
| for i in {0..3}; do
rm -rf /mnt/data${i}/*
rm -rf /mnt/data${i}/.minio.sys
done
|
1
2
3
| ● minio4:9000
Uptime: offline
Drives: 0/4 OK
|
The node status is offline.
1
| $CONTAINER_CLI start minio
|
- View the data rebuild status
1
| mc admin heal local --verbose
|
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
| mc admin heal local --verbose
Servers status:
==============
Pool 1st:
minio1:9000:
+ /mnt/data0 : HEALING
|__ Progress: 90%
|__ Started: now
|__ Capacity: 5.2 GiB/745 GiB
+ /mnt/data1 : HEALING
|__ Progress: 90%
|__ Started: now
|__ Capacity: 5.2 GiB/745 GiB
+ /mnt/data2 : HEALING
|__ Progress: 95%
|__ Started: now
|__ Capacity: 5.2 GiB/745 GiB
+ /mnt/data3 : HEALING
|__ Progress: 95%
|__ Started: now
|__ Capacity: 5.2 GiB/745 GiB
Pool 2nd:
Summary:
=======
Objects Healed: 4, 856 B (90.4%)
Objects Failed: 0
Heal rate: 142 obj/s, 30 KiB/s
2 of 4 sets exceeds reduced parity count EC:1 lost/offline disks
|
9. Common Errors
- Error on the old nodes during expansion
1
2
3
| FATAL Unable to initialize backend:
/mnt/data0 drive is already being used in another erasure deployment.
(Number of drives specified: 16 but the number of drives found in the 5th drive's format.json: 8)
|
If you run into this problem, there is currently no fix other than backing up and rebuilding the cluster. This is because minio tries to form all disks into a new pool rather than adding a new pool. It is recommended to use hosts to pin node names when deploying.