Xterm.js简介

xterm.js (https://xtermjs.org/)是一个开源的 JavaScript 库,它模拟了一个终端接口,可以在网页中嵌入一个完全功能的终端。这个库非常灵活,并且具有很多定制选项和插件系统。
下面是一些使用 xterm.js 的基本步骤:
  • 首先,需要在项目中安装 xterm.js。你可以直接从 npm 安装:
npm install xterm
  • 然后在 HTML 中创建一个容器来承载终端
<div id="terminal"></div>
  • 在你的 JavaScript 文件中,导入 Terminal 类并创建一个新的实例
import { Terminal } from 'xterm';

const term = new Terminal();
  • 把这个终端附加到 HTML 元素上
term.open(document.getElementById('terminal'));
  • 现在你就可以向终端写入数据了
term.write('Hello, World!');
  • 如果你想读取用户在终端中的输入,可以监听 onData 事件
term.onData(data => {
console.log(data);
});

以上只是最基础的使用方法。xterm.js 提供了许多其他功能,如主题定制、附加插件(例如 FitAddon 可以自动调整终端大小,WebLinksAddon 可以捕获 URL 并将其变为可点击链接)、设置光标样式、更改字体大小等等。你可以访问 xterm.js 的 GitHub (https://github.com/xtermjs/xterm.js)仓库 或者 文档 来获取更详细的信息。

使用Gin、client-go的SPDYExecutor来执行远程命令

package main

import (
"context"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
"log"
"net/http"
) // websocket 升级器配置
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
} // WSClient 结构体,封装了 WebSocket 连接和 resize 通道,用于在 WebSocket 和 remotecommand 之间进行数据交换。
type WSClient struct {
// WebSocket 连接对象
ws *websocket.Conn
// TerminalSize 类型的通道,用于传输窗口大小调整事件
resize chan remotecommand.TerminalSize
} // MSG 结构体,用于解析从 WebSocket 接收到的消息。
type MSG struct {
// 消息类型字段
MsgType string `json:"msg_type"`
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
// 输入消息的数据字段
Data string `json:"data"`
} // WSClient 的 Read 方法,实现了 io.Reader 接口,从 websocket 中读取数据。
func (c *WSClient) Read(p []byte) (n int, err error) {
// 从 WebSocket 中读取消息
_, message, err := c.ws.ReadMessage()
if err != nil {
return 0, err
}
var msg MSG
if err := json.Unmarshal(message, &msg); err != nil {
return 0, err
} // 根据消息类型进行不同的处理
switch msg.MsgType {
// 如果是窗口调整消息
case "resize":
winSize := remotecommand.TerminalSize{
Width: msg.Cols,
Height: msg.Rows,
}
// 将 TerminalSize 对象发送到 resize 通道
c.resize <- winSize
return 0, nil
// 如果是输入消息
case "input":
copy(p, msg.Data)
return len(msg.Data), err
}
return 0, nil
} // WSClient 的 Write 方法,实现了 io.Writer 接口,将数据写入 websocket。
func (c *WSClient) Write(p []byte) (n int, err error) {
// 将数据作为文本消息写入 WebSocket
err = c.ws.WriteMessage(websocket.TextMessage, p)
return len(p), err
} // Next WSClient 的 Next 方法,用于从 resize 通道获取下一个 TerminalSize 事件。
func (c *WSClient) Next() *remotecommand.TerminalSize {
// 从 resize 通道读取 TerminalSize 对象
size := <-c.resize
return &size
} // podSSH 函数,这是主要的 SSH 功能逻辑,使用 kubernetes client-go 的 SPDY executor 来执行远程命令。
func podSSH(wsClient *WSClient, q query) {
// 使用 kubeconfig 文件初始化 kubernetes 客户端配置
// 请注意,你应该替换 ./config 为你的 kubeconfig 文件路径
restClientConfig, err := clientcmd.BuildConfigFromFlags("", "./config")
if err != nil {
log.Fatalf("Failed to build config: %v", err)
} // 根据配置创建 kubernetes 客户端
clientSet, err := kubernetes.NewForConfig(restClientConfig)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
// 构造一个用于执行远程命令的请求
request := clientSet.CoreV1().RESTClient().Post().
Resource("pods").
Namespace(q.Namespace).
Name(q.PodName).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: q.ContainerName,
Command: []string{
q.Command,
},
Stdout: true,
Stdin: true,
Stderr: true,
TTY: true,
}, scheme.ParameterCodec)
// 创建 SPDY executor,用于后续的 Stream 操作
exec, err := remotecommand.NewSPDYExecutor(restClientConfig, "POST", request.URL())
if err != nil {
log.Fatalf("Failed to initialize executor: %v", err)
} // 开始进行 Stream 操作,即通过 websocket 执行命令
err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
Stderr: wsClient,
Stdout: wsClient,
Stdin: wsClient,
Tty: true,
TerminalSizeQueue: wsClient,
})
if err != nil {
log.Fatalf("Failed to start stream: %v", err)
}
} // query 结构体,用于解析和验证查询参数
type query struct {
Namespace string `form:"namespace" binding:"required"`
PodName string `form:"pod_name" binding:"required"`
ContainerName string `form:"container_name" binding:"required"`
Command string `form:"command" binding:"required"`
} func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
router.GET("/", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "ssh.html", nil)
}) // 设置 /ssh 路由
router.GET("/ssh", func(ctx *gin.Context) {
var r query
if err := ctx.ShouldBindQuery(&r); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
return
}
// 将 HTTP 连接升级为 websocket 连接
ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
log.Printf("Failed to upgrade connection: %v", err)
return
}
// 使用 podSSH 函数处理 websocket 连接
podSSH(&WSClient{
ws: ws,
resize: make(chan remotecommand.TerminalSize),
}, r)
})
router.Run(":9191")
}
  • 后端全部代码
https://gitee.com/KubeSec/pod-webssh/tree/master/pod-ssh

使用vue-admin-template和Xterm.js实现Web终端

https://github.com/PanJiaChen/vue-admin-template

https://github.com/xtermjs/xterm.js

  • 下载vue-admin-template项目

https://github.com/PanJiaChen/vue-admin-template.git

  • 安装xterm.js及插件

npm install
npm install xterm
npm install --save xterm-addon-web-links
npm install --save xterm-addon-fit
npm install -S xterm-style
  • 打开vue-admin-template项目,在src/views目录下新建目录pod-ssh,在pod-ssh目录下新建index.vue代码如下

<template>
<div class="app-container">
<!-- 使用 Element UI 的表单组件创建一个带有标签和输入框的表单 -->
<el-form ref="form" :model="form" :inline="true" label-width="120px">
<el-form-item label="namespace"> <!-- namespace 输入框 -->
<el-input v-model="form.namespace" />
</el-form-item>
<el-form-item label="pod name"> <!-- pod 名称输入框 -->
<el-input v-model="form.pod_name" />
</el-form-item>
<el-form-item label="container name"> <!-- 容器名称输入框 -->
<el-input v-model="form.container_name" />
</el-form-item>
<el-form-item label="Command"> <!-- 命令选择框 -->
<el-select v-model="form.command" placeholder="bash">
<el-option label="bash" value="bash" />
<el-option label="sh" value="sh" />
</el-select>
</el-form-item>
<el-form-item> <!-- 提交按钮 -->
<el-button type="primary" @click="onSubmit">Create</el-button>
</el-form-item>
<div id="terminal" /> <!-- 终端视图容器 -->
</el-form>
</div>
</template> <script>
import { Terminal } from 'xterm' // 导入 xterm 包,用于创建和操作终端对象
import { common as xtermTheme } from 'xterm-style' // 导入 xterm 样式主题
import 'xterm/css/xterm.css' // 导入 xterm CSS 样式
import { FitAddon } from 'xterm-addon-fit' // 导入 xterm fit 插件,用于调整终端大小
import { WebLinksAddon } from 'xterm-addon-web-links' // 导入 xterm web-links 插件,可以捕获 URL 并将其转换为可点击链接
import 'xterm/lib/xterm.js' // 导入 xterm 库 export default {
data() {
return {
form: {
namespace: 'default', // 默认命名空间为 "default"
command: 'bash', // 默认 shell 命令为 "bash"
pod_name: 'nginx', // 默认 Pod 名称为 "nginx"
container_name: 'nginx' // 默认容器名称为 "nginx"
},
}
},
methods: {
onSubmit() {
// 创建一个新的 Terminal 对象
const xterm = new Terminal({
theme: xtermTheme,
rendererType: 'canvas',
convertEol: true,
cursorBlink: true
}) // 创建并加载 FitAddon 和 WebLinksAddon
const fitAddon = new FitAddon()
xterm.loadAddon(fitAddon)
xterm.loadAddon(new WebLinksAddon()) // 打开这个终端,并附加到 HTML 元素上
xterm.open(document.getElementById('terminal')) // 调整终端的大小以适应其父元素
fitAddon.fit() // 创建一个新的 WebSocket 连接,并通过 URL 参数传递 pod, namespace, container 和 command 信息
const ws = new WebSocket('ws://127.0.0.1:9191/ssh?namespace=' + this.form.namespace + '&pod_name=' + this.form.pod_name + '&container_name=' + this.form.container_name + '&command=' + this.form.command) // 当 WebSocket 连接打开时,发送一个 resize 消息给服务器,告诉它终端的尺寸
ws.onopen = function() {
ws.send(JSON.stringify({
msg_type: 'resize',
rows: xterm.rows,
cols: xterm.cols
}))
} // 当从服务器收到消息时,写入终端显示
ws.onmessage = function(evt) {
xterm.write(evt.data)
} // 当发生错误时,也写入终端显示
ws.onerror = function(evt) {
xterm.write(evt.data)
} // 当窗口尺寸变化时,重新调整终端的尺寸,并发送一个新的 resize 消息给服务器
window.addEventListener('resize', function() {
fitAddon.fit()
ws.send(JSON.stringify({
msg_type: 'resize',
rows: xterm.rows,
cols: xterm.cols
}))
}) // 当在终端中键入字符时,发送一个 input 消息给服务器
xterm.onData((b) => {
ws.send(JSON.stringify({
msg_type: 'input',
data: b
}))
})
}
}
}
</script> <style scoped>
.line{
text-align: center;
}
</style>

在src/router/index.js文件中增加路由

{
path: '/pod-ssh',
component: Layout,
children: [
{
path: 'pod-ssh',
name: 'SSH',
component: () => import('@/views/pod-ssh/index'),
meta: { title: 'SSH', icon: 'form' }
}
]
},
  • 启动项目

npm install
npm run dev
  • 前端全部代码

https://gitee.com/KubeSec/pod-webssh/tree/master/pod-webssh

测试

  • 在kubernetes中创建测试的Pod

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

访问http://localhost:9528/#/pod-ssh/pod-ssh

Gin+Xterm.js实现远程Kubernetes Pod(一)的更多相关文章

  1. Django实现WebSSH操作Kubernetes Pod

    优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以 ...

  2. Python Django撸个WebSSH操作Kubernetes Pod

    优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以 ...

  3. Python Django撸个WebSSH操作Kubernetes Pod(下)- 终端窗口自适应Resize

    追求完美不服输的我,一直在与各种问题斗争的路上痛并快乐着 上一篇文章Django实现WebSSH操作Kubernetes Pod最后留了个问题没有解决,那就是terminal内容窗口的大小没有办法调整 ...

  4. 重定向Kubernetes pod中的tcpdump输出

    重定向Kubernetes pod中的tcpdump输出 最新发现一个比较有意思的库ksniff,它是一个kubectl 插件,使用tcpdump来远程捕获Kubernetes集群中的pod流量并保存 ...

  5. Kubernetes Pod 驱逐详解

    原文链接:Kubernetes Pod 驱逐详解 在 Kubernetes 中,Pod 使用的资源最重要的是 CPU.内存和磁盘 IO,这些资源可以被分为可压缩资源(CPU)和不可压缩资源(内存,磁盘 ...

  6. Kubernetes Pod 镜像拉取策略

    Kubernetes Pod 镜像拉取策略 官方文档:https://kubernetes.io/docs/concepts/containers/images/ • IfNotPresent:默认值 ...

  7. Kubernetes Pod 资源限制

    Kubernetes Pod 资源限制 官方文档:https://kubernetes.io/docs/concepts/configuration/manage-compute-resources- ...

  8. Kubernetes Pod 调度约束

    Kubernetes Pod 调度约束 可以将pod调度到指定的节点Node内 默认:根据节点资源利用率等分配Node节点. nodeName用于将Pod调度到指定的Node名称上 nodeSelec ...

  9. xterm.js的深入学习

    demo <template> <div id="app" class="app-box">Hello</div> < ...

  10. Kubernetes Pod故障归类与排查方法

    Pod概念 Pod是kubernetes集群中最小的部署和管理的基本单元,协同寻址,协同调度. Pod是一个或多个容器的集合,是一个或一组服务(进程)的抽象集合. Pod中可以共享网络和存储(可以简单 ...

随机推荐

  1. Comparator之用最少数量的箭引爆气球

    文章目录 前言 关于Comparator 原题 前言 今天刷个题,遇到一个很有趣的问题,关于Comparator的使用,感觉也是一个关于写代码的一些小细节的问题 关于Comparator Compar ...

  2. 2022-12-14:给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 给定两个长度为n的数组,powers和rates pow

    2022-12-14:给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 给定两个长度为n的数组,powers和rates pow ...

  3. 2020-12-24:MQ中,如何保证消息不丢失?

    福哥答案2020-12-24: 生产者丢失消息:如网络传输中丢失消息.MQ 发生异常未成功接收消息等情况. 解决办法:主流的 MQ 都有确认或事务机制,可以保证生产者将消息送达到 MQ.如 Rabbi ...

  4. Apache Arrow DataFusion原理与架构

    本篇主要介绍了一种使用Rust语言编写的查询引擎--DataFusion,其使用了基于Arrow格式的内存模型,结合Rust语言本身的优势,达成了非常优秀的性能指标 DataFusion是一个查询引擎 ...

  5. linux ssh远程登录

    目录 一.ssh概念 二.配置文件 三.ssh组成结构 四.远程控制过程 五.远程复制 六.配置密钥 七.wraooers防火墙 一.ssh概念 ssh:一种安全通道协议 功能:1.实现字符界面远程登 ...

  6. c#优雅高效的读取字节数组——不安全代码(1)

    在开发上位机的经历中,会有很多需要和下位机交互通信的场景,大多数都会定义一个和硬件的通信协议,最终在上位机代码中的形式其实就是符合通信协议的字节数组. 目录 场景 如何解析字节数组到类或结构体中 建立 ...

  7. 代码随想录算法训练营Day42 动态规划

    代码随想录算法训练营 代码随想录算法训练营Day40 动态规划|01背包问题,你该了解这些! 01背包问题,你该了解这些!滚动数组 416. 分割等和子集 01背包问题,你该了解这些! 完全背包又是也 ...

  8. 代码随想录算法训练营Day11 栈与队列|20. 有效的括号  1047. 删除字符串中的所有相邻重复项  150. 逆波兰表达式求值

    20.有效的括号 题目链接:20.有效的括号 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合 ...

  9. javascript5 定时器功能

    定时器功能: 定时器功能是window对象方法,涉及到 定时器和延时器,具体 看代码 定时器 timer=setInterval(function (){},300) 清除定时器: clearInte ...

  10. 【技术积累】Python中的Pandas库【三】

    什么是Series Series是一种带有标签的一维数组,可以容纳各种类型的数据(例如整数,浮点数和字符串).每个Series对象都有一个索引,它可以用来引用每个元素.Series对象的主要特征是可以 ...