如何使用client-go构建pod web shell
代码示例及原理
- 原理是利用websocket协议实现对pod的exec登录,利用client-go构造与远程apiserver的长连接,将对pod容器的输入和pod容器的输出重定向到我们的io方法中,从而实现浏览器端的虚拟终端的效果
- 消息体结构如下
type Connection struct {WsSocket *websocket.Conn // 主websocket连接OutWsSocket *websocket.ConnInChan 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 *ConnectionResizeEvent 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 athttp://www.apache.org/licenses/LICENSE-2.0Unless 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 uint16Height 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 = &retcase <-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 errcase <-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 copyingwg.Wait()// waits for errorStream to finish reading with an error or nilreturn <-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
循环,必须收到一个size
为nil
,才能跳出此func
,一开始我写的方法如下,这样写的话当浏览器退出的时候,是不会给我一个nil
的终端调整的事件的
// 错误写法
func (handler *ContainerStreamHandler) Next() (size *remotecommand.TerminalSize) {ret := <-handler.ResizeEvent:size = &retreturn
}
// 正确写法
func (handler *ContainerStreamHandler) Next() (size *remotecommand.TerminalSize) {select {case ret := <-handler.ResizeEvent:size = &retcase <-handler.WsConn.CloseChan:return nil // 当发现管道关闭的时候,主动返回一个nil}return
}
有问题欢迎交流
相关文章:

如何使用client-go构建pod web shell
代码示例及原理 原理是利用websocket协议实现对pod的exec登录,利用client-go构造与远程apiserver的长连接,将对pod容器的输入和pod容器的输出重定向到我们的io方法中,从而实现浏览器端的虚拟终端的效果消息体结构如下 type Connection stru…...

AI工具摸索-关于写作(1)
虽然人工智能工具非常多,但是如果想要成为生产力,能达标的工具仍然非常少,除了最常用的chatgpt,其他的工具真的能达标吗,这篇文章主要就是对比市面上的一些工具, 但我这个人非常执拗,我认为作为生产力工具的功能必然是可以真正帮助我们的,而不是说作为一个写作工具结…...

昂科烧录器支持O2Micro凹凸科技的电池组管理IC OZ7708
芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表,其中O2Micro凹凸科技的电池组管理IC OZ7708已经被昂科的通用烧录平台AP8000所支持。 OZ7708是一款高度集成、低成本的电池组管理IC,适用于5~8s Li-Ion/Polymer电池组&a…...

Spring Cloud Gateway详解
文章目录 Gateway搭建路由(route)断言(Predicate )自定义断言 过滤器(filter)自定义全局过滤器 引言 在传统的单体项目中,前端和后端的交互相对简单,只需通过一个调用地址即可实现。…...

信息系统项目管理师0103:初步可行性研究(7项目立项管理—7.2项目可行性研究—7.2.2初步可行性研究)
点击查看专栏目录 文章目录 7.2.2初步可行性研究1.初步可行性研究定义2.辅助研究的目的和作用3.初步可行性研究的作用4.初步可行性研究的主要内容记忆要点总结7.2.2初步可行性研究 1.初步可行性研究定义 初步可行性研究一般是在对市场或者客户情况进行调查后,对项目进行的初步…...

Linux 系统中,nl命令用于计算文件中的行号
在 Linux 系统中,nl命令用于计算文件中的行号。它可以将输出的文件内容自动加上行号,并且可以通过不同的选项来设置行号的显示方式,包括行号的位数、是否自动补齐 0 等。其命令格式为:nl(选项)…(文件)…。以下是一些常见的选项&a…...

知从科技战略客户经理张志强受邀出席2024 AutoSec中国汽车网络安全与数据安全峰会
4月11-12日,AutoSec8周年年会暨中国汽车网络安全及数据安全合规峰会在上海成功举办。此次峰会吸引了来自全球各地的头部汽车网络安全企业、OEM厂商、安全专家和学者等齐聚盛会,零距离共话智能网联汽车产业的新发展、新趋势。 知从科技董事长成云霞亲自带…...

2024.5.12 Pandas 基础语法day02
#describe()作用是计算出各个列的描述行统计量如平均数,方差,最大值,最小值,四分位数,返回类型是 #pandas.core.frame.DataFrame import pandas as pd df pd.read_csv("Nowcoder.csv") print(df.describe()…...

Stable Diffusion是什么?
目录 一、Stable Diffusion是什么? 二、Stable Diffusion的基本原理 三、Stable Diffusion有哪些运用领域? 一、Stable Diffusion是什么? Stable Diffusion是一个先进的人工智能图像生成模型,它能够根据文本描述创造出高质量的图…...

Netty源码分析二NioEventLoop 剖析
剖析方向 NioEventLoop是一个重量级的类,其中涉及到的方法都有很复杂的继承关系,调用链,要想把源码全部过一遍工作量实在是太大了,于是小编就基于下面的这些常见的问题来对NioEventLoop的源码来进行剖析 1.Seletor何时创建 1.1Se…...

chatGLM或chatgpt:什么是tokens以及如何计算tokens长度?
token是什么? 简单的来说tokens就是大语言模型输入的向量数据,它是从原始的文本转化而来。 比如 输入:here is a text demo tokens为:[64790, 64792, 985, 323, 260, 2254, 16948] 解码:将tokens转化为文本 [‘[gMASK]’, ‘sop’, ‘▁here’, ‘▁is’, ‘▁a’, ‘▁…...

springcloudalibaba版本发布说明
版本发布说明 | https://sca.aliyun.com 2.2.x 分支 适配 Spring Boot 为 2.4,Spring Cloud Hoxton 版本及以下的 Spring Cloud Alibaba 版本按从新到旧排列如下表(最新版本用*标记): Spring Cloud Alibaba VersionSpring Cloud…...

Obsidian/Typora设置图床
在obsidian中默认图片是保存在本地的,但是在要导出文档上传到网上时,由于图片保存在本地,会出现无法加载图片的问题。 这里引用的一段话: 这里使用picgo-core和gitee实现图床功能, 参考1: Ubuntu下PicGO配…...

【RAG论文】RAG中半结构化数据的解析和向量化方法
论文简介 论文题目: 《A Method for Parsing and Vectorization of Semi-structured Data used in Retrieval Augmented Generation》 论文链接: https://arxiv.org/abs/2405.03989 代码: https://github.com/linancn/TianGong-AI-Unstructure/tree/m…...

git提交代码异常报错error:bad signature 0x00000000
报错信息 error:bad signature 0x00000000 异常原因 git 提交过程中异常关机或重启,造成当前项目工程中的.git/index 文件损坏,无法提交 解决步骤 删除.git/index文件 rm -f .git/index 重启git git reset...

【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库实现RTMP推流 1.FFmpeg编译2.RTMP服务器搭建3.调用FFmpeg库实现RTMP推流和拉流3.1 基本框架3.2 实现代码3.3 测试3.3.1 推流3.3.2 拉流 参考:雷霄骅博士, 调用ffmpeg库进行RTMP推流 示例工程 【FFmpeg】调用FFmpeg库实现264软编 【FFmpeg】…...

Multisim 14 常见电子仪器的使用和Multisim的使用
multisim multisim,即电子电路仿真设计软件。Multisim是美国国家仪器(NI)有限公司推出的以Windows为基础的仿真工具,适用于板级的模拟/数字电路板的设计工作。它包含了电路原理图的图形输入、电路硬件描述语言输入方式࿰…...

【2024高校网络安全管理运维赛】巨细记录!
2024高校网络安全管理运维赛 文章目录 2024高校网络安全管理运维赛MISC签到考点:动态图片分帧提取 easyshell考点:流量分析 冰蝎3.0 Webphpsql考点:sql万能钥匙 fileit考点:xml注入 外带 Cryptosecretbit考点:代码阅读…...

Nuxt.js实战:Vue.js的服务器端渲染框架
创建Nuxt.js项目 首先,确保你已经安装了Node.js和yarn或npm。然后,通过命令行创建一个新的Nuxt.js项目: yarn create nuxt-app my-nuxt-project cd my-nuxt-project在创建过程中,你可以选择是否需要UI框架、预处理器等选项&…...

提高Rust安装与更新的速度
一、背景 因为rust安装过程中,默认的下载服务器为crates.io,这是一个国外的服务器,国内用户使用时,下载与更新的速度非常慢,因此,我们需要使用一个国内的服务器来提高下载与更新的速度。 本文推荐使用字节…...

【linux软件基础知识】内核代码中的就绪队列简化示例
在内核代码中,就绪队列通常使用允许高效插入和删除进程的数据结构来表示。 用于表示就绪队列的一种常见数据结构是链表。 以下是如何使用链表在内核代码中表示就绪队列的简化示例: struct task_struct {// Process control block (PCB) fields// ...struct task_struct *nex…...

《C++学习笔记---初阶篇6》---string类 上
目录 1. 为什么要学习string类 1.1 C语言中的字符串 2. 标准库中的string类 2.1 string类(了解) 2.2 string类的常用接口说明 2.2.1. string类对象的常见构造 2.2.2. string类对象的容量操作 2.2.3.再次探讨reserve与resize 2.2.4.string类对象的访问及遍历操作 2.2.5…...

mysql中的页和行
页 行即表中的真实行,‘行式数据库’的由来 虽然MySQL的数据文件(例如.ibd文件)中的数据页在物理上是通过链表连接的,但是在逻辑上,MySQL使用B树来组织和访问数据。 行:主要是dynamic类型...

Vim常用快捷键
这个是我的草稿本记录一下防止丢失,以后有时间进行整理 0 或功能键[Home]这是数字『 0 』:移动到这一行的最前面字符处 (常用)$ 或功能键[End]移动到这一行的最后面字符处(常用)G移动到这个档案的最后一行(常用)nGn 为数字。移动到这个档案的第 n 行。例…...

力扣题目汇总分析 利用树形DP解决问题
树里 任意两个节点之间的问题。而不是根节点到叶子节点的问题或者是父节点到子节点的问题。通通一个套路,即利用543的解题思路。 543.二叉树的直径 分析 明确:二叉树的 直径 是指树中任意两个节点之间最长路径的 长度。两个节点之间的最长路径是他们之…...

GO语言核心30讲 实战与应用 (第二部分)
原站地址:Go语言核心36讲_Golang_Go语言-极客时间 一、sync.WaitGroup和sync.Once 1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。 2. WaitGroup类型有三个指针方法:Wait、Add和Done,以及内部有一个计数器。 (1) Wa…...

linux设置挂载指定的usb,自动挂载
一、设置指定的USB 在Linux系统中,如果您只想让系统挂载特定的USB设备,而忽略其他的USB设备,可以通过创建自定义的udev规则来实现。以下是设置系统只能挂载指定USB设备的基本步骤: 确定USB设备的属性: 首先࿰…...

简站WordPress主题
简站WordPress主题是一种专为建立网站而设计的WordPress模板,它旨在简化网站建设过程,使得用户能够更容易地创建和管理自己的网站。简站WordPress主题具有以下特点: 易用性:简站WordPress主题被设计为简单易用,适合各…...

is和==的关系
Python中is和的关系 is判断两个变量是不是指的是同一个内存地址,也就是通过id()函数判断 判断两个变量的值是不是相同 a [1, 2, 3, 4] b [1, 2, 3, 4] print(id(a)) # 2298268712768 print(id(b)) # 2298269716992 print(a is b) # False print(a b) # Tr…...

璩静是为了薅百度羊毛
关注卢松松,会经常给你分享一些我的经验和观点。 百度副总裁璩静离职了,网传她的年薪是1500万,而璩静在4月24日注册了一个文化传媒公司,大家都认为璩静是在为离职做准备。但松松我认为不是。 我认为:璩静成立新公司是…...