参考文章: https://cloud.tencent.com/developer/beta/article/1701451
docker是什么?
Docker是一种轻量级的虚拟化技术,它允许开发人员将应用程序和所有相关的依赖项打包到一个可移植的容器中,以便在任何地方运行。
与传统的虚拟机技术相比,Docker的最大区别在于它的轻量性。传统的虚拟机技术需要虚拟化整个操作系统,包括内核,这需要大量的资源和时间来完成。而Docker只需虚拟化应用程序和依赖项,共享宿主操作系统的内核,因此它更加轻量级和高效。
这里有一个生动的比喻来解释Docker和传统虚拟机之间的区别:
传统的虚拟机就像租一间完全独立的房子,这间房子里有一张床、一张桌子和一把椅子,以及一间私人厕所和浴室。租房子需要花费很多时间和金钱,因为你需要购买和设置所有的设备和家具。
Docker就像租一间公寓,公寓里有一张床、一张桌子和一把椅子,还有一个共用的厨房和浴室。这样你就不需要为每个房间购买和设置设备,因为它们已经在公寓里了。
换句话说,传统的虚拟机技术需要完全复制整个操作系统,而Docker只需要复制应用程序和它的依赖项。这使得Docker非常适合在开发、测试和部署应用程序时使用,因为它可以快速、可靠地打包和运行应用程序,并确保在任何环境中都能按照预期工作。
虽然Docker和虚拟机都可以用于虚拟化,但它们的目的和工作原理不同,因此它们的使用方式也有所不同。
在虚拟机中,每个虚拟机都有自己的操作系统和内核,因此可以在虚拟机上运行任何类型的应用程序和操作系统。这意味着虚拟机可以像一个完整的计算机一样使用,包括运行多个不同类型的应用程序和操作系统。
与此不同,Docker容器是轻量级的虚拟化技术,它共享宿主操作系统的内核,因此每个容器只能运行一个应用程序及其依赖项。但是,Docker可以使用容器来隔离和管理应用程序及其依赖项,使其更容易打包、部署和运行。
虽然Docker容器不能像虚拟机一样完全模拟一个完整的操作系统,但它们可以提供类似的隔离和安全性,同时具有更快的启动时间、更低的资源消耗和更好的可移植性。
因此,如果您需要在一个完整的操作系统环境中运行多个应用程序和操作系统,虚拟机可能是更好的选择。但如果您只需要运行一个应用程序及其依赖项,并希望将其打包到一个可移植的容器中进行部署,那么Docker可能是更好的选择。
Docker主要由以下几个部分构成:
Docker客户端(Docker Client):Docker客户端是一个命令行工具,它允许用户通过命令行或者API与Docker守护进程交互,例如构建、运行和管理Docker容器。
Docker守护进程(Docker Daemon):Docker守护进程是Docker的核心组件,它运行在宿主机上,负责管理Docker对象,例如镜像、容器、网络和数据卷等。Docker守护进程还负责监听来自Docker客户端的请求,并相应地执行操作。
Docker镜像(Docker Image):Docker镜像是一个只读的模板,它包含了运行Docker容器所需的文件系统、应用程序和依赖项等。镜像可以用于创建Docker容器,并且可以通过构建或拉取来获取。
Docker容器(Docker Container):Docker容器是从Docker镜像创建的一个可运行的实例,它包含了镜像中的所有文件系统、应用程序和依赖项。容器可以启动、停止、重启和删除等,同时可以与宿主机或其他容器进行通信。
Docker仓库(Docker Registry):Docker仓库是一个集中存储Docker镜像的地方,它允许用户共享、管理和拉取Docker镜像。Docker Hub是一个公共的Docker仓库,用户可以在其中找到各种各样的镜像,也可以创建自己的私有仓库来存储和共享自己的镜像。
总的来说,Docker的工作流程可以简化为:通过Docker客户端构建或拉取Docker镜像,然后使用镜像创建Docker容器,并在容器中运行应用程序。Docker容器可以与宿主机或其他容器进行通信,也可以通过Docker仓库来分享和管理镜像。
以下是一些常用的Docker命令及其解释:
docker run:从Docker镜像创建并启动一个新的容器。
docker ps:列出当前正在运行的Docker容器。
docker stop:停止正在运行的Docker容器。
docker rm:删除一个或多个Docker容器。
docker images:列出本地所有可用的Docker镜像。
docker rmi:删除一个或多个Docker镜像。
docker build:根据Dockerfile构建一个新的Docker镜像。
docker pull:从Docker仓库拉取一个Docker镜像。
docker push:将一个Docker镜像推送到Docker仓库。
docker exec:在正在运行的Docker容器中执行一个命令。
docker logs:查看一个正在运行的Docker容器的日志。
docker-compose:使用docker-compose.yml文件定义和管理多个Docker容器的应用程序。
参考文章:https://blog.csdn.net/weixin_43652442/article/details/121758178
如果已经拉取了Minio镜像,并且使用docker run命令启动了Minio容器,那么当CentOS主机重新启动后,可以使用以下命令来重新启动Minio容器:
检查本地已经有哪些Docker容器正在运行,使用以下命令:
docker ps
如果看到Minio容器在运行,则可以直接使用docker start命令启动该容器,例如:
docker start minio
如果看到Minio容器不在运行状态,则可以使用以下命令启动容器:
docker run -p 9000:9000 --name minio \
-e "MINIO_ACCESS_KEY=your_access_key" \
-e "MINIO_SECRET_KEY=your_secret_key" \
-d minio/minio server /data
这将创建一个名为minio的新容器,并将其绑定到主机上的9000端口。请注意,需要将your_access_key和your_secret_key替换为自己的访问密钥和密钥。
这些命令将启动Minio容器,并在CentOS主机上重新启动Minio服务。
端口问题:
0.0.0.0:9002->9000/tcp, 0.0.0.0:9003->9001/tcp
这是Docker容器端口映射的表示方法,格式为[主机IP:]主机端口:容器端口/协议。
在您提供的示例中,0.0.0.0:9002->9000/tcp表示将主机的9002端口映射到Docker容器内的9000端口,0.0.0.0:9003->9001/tcp表示将主机的9003端口映射到Docker容器内的9001端口。
其中,0.0.0.0表示绑定所有可用的网络接口,这意味着可以通过任何网络接口(包括本地主机和远程主机)访问容器的服务。如果您想将容器服务暴露给特定的网络接口,可以将0.0.0.0替换为相应的IP地址。
tcp表示使用TCP协议进行端口映射。如果您想使用其他协议进行端口映射(例如UDP),可以将tcp替换为相应的协议名称。
总之,这个端口映射的含义是:当Docker容器内的服务(例如Minio服务)监听9000或9001端口时,可以通过主机的9002或9003端口来访问该服务。
访问http://192.168.118.128:9003/browser/test,到自己新建的桶,然后访问其中的图片http://192.168.118.128:9002/test/OIP-C.jpg,这样就成功了,注意端口的对应关系。
Minio 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
Minio是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
参考文章:https://blog.csdn.net/mrxutada/article/details/120450811
JAVA使用minio的方法
上面的是直接才界面控制台进行创建桶、上传文件、删除文件等操作,下面来使用java代码操作,写个简单demo,演示连接到一个对象存储服务,创建一个存储桶并上传一个文件到该桶中。
(1)添加依赖
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>7.1.0version>
dependency>
(2)参数配置
需要有存储服务的三个参数才能连接到该服务
参数 说明
Endpoint 对象存储服务的URL
Access Key Access key就像用户ID,可以唯一标识你的账户。
Secret Key Secret key是你账户的密码。
我这里配置在yaml文件中的:
(3)编写minio配置信息
package com.xxx.xxx.common.config;
import com.xxx.xxx.common.exception.MinioException;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.SetBucketPolicyArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* minio自动配置.
*
* @author Mei rx
* @since 2021/08/05
*/
@Configuration
@ConfigurationProperties(prefix = "minio")
@ConditionalOnClass({MinioClient.class})
@ConditionalOnProperty({"minio.endpoint"})
public class MinioAutoConfiguration {
@Autowired
private Environment environment;
public MinioAutoConfiguration() {
}
@Bean
public MinioClient minioClient() {
String endpoint = this.environment.getProperty("minio.endpoint");
String accessKey = this.environment.getProperty("minio.accessKey");
String secretKey = this.environment.getProperty("minio.secretKey");
String bucketName = this.environment.getProperty("minio.bucketName");
if (endpoint != null && !"".equals(endpoint)) {
if (accessKey != null && !"".equals(accessKey)) {
if (secretKey != null && !"".equals(secretKey)) {
if (bucketName != null && !"".equals(bucketName)) {
MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
this.makeBucket(minioClient, bucketName);
return minioClient;
} else {
throw new MinioException("存储桶名称未在application.yml配置!");
}
} else {
throw new MinioException("Minio密码未在application.yml配置!");
}
} else {
throw new MinioException("Minio用户名未在application.yml配置!");
}
} else {
throw new MinioException("Minio的URL未在application.yml配置!");
}
}
private void makeBucket(MinioClient minioClient, String bucketName) {
try {
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
String policyJson = "Bucket already exists.";
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());
}
} catch (Exception e) {
throw new MinioException("创建minio存储桶异常", e);
}
}
}
(4)编写上传代码
public static String uploadFile(InputStream inputStream, String objectName, String fileName) {
if (StringUtil.isNotBlank(fileName)) {
objectName = objectName + "/" + fileName;
}
try {
if (objectName != null && !"".equals(objectName)) {
try {
minioUtil.minioClient.putObject(PutObjectArgs
.builder()
.bucket(minioUtil.bucketName)
.object(objectName)
.stream(inputStream, inputStream.available(), -1)
.build());
log.info("文件上传成功!");
} catch (Exception var4) {
log.error("添加存储对象异常", var4);
throw new MinioException("添加存储对象异常", var4);
}
} else {
throw new MinioException("存储对象名称objectName不能为空!");
}
log.info("文件上传成功!");
return minioUtil.getUrl(objectName);
} catch (Exception var4) {
var4.printStackTrace();
log.error("上传发生错误: {}!", var4.getMessage());
return var4.getMessage();
}
}
(5)调用上传方法
/**
* 上传材料
*/
@PostMapping("/upload")
@ApiOperation("上传文件")
public String upload(
@ApiParam(value = "文件", example = "11.jpg", required = true)
@RequestPart(value = "file") MultipartFile file) throws IOException {
log.info("uploadFile:上传文件[file:{}]", file);
return MinioUtil.uploadFile(file.getInputStream(), "my-file", file.getOriginalFilename());
}
(6)测试
调用接口上传文件成功,返回一个可访问的url,访问一下
注意:如果出现如下提示,很有可能是buckets的权限问题,修改一下权限即可
工具类代码:
package com.xxx.xxx.util;
import com.xxx.core.minio.exception.MinioException;
import com.xxx.core.tool.utils.Charsets;
import com.xxx.core.tool.utils.DateUtil;
import com.xxx.core.tool.utils.StringUtil;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* minio存储对象工具类.
*
* @author Mei rx
* @since 2021/09/24
*/
@Component
@Slf4j
public class MinioUtil {
@Value("${minio.bucketName}")
private String bucketName;
@Resource
private MinioClient minioClient;
private static MinioUtil minioUtil;
public MinioUtil() {
}
@PostConstruct
public void init() {
minioUtil = this;
}
public static String uploadFile(InputStream inputStream, String objectName, String fileName) {
if (StringUtil.isNotBlank(fileName)) {
objectName = objectName + "/" + fileName;
}
try {
if (objectName != null && !"".equals(objectName)) {
try {
minioUtil.minioClient.putObject(PutObjectArgs
.builder()
.bucket(minioUtil.bucketName)
.object(objectName)
.stream(inputStream, inputStream.available(), -1)
.build());
log.info("文件上传成功!");
} catch (Exception e) {
log.error("添加存储对象异常", e);
throw new MinioException("添加存储对象异常", e);
}
} else {
throw new MinioException("存储对象名称objectName不能为空!");
}
log.info("文件上传成功!");
return minioUtil.getUrl(objectName);
} catch (Exception ex) {
ex.printStackTrace();
log.error("上传发生错误: {}!", ex.getMessage());
return ex.getMessage();
}
}
public static InputStream download(String fileUrl) {
try {
fileUrl = fileUrl.substring(fileUrl.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
InputStream inputStream = minioUtil.get(fileUrl);
log.info("下载成功");
return inputStream;
} catch (Exception e) {
e.printStackTrace();
log.error("下载发生错误: {}!", e.getMessage());
return null;
}
}
public static void batchDownload(List<String> fileUrlList, String zipName, HttpServletResponse httpServletResponse) {
ZipOutputStream zos;
ZipEntry zipEntry;
byte[] buff = new byte[1024];
if (fileUrlList != null && !fileUrlList.isEmpty()) {
try {
if (StringUtil.isEmpty(zipName)) {
zipName = "批量下载" + DateUtil.format(new Date(), "yyyyMMddHHmmss");
}
//清除缓冲区中存在的所有数据以及状态代码和标头。如果已提交响应,则此方法将抛出IllegalStateException
httpServletResponse.reset();
//Content-Disposition为属性名,attachment以附件方式下载,filename下载文件默认名字
httpServletResponse.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(zipName, Charsets.UTF_8.name()) + ".zip");
//另存为弹框加载
httpServletResponse.setContentType("application/x-msdownload");
httpServletResponse.setCharacterEncoding("utf-8");
zos = new ZipOutputStream(httpServletResponse.getOutputStream());
for (String fileUrl : fileUrlList) {
//获取minio对应路径文件流(从数据库中获取的url是编码的,这里要先进行解码,不然minioClient.getObject()方法里面会再进行一次编码,就获取不到对象)
String url = URLDecoder.decode(fileUrl,Charsets.UTF_8.name());
String downloadUrl = url.substring(url.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
InputStream inputStream = minioUtil.get(downloadUrl);
String fileName = url.substring(url.lastIndexOf("/"), url.lastIndexOf("."));
zipEntry = new ZipEntry(fileName + url.substring(url.lastIndexOf(".")));
zos.putNextEntry(zipEntry);
int length;
while ((length = inputStream.read(buff)) > 0) {
zos.write(buff, 0, length);
}
}
log.info("批量下载成功!");
zos.close();
} catch (IOException ioException) {
ioException.printStackTrace();
log.error("批量下载发生错误! msg=" + ioException.getMessage());
} finally {
}
} else {
log.error("批量下载发生错误,文件访问路径集合不能为空!");
}
}
public static void removeFile(String fileUrl) {
try {
String downloadUrl = fileUrl.substring(fileUrl.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
minioUtil.rm(downloadUrl);
log.info("文件删除成功!");
} catch (Exception e) {
e.printStackTrace();
log.error("文件删除失败! msg=" + e.getMessage());
}
}
public static void batchRemoveFile(List<String> fileUrlList) {
if (fileUrlList != null && !fileUrlList.isEmpty()) {
try {
for (String fileUrl : fileUrlList) {
String downloadUrl = fileUrl.substring(fileUrl.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
minioUtil.rm(downloadUrl);
}
log.info("文件批量删除成功!");
} catch (Exception e) {
e.printStackTrace();
log.error("文件批量删除失败! msg=" + e.getMessage());
}
}
}
public InputStream get(String objectName) {
InputStream inputStream;
try {
inputStream = this.minioClient.getObject(GetObjectArgs.builder().bucket(this.bucketName).object(objectName).build());
return inputStream;
} catch (Exception e) {
log.error("读取存储对象异常", e);
throw new MinioException("读取存储对象异常", e);
}
}
public void download(String objectName, String fileName) {
try {
this.minioClient.downloadObject(DownloadObjectArgs.builder().bucket(this.bucketName).object(objectName).filename(fileName).build());
} catch (Exception e) {
log.error("下载发生错误:[{}]", e.getMessage());
throw new MinioException("下载发生错误", e);
}
}
public String getUrl(String objectName) {
try {
return this.minioClient.getObjectUrl(this.bucketName, objectName);
} catch (Exception e) {
log.error("获取存储对象url异常", e);
throw new MinioException("获取存储对象url异常", e);
}
}
public void rm(String objectName) {
try {
this.minioClient.removeObject(RemoveObjectArgs.builder().bucket(this.bucketName).object(objectName).build());
} catch (Exception e) {
log.error("删除存储对象异常", e);
throw new MinioException("删除存储对象异常", e);
}
}
public void rmBatch(Collection<String> objectNames) {
if (!CollectionUtils.isEmpty(objectNames)) {
objectNames.forEach(this::rm);
}
}
}