如何使用client-go构建pod web shell

news2024/12/23 10:22:35

代码示例及原理

  • 原理是利用websocket协议实现对pod的exec登录,利用client-go构造与远程apiserver的长连接,将对pod容器的输入和pod容器的输出重定向到我们的io方法中,从而实现浏览器端的虚拟终端的效果
  • 消息体结构如下
type Connection struct {
	WsSocket    *websocket.Conn // 主websocket连接
	OutWsSocket *websocket.Conn
	InChan      chan *WsMessage // 输入消息管道
	OutChan     chan *WsMessage // 输出消息管道

	Mutex     sync.Mutex // 并发控制
	IsClosed  bool // 是否关闭
	CloseChan chan byte // 关闭连接管道
}
// 消息体
type WsMessage struct {
	MessageType int    `json:"messageType"`
	Data        []byte `json:"data"`
}
// terminal的行宽和列宽
type XtermMessage struct {
	Rows uint16 `json:"rows"`
	Cols uint16 `json:"cols"`
}
  • 下面需要一个handler来控制终端和接收消息,ResizeEvent用来控制终端变更的事件
type ContainerStreamHandler struct {
	WsConn      *Connection
	ResizeEvent chan remotecommand.TerminalSize
}
  • 为了控制终端,我们需要重写TerminalSize的Next方法,这个方法是client-go定义的接口,如下所示
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package remotecommand

// TerminalSize and TerminalSizeQueue was a part of k8s.io/kubernetes/pkg/util/term
// and were moved in order to decouple client from other term dependencies

// TerminalSize represents the width and height of a terminal.
type TerminalSize struct {
	Width  uint16
	Height uint16
}

// TerminalSizeQueue is capable of returning terminal resize events as they occur.
type TerminalSizeQueue interface {
	// Next returns the new terminal size after the terminal has been resized. It returns nil when
	// monitoring has been stopped.
	Next() *TerminalSize
}
  • 我们重写的Next方法如下
func (handler *ContainerStreamHandler) Next() (size *remotecommand.TerminalSize) {
	select {
	case ret := <-handler.ResizeEvent:
		size = &ret
	case <-handler.WsConn.CloseChan:
		return nil // 这里很重要, 具体见最后的解释
	}
	return
}
  • 当我们从ResizeEvent管道中接收到调整终端大小的事件之后,这个事件会被client-go接收到,源码如下
func (p *streamProtocolV3) handleResizes() {
	if p.resizeStream == nil || p.TerminalSizeQueue == nil {
		return
	}
	go func() {
		defer runtime.HandleCrash()

		encoder := json.NewEncoder(p.resizeStream)
		for {
			size := p.TerminalSizeQueue.Next() // 接收到我们的调整终端大小的事件
			if size == nil {
				return
			}
			
			if err := encoder.Encode(&size); err != nil {
				runtime.HandleError(err)
			}
		}
	}()
}
  • 最后我们给出核心的实现(只是一个大概的框架,具体细节有问题可以留言)
func ExecCommandInContainer(ctx context.Context, conn *tty.Connection, podName, namespace, containerName string) (err error) {
	kubeClient, err := k8s.CreateClientFromConfig([]byte(env.Kubeconfig))
	if err != nil {
		return
	}
	restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(env.Kubeconfig))
	if err != nil {
		return
	}
	// 构造请求
	req := kubeClient.CoreV1().RESTClient().Post().
		Resource("pods").
		Name(podName).
		Namespace(namespace).
		SubResource("exec").
		VersionedParams(&corev1.PodExecOptions{
			Command:   []string{"/bin/sh", "-c", "export LANG=\"en_US.UTF-8\"; [ -x /bin/bash ] && exec /bin/bash || exec /bin/sh"},
			Container: containerName,
			Stdin:     true,
			Stdout:    true,
			Stderr:    true,
			TTY:       true,
		}, scheme.ParameterCodec)
	// 使用spdy协议对http协议进行增量升级 
	exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
	if err != nil {
		return err
	}
	handler := &tty.ContainerStreamHandler{
		WsConn:      conn,
		ResizeEvent: make(chan remotecommand.TerminalSize),
	}
	// 核心函数,重定向标准输入和输出
	err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
		Stdin:             handler,
		Stdout:            handler,
		Stderr:            handler,
		TerminalSizeQueue: handler,
		Tty:               true,
	})
	return
}
  • 整个函数的核心是StreamWithContext函数,这个函数是client-go的一个方法,接下来我们详细分析一下
// StreamWithContext opens a protocol streamer to the server and streams until a client closes
// the connection or the server disconnects or the context is done.
func (e *spdyStreamExecutor) StreamWithContext(ctx context.Context, options StreamOptions) error {
	conn, streamer, err := e.newConnectionAndStream(ctx, options)
	if err != nil {
		return err
	}
	defer conn.Close()

	panicChan := make(chan any, 1) // panic管道
	errorChan := make(chan error, 1) // error管道
	go func() {
		defer func() {
			if p := recover(); p != nil {
				panicChan <- p
			}
		}()
		errorChan <- streamer.stream(conn)
	}()

	select {
	case p := <-panicChan:
		panic(p)
	case err := <-errorChan:
		return err
	case <-ctx.Done():
		return ctx.Err()
	}
}
  • 这个方法首先初始化了一个连接,然后定义了两个管道,分别接收panic事件和error事件,核心是streamer.stream()方法,接下来我们分析一下这个方法
func (p *streamProtocolV4) stream(conn streamCreator) error {
	// 创建一个与apiserver的连接传输流
	if err := p.createStreams(conn); err != nil {
		return err
	}

	// now that all the streams have been created, proceed with reading & copying
	// 观察流中的错误
	errorChan := watchErrorStream(p.errorStream, &errorDecoderV4{})
	// 监听终端调整的事件
	p.handleResizes()
	// 将我们的标准输入拷贝到remoteStdin也就是远端的标准输入当中
	p.copyStdin()

	var wg sync.WaitGroup
	// 将远端的标准输出拷贝到我们的标准输出当中
	p.copyStdout(&wg)
	p.copyStderr(&wg)

	// we're waiting for stdout/stderr to finish copying
	wg.Wait()

	// waits for errorStream to finish reading with an error or nil
	return <-errorChan
}
  • 整体逻辑还是很清晰的,具体实现细节看源码吧

OOM 问题

  • 这个功能上线之后,发现内存不断攀升,如下图(出现陡降是因为我重启了服务)
    在这里插入图片描述
  • 使用pprof进行问题排查,在你的main.go文件中加入下面的内容
import _ "net/http/pprof"

func main(){
	go func() {
		http.ListenAndServe("localhost:6060", nil)
	}()
}
  • 然后你可以在http://localhost:6060/debug/pprof/中看到下面的页面
    在这里插入图片描述
  • 点击full goroutine stack dump,你会看到goroutine的堆栈存储情况,如下图
    在这里插入图片描述
  • 查看之后发现出现了很多的残留goroutine,出现在Next方法中,如下图
    在这里插入图片描述
  • 这个问题出现的原因是我们重写的Next方法,当断开连接的时候必须要主动返回一个nil,否则会残留一个go func,具体看上面的handleResizes方法中有一个for循环,必须收到一个sizenil,才能跳出此func,一开始我写的方法如下,这样写的话当浏览器退出的时候,是不会给我一个nil的终端调整的事件的
// 错误写法
func (handler *ContainerStreamHandler) Next() (size *remotecommand.TerminalSize) {
	ret := <-handler.ResizeEvent:
	size = &ret
	return
}
// 正确写法
func (handler *ContainerStreamHandler) Next() (size *remotecommand.TerminalSize) {
	select {
	case ret := <-handler.ResizeEvent:
		size = &ret
	case <-handler.WsConn.CloseChan:
		return nil // 当发现管道关闭的时候,主动返回一个nil
	}
	return
}

有问题欢迎交流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1655599.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

联丰策略股票杠杆交易炒股市场四大变量,突袭A股

查查配今天早盘,港股一度冲高,但临近午盘时,亦快速杀跌,三大股指纷纷转跌。A股市场自开盘之后,就走势较为低迷。人民币和A50亦同步走弱。而从亚太市场来看,日本股市跌幅最大,日经225指数一度杀跌超1.7%。那么,究竟发生了什么? 联丰策略拥有一支由知名互联网公司和国内证券金融…

Java进阶07集合(续)

Java进阶07 集合&#xff08;续&#xff09; 一、数据结构&#xff08;树&#xff09; 1、关于树 1.1 相关概念 节点&#xff1a;树中每个单独的分支 节点的度&#xff1a;每个节点的子节点数量 树高&#xff1a;树的总层数 根节点&#xff1a;最顶层节点 左子节点&…

融知财经:期货交易原理是怎样的?期货交易有哪些特征?

期货的原理是基于对某期货品种未来走势的判断而形成对其合约的买卖交易&#xff0c;因此期货可以解释为买涨或买跌。买涨&#xff0c;即看多交易&#xff0c;预期某期货品种未来价格上涨而进行的买入开仓交易&#xff1b;买跌&#xff0c;即看空交易&#xff0c;预期某期货品种…

招聘招商求职系统asp.net+sqlserver

招聘招商求职系统asp.netsqlserver 首 页 招聘信息 公寓信息 物品求购 发布信息 管理员后台登陆 账号密码TSoft 111 审核 招聘信息 求职信息 培训信息 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql s…

首席数据官CCRC-CDO如何构筑企业数据合规的坚固防线

在当今信息化快速发展的时代&#xff0c;数据已经成为企业最宝贵的资产之一。然而&#xff0c;随着数据规模的迅速增长&#xff0c;数据合规问题也日益凸显。首席数据官&#xff08;CDO&#xff09;作为企业中负责数据战略和管理的核心人物&#xff0c;构筑企业数据合规的坚固防…

【C++】map和set的基础详解

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

500 个 AI 项目代码库:计算机视觉到 NLP | 开源日报 No.248

ashishpatel26/500-AI-Machine-learning-Deep-learning-Computer-vision-NLP-Projects-with-code Stars: 16.8k License: NOASSERTION 500-AI-Machine-learning-Deep-learning-Computer-vision-NLP-Projects-with-code 是一个包含 500 多个 AI、机器学习、深度学习、计算机视觉…

变配电工程 变配电室智能监控系统 门禁 视频 环境 机器人

一、方案背景 要真正了解无人值守配电房的运行模式&#xff0c;我们必须对“无人值守”这一概念有准确的理解。它并不意味着完全没有工作人员管理&#xff0c;而是通过技术设备和人机协作来确保配电房的正常运行。 利用变配电室智能监控系统&#xff0c;可以实时获得配电室各…

【全开源】Java外卖微信小程序京东拼多多外卖cps|外卖红包优惠券源码美团饿了么红包

1.京东优惠小程序&#xff1a; 特色功能&#xff1a;京东优惠小程序提供丰富的商品优惠信息&#xff0c;包括京东独家的优惠券、折扣活动等。用户可以直接在小程序内浏览商品、领取优惠券、下单购买&#xff0c;无需跳转到京东APP或网页。优势&#xff1a;京东作为中国领先的电…

Android 按键消息流程源码分析

在Android系统中&#xff0c;键盘按键事件是由SystemServer服务来管理的&#xff1b;然后在以消息的形式分发给应用程序处理。产生键盘按键事件则是有Linux kernel的相关驱动来实现。键盘消息有别于其他类型的消息&#xff1b;需要从Linux kernel drivers产生由上层APP来处理。…

正点原子Linux学习笔记(六)在 LCD 上显示 jpeg 图像

在 LCD 上显示 jpeg 图像 20.1 JPEG 简介20.2 libjpeg 简介20.3 libjpeg 移植下载源码包编译源码安装目录下的文件夹介绍移植到开发板 20.4 libjpeg 使用说明错误处理创建解码对象设置数据源读取 jpeg 文件的头信息设置解码处理参数开始解码读取数据结束解码释放/销毁解码对象 …

[华为OD]C卷 BFS 亲子游戏 200

题目&#xff1a; 宝宝和妈妈参加亲子游戏&#xff0c;在一个二维矩阵&#xff08;N*N&#xff09;的格子地图上&#xff0c;宝宝和妈妈抽签决定各自 的位置&#xff0c;地图上每个格子有不同的Q糖果数量&#xff0c;部分格子有障碍物。 游戏规则Q是妈妈必须在最短的时间&a…

力扣刷题第1天:消失的数字

大家好啊&#xff0c;从今天开始将会和大家一起刷题&#xff0c;从今天开始小生也会开辟新的专栏。&#x1f61c;&#x1f61c;&#x1f61c; 目录 第一部分&#xff1a;题目描述 第二部分&#xff1a;题目分析 第三部分&#xff1a;解决方法 3.1 思路一&#xff1a;先排序…

WebView基础知识以及Androidx-WebKit的使用

文章目录 摘要WebView基础一、启动调整模式二、WebChromeClient三、WebViewClient四、WebSettings五、WebView和Native交互 Androidx-WebKit一、启动安全浏览服务二、设置代理三、安全的 WebView 和 Native 通信支持四、文件传递五、深色主题的支持六、JavaScript and WebAssem…

邮件大附件系统如何进行安全、高效的大附件发送?

邮件大附件系统是一套解决传统电子邮件系统&#xff0c;在发送大文件时遇到限制的解决方案。由于传统电子邮件系统通常对附件大小有限制&#xff0c;这使得发送大文件变得困难。邮件大附件系统通过各种技术手段&#xff0c;允许用户发送超过传统限制的大文件&#xff0c;通常在…

【随笔】Git 高级篇 -- 上传命令的参数 (下)git push(三十七)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

目标检测——低光可见光-红外配对数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

TypeScript学习日志-第二十四天(webpack构建ts+vue3)

webpack构建tsvue3 一、构建项目目录 如图&#xff1a; shim.d.ts 这个文件用于让ts识别.vue后缀的 后续会说 并且给 tsconfig.json 增加配置项 "include": ["src/**/*"] 二、基础构建 安装依赖 安装如下依赖&#xff1a; npm install webpack -D …

11.偏向锁原理及其实战

文章目录 偏向锁原理及其实战1.偏向锁原理2.偏向锁案例代码演示2.1.偏向锁案例代码2.2.1.无锁情况下状态2.1.2.偏向锁状态2.1.3.释放锁后的状态 2.2.偏向锁的膨胀和撤销2.2.1.偏向锁撤销的条件2.2.2.偏向锁的撤销 2.2.3.偏向锁的膨胀 2.3.全局安全点原理和偏向锁撤销性能问题2.…

在R的 RGui中,使用devtools 安装trajeR

创建于&#xff1a;2024.5.5 文章目录 1. 报错信息2. 尝试使用指定的清华镜像&#xff0c;没有解决3. 找到原因&#xff1a;官网把包删除了4. 尝试从网上下载&#xff0c;然后安装。没有成功5. 使用devtools安装5.1 尝试直接安装&#xff1a;install.packages("devtools&q…