Golang+Vue2 construye el sistema de gestión de fondo K8S desde cero (6)——terminal web implementa pod shell

Tabla de contenido

descripción general

El método básico para ejecutar comandos de pod de forma remota

Implementación de websocket backend

Interfaz

Resumir


descripción general

En el capítulo anterior, la lectura y visualización del registro del pod se realizó a través de una conexión larga fragmentada de http;

Este capítulo implementará el terminal del módulo en la página del navegador a través de la biblioteca xterm.js y websocket front-end.

El método básico para ejecutar comandos de pod de forma remota

Primero crea una solicitud

	option := &v1.PodExecOptions{
		Container: container,
		Command:   command,
		//如果是一次性执行命令"sh -c ls"这种就关闭stdin(打开也不影响)
		Stdin:  true,
		Stdout: true,
		Stderr: true,
		//终端
		TTY: true,
	}
	//构建一个地址
	req := client.CoreV1().RESTClient().Post().Resource("pods").
		Namespace(ns).
		Name(pod).
		SubResource("exec").
		Param("color", "false").
		VersionedParams(
			option,
			scheme.ParameterCodec,
		)

Si es un comando de ejecución de una sola vez, ordenaremos a

[]string{"El comando que desea ejecutar"}

El valor se pasa;

Obtenga la dirección URL con esta solicitud para crear un objeto de ejecución de comando remoto

exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())

Vale la pena mencionar que SPDY es un protocolo de comunicación desarrollado por Google. Además de la capa TCP, las funciones clave de HTTP/2 provienen principalmente de la tecnología SPDY. En otras palabras, los resultados de SPDY se adoptaron y finalmente evolucionaron a HTTP/. 2.

A través del objeto de ejecución de comando remoto obtenido en el paso anterior, podemos comenzar a transmitir y especificar la dirección de entrada/salida estándar

	exec.Stream(remotecommand.StreamOptions{ 
			Stdin:  os.Stdin,
			Stdout: os.Stdout,
			Stderr: os.Stderr,
			Tty:    true,
		})

 Ejecute, y podrá observar el resultado correspondiente devuelto en la consola de Terminal.

Implementación de websocket backend

Lo sabemos cuando escribimos una instrucción en el front-end y la enviamos al back-end. Este comando no puede codificarse de forma rígida, lo que requiere una cierta cantidad de interacción antes y después, y el resultado después de la ejecución del back-end debe notificarse al front-end para representar la página. Entonces necesitamos usar websocket. En la sección anterior, dirigimos el resultado de retorno del objeto de comando de ejecución remota a la salida estándar del sistema operativo, es decir, la consola. En esta sección, tratamos de devolverlo al front-end a través del cliente websocket.

Este artículo utiliza el marco de gin para demostrar;

Obtenga nuestros parámetros necesarios de *gin.Context, es decir, el contenedor interactivo

    ns := c.Query("ns")
	pod := c.Query("name")
	container := c.Query("cname")

 La vieja rutina, actualizar la conexión http a una conexión websocket

    wsClient, err := wscore.Upgrader.Upgrade(c.Writer, c.Request, nil)

El siguiente paso es crítico, necesitamos reemplazar la salida estándar del sistema operativo con este cliente ws como la salida del objeto de comando remoto.

Ver las variables miembro de StreamOptions

type StreamOptions struct { 
   Stdin io.Reader 
   Stdout io.Writer 
   Stderr io.Writer 
   Tty bool 
   TerminalSizeQueue TerminalSizeQueue 
}

Por lo tanto, necesitamos construir objetos a través del cliente ws e implementar la interfaz Lector/Escritor, que en realidad está llenando/recuperando datos en el cliente ws

type WsShellClient struct {
	client *websocket.Conn
}

func NewWsShellClient(client *websocket.Conn) *WsShellClient {
	return &WsShellClient{client: client}
}

func (this *WsShellClient) Write(p []byte) (n int, err error) {
	err = this.client.WriteMessage(websocket.TextMessage,
		p)
	if err != nil {
		return 0, err
	}
	return len(p), nil
}
func (this *WsShellClient) Read(p []byte) (n int, err error) {

	_, b, err := this.client.ReadMessage()

	if err != nil {
		return 0, err
	}
	return copy(p, string(b)), nil
}

En este punto, la parte de back-end se completa; a través del acceso a la interfaz correspondiente, el objeto de ejecución de comando remoto ejecutará el mensaje recibido a través de ws como un comando y devolverá el resultado de la ejecución del comando a través de la conexión ws.

Código completo:

func PodConnect(c *gin.Context) {
	//获取容器相关对应参数
	ns := c.Query("ns")
	pod := c.Query("name")
	container := c.Query("cname")
	//升级http客户端到ws客户端
	wsClient, err := wscore.Upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		log.Println(err)
		return
	}
	shellClient := wscore.NewWsShellClient(wsClient) //以ws客户端构建实现reader/writer接口的对象
    //构建远程执行命令对象
	err = helpers.HandleCommand(ns, pod, container, this.Client, this.Config, []string{"sh"}).
		Stream(remotecommand.StreamOptions{ //以流的方式来读取结果
			Stdin:  shellClient,
			Stdout: shellClient,
			Stderr: shellClient,
			Tty:    true,
		})
	return
}

Interfaz

Se adjunta el código completo del front-end

<template>

  <div
    style="min-height: 500px;
    padding: 10px"
  >
    <div style="padding-left: 20px;padding-top:30px">
    容器: 
      <el-select  @change="containerChange"  placeholder="选择容器"
                        v-model="selectedContainer">
        <el-option v-for="c in containers "
                  :label="c.Name"
                  :value="c.Name"/>
      </el-select>
    </div>
    <div id="terminal" ref="terminal"></div>
  </div>

</template>
<script>
  import { Terminal } from "xterm";
  import "xterm/css/xterm.css";
  import { getPodContainers } from "@/api/pod";

  export default {

    data(){
      return {
        Name: "",
        NameSpace: "",
        containers: [],
        selectedContainer: "",
        rows: 40,
        cols: 100,
        term:null,//终端对象
        ws:null, //ws 客户端
        wsInited:false  //是否初始化完毕
      }
    },
    created() {
      this.Name = this.$route.params.name
      this.NameSpace = this.$route.params.ns
      if(this.Name===undefined||this.NameSpace===undefined){
        alert("错误的参数!")
      } else {
        getPodContainers(this.NameSpace,this.Name).then(rsp=>{
          this.containers=rsp.data
        })
      }
    },
    methods:{
      containerChange(){
        this.initWS()// 初始化 websocket
        this.initTerm()
      },
      initTerm(){
        let term = new Terminal({
          rendererType: "canvas", //渲染类型
          rows: parseInt(this.rows), //行数
          cols: parseInt(this.cols), // 不指定行数,自动回车后光标从下一行开始
          convertEol: true, //启用时,光标将设置为下一行的开头
          disableStdin: false, //是否应禁用输入。
          cursorStyle: "underline", //光标样式
          cursorBlink: true, //光标闪烁
          theme: {
            foreground: "#7e9192", //字体
            background: "#002833", //背景色
            cursor: "help", //设置光标
            lineHeight: 16
          }
        });
        // 创建terminal实例
        term.open(this.$refs["terminal"]);
        term.prompt = () => {
          term.writeln("\n\n Welcome. ");
          term.writeln("\n 正在初始化终端");
        };
        term.prompt();

        //回车触发
        term.onData((key)=> {
            if(this.wsInited){
              this.ws.send(key)
            }
        });


        this.term=term
      },
      //初始化 websocket 客户端
      initWS(){
        var ws = new WebSocket("ws://localhost:8080/podws?ns="+
        this.NameSpace+"&name="+this.Name+"&cname="+this.selectedContainer);
        ws.onopen = function(){
          console.log("open");
        }
        ws.onmessage = (e)=>{
          this.wsInited=true //初始化完毕
          this.term.write(e.data) //调用term的打印方法打印后端返回的消息结果
        }
        ws.onclose = function(e){
          console.log("close");
        }
        ws.onerror = function(e){
          console.log(e);
        }
        this.ws=ws
      }
    }

  }

</script>

Cuando escribimos un comando en el cuadro de entrada del terminal y presionamos Enter para finalizar, se activará el evento onData del componente del terminal, y el evento onData enviará esta parte de los datos al backend a través del cliente ws. El backend devuelve el resultado procesado al frontend a través de la conexión ws, activa el evento onmessage del cliente ws y luego llama al método de escritura formado por el terminal para imprimir el resultado.

Resumir

Hasta ahora, hemos completado la implementación del shell del pod.

En el próximo capítulo, seguiremos implementando el shell del nodo sobre esta base. De hecho, el principio es similar.La idea básica es abrir una sesión a través de ssh y también usar su estructura para realizar la estructura de las interfaces io.Reader e io.Writer.

Supongo que te gusta

Origin blog.csdn.net/kingu_crimson/article/details/128015465
Recomendado
Clasificación