Golang+Vue2 builds K8S background management system from scratch (5) - Container log and continuous output

Table of contents

overview

rear end

front end

achieve effect

Summarize


overview

In the last chapter, we realized the list display of deployment, etc., and when some resources were added, deleted, or modified, the new list would be automatically re-rendered and loaded.

What this chapter wants to achieve is to capture the logs of the Container (optional) in the Pod through go-client, and continuously output to the console.

rear end

The acquisition of logs is also obtained through the API interface.

It's just that the interface defines a long connection and does not return any data

The required parameters are namespace, pod name, container name

Then get rest.request according to go-client

Then get the request stream to get io.Reader

func (this *PodLogsCtl) GetLogs(c *gin.Context) {
	ns := c.DefaultQuery("ns", "default")
	podname := c.DefaultQuery("podname", "")
	cname := c.DefaultQuery("cname", "")
	req := this.Client.CoreV1().Pods(ns).GetLogs(podname, &v1.PodLogOptions{
		Container: cname,
        //follow为true代表的是流式获取,否则只返回单次日志
		Follow:    true,
	})
	//单次获取
	//res, err := req.DoRaw(context.Background())
	//goft.Error(err)
	//return gin.H{
	//	"code": 20000,
	//	"data": string(res),
	//}
	//流式获取
	//gin会给每个请求都起一个协程,不设超时时间就会阻塞在read,导致每刷新一次多一个协程
	cc, _ := context.WithTimeout(context.Background(), time.Minute*5)
    //通过request的流获取到ioReader
	reader,_ := req.Stream(cc)
    //延迟reader的关闭
	defer reader.Close()
	for {
		b := make([]byte, 1024)
		n, err := reader.Read(b)
		if err != nil && err == io.EOF {
			break
		}
		if n > 0 {
			//长连接分段传输
			c.Writer.Write(b[0:n])
			c.Writer.(http.Flusher).Flush()
		}
	}
	return
}

Next, just use byte slices to fetch from the reader, and write this part of the data into the context Writer and push it with Flush, the front end can receive the information

front end

Next look at the front end

Because it is a long connection, so for the front-end request of the front-end and back-end joint debugging part, my approach is to create a dedicated long connection longrequest.js

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 0 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

The difference from the original file request.js is that the timout of axios used for the request is set to 0 (default is 5000, 0 means no timeout)

Therefore, in the Vue file, there is an introduction like this

//这里使用了长连接,无超时时间
import request from '@/utils/longrequest';

I have added an operation column to the Pod list, which has a link to the log page. Click it to jump to the log viewing interface of the corresponding Pod. This interface is hidden in the index navigation bar.

Labels are defined like this:

<router-link :to="{name:'Podlogs',params:{ns:scope.row.NameSpace,name:scope.row.Name}}"> <el-link  >日志<i class="el-icon-view el-icon--right"></i></el-link></router-link>

Turned to the component named Podlogs, and passed the namespace and name in the row data as parameters to the Vue component.

In the created system function of the Podlogs component, you need to pick up the routed parameters

this.Name = this.$route.params.name
this.NameSpace = this.$route.params.ns

According to these parameters, we can request the backend API again to obtain the list of Containers in the corresponding Pod, so that the user can select the container that needs to view the Log on the interface.

This leads to our interface layout, which is very simple. Later, display optimization can be performed by introducing third-party styles.

<template>
  <div>
    <div style="padding-left: 20px;padding-top:30px">
      容器: <el-select  @change="containerChange"  placeholder="选择容器"
                      v-model="selectedContainer">
      <el-option v-for="c in containers "
                 :label="c.Name"
                 :value="c.Name"/>
    </el-select>
    </div>
    <div class="logs">
      {
   
   {logs}}
    </div>
  </div>
</template>
<style>
    .logs{
      overflow: auto;

      margin:10px auto;
      min-height: 200px;
      max-height: 400px;
      border: solid 1px black;
      background-color: #454545;
      padding: 10px;

      color:#27aa5e;
      line-height: 21pt;
      white-space: pre;
      width: 90%
    }
</style>

Among them, when the value in the container selection box changes, the bound function will be called automatically

This function will request us to implement a good long connection on the backend to obtain the log api. The function see below

containerChange(){
  const ns=this.NameSpace
  const podname=this.Name
  const cname=this.selectedContainer
  request({
    url: '/v1/pods/logs?ns=' + ns + '&podname=' +podname + '&cname=' +cname,
    method: 'GET',
    //长连接等待后端传输数据,常用于下载进度条
    onDownloadProgress: e => {
      const dataChunk = e.currentTarget.response;
      this.logs+=dataChunk
    }
  });

}

Here we will request through the request we defined earlier.

It is worth mentioning that the onDownloadProgress method is used here. This method is often used to display the progress bar through a long connection between the front end and the back end. It is also applicable here. You only need to add the obtained log content to data for interpolation when triggered variables of the expression.

achieve effect

Summarize

The pod's log capture can be easily realized through a long connection. In the next chapter, we will use the websocket and xterm library to realize the pod's remote terminal

Guess you like

Origin blog.csdn.net/kingu_crimson/article/details/127503106