20人参与 • 2026-04-29 • Linux
在当今数据驱动的时代,企业与个人对数据存储的需求日益增长。公有云虽然提供了便捷的解决方案,但在安全性、成本控制和定制化方面往往难以满足特定需求。minio 作为一个开源、高性能、兼容 amazon s3 api 的对象存储系统,成为构建私有云存储的理想选择。它轻量、易部署、支持分布式架构,并拥有活跃的社区支持和丰富的生态工具。
本文将带你从零开始,在 linux 系统上搭建 minio 私有云存储服务,并通过 java 代码示例演示如何与其交互,实现文件上传、下载、管理等操作。无论你是 devops 工程师、后端开发者,还是对云原生技术感兴趣的爱好者,本文都将为你提供实用的指导和深入的理解。
minio 是一个基于 apache license v2.0 开源的对象存储服务器,专为云原生应用设计。它的核心目标是提供简单、高效、可扩展的存储方案,同时完全兼容 amazon s3 api —— 这意味着你可以无缝迁移现有 s3 应用到 minio,或直接使用任何支持 s3 的客户端工具(如 aws cli、s3 browser、cyberduck 等)。
官方网站:https://min.io
文档中心:https://docs.min.io
我们将在一台运行 ubuntu 22.04 lts 的服务器上部署 minio。你也可以选择 centos、debian 或其他主流发行版,步骤基本一致。
出于安全考虑,建议不要以 root 用户运行 minio:
sudo adduser minio-user sudo usermod -ag sudo minio-user sudo su - minio-user
minio 将在此目录中存储对象数据:
mkdir -p ~/minio/data
如果你计划部署分布式集群,可以创建多个目录或挂载不同磁盘:
mkdir -p ~/minio/data{1..4}
minio 提供了两种部署方式:单节点单驱动器(适合开发/测试)和分布式集群(适合生产)。我们先从最简单的单节点开始。
前往官方下载页获取最新版本(截至本文撰写时为 release.2024-05-22t00-38-56z):
wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio sudo mv minio /usr/local/bin/
验证安装:
minio --version # 输出示例:minio version release.2024-05-22t00-38-56z
minio 要求设置访问密钥和密码,否则无法启动:
export minio_root_user=minioadmin export minio_root_password=minioadmin123
生产环境中请务必使用强密码,并考虑使用 .env 文件或 systemd 服务管理这些变量。
minio server ~/minio/data --console-address ":9001"
你会看到类似输出:
api: http://192.168.1.100:9000 http://127.0.0.1:9000 console: http://192.168.1.100:9001 http://127.0.0.1:9001 documentation: https://min.io/docs/minio/linux/index.html warning: the standard parity is set to 0. this can lead to data loss.
--console-address ":9001" 用于指定控制台端口,默认是随机端口,固定便于访问。
sudo ufw allow 9000 sudo ufw allow 9001 sudo ufw reload
打开浏览器,访问 http://<your-server-ip>:9001,使用之前设置的用户名和密码登录:
minioadminminioadmin123你将看到如下界面:
首次登录后建议立即修改默认密码!
mc 是 minio 官方提供的命令行工具,功能强大,类似 awscli。
wget https://dl.min.io/client/mc/release/linux-amd64/mc chmod +x mc sudo mv mc /usr/local/bin/
mc alias set myminio http://localhost:9000 minioadmin minioadmin123
# 列出所有存储桶 mc ls myminio # 创建新存储桶 mc mb myminio/mybucket # 上传文件 mc cp ./example.txt myminio/mybucket/ # 下载文件 mc cp myminio/mybucket/example.txt ./ # 删除文件 mc rm myminio/mybucket/example.txt # 设置存储桶为公开读取 mc anonymous set download myminio/mybucket # 查看存储桶策略 mc policy get myminio/mybucket
为了确保 minio 在系统重启后自动运行,我们将其注册为 systemd 服务。
sudo nano /etc/systemd/system/minio.service
粘贴以下内容(请根据实际情况修改路径和用户):
[unit]
description=minio
documentation=https://docs.min.io
wants=network-online.target
after=network-online.target
assertfileisexecutable=/usr/local/bin/minio
[service]
workingdirectory=/home/minio-user
user=minio-user
group=minio-user
environmentfile=/etc/default/minio
execstartpre=/bin/bash -c "if [ -z \"${minio_volumes}\" ]; then echo \"variable minio_volumes not set in /etc/default/minio\"; exit 1; fi"
execstart=/usr/local/bin/minio server $minio_opts $minio_volumes
standardoutput=journal
standarderror=inherit
restart=always
restartsec=30s
[install]
wantedby=multi-user.targetsudo mkdir -p /etc/default sudo nano /etc/default/minio
填入以下内容:
# volume to be used for minio server. minio_volumes="/home/minio-user/minio/data" # use if you want to run minio on a custom port. minio_opts="--console-address :9001" # access key and secret key minio_root_user=minioadmin minio_root_password=minioadmin123
sudo systemctl daemon-reload sudo systemctl enable minio sudo systemctl start minio sudo systemctl status minio
现在 minio 将随系统启动,并在后台稳定运行。
单节点适合开发,但生产环境推荐使用分布式部署以获得高可用性和横向扩展能力。
minio 分布式模式最少需要 4 个节点(或 4 个驱动器),采用纠删码(erasure coding)机制,即使部分节点故障也能保证数据完整。

示例启动命令(在每台机器上执行):
export minio_root_user=minioadmin export minio_root_password=minioadmin123 minio server http://node1/data http://node2/data http://node3/data http://node4/data --console-address ":9001"
所有节点必须使用相同的 minio_root_user 和 minio_root_password,且时间需同步(建议使用 ntp)。
minio 提供了官方 java sdk,支持 jdk 8+,maven 中央仓库可直接引入。
在你的 pom.xml 中添加:
<dependency>
<groupid>io.minio</groupid>
<artifactid>minio</artifactid>
<version>8.5.10</version>
</dependency>
<!-- 日志依赖(可选) -->
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-simple</artifactid>
<version>2.0.9</version>
</dependency>创建一个工具类 minioclientutil.java:
import io.minio.minioclient;
import io.minio.errors.invalidendpointexception;
import io.minio.errors.invalidportexception;
public class minioclientutil {
private static minioclient minioclient;
static {
try {
minioclient = minioclient.builder()
.endpoint("http://your-server-ip:9000")
.credentials("minioadmin", "minioadmin123")
.build();
} catch (exception e) {
e.printstacktrace();
throw new runtimeexception("minio 客户端初始化失败", e);
}
}
public static minioclient getinstance() {
return minioclient;
}
}import io.minio.makebucketargs;
import io.minio.minioclient;
import io.minio.errors.*;
import java.io.ioexception;
import java.security.invalidkeyexception;
import java.security.nosuchalgorithmexception;
public class bucketexample {
public static void createbucket(string bucketname) {
try {
minioclient minioclient = minioclientutil.getinstance();
boolean found = minioclient.bucketexists(
makebucketargs.builder().bucket(bucketname).build()
);
if (!found) {
minioclient.makebucket(
makebucketargs.builder().bucket(bucketname).build()
);
system.out.println("✅ 存储桶 '" + bucketname + "' 创建成功");
} else {
system.out.println("ℹ️ 存储桶 '" + bucketname + "' 已存在");
}
} catch (exception e) {
system.err.println("❌ 创建存储桶失败: " + e.getmessage());
e.printstacktrace();
}
}
public static void main(string[] args) {
createbucket("my-first-bucket");
}
}
支持本地文件上传和 inputstream 流上传。
import io.minio.putobjectargs;
import io.minio.minioclient;
import java.io.file;
import java.io.fileinputstream;
import java.io.inputstream;
public class uploadexample {
// 方式一:上传本地文件
public static void uploadfile(string bucketname, string objectname, string filepath) {
try {
minioclient minioclient = minioclientutil.getinstance();
minioclient.uploadobject(
putobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.filename(filepath)
.build()
);
system.out.println("✅ 文件上传成功: " + objectname);
} catch (exception e) {
system.err.println("❌ 文件上传失败: " + e.getmessage());
e.printstacktrace();
}
}
// 方式二:上传 inputstream
public static void uploadstream(string bucketname, string objectname, inputstream stream, long size, string contenttype) {
try {
minioclient minioclient = minioclientutil.getinstance();
minioclient.putobject(
putobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.stream(stream, size, -1) // -1 表示不限制 partsize
.contenttype(contenttype)
.build()
);
system.out.println("✅ 流上传成功: " + objectname);
} catch (exception e) {
system.err.println("❌ 流上传失败: " + e.getmessage());
e.printstacktrace();
}
}
public static void main(string[] args) throws exception {
uploadfile("my-first-bucket", "photo.jpg", "/path/to/photo.jpg");
// 示例:上传字符串作为文件
string content = "hello minio from java!";
inputstream stream = new java.io.bytearrayinputstream(content.getbytes());
uploadstream("my-first-bucket", "hello.txt", stream, content.length(), "text/plain");
}
}import io.minio.getobjectargs;
import io.minio.getobjectresponse;
import io.minio.minioclient;
import java.io.*;
public class downloadexample {
// 下载到本地文件
public static void downloadtofile(string bucketname, string objectname, string destfilepath) {
try {
minioclient minioclient = minioclientutil.getinstance();
getobjectresponse response = minioclient.getobject(
getobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.build()
);
file file = new file(destfilepath);
fileoutputstream fos = new fileoutputstream(file);
byte[] buf = new byte[16384]; // 16kb buffer
int bytesread;
while ((bytesread = response.read(buf)) != -1) {
fos.write(buf, 0, bytesread);
}
fos.close();
response.close();
system.out.println("✅ 文件下载成功: " + destfilepath);
} catch (exception e) {
system.err.println("❌ 文件下载失败: " + e.getmessage());
e.printstacktrace();
}
}
// 下载为字符串(适用于文本文件)
public static string downloadasstring(string bucketname, string objectname) {
try {
minioclient minioclient = minioclientutil.getinstance();
getobjectresponse response = minioclient.getobject(
getobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.build()
);
stringbuilder sb = new stringbuilder();
bufferedreader reader = new bufferedreader(new inputstreamreader(response));
string line;
while ((line = reader.readline()) != null) {
sb.append(line).append("\n");
}
reader.close();
response.close();
return sb.tostring();
} catch (exception e) {
system.err.println("❌ 下载为字符串失败: " + e.getmessage());
e.printstacktrace();
return null;
}
}
public static void main(string[] args) {
downloadtofile("my-first-bucket", "photo.jpg", "./downloaded_photo.jpg");
string content = downloadasstring("my-first-bucket", "hello.txt");
if (content != null) {
system.out.println("📄 文件内容: " + content);
}
}
}import io.minio.listobjectsargs;
import io.minio.result;
import io.minio.messages.item;
import io.minio.minioclient;
public class listobjectsexample {
public static void listobjects(string bucketname) {
try {
minioclient minioclient = minioclientutil.getinstance();
iterable<result<item>> results = minioclient.listobjects(
listobjectsargs.builder()
.bucket(bucketname)
.build()
);
system.out.println("📦 存储桶 [" + bucketname + "] 中的对象列表:");
for (result<item> result : results) {
item item = result.get();
system.out.printf(" 名称: %s, 大小: %d 字节, 修改时间: %s%n",
item.objectname(),
item.size(),
item.lastmodified()
);
}
} catch (exception e) {
system.err.println("❌ 列出对象失败: " + e.getmessage());
e.printstacktrace();
}
}
public static void main(string[] args) {
listobjects("my-first-bucket");
}
}import io.minio.removebucketargs;
import io.minio.removeobjectargs;
import io.minio.minioclient;
public class deleteexample {
// 删除单个对象
public static void deleteobject(string bucketname, string objectname) {
try {
minioclient minioclient = minioclientutil.getinstance();
minioclient.removeobject(
removeobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.build()
);
system.out.println("🗑️ 对象已删除: " + objectname);
} catch (exception e) {
system.err.println("❌ 删除对象失败: " + e.getmessage());
e.printstacktrace();
}
}
// 删除空存储桶
public static void deletebucket(string bucketname) {
try {
minioclient minioclient = minioclientutil.getinstance();
// 先清空存储桶(minio 要求存储桶为空才能删除)
iterable<result<item>> results = minioclient.listobjects(
listobjectsargs.builder().bucket(bucketname).build()
);
for (result<item> result : results) {
item item = result.get();
minioclient.removeobject(
removeobjectargs.builder()
.bucket(bucketname)
.object(item.objectname())
.build()
);
system.out.println("🗑️ 清理对象: " + item.objectname());
}
// 删除存储桶
minioclient.removebucket(
removebucketargs.builder().bucket(bucketname).build()
);
system.out.println("🗑️ 存储桶已删除: " + bucketname);
} catch (exception e) {
system.err.println("❌ 删除存储桶失败: " + e.getmessage());
e.printstacktrace();
}
}
public static void main(string[] args) {
deleteobject("my-first-bucket", "hello.txt");
// deletebucket("my-first-bucket"); // 谨慎调用!
}
}minio 支持通过 xml 策略配置对象自动过期。
import io.minio.setbucketlifecycleargs;
import io.minio.minioclient;
import io.minio.messages.lifecycleconfiguration;
import io.minio.messages.rule;
import io.minio.messages.expiration;
import io.minio.messages.filter;
import io.minio.messages.andoperator;
public class lifecycleexample {
public static void setexpirationpolicy(string bucketname, int days) {
try {
minioclient minioclient = minioclientutil.getinstance();
lifecycleconfiguration config = new lifecycleconfiguration(
new rule[]{
new rule(
"delete old files",
"enabled",
new expiration(days, null, null),
null,
new filter(new andoperator(null, "logs/"), null),
null,
null,
null
)
}
);
minioclient.setbucketlifecycle(
setbucketlifecycleargs.builder()
.bucket(bucketname)
.config(config)
.build()
);
system.out.println("⏱️ 生命周期策略设置成功:匹配 'logs/' 前缀的对象将在 " + days + " 天后删除");
} catch (exception e) {
system.err.println("❌ 设置生命周期失败: " + e.getmessage());
e.printstacktrace();
}
}
public static void main(string[] args) {
setexpirationpolicy("my-first-bucket", 30);
}
}适用于分享私有文件给第三方临时访问。
import io.minio.getpresignedobjecturlargs;
import io.minio.http.method;
import io.minio.minioclient;
import java.time.zoneddatetime;
import java.time.temporal.chronounit;
public class presignedurlexample {
public static string generatepresignedurl(string bucketname, string objectname, int expiryhours) {
try {
minioclient minioclient = minioclientutil.getinstance();
zoneddatetime expiration = zoneddatetime.now().plus(expiryhours, chronounit.hours);
string url = minioclient.getpresignedobjecturl(
getpresignedobjecturlargs.builder()
.method(method.get)
.bucket(bucketname)
.object(objectname)
.expiry(expiryhours * 3600) // 单位:秒
.build()
);
system.out.println("🔗 生成临时访问链接(有效期 " + expiryhours + " 小时): " + url);
return url;
} catch (exception e) {
system.err.println("❌ 生成预签名 url 失败: " + e.getmessage());
e.printstacktrace();
return null;
}
}
public static void main(string[] args) {
generatepresignedurl("my-first-bucket", "photo.jpg", 24);
}
}下面是一个完整的 spring boot 应用示例,包含配置类、服务层和控制器。
minio: endpoint: http://your-server-ip:9000 accesskey: minioadmin secretkey: minioadmin123 bucket: my-spring-bucket
import io.minio.minioclient;
import org.springframework.beans.factory.annotation.value;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
@configuration
public class minioconfig {
@value("${minio.endpoint}")
private string endpoint;
@value("${minio.accesskey}")
private string accesskey;
@value("${minio.secretkey}")
private string secretkey;
@bean
public minioclient minioclient() {
return minioclient.builder()
.endpoint(endpoint)
.credentials(accesskey, secretkey)
.build();
}
}import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.bucket;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.service;
import org.springframework.web.multipart.multipartfile;
import java.io.inputstream;
import java.util.list;
import java.util.stream.collectors;
@service
public class minioservice {
@autowired
private minioclient minioclient;
@value("${minio.bucket}")
private string defaultbucket;
// 确保存储桶存在
public void ensurebucketexists(string bucketname) throws exception {
boolean found = minioclient.bucketexists(
makebucketargs.builder().bucket(bucketname).build()
);
if (!found) {
minioclient.makebucket(
makebucketargs.builder().bucket(bucketname).build()
);
}
}
// 上传文件
public string uploadfile(multipartfile file, string bucketname) throws exception {
if (bucketname == null || bucketname.isempty()) {
bucketname = defaultbucket;
}
ensurebucketexists(bucketname);
string objectname = system.currenttimemillis() + "_" + file.getoriginalfilename();
inputstream inputstream = file.getinputstream();
minioclient.putobject(
putobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.stream(inputstream, file.getsize(), -1)
.contenttype(file.getcontenttype())
.build()
);
return objectname;
}
// 下载文件流
public inputstream downloadfile(string bucketname, string objectname) throws exception {
return minioclient.getobject(
getobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.build()
);
}
// 获取文件 url
public string getfileurl(string bucketname, string objectname, int expiryhours) throws exception {
return minioclient.getpresignedobjecturl(
getpresignedobjecturlargs.builder()
.method(method.get)
.bucket(bucketname)
.object(objectname)
.expiry(expiryhours * 3600)
.build()
);
}
// 删除文件
public void deletefile(string bucketname, string objectname) throws exception {
minioclient.removeobject(
removeobjectargs.builder()
.bucket(bucketname)
.object(objectname)
.build()
);
}
// 列出所有存储桶
public list<string> listbuckets() throws exception {
return minioclient.listbuckets().stream()
.map(bucket::name)
.collect(collectors.tolist());
}
}import org.springframework.beans.factory.annotation.autowired;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.multipartfile;
@restcontroller
@requestmapping("/api/minio")
public class miniocontroller {
@autowired
private minioservice minioservice;
@postmapping("/upload")
public responseentity<string> uploadfile(@requestparam("file") multipartfile file,
@requestparam(value = "bucket", required = false) string bucket) {
try {
string objectname = minioservice.uploadfile(file, bucket);
return responseentity.ok("文件上传成功,对象名:" + objectname);
} catch (exception e) {
return responseentity.badrequest().body("上传失败:" + e.getmessage());
}
}
@getmapping("/download-url")
public responseentity<string> getdownloadurl(@requestparam string objectname,
@requestparam(value = "bucket", required = false) string bucket,
@requestparam(defaultvalue = "1") int hours) {
try {
if (bucket == null || bucket.isempty()) {
bucket = "my-spring-bucket"; // 默认值
}
string url = minioservice.getfileurl(bucket, objectname, hours);
return responseentity.ok(url);
} catch (exception e) {
return responseentity.badrequest().body("生成链接失败:" + e.getmessage());
}
}
@deletemapping("/delete")
public responseentity<string> deletefile(@requestparam string objectname,
@requestparam(value = "bucket", required = false) string bucket) {
try {
minioservice.deletefile(bucket != null ? bucket : "my-spring-bucket", objectname);
return responseentity.ok("文件删除成功");
} catch (exception e) {
return responseentity.badrequest().body("删除失败:" + e.getmessage());
}
}
@getmapping("/buckets")
public responseentity<?> listbuckets() {
try {
var buckets = minioservice.listbuckets();
return responseentity.ok(buckets);
} catch (exception e) {
return responseentity.badrequest().body("获取存储桶列表失败:" + e.getmessage());
}
}
}启动 spring boot 应用后,你可以通过如下接口操作 minio:
post /api/minio/upload — 上传文件get /api/minio/download-url — 获取下载链接delete /api/minio/delete — 删除文件get /api/minio/buckets — 列出所有存储桶虽然 minio 默认提供基础认证,但在生产环境中仍需进一步加固:
购买或申请免费 ssl 证书(如 let’s encrypt),然后启动时指定:
export minio_server_url="https://your-domain.com" minio server ~/minio/data --certs-dir /path/to/certs --console-address ":9001"
证书文件需命名为 public.crt 和 private.key。
通过控制台或 mc 命令创建子用户并分配最小权限策略:
mc admin user add myminio appuser appuser123 mc admin policy attach myminio readwrite --user appuser
export minio_audit_webhook_endpoint="http://audit-server:port" minio server ~/minio/data
sudo ufw allow from 192.168.1.0/24 to any port 9000 sudo ufw deny 9000
minio 提供了丰富的监控指标,可通过 prometheus + grafana 可视化。
minio 默认在 :9000/minio/v2/metrics/cluster 提供 prometheus 格式指标。
在 prometheus.yml 中添加:
scrape_configs:
- job_name: 'minio'
static_configs:
- targets: ['your-minio-ip:9000']
官方提供预设 dashboard id:11945
grafana dashboard:https://grafana.com/grafana/dashboards/11945
检查环境变量是否正确设置:
echo $minio_root_user echo $minio_root_password
确保没有拼写错误,且在启动前已 export。
如果你使用的是自签名证书或 http,请在客户端禁用 ssl 验证(仅限测试):
minioclient = minioclient.builder()
.endpoint("http://ip:9000")
.credentials("user", "pass")
.httpclient(
new okhttpclient.builder()
.sslsocketfactory(getunsecuredsslsocketfactory(), (hostname, session) -> true)
.hostnameverifier((hostname, session) -> true)
.build()
)
.build();生产环境切勿禁用 ssl 验证!
默认分片大小可能不足,可调整:
minioclient.putobject(
putobjectargs.builder()
.bucket(bucket)
.object(object)
.stream(stream, size, 64 * 1024 * 1024) // 设置分片为 64mb
.build()
);
确认 --console-address 参数是否设置,以及防火墙是否放行 9001 端口。
对象存储对 i/o 敏感,ssd 可显著提升吞吐量。
# 增加文件描述符限制 echo "fs.file-max = 1000000" >> /etc/sysctl.conf # 优化网络缓冲区 echo "net.core.rmem_max = 16777216" >> /etc/sysctl.conf echo "net.core.wmem_max = 16777216" >> /etc/sysctl.conf
minio gateway cache s3 https://s3.amazonaws.com
minio 以其简洁、高效、兼容性强的特点,成为构建私有云存储系统的首选方案。无论是个人项目、中小企业,还是大型分布式架构,minio 都能提供稳定可靠的对象存储服务。
通过本文,你已经掌握了:
minio 不仅仅是一个存储引擎,更是现代云原生架构的重要基石。结合 kubernetes、docker、ci/cd 流水线,你可以构建出弹性伸缩、自动备份、智能分发的企业级存储平台。
数据是新时代的石油,而存储系统就是炼油厂。选择 minio,意味着你选择了开源、自由、高性能和未来。希望本文能帮助你在 linux 世界中轻松搭建属于自己的“云存储帝国”。
以上就是linux搭建私有云存储系统minio的详细过程的详细内容,更多关于linux搭建私有云存储系统minio的资料请关注代码网其它相关文章!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论