1.背景
最近有客户提出能从paas平台上直接进入容器操作(当然前提是容器内有ssh), 本来一直想着直接连接到容器容器,但是面临很多安全问题(如证书拷贝),后续想到之前用过的阿里云的rds也是实现模式,借鉴并实现。
当然dashboard中也有实现方式,大家可以参考。
2.前端实现
当前命名空间:default 当前容器:gluster-nginx-7465bd6456-bc7xq
执行
命令区:
结果区:
借助element-ui将页面调整如下,上面为命令区(可写),下面为结果区(只读区),样式可自行调整,本实例主要为了说明过程
3.后端实现,工程基于springboot
- 配置WebSocket的配置文件
package com.ecloud.common;
import com.ecloud.common.interceptor.HandshakeInterceptor;
import com.ecloud.socket.SSHWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.*;
/**
*Created by dubblegao on 2018/11/13.
*/
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
public WebSocketConfig() {
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(sshSocketHandler(), "/ssh")
.addInterceptors(new HandshakeInterceptor())
.setAllowedOrigins("*");
registry.addHandler(sshSocketHandler(), "/sockjs/ssh")
.addInterceptors(new HandshakeInterceptor())
.setAllowedOrigins("*");
}
@Bean
public SSHWebSocketHandler sshSocketHandler() {
return new SSHWebSocketHandler();
}
}
- 配置WebSocket的拦截器
package com.ecloud.common.interceptor;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
/**
* Created by dubblegao on 2018/11/13.
*/
@Component
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
//解决The extension [x-webkit-deflate-frame] is not supported问题
if(request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {
request.getHeaders().set("Sec-WebSocket-Extensions", "permessage-deflate");
}
System.out.println("Before Handshake");
boolean result = super.beforeHandshake(request, response, wsHandler, attributes);
return result;
}
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
System.out.println("After Handshake");
super.afterHandshake(request, response, wsHandler, ex);
}
}
- 配置WebSocket的处理器
package com.ecloud.socket;
import com.alibaba.fastjson.JSON;
import com.ecloud.common.model.SocketMessage;
import com.ecloud.framework.cache.MessageCache;
import com.ecloud.imwh.image.handler.ImageWebSocketHandler;
import com.google.common.collect.Maps;
import io.kubernetes.client.Exec;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.socket.*;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
*Created by dubblegao on 2018/11/13.
*/
public class SSHWebSocketHandler implements WebSocketHandler {
protected static final Logger LOG = LoggerFactory.getLogger(SSHWebSocketHandler.class);
public static Map sessions = Maps.newConcurrentMap();
private static WebSocketSession session = null;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
this.session = session;
sessions.put(session.getId(),this);
//发送给客户端
SocketMessage message = SocketMessage.build("OPEN",session.getId());
sendMessage(message);
}
@Override
public void handleMessage(WebSocketSession wss, WebSocketMessage> wsm) throws Exception {
ExecBean execBean = JSON.parseObject(wsm.getPayload().toString(),ExecBean.class);
this.exec(execBean);
sendMessage(wsm.getPayload());
}
@Override
public void handleTransportError(WebSocketSession wss, Throwable throwable) throws Exception {
if(wss.isOpen()){
wss.close();
}
System.out.println("socket connection closed......");
}
@Override
public void afterConnectionClosed(WebSocketSession wss, CloseStatus cs) throws Exception {
System.out.println("socket connection closed......");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
public void sendMessage(Object message){
try {
session.sendMessage(new TextMessage(JSON.toJSONString(message)));
}catch (Exception e){
LOG.error(e.getMessage());
}
}
public void exec(ExecBean bean){
//此处的Exec 用的是kubernetes官方提供的客户端中的API
//https://kubernetes.io/docs/reference/using-api/client-libraries/
Exec exec = new Exec();
String[] command = {"sh", "-c", bean.getCommand()};
try {
final Process proc = exec.exec(
bean.getNamespaces(),
bean.getPodName(),
command,
bean.getContainer(),
false,
false);
if(proc.waitFor()==0){
StringWriter writer = new StringWriter();
IOUtils.copy(proc.getInputStream(), writer, StandardCharsets.UTF_8.name());
SocketMessage msg = SocketMessage.build("SSH",writer.toString());
SSHWebSocketHandler.sessions.get(bean.getSessionId()).sendMessage(msg);
proc.destroy();
}
/* 生产下建议用如下方式,上述方式会存在卡死
Thread out = new Thread(
new Runnable() {
public void run() {
try {
StringWriter writer = new StringWriter();
IOUtils.copy(proc.getInputStream(), writer, StandardCharsets.UTF_8.name());
SocketMessage msg = SocketMessage.build("SSH",writer.toString());
SSHWebSocketHandler.sessions.get(bean.getSessionId()).sendMessage(msg);
//ByteStreams.copy(proc.getInputStream(), System.out);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
out.start();
proc.waitFor();
// wait for any last output; no need to wait for input thread
out.join();
proc.destroy();
System.exit(proc.exitValue());*/
}catch (Exception e){
e.printStackTrace();
}
}
}
4.测试
本例子中的参数,为了调试都是固定的,可以根据业务调整成动态
-
查看目录文件
-
查看文件内容
4.说明
另外如果想做到实时交互,可以调整 stdin和tty参数,将结果实时输出到输出流,将输入流拷贝的command中
该方式可以避免权限证书拷贝的问题,同时保证操作对象仅仅为容器。进供参考