阿里云服务器开启HTTPS/SSL by Nginx

1.获取免费SSL证书#

云服务器是阿里云的,同时阿里云也赠送免费DV单域名证书(0元购买)

1
2
每个实名主体个人/企业,一个自然年内可以领取一次数量为20的免费证书资源包
免费资源包到自然年结束时,会自动清除未签发的数量(每个自然年12月31日24:00)

为每个单域名申请证书,等待签发后,然后根据服务器类型(Tomcat,Apache,Nginx等)下载相应的证书

2.配置Nginx支持HTTPS#

1
cat /etc/nginx/conf.d/vhost/ysmblog.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
listen 80;
listen [::]:80;
server_name blog.yeshimin.com;
rewrite ^(.*) https://$server_name$1 permanent;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name blog.yeshimin.com;

ssl_certificate "/etc/nginx/ssl/blog.yeshimin.com.pem";
ssl_certificate_key "/etc/nginx/ssl/blog.yeshimin.com.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

location / {
root /var/www/ysmblog;
}
}
1
nginx -s reload

3.资料#

4.版本#

  • Nginx 1.20.1

SpringCloudGateway集成Swagger

1.背景#

微服务架构下,各个服务单独集成使用Swagger不方便,所以需要将其集中到网关使用

2.原理#

SwaggerResourcesProvider接口负责提供文档信息
官方默认提供实现类InMemorySwaggerResourcesProvider,只针对单服务场景
可以自定义实现类并配合网关或服务发现机制提供各服务的文档信息

3.实现#

自定义实现类核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Primary
@Component
public class CustomSwaggerResourcesProvider implements SwaggerResourcesProvider {

@Lazy
@Autowired
private RouteLocator routeLocator;

@Override
public List<SwaggerResource> get() {
List<Route> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(routes::add);
return routes.stream().map(route -> this.resource(route.getUri().getHost())).collect(Collectors.toList());
}

private SwaggerResource resource(String service) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(service);
swaggerResource.setUrl("/" + service + "/v3/api-docs");
swaggerResource.setSwaggerVersion("3.0.3");
return swaggerResource;
}
}

利用SpringCloudGateway的RouteLocator获取服务信息,组装并返回文档信息
@Primary注解使该自定义实现类代替默认的InMemorySwaggerResourcesProvider
网关只做此简单配置,Docket在各服务配置

4.补充#

问题:
如上配置后,在网关已能打开Swagger并出现各服务列表
但是发现服务接口调用不通,原因是网关截掉了路径中的服务信息,导致此处Swagger的Servers列表中的基地址缺少服务信息,所以无法路由到具体服务
而下游服务中的Swagger过滤器WebMvcBasePathAndHostnameTransformationFilter也未对此种情况进行支持(即便网关向下游服务提供了X-Forwarded-相关头部信息),所以提供给上游网关的Servers信息中才没有服务信息

解决方案:
在下游服务中自定义WebMvcOpenApiTransformationFilter实现类(可直接继承官方默认实现类WebMvcBasePathAndHostnameTransformationFilter),补充服务信息
确保优先级小于WebMvcBasePathAndHostnameTransformationFilter

自定义Filter核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
public class PrefixedWebMvcOpenApiTransformationFilter extends WebMvcBasePathAndHostnameTransformationFilter {

private static final String HEADER_X_FORWARDED_PREFIX = "X-Forwarded-Prefix";

public PrefixedWebMvcOpenApiTransformationFilter(String oasPath) {
super(oasPath);
}

@Override
public OpenAPI transform(OpenApiTransformationContext<HttpServletRequest> context) {
OpenAPI openApi = context.getSpecification();
context.request().ifPresent(servletRequest -> {
String headerValue = servletRequest.getHeader(HEADER_X_FORWARDED_PREFIX);
if (!StringUtils.isEmpty(headerValue)) {
openApi.getServers().forEach(server -> server.setUrl(server.getUrl() + headerValue));
}
});
return openApi;
}
}

配置该自定义Filter,核心代码如下:

1
2
3
4
5
6
7
8
9
@Configuration
public class SwaggerConfiguration {

@Bean
public WebMvcOpenApiTransformationFilter prefixedWebMvcOpenApiTransformer(
@Value(springfox.documentation.oas.web.SpecGeneration.OPEN_API_SPECIFICATION_PATH) String oasPath) {
return new PrefixedWebMvcOpenApiTransformationFilter(oasPath);
}
}

5.资料#

6.版本#

  • Spring Cloud Gateway 2.2.5
  • SpringFox(Swagger) 3.0.0

基于SpringCloudGateway+SpringSecurity的微服务鉴权方案

1.Spring Cloud Gateway集成Spring Security#

网关只进行服务级别的认证,不做授权逻辑判断

1.1 Spring Secuty核心配置#

核心代码如下:

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
37
38
39
40
41
42
43
44
45
46
47
@EnableWebFluxSecurity
public class WebSecurityConfiguration {

@Autowired
private JwtTokenAuthenticationManager jwtTokenAuthenticationManager;

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
// 鉴权配置
AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(jwtTokenAuthenticationManager);
authenticationWebFilter.setServerAuthenticationConverter(new JwtTokenAuthenticationConverter());
authenticationWebFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
http.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);

// 清除一次请求后的SecurityContext
http.addFilterBefore(new ClearExchangeSecurityContextWebFilter(), SecurityWebFiltersOrder.REACTOR_CONTEXT);

// 跨域配置
http.cors().configurationSource(corsConfigurationSource());

http.csrf().disable();

http.authorizeExchange()
.pathMatchers("/**/login").permitAll()
.pathMatchers("/**/logout").authenticated()
.pathMatchers("/upms/**").authenticated()
.pathMatchers("/product/**").authenticated()
.anyExchange().denyAll();

return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();

config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);

source.registerCorsConfiguration("/**", config);
return source;
}
}

上述鉴权配置使用Spring Security自带的AuthenticationWebFilter类,对其进行定制

1.2 定制ServerAuthenticationConverter,用于匹配并生成所需Authentication(即JwtTokenAuthenticationToken)#

自定义JwtTokenAuthenticationConverter核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JwtTokenAuthenticationConverter implements ServerAuthenticationConverter {

@Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
return apply(exchange);
}

public Mono<Authentication> apply(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();

String token = request.getHeaders().getFirst(HeaderNames.X_TOKEN);
if (StringUtils.isBlank(token)) {
return Mono.empty();
}

return Mono.just(new JwtTokenAuthenticationToken(token));
}
}

自定义JwtTokenAuthenticationToken核心代码如下:

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
public class JwtTokenAuthenticationToken extends AbstractAuthenticationToken {

private String token;
private Long userId;

public JwtTokenAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
super(authorities);
}

public JwtTokenAuthenticationToken(String token) {
super(null);
this.token = token;
}

public void setUserId(Long userId) {
this.userId = userId;
}

public Long getUserId() {
return this.userId;
}

@Override
public Object getCredentials() {
return token;
}

@Override
public Object getPrincipal() {
return null;
}

@Override
public boolean implies(Subject subject) {
return false;
}

1.3 定制ReactiveAuthenticationManager,用于执行鉴权逻辑#

自定义JwtTokenAuthenticationManager核心代码如下:

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
@Slf4j
@Component
public class JwtTokenAuthenticationManager implements ReactiveAuthenticationManager {

@Autowired
private AuthFeignService authFeignService;

@Override
public Mono<Authentication> authenticate(Authentication authentication) throws AuthenticationException {
JwtTokenAuthenticationToken requestToken = (JwtTokenAuthenticationToken) authentication;
String token = (String) requestToken.getCredentials();

AuthDto authDto = new AuthDto();
authDto.setToken(token);
// 服务级别只认证,不做授权逻辑判断
authDto.setOnlyAuthenticate(true);
ResultVo<AuthVo> resultVo = authFeignService.auth(authDto);
if (resultVo.getCode() != ErrorCodeEnum.SUCCESS.getCode() || !resultVo.getData().getAuthenticated()) {
log.error("调用auth服务鉴权失败!");
return Mono.error(new BadCredentialsException("调用auth服务鉴权失败!"));
}

// set authorities
List<GrantedAuthority> authorities = Collections.emptyList();
JwtTokenAuthenticationToken resultToken = new JwtTokenAuthenticationToken(authorities);
resultToken.setAuthenticated(true);
resultToken.setUserId(resultVo.getData().getUserId());

return Mono.just(resultToken);
}
}

1.4 定制ServerSecurityContextRepository,用于存取SecurityContext(安全上下文)#

由于Spring Cloud Gateway基于WebFlux构建,有别于javax servlet模型,不可使用基于ThreadLocal的SecurityContextHolder存取SecurityContext
可以使用Spring Security的基于session的WebSessionServerSecurityContextRepository类
代码见源码:WebSessionServerSecurityContextRepository

1.5 清除安全上下文#

由于访问凭证使用的是jwt token方案,而Spring Security的WebSessionServerSecurityContextRepository会使用到session
出现一个问题,当执行一次请求,触发鉴权操作,会生成一个session,即使jwt token失效后或者不带上jwt token,浏览器会自动带上sessionid,而服务端的session可能还未失效,这时该次请求会从session中获取缓存的已认证通过的Authentication
解决方案之一:使session和jwt token的过期时间保持一致,配置Spring Security的logout以使调用登出接口时清除相应SecurityContext
解决方案之二:自定义ServerSecurityContextRepository,避开session
解决方案之三:每次请求结束前清除相应的SecurityContext

此处使用方案三,核心代码如下:

1
2
3
4
5
6
7
8
9
public class ClearExchangeSecurityContextWebFilter implements WebFilter {

private ServerSecurityContextRepository securityContextRepository = new WebSessionServerSecurityContextRepository();

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange).then(securityContextRepository.save(exchange, null));
}
}

1.6 网关向下游服务传递Header参数#

网关做完鉴权操作后,要向下游服务传递所需的(Header)参数,比如userId
使用自定义的Spring Cloud Gateway的GlobalFilter

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class GatewayWebContextGlobalFilter implements GlobalFilter {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(authentication -> authentication instanceof JwtTokenAuthenticationToken)
.map(authentication -> (JwtTokenAuthenticationToken) authentication)
.flatMap(authentication -> {
String headerName = HeaderNames.X_USER_ID;
String headerValue = String.valueOf(authentication.getUserId());

ServerHttpRequest request = exchange.getRequest().mutate().header(headerName, headerValue).build();

return chain.filter(exchange.mutate().request(request).build());
})
.switchIfEmpty(chain.filter(exchange));
}
}

配置自定义GlobalFilter,核心代码如下:

1
2
3
4
5
6
7
8
@Configuration
public class GatewayConfiguration {

@Bean
public GatewayWebContextGlobalFilter gatewayWebContextGlobalFilter() {
return new GatewayWebContextGlobalFilter();
}
}

2.下游服务集成Spring Security#

下游服务做接口方法级别的鉴权
需要开启@EnableGlobalMethodSecurity
需要鉴权的接口方法添加@PreAuthorize注解

2.1 Spring Security核心配置#

核心代码如下:

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
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private JwtTokenAuthenticationProvider jwtTokenAuthenticationProvider;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors().disable();

http.addFilterAfter(new JwtTokenAuthenticationFilter(authenticationManagerBean()), LogoutFilter.class);
}

/**
* 如果重写了authenticationManagerBean(),需要同时重写该方法
*
* @see WebSecurityConfigurerAdapter#authenticationManagerBean()
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtTokenAuthenticationProvider);
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

2.2 自定义Authentication#

核心代码如下:

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
public class JwtTokenAuthenticationToken extends AbstractAuthenticationToken {

private String token;

public JwtTokenAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
super(authorities);
}

public JwtTokenAuthenticationToken(String token) {
super(null);
this.token = token;
}

@Override
public Object getCredentials() {
return token;
}

@Override
public Object getPrincipal() {
return null;
}

@Override
public boolean implies(Subject subject) {
return false;
}
}

2.3 自定义Filter#

核心代码如下:

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
37
38
39
40
41
42
43
44
45
@Slf4j
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {

private AuthenticationManager authenticationManager;

public JwtTokenAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

logger.debug("doFilterInternal()");

// If required, authenticate and set web context
authenticateIfRequired(request);

filterChain.doFilter(request, response);

// Clear web context
WebContextUtils.clear();
}

/**
* Authenticate if required
*/
private void authenticateIfRequired(HttpServletRequest request) {
String token = request.getHeader(Common.TOKEN_HEADER_KEY);
if (StringUtils.isBlank(token)) {
log.debug("no token, as anonymous in later AnonymousAuthenticationFilter");
// // 在过滤链AnonymousAuthenticationFilter中设置为匿名用户
// // 这里要先clear,否则在匿名Filter中会存在JwtTokenAuthenticationToken;具体原因暂时未知
SecurityContextHolder.clearContext();
return;
}

// wrap request authentication
Authentication authRequest = new JwtTokenAuthenticationToken(token);
// authenticate for result (delegate by ProviderManager)
Authentication authResult = authenticationManager.authenticate(authRequest);
// set to security context
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}

2.4 自定义AuthenticationProvider#

在网关已经做了认证,此处主要是做授权

核心代码如下:

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
37
38
39
40
41
42
43
44
45
@Slf4j
@Component
public class JwtTokenAuthenticationProvider implements AuthenticationProvider {

@Autowired
private AuthFeignService authFeignService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JwtTokenAuthenticationToken requestToken = (JwtTokenAuthenticationToken) authentication;
String token = (String) requestToken.getCredentials();

AuthDto authDto = new AuthDto();
authDto.setToken(token);
ResultVo<AuthVo> resultVo = authFeignService.auth(authDto);
if (resultVo.getCode() != ErrorCodeEnum.SUCCESS.getCode() || !resultVo.getData().getAuthenticated()) {
log.error("调用auth服务鉴权失败!");
throw new BaseException(ErrorCodeEnum.AUTH_FAIL, "调用auth服务鉴权失败!");
}

// set web context
WebContextUtils.setToken(token);
WebContextUtils.setUserId(resultVo.getData().getUserId());

AuthVo authVo = resultVo.getData();
// role
List<String> listAuthority = authVo.getRoles().stream().map(s -> "ROLE_" + s).collect(Collectors.toList());
// add resource
listAuthority.addAll(authVo.getResources());
String commaSeparatedRoles = String.join(",", listAuthority);

// set authorities
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(commaSeparatedRoles);
// new authentication
JwtTokenAuthenticationToken resultToken = new JwtTokenAuthenticationToken(authorities);
resultToken.setAuthenticated(true);

return resultToken;
}

@Override
public boolean supports(Class<?> authentication) {
return JwtTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
}

2.5 网关向下游服务传递Header参数,此处做提取并放置到WebContext(Utils)中#

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
public class ServiceWebContextFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String token = request.getHeader(HeaderNames.X_TOKEN);
String userId = request.getHeader(HeaderNames.X_USER_ID);

log.debug("WebContext.token: {}", token);
log.debug("WebContext.userId: {}", userId);

if (token != null) {
WebContextUtils.setToken(token);
}
if (userId != null) {
WebContextUtils.setUserId(Long.parseLong(userId));
}

filterChain.doFilter(request, response);
}
}

配置提取Header参数的Filter,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class ContextConfiguration {

@Bean
public FilterRegistrationBean<ServiceWebContextFilter> serviceWebContextFilter() {
FilterRegistrationBean<ServiceWebContextFilter> registrationBean = new FilterRegistrationBean<>();

registrationBean.setFilter(new ServiceWebContextFilter());
registrationBean.setOrder(Ordered.LOWEST_PRECEDENCE);

return registrationBean;
}
}

3.网关跨域的问题#

Spring Cloud Gateway集成Spring Security后出现跨域(Gateway配置中已经开启跨域配置并允许所有)

解决方案:
Spring Security也添加跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
return source;
}

仅添加以上配置即可(Spring Security会获取到),也可以额外显式设置一下,在 SecurityWebFilterChain 配置中:
http.cors().configurationSource(corsConfigurationSource());

以上解决方案的原因:
按照网上的说法,Gateway的跨域处理较Spring Security靠后,Spring Security发现跨域问题后直接响应了
但是对Spring Security的cors进行关闭也无效:http.cors().disable();
具体细节暂不清楚

4.网关OpenFeign调用错误的问题#

错误信息:feign.codec.EncodeException: No qualifying bean of type ‘org.springframework.boot.autoconfigure.http.HttpMessageConverters’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
原因:OpenFeign与基于WebFlux的Gateway不能完美适配导致报错
解决方案:

1
2
3
4
5
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}

按照官方说法,以上解决方案有缺陷,因为HttpMessageConvert会阻塞,可能会造成WebFlux应用中断
https://github.com/spring-cloud/spring-cloud-openfeign/issues/235

Spring官方目前推荐的做法是使用第三方的替代方案(feign-reactive https://github.com/Playtika/feign-reactive)
https://cloud.spring.io/spring-cloud-openfeign/reference/html/#reactive-support

由于不熟悉后者的reactive编程,所以暂时仍然使用前者

5.版本#

  • Spring Cloud Gateway 2.2.5
  • Spring Security 5.3.5

KVM迁移到Hyper-V

1.导出KVM下的虚拟磁盘文件#

1.1 停止所有虚拟机实例#

1.2 转换虚拟磁盘文件格式 raw -> vhdx#

参考底部资料

1
2
# 示例
qemu-img convert -f raw "vhost1.raw" -O vhdx -o subformat=dynamic "vhost1.vhdx"

2.导入虚拟磁盘文件至Hyper-V#

3.修复引导系统#

导入后启动失败,好像是因为设备环境变动导致的,需要修复引导系统
虚拟机实例内安装的操作系统为CentOS7

从iso光盘启动 -> Troubleshooting -> Rescue a CentOS system
输入 1 回车,原操作系统会被挂载到 /mnt/sysimage下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 从rescue系统切换根到原操作系统
chroot /mnt/sysimage
# 清空/boot目录
rm -rf /boot/* && rm -rf /boot/.vm*
# 挂载iso光盘
mount /dev/sr0 /mnt
# 安装内核
rpm -ivh /mnt/Packages/kernel-3-10.0-1127.e17.x86_64.rpm --force
# 安装grub2
grub2-install /dev/sda
# 生成引导配置
grub2-mkconfig -o /boot/grub2/grub.cfg
# 退出原系统至rescue系统
exit
# 重启
reboot

如果还是启动失败,试试从原系统的rescue模式进入尝试修复(因为是直接进入原系统,所以免去chroot命令和exit命令)

4.资料#

https://docs.microsoft.com/zh-cn/archive/blogs/virtual_pc_guy/handy-tool-for-converting-kvm-vmware-images-to-hyper-v
https://cloudbase.it/qemu-img-windows/

MinIO对象存储服务搭建

1.简介#

MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储。 它是与 Amazon S3 云存储服务兼容的 API。 使用 MinIO 为机器学习、分析和应用程序数据工作负载构建高性能基础架构。

2.安装(Docker方式)#

1
2
docker run -d -p 9000:9000 -p 9001:9001 -v /var/lib/minio/data:/data \
--restart=always minio/minio server /data --console-address :9001
  • 9000为api端口
  • 9001为web管理端口
  • /var/lib/minio/data为宿主机数据存储目录

3.管理#

3.1 web管理方式#

3.1.1 管理地址和用户名、密码#

3.2 命令行方式#

3.2.1 下载管理工具#

1
wget 'https://dl.min.io/client/mc/release/linux-amd64/mc'

3.2.2 添加可执行权限#

1
chmod +x mc

3.2.3 配置服务host#

1
./mc config host add minio http://localhost:9000 minioadmin minioadmin

3.2.4 添加用户#

1
./mc admin user add minio user1 {secret-key}

3.2.5 设置策略#

1
./mc admin policy set minio readwrite user=user1

4.注意事项#

1
2
3
环境变量:控制台账号密码
MINIO_ACCESS_KEY, MINIO_SECRET_KEY 已废弃
若需要,使用 MINIO_ROOT_USER, MINIO_ROOT_PASSWORD

5.资料#

6.版本#

  • MinIO 7.0.2