120.kubernetes优雅的实现浏览器进去容器

1.背景

最近有客户提出能从paas平台上直接进入容器操作(当然前提是容器内有ssh), 本来一直想着直接连接到容器容器,但是面临很多安全问题(如证书拷贝),后续想到之前用过的阿里云的rds也是实现模式,借鉴并实现。
当然dashboard中也有实现方式,大家可以参考。

2.前端实现







借助element-ui将页面调整如下,上面为命令区(可写),下面为结果区(只读区),样式可自行调整,本实例主要为了说明过程

120.kubernetes优雅的实现浏览器进去容器_第1张图片
style.png

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.测试

本例子中的参数,为了调试都是固定的,可以根据业务调整成动态

  • 查看目录文件


    120.kubernetes优雅的实现浏览器进去容器_第2张图片
    ls.png
  • 查看文件内容


    120.kubernetes优雅的实现浏览器进去容器_第3张图片
    cat.png

4.说明

另外如果想做到实时交互,可以调整 stdin和tty参数,将结果实时输出到输出流,将输入流拷贝的command中
该方式可以避免权限证书拷贝的问题,同时保证操作对象仅仅为容器。进供参考

你可能感兴趣的:(120.kubernetes优雅的实现浏览器进去容器)