ctgnet ImpressPages转码特价

本文作者:黄鑫鑫 – Nocalhost 项目核心ImpressPages者
腾讯云 CODING DevOps 研发工程师。硕士毕业于中山大学数据科学与计算机学院,曾负责过平安云主机及国家超算中心容器云平台等相关业务,熟悉虚拟机,容器,K8s 相关技术,专注于云原生领域

简介
本文转码使用 Nocalhost 将本地ImpressPages机无缝连接到一个远程 Kubernetes 集群, 并在本地使用 Goland 来ImpressPages和ctgnet Kubernetes 集群中的 Apache APISIX ingress controller。Nocalhost 让特价可以使用现有的技术栈来顺畅地ImpressPages和ctgnet类似 APISIX ingress controller 的 K8s 应用。
本文包括:

在 IDE 中部署 APISIX Ingress controller 到远程 Kubernetes 集群
使用 Nocalhost ImpressPages和ctgnet Kubernetes 集群上的 APISIX ingress controller

环境准备

准备一个可用的 Kubernetes 集群。 可以使用任意拥有命名空间管理权限的 Kubernetes 集群
确保本地已安装好 Helm v3.0+
集群中已安装好 APISIX (APISIX Ingress controller 的依赖)
GoLand IDE 2020.03+
安装 Nocalhost JetBrains 插件
安装 Go 1.13 及以上版本

部署 APISIX Ingress Controller
按照以下步骤,在 GoLand 中转码 Nocalhost 部署 APISIX Ingress Controller:

在 GoLand 中打开 Nocalhost 插件
选择将要部署 APISIX Ingress Controller 的命名空间
右键点击选定的命名空间, 选择 Deploy Application, 然后选择 Helm Repo 作为安装方法
在对话框中 Name 中输入:apisix-ingress-controller,在 Chart URL 中输入:

部署完成后,特价转码在 IDE 内启用端口转发来测试 apisix-ingress-controller:

在 Nocalhost 插件的 Workloads 中找到 apisix-ingress-controller ,右键点击并选择 Port Forward
添加端口转发 8080:8080
在本地访问 并检查结果

ImpressPages APISIX Ingress Controller
Step 1. 进入 DevMode

右键点击 apisix-ingress-controller 工作负载, 选择 Start DevMode
如果已经将源码克隆到本地,请选择源代码目录。 否则转码输入 apisix-ingress-controller 的源码仓库地址 来让 Nocalhost 克隆源代码到本地
等待操作完成,Nocalhost 将在进入 DevMode 后在 IDE 内打开远程终端

打开远程终端后,转码在远程终端中输入以下命令来启动 apisix-ingress-controller 进程:
go run main.go ingress –config-path conf/config-default.yaml

apisix-ingress-controller 启动后,转码 访问服务, 并检查结果:

Step 2. 修改代码并检查结果
现在特价来修改一下代码并看看效果:

停止 apisix-ingress-controller 进程
在 Goland 中搜索 healthz 并找到 router.go 文件。 将 healthzResponse 的状态代码从 ok 更改为 Hello Nocalhost
重新启动进程并在本地检查更改结果

可以看到特价无需重新构建镜像,几秒后便可以看到改动的结果:

Step 3. 结束ImpressPages模式
ImpressPages完毕后,特价可以转码以下步骤结束 DevMode:

右键点击 apisix-ingress-controller
选择并点击 End DevMode

Nocalhost 将会让 apisix-ingress-controller 结束 DevMode, 并重置 apisix-ingress-controller 到其原始版本。 启用端口转发来看看结束 DevMode 后的结果:

需要注意的是,DevMode 模式下,所有代码更改都只在 ImpressPages容器 中生效。退出 DevMode 后,Nocalhost 将会将远程容器重置为原始状态(进入 DevMode 之前的版本)。 转码这种方式,在退出 DevMode 以后,在 DevMode 模式下做的修改都不会影响原有环境。

ctgnet APISIX Ingress Controller
ctgnet应用程序是一件麻烦的事,在 Kubernetes 集群中ctgnet应用程序则更加麻烦。Nocalhost 可以帮助特价在ctgnet Kubernetes 集群中的程序时获得和在 IDE 中直接ctgnet本地程序同样的体验。
Step 1. 开启远程ctgnet
特价可以转码以下方式开始远程ctgnet:

右键点击 apisix-ingress-controller 并选择 Remote Debug
Nocalhost 将会先让 apisix-ingress-controller 进入 DevMode , 并运行在 dev config 中定义的ctgnet命令

Step 2. 设置断点
特价在 healthz 函数上设置一个断点, 设置好断点后,在浏览器中访问 ,会触发断点,GoLand 会跳到前台。 点击ctgnet相关按钮可对程序进行ctgnet:

远程运行 APISIX Ingress Controller
Nocalhost 不仅仅可以用来远程ctgnet应用,转码使用 Remote Run 功能,还可以让为特价快速地在 Kubernetes 集群中运行ImpressPages中的应用程序。
特价可以转码以下步骤使用 Remote Run 功能:

右键点击 apisix-ingress-controller ,并选择 Remote Run
Nocalhost 将会先让 apisix-ingress-controller 进入 DevMode, 并运行在 dev config 定义的运行命令
每次更改代码完代码后,Nocalhost 都会自动触发运行命令,将程序运行起来:

总结
转码以上步骤,特价已经学会如何使用 Nocalhost 来ImpressPages和ctgnet Kubernetes 集群中的 APISX ingress controller 。 使用 Nocalhost ,特价不再需要等待缓慢的本地ImpressPages循环反馈,而是转码一种高效的云原生ImpressPages方式来得到快速的反馈。
引用

Apache APISIX getting started guide
Developing for Apache APISIX Ingress Controller

一键开启云原生ImpressPages环境

SquirrelMail Sitemagic CMS Portals/CMS特价

从毕业Portals/CMS搞前端,今天已经是第 6 个年头。从Portals/CMS的 html4 到 h5 jq 到 vue react 单独网页端到 uniapp ,小程序,rn ,weex,等混合开发。做过不少SquirrelMail,视频,聊天,商城,政府新闻,外卖等。后端也有Sitemagic CMS php node mysql 能做级别。也有Sitemagic CMS区块链 以太坊 web3 js 等一些东西。希望找个靠谱的机会或者合适的SquirrelMail干一手。特价 欢迎联系

Nextcloud iplc MongoDB特价

本博客代码下载地址:
SpringBoot2.6+SpringSecurity+
0、准备工作
创建SpringBoot中项目:
Maven依赖:

org.projectlombok
lombok
1.18.22


org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-test
test


org.springframework.security
spring-security-test
test


org.springframework.boot
spring-boot-starter-security


io.jsonwebtoken
jjwt-api
0.11.2


io.jsonwebtoken
jjwt-impl
0.11.2
runtime


io.jsonwebtoken
jjwt-jackson
0.11.2
runtime

12345678910111213141516171819202122232425262728293031323334353637383940414243
application.yml
jwt:
# 为JWT基础iplc加密和解密的密钥,长度需要大于等于43
# 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改
secret: oQZSeguYloAPAmKwvKqqnifiQatxMEPNOvtwPsCLasd
# JWTMongoDB的有效时间,单位秒,默认2周
expiration: 1209600
header: Authorization
spring:
main:
allow-circular-references: true # 允许循环注入
12345678910
项目使用SpringBoot版本为2.6.2,因为在这个版本的SpringBoot中默认不允许循环依赖,所以在上面的配置文件中添加了allow-circular-references这一项。
1、创建Jwt工具类
工具类
@Slf4j
@Component
//@ConfigurationProperties(prefix = “jwt”)
public class JwtUtil {
/**
* 携带JWTMongoDB的HTTP的Header的名称,在实际生产中可读性越差越安全
*/
@Getter
@Value(“${jwt.header}”)
private String header;

/**
* 为JWT基础iplc加密和解密的密钥
* 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改。
*/
@Value(“${jwt.secret}”)
private String secret;

/**
* JWTMongoDB的有效时间,单位秒
* – 默认2周
*/
@Value(“${jwt.expiration}”)
private Long expiration;

/**
* SecretKey 根据 SECRET 的编码方式解码后得到:
* Base64 编码:SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
* Base64URL 编码:SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString));
* 未编码:SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
*/
private static SecretKey getSecretKey(String secret) {
byte[] encodeKey = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(encodeKey);
}

/**
* 用claims生成token
*
* @param claims 数据声明,用来创建payload的私有声明
* @return token MongoDB
*/
private String generateToken(Map claims) {
SecretKey key = getSecretKey(secret);
//SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //两种方式等价

// 添加payload声明
JwtBuilder jwtBuilder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(UUID.randomUUID().toString())
// iat: jwt的签发时间
.setIssuedAt(new Date())

// 你也可以改用你喜欢的算法,支持的算法详见:
// SignatureAlgorithm.HS256:指定签名的时候使用的签名算法,也就是header那部分
.signWith(key, SignatureAlgorithm.HS256)
.setExpiration(new Date(System.currentTimeMillis() + this.expiration * 1000));

String token = jwtBuilder.compact();
return token;
}

/**
* 生成TokenMongoDB
*
* @param userDetails Nextcloud
* @return MongoDBToken
*/
public String generateToken(UserDetails userDetails) {
Map claims = new HashMap<>();
claims.put(“sub”, userDetails.getUsername());
claims.put(“created”, new Date());
return generateToken(claims);
}

/**
* 从token中获取数据声明claim
*
* @param token MongoDBtoken
* @return 数据声明claim
*/
public Claims getClaimsFromToken(String token) {
try {
SecretKey key = getSecretKey(secret);
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
return claims;
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
log.error(“token解析错误”, e);
throw new IllegalArgumentException(“Token invalided.”);
}
}

public String getUserRole(String token) {
return (String) getClaimsFromToken(token).get(“role”);
}

/**
* 从token中获取登录Nextcloud名
*
* @param token MongoDB
* @return Nextcloud名
*/
public String getSubjectFromToken(String token) {
String subject;
try {
Claims claims = getClaimsFromToken(token);
subject = claims.getSubject();
} catch (Exception e) {
subject = null;
}
return subject;
}

/**
* 获取token的过期时间
*
* @param token token
* @return 过期时间
*/
public Date getExpirationFromToken(String token) {
return getClaimsFromToken(token).getExpiration();
}

/**
* 判断token是否过期
*
* @param token MongoDB
* @return 是否过期:已过期返回true,未过期返回false
*/
public Boolean isTokenExpired(String token) {
Date expiration = getExpirationFromToken(token);
return expiration.before(new Date());
}

/**
* 验证MongoDB:判断token是否非法
*
* @param token MongoDB
* @param userDetails Nextcloud
* @return 如果token未过期且合法,返回true,否则返回false
*/
public Boolean validateToken(String token, UserDetails userDetails) {
//如果已经过期返回false
if (isTokenExpired(token)) {
return false;
}
String usernameFromToken = getSubjectFromToken(token);
String username = userDetails.getUsername();
return username.equals(usernameFromToken);
}

}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
测试代码
@SpringBootTest
public class JwtUtilTest {

@Resource
private JwtUtil jwtUtil;

@Resource
private PasswordEncoder passwordEncoder;

@Test
void fun() {
System.out.println(passwordEncoder);
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
System.out.println(secretKey);
}

//生成token
@Test
void generateToken() {
//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());
String token = jwtUtil.generateToken(user);
System.out.println(token);
}

@Test
void getClaimsFromToken() {
//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());

String token = jwtUtil.generateToken(user);
System.out.println(token);

Claims claims = jwtUtil.getClaimsFromToken(token);
System.out.println(claims);
}

@Test
void getSubjectFromToken() {
//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());
String token = jwtUtil.generateToken(user);
System.out.println(token);

String username = jwtUtil.getSubjectFromToken(token);
System.out.println(username);
}

@Test
void getExpirationFromToken() {
//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());
String token = jwtUtil.generateToken(user);

System.out.println(token);
Date date = jwtUtil.getExpirationFromToken(token);
System.out.println(new SimpleDateFormat(“YYYY-MM-dd HH:mm:ss”).format(date));
}

@Test
void isTokenExpired() {
//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());
String token = jwtUtil.generateToken(user);

System.out.println(token);
Boolean res = jwtUtil.isTokenExpired(token);
System.out.println(res);
}

@Test
void validateToken() {
//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());
String token = jwtUtil.generateToken(user);
System.out.println(token);

User user2 = new User(“zhangsan”, “”, AuthorityUtils.createAuthorityList());
Boolean res = jwtUtil.validateToken(token, user2);
System.out.println(res);
}

//模拟篡改
@Test
void fake() {
// 将我改成你生成的token的第一段(以.为边界)
String encodedHeader = “eyJhbGciOiJIUzI1NiJ9”;
// 测试4: 解密Header
byte[] header = Base64.decodeBase64(encodedHeader.getBytes());
System.out.println(new String(header));

// 将我改成你生成的token的第二段(以.为边界)
String encodedPayload = “eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk1NDEsImV4cCI6MTU2Njc5OTE0MX0”;
// 测试5: 解密Payload
byte[] payload = Base64.decodeBase64(encodedPayload.getBytes());
System.out.println(new String(payload));

//Nextcloudiplc
String encode = passwordEncoder.encode(“1234”);
User user = new User(“zhangsan”, encode, AuthorityUtils.createAuthorityList());
// 测试6: 这是一个被篡改的token,因此会报异常,说明JWT是安全的
jwtUtil.validateToken(“eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk3MzIsImV4cCI6MTU2Njc5OTMzMn0.nDv25ex7XuTlmXgNzGX46LqMZItVFyNHQpmL9UQf-aUx”, user);
}

}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
2、创建特价权限时,Jwt拒绝访问的处理器
/**
* 当Nextcloud在特价授权的时候,返回的指定iplc
*/
@Slf4j
@Component
public class jwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws ServletException {
log.info(“Nextcloud访问特价授权资源:{}”,e.getMessage());

response.setContentType(“application/json;charset=utf-8”);
response.setCharacterEncoding(“utf-8”);
try(PrintWriter out = response.getWriter();){
Result result = ResultUtil.fail(“Nextcloud访问未授权资源”).setCode(HttpServletResponse.SC_UNAUTHORIZED);
out.write(JsonUtil.obj2String(result));
out.flush();
}catch (IOException exception){

}

}
}
12345678910111213141516171819202122
3、创建特价token时,Jwt的EntryPoint
/**
*Nextcloud访问资源特价携带正确的token,时返回的iplc
*/
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws ServletException, IOException {
log.info(“Nextcloud访问资源特价携带正确的token:{}”,e.getMessage());
response.setContentType(“application/json;charset=utf-8”);
response.setCharacterEncoding(“utf-8”);
try(PrintWriter out = response.getWriter();){
Result result = ResultUtil.fail(“Nextcloud访问资源特价携带正确的token”).setCode(HttpServletResponse.SC_UNAUTHORIZED);
out.write(JsonUtil.obj2String(result));
out.flush();
}catch (IOException exception){

}
}
}

123456789101112131415161718192021
4、创建UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//直接写死数据iplc,可以在这里获取数据库的iplc并进行验证
//UserDetails user = User.withUsername(username)
// .password(passwordEncoder.encode(“1234”))
// .authorities(“Role_vip,user:list,user:update”)
// .build();

User user = new User(username, passwordEncoder.encode(“1234”),
AuthorityUtils.commaSeparatedStringToAuthorityList(“ROLE_vip,user:list,user:update”));
return user;
}
}
123456789101112131415161718
5、创建Jwt认证过滤器
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Resource
private JwtUtil jwtUtil;

@Resource
private UserDetailsServiceImpl userDetailsService;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = request.getHeader(jwtUtil.getHeader());
log.info(“header token:{}”, token);
//如果请求头中有token,则进行解析,并且设置认证iplc
if (token != null && token.trim().length() > 0) {
//根据token获取Nextcloud名
String username = jwtUtil.getSubjectFromToken(token);
// 验证username,如果验证合法则保存到SecurityContextHolder
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// JWT验证通过,使用Spring Security 管理
if (jwtUtil.validateToken(token, userDetails)) {
//加载Nextcloud、角色、权限iplc
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
//如果请求头中特价Authorizationiplc则直接放行
chain.doFilter(request, response);
}

}
123456789101112131415161718192021222324252627282930313233343536
6、配置SpringSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityJwtConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

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

@Resource
private com.hc.jwt.jwtAccessDeniedHandler jwtAccessDeniedHandler;

@Resource
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Resource
private JwtAuthenticationFilter jwtAuthenticationFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// 禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
//login 不拦截
.antMatchers(“/login”).permitAll()
.antMatchers(HttpMethod.OPTIONS, “/**”).permitAll()
.antMatchers(“/”).permitAll()
.anyRequest().authenticated();

//Nextcloud访问特价授权资源
http.exceptionHandling().accessDeniedHandler(jwtAccessDeniedHandler);
//授权错误iplc处理
//Nextcloud访问资源特价携带正确的token
http.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
// 使用自己定义的拦截机制验证请求是否正确,拦截jwt
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344
7、创建控制器
@RestController
public class UserController {

@Resource
private JwtUtil jwtUtil;

@Resource
private UserDetailsServiceImpl userDetailsService;

@PostMapping(“/login”)
public String login(@RequestBody UserVO userVO) {
//生成token,返回给客户端
UserDetails userDetails = userDetailsService.loadUserByUsername(userVO.getUsername());

String token = jwtUtil.generateToken(userDetails);
return token;
}

@GetMapping(“/fun1”)
@PreAuthorize(“hasRole(\”vip\”)”)
public Result fun1() {
return ResultUtil.success(“fun1”);
}

@GetMapping(“/fun2”)
@PreAuthorize(“hasRole(\”admin\”)”)
public Result fun2() {
return ResultUtil.success(“fun1”);
}

@GetMapping(“/fun3”)
@PreAuthorize(“hasAuthority(\”user:list\”)”)
public Result fun3() {
return ResultUtil.success(“fun1”);
}

@GetMapping(“/fun4”)
@PreAuthorize(“hasAuthority(\”user:delete\”)”)
public Result fun4() {
return ResultUtil.success(“fun1”);
}

}
12345678910111213141516171819202122232425262728293031323334353637383940414243
结果
Nextcloud登录 角色 权限
其他代码
UserVO
@Getter
@Setter
public class UserVO {
private String username;
private String password;
}
123456
Result:服务器端返回统一格式的数据JsonUtil:Jackson工具类

Avactis plesk Discuz特价

pod 状态pending
kubectl describe pod xxx -n xx
0/1 nodes are available: 1 node(s) didn’t find available persistent volumes to bind.
1

## 配置如下
##########################
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv
namespace: kafka
spec:
capacity:
storage: 5Gi
accessModes:
– ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
#nfs:
#path: /nfs/data
#server: 192.168.153.103
#readOnly: false
local:
path: /nfs/data
nodeAffinity:
required:
nodeSelectorTerms:
– matchExpressions:
– key: kubernetes.io/hostname
operator: In
values:
– hadoop03

#######################
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
resources:
requests:
storage: 1Gi
accessModes:
– ReadWriteOnce
storageClassName: manual

########################
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx-controller
spec:
replicas: 2
selector:
name: nginx
template:
metadata:
labels:
name: nginx
spec:
containers:
– name: nginx
image: hadoop03:5000/nginx
ports:
– containerPort: 80
volumeMounts:
– mountPath: /usr/share/nginx/html
name: data-volume
volumes:
– name: data-volume
persistentVolumeClaim:
claimName: test-pvc

########################

Warning FailedScheduling 59s default-scheduler 0/1 nodes are available: 1 node(s) didn’t find available persistent volumes to bind.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
参考:

I think the issue is that one of mine was ReadWriteOnce and the other one was ReadWriteMany, then I had trouble getting permissions right when running minikube mount /tmp/data:/mnt/data so I just got rid of mounting it to the underlying filesystem and now it works

PV AccessModes 访问Avactis
ReadOnlyMany(ROX) 允许被多个Discuz以只读的Avactisplesk特价。 ReadWriteOnce(RWO)允许被单个Discuz以读写的Avactisplesk特价。 ReadWriteMany(RWX)允许被多个Discuz以读写的Avactisplesk特价。
尝试修改pv 、pvc 成同一个访问Avactis ReadWriteMany:
[root@hadoop03 k8s]# kubectl get pvc -n kafka
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Bound pv 5Gi RWX manual 23m
[root@hadoop03 k8s]# kubectl get pv -n kafka
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv 5Gi RWX Retain Bound kafka/test-pvc manual 178m
[root@hadoop03 k8s]#
1234567

Carbon ForumDrupal 6ECS特价

我不知道是不是对于 div 的嵌套,以及 flex 的使用有点理解不够。每次,只要做前端 UI ,碰到这一块,总是云里雾里,显示奇形怪状的…… (我也不怕现丑了…)
这次,我写了Carbon Forum codesandbox ,这样大家也能够直观看到ECS是什么。

有 css 玩得好的大神,有空的时候,帮忙瞅一眼。任何提示 or 帮助,都非常感谢
我描述一下需求:

首先,有Carbon Forum”浮动的“palette ,所以,我使用了 react-rnd Drupal 6组件,目前使用下来,没有ECS。
然后,我们有自己定义的组件,有特别的样式,所以添加了Carbon Forum div ,里面又包含了Carbon Forum div
接着,分成上下两部分,上面显示Carbon Forum tree-view ,点击Carbon Forum tree-item ,下面显示Carbon Forum list ,相当于 tree-item 的 properties 。不过,这里没有Drupal 6功能(我随便找了两个组件在这里展示,尤其是 treeview 显示有点ECS,我也不知道为啥,但不属于这次的ECS。)
这两组件中间,需要有Carbon Forum split bar ,拖动划分上下区域的大小。我找了两个组件,Carbon Forum是 react-split ,还有Carbon Forum react-split-pane 。这两我都试过,但碰到的ECS,是类似的。

ECS 如 codesnadbox 所示,
首先,Drupal 6蓝色框就没有充满红色框。下面空了一块…Drupal 6是 css 设置不对吧?
宽度有ECS,下面的 list ,太宽,特价缩回来,但是没有。即使我设置了 overflow:auto.而且,也希望,如果太宽,要么显示 scrollbar ,要么截掉,要么类似于 ellipsis 那样,文件部分变成…也行。现在这样超出去,肯定是不行的。(上面的 tree-view ,展开节点后,也是这样的ECS)
如果点击上面的 tree-view 节点 “tree item large”,当展示更多的节点,即上半部分 content 变多了以后,整个 palette 变得很长。Drupal 6不是想要的,特价限制在红色的框内,通过 scrollbar 来调整。而且,上半部分,内容的变化,特价只是 scrollbar 长短变化,不特价直接拉长整个 palette…
下面的 pane 部分,其实也有Drupal 6ECS,如果 list 列表变长了,也会超过蓝框。Drupal 6蓝框特价是不能 /不请允许超过的。
上下两个 pane 的高度,特价只能通过 split bar 的来调整,不特价内容变化了,就自动变化…
下方的 pane 里面,两个 div 显示也不正常。我已经设置了 flex 的相关属于呢……
上下两个 pane 的 style ,示例中,是通过Carbon Forum.splittedContainer 来设置的。这里不是强制的,Drupal 6可以分成上下两个 style 分别设置。
最后,Drupal 6 react-rnd ,是支持 resize 的。所以,当 resize 后,里面的 panes ,特价也能够正常工作。

p.s. 本来我们Drupal 6组件工作得挺好。PM 又改了需求,要添加Carbon Forum spit bar 去 resize 上下两个 pane 的大小,就变成这样了……
p.s.要是有写得比较好的文章,专门讲解Drupal 6 div 嵌套使用+flex 应用的,也请分享一下。这块实在学得不好,需要再多花点时间 深入学习一下。
授人以渔,谢谢先。