基于Golang + xtermjs构建Kubernetes多集群管理Web Terminal

科技   2024-07-31 17:34   上海  

背景

近期,一位星球小伙伴入职新公司,之前习惯使用kubectl命令进行Kubernetes运维,但新公司所有集群都托管在阿里云ACK上。于是,他产生了一个想法:能否构建一个支持Kubernetes多集群管理的Web Terminal,通过kubectl命令来管理这些集群?答案是肯定的!接下来,我们将使用Golang和xtermjs来实现这一Kubernetes多集群管理Web Terminal。

技术栈

  • 前端:Vue+Typescript+ArcoDesign+xtermjs

  • 后端:Golang+Gin+Gorm+WebSocket

  • 依赖:Kubectl、Client.go

主要功能

image

xterm.js使用指南

xterm.js 是一个强大的终端仿真库,可以在网页上创建一个功能完整的终端。它广泛用于构建Web终端应用,比如Kubernetes多集群管理Web Terminal。

首先,使用npm安装xterm.js和xterm-addon-fit:

$ npm install xterm xterm-addon-fit

参考代码(Vue3):

<template>
  <!-- 终端的容器 -->
  <div ref="terminalContainer" style="width: 100%; height: 100%"></div>
</template>

<script lang="ts" setup>
  // 从 Vue 和 xterm 导入必要的模块
  import { onMounted, onBeforeUnmount, ref } from 'vue';
  import { Terminal } from 'xterm';
  import 'xterm/css/xterm.css';
  import { Base64 } from 'js-base64';

  // 终端容器的引用
  const terminalContainer = ref<HTMLDivElement | null>(null);

  // 终端和 WebSocket 的变量
  let term: Terminal;
  let socket: WebSocket;
  let inputBuffer = ''// 用于存储输入内容的缓冲区

  // 组件挂载时的生命周期钩子
  onMounted(() => {
    if (terminalContainer.value) {
      // 使用特定选项初始化终端
      term = new Terminal({
        convertEoltrue,
        disableStdinfalse,
        cursorBlinktrue,
        fontSize14,
        rows50,
        cols200,
        theme: {
          foreground"#ECECEC",
          background"#000000",
          cursor'help',
        },
      });
      // 在指定容器中打开终端
      term.open(terminalContainer.value);

      // 连接 WebSocket
      socket = new WebSocket('ws://10.0.63.28:8087/ws');
      // WebSocket 打开事件监听器
      socket.addEventListener('open', () => {
        term.write('\r\n集群连接成功!\r\n'); // 向终端写入成功消息
        term.write('> '); // 显示提示符
      });

      // WebSocket 收到消息事件监听器
      socket.addEventListener('message', (event) => {
        if (event.data !== 'Cg==') {
          console.log(event.data);
          term.write(Base64.decode(event.data)); // 解码并将消息写入终端
        }
        term.write('> '); // 显示提示符
      });

      // 处理终端输入
      term.onData((e) => {
        const char = e;
        if (char === '\r') { // 检测 Enter 键
          socket.send(inputBuffer); // 将缓冲区中的输入发送到 WebSocket
          inputBuffer = ''// 清空输入缓冲区
          term.write('\r\n'); // 在终端中换行
        } else if (char === '\u007F') { // 检测 Backspace 键
          if (inputBuffer.length > 0) {
            inputBuffer = inputBuffer.slice(0-1); // 从缓冲区中删除最后一个字符
            term.write('\b \b'); // 从终端显示中删除最后一个字符
          }
        } else {
          inputBuffer += char; // 将字符添加到缓冲区
          term.write(char); // 在终端中显示字符
        }
      });
    }
  });

  // 组件卸载前的生命周期钩子
  onBeforeUnmount(() => {
    if (term) {
      term.dispose(); // 释放终端资源
    }
    if (socket) {
      socket.close(); // 关闭 WebSocket 连接
    }
  });
</script>

<style scoped></style>

options配置:

cmOptions: {
        mode: "application/json", // 语言及语法模式
        theme: "idea", // 主题
        autoRefresh: true, // 自动刷新
        line: true, // 显示函数
        lint: true, // 校验
        matchBrackets: true, // 括号匹配显示
        autoCloseBrackets: true, // 输入和退格时成对
        indentUnit: 2, // 缩进单位,默认2
        lineWrapping: true, // 软换行
        tabSize: 4, // tab宽度
        lineNumbers: true, // 显示行数
        foldGutter: true,
        smartIndent: true, // 智能缩进
        gutters: [
          "CodeMirror-linenumbers",
          "CodeMirror-foldgutter",
          "CodeMirror-lint-markers", // 实现语法报错
        ],
},

使用Gin框架实现WebSocket

首先,你需要确保已经安装了Gin和Gorilla WebSocket库。这两个库可以通过以下命令进行安装:

$ go get -u github.com/gin-gonic/gin
$ go get -u github.com/gorilla/websocket

参考代码:

package main

import (
 "net/http"

 "github.com/gin-gonic/gin"
 "github.com/gorilla/websocket"
)

// WebSocket升级器
var upgrader = websocket.Upgrader{
 CheckOrigin: func(r *http.Request) bool {
  // 允许所有请求源
  return true
 },
}

// WebSocket处理程序
func handleWebSocket(c *gin.Context) {
 conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
 if err != nil {
  c.JSON(http.StatusInternalServerError, gin.H{"message""Failed to upgrade to WebSocket"})
  return
 }
 defer conn.Close()

 for {
  // 读取消息
  mt, message, err := conn.ReadMessage()
  if err != nil {
   break
  }
  // 打印消息
  println("Received:"string(message))
  // 回显消息
  err = conn.WriteMessage(mt, message)
  if err != nil {
   break
  }
 }
}

func main() {
 r := gin.Default()

 // 设置WebSocket路由
 r.GET("/ws", handleWebSocket)

 // 启动服务器
 r.Run(":8080")
}

操作展示

创建集群:

image

填写集群名称、集群凭证:

image

点击终端:

image

连接成功:

image

此时可以输入命令体验一下喽:

image

敬请期待后续文章!

👇

云原生运维圈
专注于Docker、Kubernetes、Prometheus、Istio、Terraform、OpenTelemetry等云原生技术分享!
 最新文章