【k8s多集群管理平台开发实践】三、实现pod列表,pod的ssh终端、读取pod的日志
本章节主要讲解通过client-go实现读取pod的列表,并实现将通过实现web终端登录pod容器内部执行命令和查看文件,实现将pod stdout的日志加载到浏览器进行查看。
文章目录
简介
本章节主要讲解通过client-go实现读取pod的列表,并实现将通过实现web终端登录pod容器内部执行命令和查看文件,实现将pod stdout的日志加载到浏览器进行查看。
一.读取k8spod列表功能
1.1.controllers控制器代码
该列表可以通过传递节点名称、deployment名称、标签、集群名称、命名空间来进行查询pod,在controlers目录下新建pod.go,代码参考如下:
func (this *PodController) List() {
clusterId := this.GetString("clusterId") //集群ID
nameSpace := this.GetString("nameSpace") //命名空间
deployName := this.GetString("deployName") //deployment名称
podName := this.GetString("podName") //pod名称
nodeName := this.GetString("nodeName") //节点名称
labels := this.GetString("labels") //标签,格式:key:value
labelsKV := strings.Split(labels, ":") //yaml里没有对应的appname,deployment不会传递过来
var labelsKey, labelsValue string
if len(labelsKV) == 2 {
labelsKey = labelsKV[0]
labelsValue = labelsKV[1]
}
//从models的PodList函数进行读取数据
podList, err := m.PodList(clusterId, nameSpace, deployName, podName, labelsKey, labelsValue, nodeName)
msg := "success"
code := 0
if err != nil {
log.Println(err)
code = -1
msg = err.Error()
}
count := len(podList)
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &podList}
this.ServeJSON()
}
1.2.models模型代码
在处理pod信息的过程中,需要两个结构体,Podinfo是pod信息,Container是pod中Container的结构体信息, 在models目录下新建podModel.go,代码参考如下:
type Podinfo struct {
PodName string `json:"podName"` //podname 和podip
PodIp string `json:"podIp"`
NameSpace string `json:"nameSpace"`
NodeName string `json:"nodeName"` //节点名称和节点IP
HostIp string `json:"hostIp"`
PodPhase string `json:"podPhase"` //容器状态 Running
ImgUrl string `json:"imgUrl"` //镜像地址
//PodStatus PodStatusItem
RestartCount int32 `json:"restartCount"` //重启次数
Labels string `json:"labels"` //标签
ResStatus string `json:"resStatus"` //cpu及内存使用率
CreateTime string `json:"createTime"`
}
type Container struct {
ContainerName string `json:"containerName"` //container 名称
Envs string `json:"envs"` //设置的变量
Mounts string `json:"mounts"` //挂载路径
ContainerImage string `json:"containerImage"` //镜像地址
PullPolicy string `json:"pullPolicy"` //镜像拉取策略
Ports string `json:"ports"` //端口
ResLimits string `json:"resLimits"` //限制的资源
ResRequests string `json:"resRequests"` //请求的资源
}
func PodList(kubeconfig, namespace, deployName, podName string, labelsKey, labelsValue, nodeName string) ([]Podinfo, error) {
if namespace == "" {
//namespace = corev1.NamespaceDefault
namespace = corev1.NamespaceAll
}
clientset := common.ClientSet(kubeconfig)
var podList *corev1.PodList
var err error
//设置ListOptions
var listOptions = metav1.ListOptions{}
if labelsKey != "" && labelsValue != "" {
listOptions = metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue),
//FieldSelector: "status.phase=Running,spec.nodeName=ais-master1",
}
}
if nodeName != "" {
listOptions = metav1.ListOptions{
FieldSelector: fmt.Sprintf("status.phase!=Succeeded,spec.nodeName=%s", nodeName),
}
}
podList, err = clientset.CoreV1().Pods(namespace).List(context.Background(), listOptions)
if err != nil {
log.Printf("list pods error:%v\n", err)
}
var bbb = make([]Podinfo, 0)
//循环处理pod列表的信息,并追加到结构体数组中
for _, pod := range podList.Items {
//搜索
if podName != "" {
if !strings.Contains(pod.Name, podName) {
//if !strings.HasPrefix(pod.Name,podName)
continue
}
}
if deployName != "" {
if !strings.HasPrefix(pod.Name, deployName) {
continue
}
}
var labelsStr, imgurlStr string
for kk, vv := range pod.ObjectMeta.Labels {
labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
}
if len(labelsStr) > 0 {
labelsStr = labelsStr[0 : len(labelsStr)-1]
}
var containerState = fmt.Sprintf("%v", pod.Status.Phase)
var restartNum int32
if len(pod.Status.ContainerStatuses) > 0 {
imgurlStr = pod.Status.ContainerStatuses[0].Image
restartNum = pod.Status.ContainerStatuses[0].RestartCount
if containerState == "Pending" {
containerState = pod.Status.ContainerStatuses[0].State.Waiting.Reason
}
}
Items := &Podinfo{
PodName: pod.Name,
PodIp: pod.Status.PodIP,
ImgUrl: imgurlStr,
NameSpace: pod.ObjectMeta.Namespace,
NodeName: pod.Spec.NodeName,
HostIp: pod.Status.HostIP,
PodPhase: containerState,
Labels: labelsStr,
RestartCount: restartNum,
CreateTime: pod.ObjectMeta.CreationTimestamp.Format("2006-01-02 15:04:05"),
}
bbb = append(bbb, *Items)
}
return bbb, err
}
二.routers路由配置
路由配置,配置URL指向到控制器的函数,将这段代码添加到routes/route.go,
beego.Router("/pod/v1/List", &controllers.PodController{}, "*:List") //pod列表
beego.Router("/pod/v1/ContainerList", &controllers.PodController{}, "*:ContainerList") //读取pod中container的列表
beego.Router("/pod/v1/Log", &controllers.PodController{}, "*:Log") //读取日志
beego.Handler("/pod/terminal/ws", &controllers.TerminalSockjs{}, true) //pod终端
三.pod的终端登录功能
3.1.controllers控制器代码
由于pod终端采用websocket来进行交互,代码中部分代码有参考了别人的,所以部分代码完整拷贝了过来,此部分功能无模型部分代码,调度的控制器在controllers下新建文件pod_terminal_websocket.go完整代码如下
// pod_terminal_websocket.go
package controllers
import (
"encoding/json"
"fmt"
"net/http"
"myk8s/common"
beegolog "github.com/beego/beego/v2/core/logs"
"gopkg.in/igm/sockjs-go.v2/sockjs"
"k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
)
func (self TerminalSockjs) Read(p []byte) (int, error) {
var reply string
var msg map[string]uint16
reply, err := self.conn.Recv()
if err != nil {
return 0, err
}
if err := json.Unmarshal([]byte(reply), &msg); err != nil {
return copy(p, reply), nil
} else {
self.sizeChan <- &remotecommand.TerminalSize{
msg["cols"],
msg["rows"],
}
return 0, nil
}
}
func (self TerminalSockjs) Write(p []byte) (int, error) {
err := self.conn.Send(string(p))
return len(p), err
}
type TerminalSockjs struct {
conn sockjs.Session
sizeChan chan *remotecommand.TerminalSize
context string
clusterId string
namespace string
pod string
container string
}
// 实现tty size queue
func (self *TerminalSockjs) Next() *remotecommand.TerminalSize {
size := <-self.sizeChan
beegolog.Debug(fmt.Sprintf("terminal size to width: %d height: %d", size.Width, size.Height))
return size
}
// 处理输入输出与sockjs 交互
func Handler(t *TerminalSockjs, cmd string) error {
//restclient, config := common.RestClient(t.clusterId)
clientset, config := common.ClientSetConfig(t.clusterId)
req := clientset.CoreV1().RESTClient().Post().
//req := restclient.Post().
Resource("pods").
Name(t.pod).
Namespace(t.namespace).
SubResource("exec").
Param("container", t.container).
Param("stdin", "true").
Param("stdout", "true").
Param("stderr", "true").
Param("command", cmd).Param("tty", "true")
req.VersionedParams(
&v1.PodExecOptions{
Container: t.container,
Command: []string{},
Stdin: true,
Stdout: true,
Stderr: true,
TTY: true,
},
scheme.ParameterCodec,
)
executor, err := remotecommand.NewSPDYExecutor(
config, http.MethodPost, req.URL(),
)
if err != nil {
return err
}
return executor.Stream(remotecommand.StreamOptions{
Stdin: t,
Stdout: t,
Stderr: t,
Tty: true,
TerminalSizeQueue: t,
})
}
// 实现http.handler 接口获取入参
func (self TerminalSockjs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
context := r.FormValue("context")
clusterId := r.FormValue("clusterId")
namespace := r.FormValue("nameSpace")
pod := r.FormValue("podName")
container := r.FormValue("container")
Sockjshandler := func(session sockjs.Session) {
t := &TerminalSockjs{session, make(chan *remotecommand.TerminalSize),
context, clusterId, namespace, pod, container}
if err := Handler(t, "/bin/sh"); err != nil {
beegolog.Error(err)
beegolog.Error(Handler(t, "/bin/bash"))
}
}
sockjs.NewHandler("/terminal/ws", sockjs.DefaultOptions, Sockjshandler).ServeHTTP(w, r)
}
四.pod的日志功能
4.1.controllers控制器代码
该部分代码在pod.go中,根据传入集群名称,命名空间,pod的名称来读取日志,由于在读取日志时是从具体的container中去读取,所以需要加载container列表并选择container来读取日志,代码参考如下
//加载container列表
func (this *PodController) ContainerList() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
podName := this.GetString("podName")
code := 0
msg := "success"
xList, err := m.PodContainerList(clusterId, nameSpace, podName)
if err != nil {
code = -1
msg = err.Error()
log.Printf("[ERROR] ContainerList error:%s\n", err)
}
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": len(xList), "data": &xList}
this.ServeJSON()
}
//读取日志
func (this *PodController) Log() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
podName := this.GetString("podName")
download, _ := this.GetBool("download")
container := this.GetString("container")
logLine, _ := this.GetInt64("logLine")
if this.Ctx.Input.Method() == "POST" {
gp := gjson.ParseBytes(this.Ctx.Input.RequestBody)
podName = gp.Get("podName").String()
logLine = gp.Get("logLine").Int()
container = gp.Get("container").String()
}
if logLine == 0 { //没设置行时,默认为100
logLine = 100
}
if download { //当日志时下载时,不限制行数
logLine = 0
}
log := m.PodLog(clusterId, nameSpace, podName, container, logLine)
this.Ctx.WriteString(log)
}
4.2.models模型代码
PodContainerList函数是读取pod中的container,PodLog函数是读取日志。
func PodContainerList(kubeconfig, nameSpace, podName string) ([]Container, error) {
var ccc = make([]Container, 0)
pod, err := common.ClientSet(kubeconfig).CoreV1().Pods(nameSpace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
return ccc, err
}
for _, v1 := range pod.Spec.Containers {
xItems := &Container{
ContainerName: v1.Name,
Envs: "",
Mounts: "",
ContainerImage: v1.Image,
PullPolicy: fmt.Sprintf("%v", v1.ImagePullPolicy),
Ports: "",
ResLimits: fmt.Sprintf("CPU:%s,Memory:%s", v1.Resources.Limits.Cpu().String(), v1.Resources.Limits.Memory().String()),
ResRequests: fmt.Sprintf("CPU:%s,Memory:%s", v1.Resources.Requests.Cpu().String(), v1.Resources.Requests.Memory().String()),
}
ccc = append(ccc, *xItems)
}
return ccc, nil
}
func PodLog(kubeconfig, nameSpace, podName, container string, logLine int64) string {
clientset := common.ClientSet(kubeconfig)
pod, err := clientset.CoreV1().Pods(nameSpace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get pod %q: %v\n", podName, err)
//os.Exit(1)
}
// 获取指定行数日志
//logs, err := clientset.CoreV1().Pods(nameSpace).GetLogs(podName, &corev1.PodLogOptions{TailLines: &lines})
//获取实时日志
var logOptions = &corev1.PodLogOptions{}
logOptions.Follow = false //持续输出
logOptions.Timestamps = false //显示时间戳
//var line int64 = 50
if logLine > 0 {
//line := logLine
logOptions.TailLines = &logLine //获取多少行日志 需要指针类型https://blog.csdn.net/weixin_36469852/article/details/127432931
}
var containerPt *string = &container
if container == "" {
// [CONTAINER] (container as arg not flag) is supported as legacy behavior. See PR #10519 for more details.
if len(pod.Spec.Containers) != 1 {
podContainersNames := []string{}
for _, container := range pod.Spec.Containers {
podContainersNames = append(podContainersNames, container.Name)
}
} else {
containerPt = &pod.Spec.Containers[0].Name
}
}
logOptions.Container = *containerPt //指定container来获取日志,需要指针类型https://blog.csdn.net/weixin_36469852/article/details/127432931
podLogs, err := clientset.CoreV1().Pods(nameSpace).GetLogs(podName, logOptions).Stream(context.TODO())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get logs for pod %q: %v\n", podName, err)
//os.Exit(1)
return "error"
}
defer podLogs.Close()
//单次输出
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "error copy"
}
return buf.String()
//流式输出日志内容
// buf := make([]byte, 1024)
// for {
// n, err := podLogs.Read(buf)
// if err != nil && err == io.EOF {
// break
// }
// fmt.Print(string(buf[0:n]))
// }
// return "test"
}
五.前端html代码实现
5.1.pod列表html代码
前端部分html代码:在views\front\page\xkube 创建一个podList.html【pod列表】,podLog.html【pod日志】,podTerminal.html【pod终端】文件,其中podTerminal.html中会调用到:bootstrap.min.js,sockjs.min.js,bootstrap.min.css,xterm.css可以在网上下载来将js放到js目录下,css文件放到css目录下,或者
html代码如下:
podList.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>pod列表</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="/css/public.css" media="all">
<script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script src="/js/xkube.js?v=1" charset="utf-8"></script>
<script src="/js/lay-config.js?v=1.0.4" charset="utf-8"></script>
<style type="text/css">
.layui-table-cell {
height: auto;
line-height: 15px !important;
text-overflow: inherit;
overflow: ellipsis;
white-space: normal;
}
.layui-table-cell .layui-table-tool-panel li {
word-break: break-word;
}
</style>
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<table class="layui-table" id="currentTableId" lay-filter="currentTableFilter"></table>
<script type="text/html" id="currentTableBar">
<a class="layui-btn layui-btn-sm" lay-event="podLog">日志</a>
<a class="layui-btn layui-btn-normal layui-btn-sm" lay-event="terminal">终端</a>
</script>
</div>
</div>
</body>
<script type="text/html" id="TagTpl">
{{# if (d.labels != "") { }}
{{# layui.each(d.labels.split(','), function(index, item){ }}
{{# if(index == 0) { }}
<span>{{ item }}</span>
{{# }else{ }}
<br><span>{{ item }}</span>
{{# } }}
{{# }); }}
{{# }else{ }}
<span></span>
{{# } }}
</script>
<script type="text/html" id="podPhaseTpl">
{{# if ( d.podPhase == 'Running' ) { }}
<span style="color:#218868">{{ d.podPhase}}</span>
{{# } else if ( d.podPhase == 'Succeeded' ) { }}
<span style="color:#1E9FFF">{{ d.podPhase}}</span>
{{# } else { }}
<span style="color:#FF5722">{{ d.podPhase}}</span>
{{# } }}
</script>
<script>
var clusterId = getQueryString("clusterId");
if (clusterId == null) {
clusterId = getCookie("clusterId")
}
layui.use(['form', 'table','miniTab'], function () {
var $ = layui.jquery,
form = layui.form,
table = layui.table;
miniTab = layui.miniTab,
miniTab.listen();
form.render();
table.render({
elem: '#currentTableId',
url: '/pod/v1/List?clusterId='+clusterId,
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', {
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips'
}],
parseData: function(res) { //实现加载全部数据后再分页
if(this.page.curr) {
result=res.data.slice(this.limit*(this.page.curr-1),this.limit*this.page.curr);
}else{
result=res.data.slice(0,this.limit);
}
return {
"code": res.code,
"msg":'',
"count":res.count,
"data":result
};
},
cols: [[
//{type: "checkbox", width: 50},
{field: 'podName',title: '名称'},
{field: 'imgUrl',title: '镜像'},
{field: 'podPhase', title: '状态',sort: true,templet: '#podPhaseTpl'},
{field: 'nameSpace', title: '命名空间', sort: true},
{field: 'restartCount',title: '重启',sort: true},
{field: 'podIp',title: 'podIp',sort: true},
{field: 'hostIp',title:'节点IP'},
{field: 'nodeName',title:'节点名称',edit:true,hide:true},
{field: 'labes', title: '标签',edit:true,hide:true, sort: true,templet: '#TagTpl'},
{field: 'createTime',hide:true, title: '创建时间'},
{title: '操作', minWidth:420, toolbar: '#currentTableBar', align: "center"}
]],
//done: function(res, curr, count) {
// tableMerge.render(this)
//},
//size:'lg',
limits: [25, 50, 100],
limit: 25,
page: true
});
table.on('tool(currentTableFilter)', function (obj) {
var data = obj.data;
if (obj.event === 'podLog') {
var index = layer.open({
title: '日志',
type: 2,
shade: 0.2,
maxmin:true,
shadeClose: true,
area: ['55%', '92%'],
content: '/page/xkube/podLog.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+"&podName="+data.podName,
});
$(window).on("resize", function () {
layer.full(index);
});
return false;
} else if (obj.event === 'terminal') {
var index = layer.open({
title: '终端',
type: 2,
shade: 0.2,
maxmin:true,
shadeClose: true,
area: ['55%', '92%'],
content: '/page/xkube/podTerminal.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+"&podName="+data.podName,
});
$(window).on("resize", function () {
layer.full(index);
});
return false;
}
});
});
</script>
</html>
5.2.pod日志查看html代码
podLog.html的代码,放在views\front\page\xkube下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>pod日志</title>
<meta name="renderer" content="webkit">
<link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="/css/public.css" media="all">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script src="/js/xkube.js?v=1" charset="utf-8"></script>
<script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<div class="layui-inline">
<div class="layui-input-inline" style="width:200px">
<select name="podName" id="pod_Name" lay-filter="pod_Name">
</select>
</div>
</div>
<div class="layui-inline">
<div class="layui-input-inline" style="width:150px">
<select name="container" id="container" lay-filter="container">
</select>
</div>
</div>
<div class="layui-inline">
<div class="layui-input-inline" style="width:100px">
<select name="logLine" id="logLine">
<option value="" selected="">行数</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
<option value="500">500</option>
</select>
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn" lay-submit lay-filter="ReloadLog"><i class="layui-icon"></i>刷新</button>
<button class="layui-btn layui-btn-normal" id="DownLoadBtn">下载日志</button>
</div>
</div>
</form>
<pre class="layui-code"><div id="logtext"></div></pre>
</div>
</div>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
function getQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
};
return null;
};
layui.use(['form', 'table','code'], function () {
var $ = layui.jquery;
var form = layui.form;
layui.code({
title: 'pod日志'
,skin: 'notepad' //如果要默认风格,不用设定该key。
});
var deployName = getQueryString("deployName");
var podName = getQueryString("podName");
$('#appNameTitle').html(deployName);
$.get('/pod/v1/List' + location.search, function (resp) {
$.each(resp.data,function(i,item){
if (item.podName == podName ) {
var html1 = '<option value="'+item.podName+'" selected>'+item.podName+'</option>'
}else {
var html1 = '<option value="'+item.podName+'">'+item.podName+'</option>'
}
$("#pod_Name").append(html1);
});
//form.render('select', 'pod_Name');
form.render('');
});
$.get('/pod/v1/ContainerList' + location.search, function (resp) {
$.each(resp.data,function(i,item){
if (i == 0 ) {
var html1 = '<option value="'+item.containerName+'" selected>'+item.containerName+'</option>'
}else {
var html1 = '<option value="'+item.containerName+'">'+item.containerName+'</option>'
}
$("#container").append(html1);
});
form.render('');
});
$.ajax({
url: "/pod/v1/Log"+location.search+"&logLine=50",
type: "GET",
success: function (resp) {
$('#logtext').html(resp);
window.scrollTo(0, document.body.scrollHeight);
}
});
//添加
form.on('submit(ReloadLog)', function(datas){
console.log(JSON.stringify(datas.field));
$.ajax({
url: "/pod/v1/Log"+location.search,
type: "POST",
data: JSON.stringify(datas.field),
success: function (resp) {
$('#logtext').html(resp);
window.scrollTo(0, document.body.scrollHeight);
}
});
return false;
});
$('#DownLoadBtn').on("click",function(){
$.ajax({
url: "/pod/v1/Log"+location.search,
type: "GET",
success: function (resp) {
//$('#logtext').html(resp);
var xName = getQueryString("podName");
ExportRaw(xName+'.log',resp);
}
});
return false
});
});
</script>
</body>
</html>
5.3.pod终端html代码
podTerminal.html的代码,放在views\front\page\xkube下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>pod终端</title>
<meta name="renderer" content="webkit">
<!--
<link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="/css/public.css" media="all">
-->
<link rel="stylesheet" href="/css/xterm.css" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<script src="/js/xterm.js"></script>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script> -->
<script src="/js/sockjs.min.js"></script>
<style>
body {
color: #111;
margin: 20px;
}
#terminal-container{
margin: 0 auto;
}
#connect {
margin: 0 auto;
}
#terminal-container a {
color: #fff;
}
.panel-body{
background-color: #000;
}
.xterm-rows {
color: #e9e7e7;
font-size: 14px;
}
</style>
</head>
<body style="border-width: 0px;margin:0px;height:100%;">
<div style="padding: 0px;border: 0px;margin: 0px">
<div id="terminal-container"></div>
</div>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
function getQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
};
return null;
};
$(document).ready(function(){
ws_connect();
});
// 获取宽度和高度
console.log(document.body.clientWidth)
console.log(document.documentElement.clientHeight)
cols=parseInt(document.body.clientWidth /9)
rows=parseInt(document.documentElement.clientHeight / 20)
console.log(rows,cols)
// 定义term对象
var term = new Terminal({
"backgroundColor":'#fff',
"cursorBlink":true,
"rows":rows,
"cols":cols,
});
// 定义ws链接
function ws_connect(){
var socket
// 隐藏连接按钮显示断开按钮
$("#connect_container").hide()
$("#drop_container").show()
h=$("input[name=h]").val()
p=$("input[name=p]").val()
// 获取容器id
containers_id=$("input[name=containers_id]").val()
// 打印容器id
console.log(h,p,containers_id)
if( h == "" || p == "" || containers_id == ""){
alert("不能为空!")
return false
}
// 获取term div
container = document.getElementById('terminal-container');
// 生成参数
//localurl=window.location.href.split('/')[2]
url = '/pod/terminal/ws' + location.search + '&context=&container=&rows='+rows+'&cols='+cols
console.log(url)
// 生成socket对象
socket = new SockJS(url);
$("#terminal-container").html("")
term.open(document.getElementById('terminal-container'));
term.on('data', function (data) {
if (socket.readyState == 1) {
socket.send(data);
}
});
socket.onmessage = function (e) {
term.write(e.data);
};
socket.onclose = function (e) {
term.write("session is close");
$("#connect_container").show()
$("#drop_container").hide()
};
socket.onopen = function () {
resize(socket)
};
window.onresize=function(){
resize(socket)
}
}
function resize(socket) {
cols=parseInt(document.body.clientWidth /9)
rows=parseInt(document.documentElement.clientHeight / 20)
term.resize(cols,rows)
socket.send('{"cols":'+cols+',"rows":'+rows+'}')
}
layui.use(['form', 'table'], function () {
var $ = layui.jquery;
var htmls = getQueryString("deploy");
$('#appNameTitle').html(htmls);
});
</script>
</body>
</html>
六.完整的控制器和模型代码
6.1.controllers控制器完整代码
控制器部分代码pod.go,放controllers下
// pod.go
package controllers
import (
//"encoding/json"
//"fmt"
"log"
//"myk8s/common"
m "myk8s/models"
"strings"
beego "github.com/beego/beego/v2/server/web"
"github.com/tidwall/gjson"
)
type PodController struct {
beego.Controller
}
func (this *PodController) List() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
deployName := this.GetString("deployName")
podName := this.GetString("podName")
nodeName := this.GetString("nodeName")
labels := this.GetString("labels")
labelsKV := strings.Split(labels, ":") //yaml里没有对应的appname,deployment不会传递过来
var labelsKey, labelsValue string
if len(labelsKV) == 2 {
labelsKey = labelsKV[0]
labelsValue = labelsKV[1]
}
podList, err := m.PodList(clusterId, nameSpace, deployName, podName, labelsKey, labelsValue, nodeName)
msg := "success"
code := 0
if err != nil {
log.Println(err)
code = -1
msg = err.Error()
}
count := len(podList)
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &podList}
this.ServeJSON()
}
func (this *PodController) ContainerList() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
podName := this.GetString("podName")
code := 0
msg := "success"
xList, err := m.PodContainerList(clusterId, nameSpace, podName)
if err != nil {
code = -1
msg = err.Error()
log.Printf("[ERROR] ContainerList error:%s\n", err)
}
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": len(xList), "data": &xList}
this.ServeJSON()
}
func (this *PodController) Log() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
podName := this.GetString("podName")
download, _ := this.GetBool("download")
container := this.GetString("container")
logLine, _ := this.GetInt64("logLine")
if this.Ctx.Input.Method() == "POST" {
gp := gjson.ParseBytes(this.Ctx.Input.RequestBody)
podName = gp.Get("podName").String()
logLine = gp.Get("logLine").Int()
container = gp.Get("container").String()
}
if logLine == 0 { //没设置行时,默认为100
logLine = 100
}
if download { //当日志时下载时,不限制行数
logLine = 0
}
log := m.PodLog(clusterId, nameSpace, podName, container, logLine)
this.Ctx.WriteString(log)
}
6.2.models模型完整代码
podModel.go代码放models下
// podModel.go
package models
import (
"bytes"
"context"
"fmt"
"io"
"log"
"myk8s/common"
"os"
//"os"
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
//"k8s.io/apimachinery/pkg/runtime"
//"sigs.k8s.io/yaml"
)
type Podinfo struct {
PodName string `json:"podName"` //podname 和podip
PodIp string `json:"podIp"`
NameSpace string `json:"nameSpace"`
NodeName string `json:"nodeName"` //节点名称和节点IP
HostIp string `json:"hostIp"`
PodPhase string `json:"podPhase"` //容器状态 Running
ImgUrl string `json:"imgUrl"`
//PodStatus PodStatusItem
RestartCount int32 `json:"restartCount"` //重启次数
Labels string `json:"labels"` //标签
ResStatus string `json:"resStatus"` //cpu及内存使用率
CreateTime string `json:"createTime"`
}
type Container struct {
ContainerName string `json:"containerName"`
Envs string `json:"envs"`
Mounts string `json:"mounts"`
ContainerImage string `json:"containerImage"`
PullPolicy string `json:"pullPolicy"`
Ports string `json:"ports"`
ResLimits string `json:"resLimits"`
ResRequests string `json:"resRequests"`
}
func PodList(kubeconfig, namespace, deployName, podName string, labelsKey, labelsValue, nodeName string) ([]Podinfo, error) {
if namespace == "" {
//namespace = corev1.NamespaceDefault
namespace = corev1.NamespaceAll
}
clientset := common.ClientSet(kubeconfig)
var podList *corev1.PodList
var err error
//设置ListOptions
var listOptions = metav1.ListOptions{}
if labelsKey != "" && labelsValue != "" {
listOptions = metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue),
//FieldSelector: "status.phase=Running,spec.nodeName=ais-master1",
}
}
if nodeName != "" {
listOptions = metav1.ListOptions{
FieldSelector: fmt.Sprintf("status.phase!=Succeeded,spec.nodeName=%s", nodeName),
}
}
podList, err = clientset.CoreV1().Pods(namespace).List(context.Background(), listOptions)
if err != nil {
log.Printf("list pods error:%v\n", err)
}
var bbb = make([]Podinfo, 0)
for _, pod := range podList.Items {
//搜索
if podName != "" {
if !strings.Contains(pod.Name, podName) {
//if !strings.HasPrefix(pod.Name,podName)
continue
}
}
if deployName != "" {
if !strings.HasPrefix(pod.Name, deployName) {
continue
}
}
var labelsStr, imgurlStr string
for kk, vv := range pod.ObjectMeta.Labels {
labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
}
if len(labelsStr) > 0 {
labelsStr = labelsStr[0 : len(labelsStr)-1]
}
var containerState = fmt.Sprintf("%v", pod.Status.Phase)
var restartNum int32
if len(pod.Status.ContainerStatuses) > 0 {
imgurlStr = pod.Status.ContainerStatuses[0].Image
restartNum = pod.Status.ContainerStatuses[0].RestartCount
if containerState == "Pending" {
containerState = pod.Status.ContainerStatuses[0].State.Waiting.Reason
}
}
Items := &Podinfo{
PodName: pod.Name,
PodIp: pod.Status.PodIP,
ImgUrl: imgurlStr,
NameSpace: pod.ObjectMeta.Namespace,
NodeName: pod.Spec.NodeName,
HostIp: pod.Status.HostIP,
PodPhase: containerState,
Labels: labelsStr,
RestartCount: restartNum,
CreateTime: pod.ObjectMeta.CreationTimestamp.Format("2006-01-02 15:04:05"),
}
bbb = append(bbb, *Items)
}
return bbb, err
}
func PodContainerList(kubeconfig, nameSpace, podName string) ([]Container, error) {
var ccc = make([]Container, 0)
pod, err := common.ClientSet(kubeconfig).CoreV1().Pods(nameSpace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
return ccc, err
}
for _, v1 := range pod.Spec.Containers {
xItems := &Container{
ContainerName: v1.Name,
Envs: "",
Mounts: "",
ContainerImage: v1.Image,
PullPolicy: fmt.Sprintf("%v", v1.ImagePullPolicy),
Ports: "",
ResLimits: fmt.Sprintf("CPU:%s,Memory:%s", v1.Resources.Limits.Cpu().String(), v1.Resources.Limits.Memory().String()),
ResRequests: fmt.Sprintf("CPU:%s,Memory:%s", v1.Resources.Requests.Cpu().String(), v1.Resources.Requests.Memory().String()),
}
ccc = append(ccc, *xItems)
}
return ccc, nil
}
func PodLog(kubeconfig, nameSpace, podName, container string, logLine int64) string {
clientset := common.ClientSet(kubeconfig)
pod, err := clientset.CoreV1().Pods(nameSpace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get pod %q: %v\n", podName, err)
//os.Exit(1)
}
// 获取指定行数日志
//logs, err := clientset.CoreV1().Pods(nameSpace).GetLogs(podName, &corev1.PodLogOptions{TailLines: &lines})
//获取实时日志
var logOptions = &corev1.PodLogOptions{}
logOptions.Follow = false //持续输出
logOptions.Timestamps = false //显示时间戳
//var line int64 = 50
if logLine > 0 {
//line := logLine
logOptions.TailLines = &logLine //获取多少行日志 需要指针类型https://blog.csdn.net/weixin_36469852/article/details/127432931
}
var containerPt *string = &container
if container == "" {
// [CONTAINER] (container as arg not flag) is supported as legacy behavior. See PR #10519 for more details.
if len(pod.Spec.Containers) != 1 {
podContainersNames := []string{}
for _, container := range pod.Spec.Containers {
podContainersNames = append(podContainersNames, container.Name)
}
} else {
containerPt = &pod.Spec.Containers[0].Name
}
}
logOptions.Container = *containerPt //指定container来获取日志,需要指针类型https://blog.csdn.net/weixin_36469852/article/details/127432931
podLogs, err := clientset.CoreV1().Pods(nameSpace).GetLogs(podName, logOptions).Stream(context.TODO())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get logs for pod %q: %v\n", podName, err)
//os.Exit(1)
return "error"
}
defer podLogs.Close()
//单次输出
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "error copy"
}
return buf.String()
//流式输出日志内容
// buf := make([]byte, 1024)
// for {
// n, err := podLogs.Read(buf)
// if err != nil && err == io.EOF {
// break
// }
// fmt.Print(string(buf[0:n]))
// }
// return "test"
}
七.效果如下图
更多推荐
所有评论(0)