Tabla de contenido
El método básico para ejecutar comandos de pod de forma remota
Implementación de websocket backend
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.