【k8s多集群管理平台开发实践】八、client-go实现service读取列表、创建service、读取yaml配置并更新

文章目录

简介

本章节主要讲解通过client-go实现service的列表读取和界面创建service,sevice的yaml配置文件读取和修改,并通过layui实现界面操作,其中包含控制器这部分的代码,模型这部分代码,以及前端的html代码。

一.k8s的service列表

1.1.controllers控制器代码

通过传递集群id、命名空间、服务名称并调用模型代码中serviceList函数来读取列表

golang 复制代码
func (this *SvcController) List() {
	clusterId := this.GetString("clusterId")    //集群名称
	nameSpace := this.GetString("nameSpace")    //命名空间
	serviceName := this.GetString("serviceName")    //服务名称
	labels := this.GetString("labels")  //标签:labels=key:value
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}
	//根据传递的参数进行过滤返回服务列表
	svcList, err := m.SvcList(clusterId, nameSpace, serviceName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(svcList)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &svcList}
	this.ServeJSON()
}
1.2.models模型代码

先定义一个结构体Service,通过传递过来的参数,在svcList.Items中循环读取service的信息,然后赋值结构体Service,并追加到定义一个结构体数组var bbb = make([]Service, 0)

golang 复制代码
type Service struct {
	ServiceName string `json:"serviceName"` //服务名称
	NameSpace   string `json:"nameSpace"`   //命名空间
	Labels      string `json:"labels"`  //标签
	SvcType     string `json:"svcType"` //service的类型
	SvcIp       string `json:"svcIp"`   //服务IP
	SvcPort     string `json:"svcPort"`
	LanEndpoint string `json:"lanEndpoint"` //内部端点
	WanEndpoint string `json:"wanEndpoint"` //外部端点
	CreateTime  string `json:"createTime"`
}

type ServicePort struct {
	PortName   string `json:"portName"`
	SvcPort    int32  `json:"svcPort"`
	TargetPort int32  `json:"targetPort"`
}

func SvcList(kubeconfig, namespace, serviceName string, labelsKey, labelsValue string) ([]Service, error) {
    //获取链接
	clientset := common.ClientSet(kubeconfig)
	if namespace == "" {    //判断namespace是否为空,空则读取全部命名空间
		namespace = corev1.NamespaceAll
	}
    //定义标签过滤条件
	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
	}
    //读取service信息
	svcList, err := clientset.CoreV1().Services(namespace).List(context.TODO(), listOptions)
	if err != nil {
		log.Printf("list service error:%v\n", err)
	}

	//fmt.Println("svc count:", len(svcList.Items))
	//循环items并追加到结构体数组bbb上
	var bbb = make([]Service, 0)
	for _, svc := range svcList.Items {
		//搜索
		if serviceName != "" {
			if !strings.Contains(svc.Name, serviceName) {
				continue
			}
		}
		//提取标签
		var labelsStr string
		for kk, vv := range svc.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		//提取所有映射的端口
		var wanEndPoint string
		svcType := fmt.Sprintf("%s", svc.Spec.Type)
		if svcType == "LoadBalancer" {
			wanEndPoint = fmt.Sprintf("%s:%d", svc.Status.LoadBalancer.Ingress[0].IP, svc.Spec.Ports[0].Port)
		}
		lanEndPoint := ""
		var svcPort string
		if len(svc.Spec.Ports) > 0 {
			vsapp := svc.Spec.Selector["app"]
			if vsapp == "" {
				vsapp = svc.Spec.Selector["k8s-app"]
			}
			for _, vv := range svc.Spec.Ports {
				//lanEndPoint += fmt.Sprintf("%s:%s:%s\n", svc.Spec.Selector["app"], svc.Spec.Ports[0].TargetPort.StrVal, svc.Spec.Ports[0].Protocol)
				//svcPort += fmt.Sprintf("%s:", svc.Spec.Ports[0].Port)
				var vport string
				if vv.TargetPort.Type == 0 {
					vport = fmt.Sprintf("%d", vv.TargetPort.IntVal)
				} else {
					vport = fmt.Sprintf("%s", vv.TargetPort.StrVal)
				}
				lanEndPoint += fmt.Sprintf("%s:%s:%s,", vsapp, vport, vv.Protocol)
				svcPort += fmt.Sprintf("%d,", vv.Port)
			}
			if len(svcPort) > 0 {
				svcPort = svcPort[0 : len(svcPort)-1]
			}
			lanEndPoint = lanEndPoint[0 : len(lanEndPoint)-1]
		}
		//赋值到结构体
		Items := &Service{
			ServiceName: svc.Name,
			NameSpace:   svc.Namespace,
			SvcType:     svcType,
			SvcIp:       svc.Spec.ClusterIP,
			Labels:      labelsStr,
			SvcPort:     svcPort,
			LanEndpoint: lanEndPoint,
			WanEndpoint: wanEndPoint,
			CreateTime:  svc.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		//追加到数组bbb
		bbb = append(bbb, *Items)
	}
	return bbb, err
}

二.创建service

2.1.controllers控制器代码

将前端的form 表单提交的json数据传递到模型函数serviceCreate来进行解析

golang 复制代码
func (this *SvcController) Create() {
	clusterId := this.GetString("clusterId") //集群ID
	code := 0
	msg := "success"
	//log.Println(string(this.Ctx.Input.RequestBody))
	//将传递过来的json 二进制 body直接传递到模型中的函数SvcCreate处理
	err := m.SvcCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] configmap Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}
2.2.models模分代码

将控制器Create函数中传递过来的body数据进行json解析,创建一个service实例结构体svcInstance := &corev1.Service,并将解析json出来的数据赋值到结构体中,再调用clientset.CoreV1().Services(nameSpace).Create来进行创建.

golang 复制代码
func SvcCreate(kubeconfig string, bodys []byte) error {
    //解析json并提取数据
	gp := gjson.ParseBytes(bodys)
	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	serviceName := gp.Get("serviceName").String()   //服务名称
	nameSpace := gp.Get("nameSpace").String()   //命名空间
	svcType := gp.Get("svcType").String()   //服务类型
	deployName := gp.Get("deployName").String() //关联的负载名称
	isHeadless := gp.Get("isHeadless").Str  //是否是无头服务

	var labelsMap = make(map[string]string) //提取标签
	labelsMap["app"] = serviceName
	for _, vv := range gp.Get("lables").Array() {
		labelsMap[vv.Get("key").Str] = vv.Get("value").Str
	}

	selectApp := serviceName    //默认将service的名称和deployment的名称一致,deployName不为空时,按照deployName的名称创建
	if deployName != "" {
		selectApp = deployName
	}

	var serviceType corev1.ServiceType  //赋值服务类型
	switch svcType {
	case "NodePort":
		serviceType = corev1.ServiceTypeNodePort
	case "LoadBalancer":
		serviceType = corev1.ServiceTypeLoadBalancer
	default:
		serviceType = corev1.ServiceTypeClusterIP
	}

	if isHeadless == "on" { //判断是否是无头服务
		serviceType = corev1.ServiceTypeClusterIP
	}

    //将json解析的数据赋值到service实例结构体上
	svcInstance := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      serviceName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: corev1.ServiceSpec{
			Selector: map[string]string{
				"app": selectApp,
			},
			Type: serviceType,
		},
	}
	if isHeadless == "on" {
		svcInstance.Spec.ClusterIP = corev1.ClusterIPNone
	}

	ports := gp.Get("ports").Array()
	var svcPorts = make([]corev1.ServicePort, 0)
	for _, vv := range ports {
		var svcProtocol corev1.Protocol
		if vv.Get("protocol").Str == "UDP" {
			svcProtocol = corev1.ProtocolUDP
		} else {
			svcProtocol = corev1.ProtocolTCP
		}
		svcPort := &corev1.ServicePort{
			Name:       vv.Get("portName").Str,
			Port:       int32(vv.Get("svcPort").Int()),
			Protocol:   svcProtocol,
			TargetPort: intstr.FromInt32(int32(vv.Get("targetPort").Int())),
		}
		svcPorts = append(svcPorts, *svcPort)
	}
	svcInstance.Spec.Ports = svcPorts
    
    //根据传递的集群ID进行创建service
	clientset := common.ClientSet(kubeconfig)
	_, err := clientset.CoreV1().Services(nameSpace).Create(context.TODO(), svcInstance, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

三.读取和更新service的yaml配置

3.1.controllers控制器代码

通过传递集群id、命名空间、服务名称来进行读取service,并调用模型代码中GetSvcYaml函数来读取yaml配置

golang 复制代码
//读取yaml配置
func (this *SvcController) Yaml() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	serviceName := this.GetString("serviceName")
	yamlStr, _ := m.GetSvcYaml(clusterId, nameSpace, serviceName)
	this.Ctx.WriteString(yamlStr)
}
//更新yaml配置
func (this *SvcController) ModifyByYaml() {
	clusterId := this.GetString("clusterId")
	//nameSpace := gp.Get("nameSpace").String()
	//log.Println(string(this.Ctx.Input.RequestBody))
	//提取body并替换掉%,否则解析出错
	bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
	err := m.SvcYamlModify(clusterId, bodyByte)
	if err != nil {
		log.Println(err)
	}
	this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
	this.ServeJSON()
}
3.2.models模型代码

先读取service实例信息,然后将实例信息解析成解构,然后解构的字节数据进行yaml序列化处理,并转成字符串进行返回

golang 复制代码
//读取yaml配置
func GetSvcYaml(kubeconfig, namespace, serviceName string) (string, error) {
    //先通过传递的信息读取service实例
	servicesClient := common.ClientSet(kubeconfig).CoreV1().Services(namespace)
	service, err := servicesClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
	//将读取的service进行解构
	serviceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(service)
	if err != nil {
		return "", err
	}
	//将结构的service数据进行序列号成yaml的bytes
	yamlBytes, err := yaml.Marshal(serviceUnstructured)
	if err != nil {
		return "", err
	}
	//以字符串的形式返回
	return string(yamlBytes), nil
}

//更新yaml配置
func SvcYamlModify(kubeconfig string, yamlData []byte) error {
    //转成json格式
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	//反序列化到service结构体实例
	service := &corev1.Service{}
	err = json.Unmarshal(data, service)
	if err != nil {
		return err
	}
    
    //提取出名称和命名空间
	namespace := service.ObjectMeta.Namespace
	serviceName := service.ObjectMeta.Name
	
	//调用更新api进行更新
	clientset := common.ClientSet(kubeconfig)
	_, err = clientset.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	fmt.Println(namespace, serviceName)
	return err
}

四.路由设置

4.1.路由设置

将以下代码放到routers/route.go中

golang 复制代码
	beego.Router("/svc/v1/List", &controllers.SvcController{}, "*:List")    //读取service列表
	beego.Router("/svc/v1/Create", &controllers.SvcController{}, "*:Create") //创建service
	beego.Router("/svc/v1/ModifyByYaml", &controllers.SvcController{}, "*:ModifyByYaml") //更新yaml配置
	beego.Router("/svc/v1/Yaml", &controllers.SvcController{}, "*:Yaml") //读取yaml配置

五.前端代码

5.1.列表部分html代码

5.1 serviceList.html,放到views/front/page/xkube下

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>service列表</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: 22px !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">
        <script type="text/html" id="toolbarDemo">
            <div class="layui-btn-container">
                <button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="create"><i class="layui-icon">&#xe61f;</i>创建service</button>
            </div>
        </script>

        <table class="layui-table" id="currentTableId" lay-filter="currentTableFilter"></table>

        <script type="text/html" id="currentTableBar">
            <a class="layui-btn layui-btn-normal layui-btn-sm" lay-event="viewYaml">yaml编辑</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="ipPortTpl">
        <span>{{ d.svcIp }}:{{ d.svcPort }}</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();

        table.render({
            elem: '#currentTableId',
            url: '/svc/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: 'serviceName',title: '名称', sort: true},
                {field: 'nameSpace',title: '命名空间', sort: true},
                {field: 'svcType', title: '类型', sort: true},
                {field: 'svcIp', title: 'IP', sort: true,hide:true},
                {field: 'svcPort', title: '端口', sort: true,hide:true},
                {field: '', width:150,title: 'ip端口', sort: true,templet: '#ipPortTpl'},
                {field: 'lanEndpoint',title: '内部端点', sort: true},
                {field: 'wanEndpoint', title: '外部端点', sort: true,hide:true},
                {field: 'labels', title: '标签', sort: true,templet: '#TagTpl'},
                {field: 'createTime', title: '创建时间'},
                {title: '操作', minWidth: 250, toolbar: '#currentTableBar', align: "center"}
            ]],
            //size:'lg',
            limits: [25, 50, 100],
            limit: 25,
            page: true
        });

        /**
         * toolbar监听事件
         */
        table.on('toolbar(currentTableFilter)', function (obj) {
            if (obj.event === 'create') {  // 监听添加操作
                var index = layer.open({
                    title: '创建',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['60%', '90%'],
                    content: '/page/xkube/serviceCreate.html?v=1',
                    //end: function(){
                    //	window.parent.location.reload();//关闭open打开的页面时,刷新父页面
                    //}
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
            } 
        });

        table.on('tool(currentTableFilter)', function (obj) {
            var data = obj.data;
            if (obj.event === 'viewYaml') {
                var index = layer.open({
                    title: 'yaml',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['45%', '92%'],
                    content: '/page/xkube/serviceYaml.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&serviceName='+data.serviceName,
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
                return false;
            }
        });


    });
</script>

</body>
</html>
5.2.创建表单html代码

5.2 serviceCreate.html,放到views/front/page/xkube下

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>创建</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>
    <style>
        body {
            background-color: #ffffff;
        }
    </style>
</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">
                    <label class="layui-form-label">当前集群</label>
                    <div class="layui-input-inline">
                        <select name="clusterId" lay-filter="cluster_Id" lay-search="" id="cluster_Id">
                          <option value="">请选择集群</option>
                        </select>
                    </div>
                    <label class="layui-form-label">命名空间</label>
                    <div class="layui-input-inline">
                      <select name="nameSpace" lay-filter="name_Space" lay-search="" id="name_Space">
      		            </select>
                    </div>
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label required">svc名称</label>
                <div class="layui-input-inline">
                    <input type="text" name="serviceName" placeholder="不能为空" value="" class="layui-input">
                </div>
                <label class="layui-form-label">关联</label>
                <div class="layui-input-inline">
                    <input type="text" name="deployName" placeholder="关联的deploy或sts" value="" class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">svc类型</label>
                <div class="layui-input-inline">
                  <select name="svcType" lay-filter="svcType" lay-search="" id="svcType">
              			<option value="ClusterIp" selected="">ClusterIp</option>
              			<option value="NodePort">NodePort</option>
              			<option value="LoadBalancer">LoadBalancer</option>
  		            </select>
                </div>
                <div id="isHeadlessTpl">
                  <div class="layui-input-inline">
                    <input type="checkbox" name="isHeadless" lay-skin="primary" lay-filter="isHeadless" title="实例间服务发现(Headless Service)">   
                  </div>             
                </div>
            </div>

            <div class="serviceTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">svc端口</label>
                  <div class="layui-input-inline" style="width:120px">  
                      <input type="text" id="svc_portsName0" name="svc_portsName[]" placeholder="名称" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:120px">  
                      <input type="text" id="svc_port0" name="svc_port[]" placeholder="服务端口" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:120px">  
                      <input type="text" id="svc_targetPort0" name="svc_targetPort[]" placeholder="容器端口" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:70px">  
                    <select id="svc_protocol0" name="svc_protocol[]">
                			<option value="TCP" selected="">TCP</option>
                			<option value="UDP">UDP</option>
    		            </select>
                  </div>
                  <div class="layui-input-inline" style="width:80px">  
                      <button class="layui-btn layui-btn-normal" id="svcaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>
            <fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
                <legend>标签与注解</legend>
            </fieldset>
            <div class="labelsTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">标签</label>
                  <div class="layui-input-inline" style="width:150px">  
                      <input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:150px">  
                      <input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:80px">  
                      <button class="layui-btn layui-btn-normal" id="newaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>

            <!--
            <div class="annotationTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">注解</label>
                  <div class="layui-input-inline" style="width:240px">  
                      <input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:240px">  
                      <input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline">  
                      <button class="layui-btn layui-btn-normal" id="annotateaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>
            -->

  <br>
            <div class="layui-form-item">
                <div class="layui-input-block">
                    <button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
                </div>
            </div>
        </form>
    </div>
</div>
</body>
<script>
    //标签删除
    var TplIndex = 0;
    function delTpl(id) {
      TplIndex--;
      $("#tpl-"+id).remove();
    }

    //服务删除
    var sTplIndex = 0;
    function delsTpl(id) {
      sTplIndex--;
      $("#stpl-"+id).remove();
    }

    layui.use(['form'], function () {
        var form = layui.form,
            layer = layui.layer,
            $ = layui.$;
            
        $("#isHeadlessTpl").show();
        form.on('select(svcType)', function (data) { 
          if (data.value == "ClusterIp") {
            $("#isHeadlessTpl").show();
          }else{
            $("#isHeadlessTpl").hide();
          }
        });

        //labes add
        $('#newaddbtn').on("click",function(){
          TplIndex++;
          var addTpl =
              '<div class="layui-form-item" id="tpl-'+TplIndex+'">' +
                  '<label class="layui-form-label">标签</label>' +
                  '<div class="layui-input-inline" style="width:150px">' +
        	              '<input type="text" name="labels_key[]" id="labels_key'+TplIndex+'" placeholder="key" value="" class="layui-input">' +
                  '</div>' +    
                  '<div class="layui-input-inline" style="width:150px">' +  
                      '<input type="text" name="labels_value[]" id="labels_value'+TplIndex+'" placeholder="value" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:80px">' +  
                      '<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:60px" type="button" id="newDelbtn" value="&#xe616;" οnclick="delTpl('+TplIndex+');" />' +
                  '</div>' +                
              '</div>'
            $('.labelsTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //service增加
        $('#svcaddbtn').on("click",function(){
          sTplIndex++;
          var addTpl =
              '<div class="layui-form-item" id="stpl-'+sTplIndex+'">' +
                  '<label class="layui-form-label">svc端口</label>' +
                  '<div class="layui-input-inline" style="width:120px">' +
        	              '<input type="text" name="svc_portsName[]" id="svc_portsName'+sTplIndex+'" placeholder="名称" value="" class="layui-input">' +
                  '</div>' +    
                  '<div class="layui-input-inline" style="width:120px">' +  
                      '<input type="text" name="svc_port[]" id="svc_port'+sTplIndex+'" placeholder="服务端口" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:120px">' +  
                      '<input type="text" name="svc_targetPort[]" id="svc_targetPort'+sTplIndex+'" placeholder="容器端口" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:70px">' +  
                    '<select name="svc_protocol[]" id="svc_protocol'+sTplIndex+'" >' +
                			'<option value="TCP" selected="">TCP</option>' +
                			'<option value="UDP">UDP</option>' +
    		            '</select>' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:80px">' +  
                      '<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:60px" type="button" value="&#xe616;" οnclick="delsTpl('+sTplIndex+');" />' +
                  '</div>' +                
              '</div>'
            $('.serviceTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //监听提交
        form.on('submit(saveBtn)', function (data) {
                data.field.serviceName = data.field.serviceName.replace(/^\s*|\s*$/g,""); //替换空格

                if (data.field.svcType != "ClusterIp") {
                  delete data.field.isHeadless;
                }         
                
                //lables 处理
                var labelsArry = [];
                for (var i=0;i<=TplIndex;i++) {
                  //delete data.field.lables_key[i];                  
                  //delete data.field.labels_value[i];
                  var kk = document.getElementById("labels_key"+i).value;
                  var vv = document.getElementById("labels_value"+i).value; 
                  if ( kk != "" && vv != "") {
                    labelsArry.push(
                      {
                        key:kk,
                        value:vv,
                      }
                    )
                  }
                }
                if (labelsArry.length > 0) {
                  data.field.lables = labelsArry;
                }

                //service 处理
                var svcArry = [];
                for (var i=0;i<=sTplIndex;i++) {
                  var kk = document.getElementById("svc_portsName"+i).value;
                  var vv = document.getElementById("svc_port"+i).value; 
                  var ss = document.getElementById("svc_targetPort"+i).value; 
                  var tt = document.getElementById("svc_protocol"+i).value;
                  if ( kk != "" && vv != "" && ss != "") {
                    svcArry.push(
                      {
                        portName:kk,
                        svcPort:vv,
                        targetPort:ss,
                        protocol:tt,
                      }
                    )
                  }
                }
                if (svcArry.length > 0) {
                  data.field.ports = svcArry;
                }

					      console.log(data.field);
			          layer.confirm('确定添加?', {icon: 3, title:'提示',yes: function(index){
                     var index2 = layer.load(0, {shade: false});
                     layer.msg('稍等片刻');
                     $.ajax({
                       url: "/svc/v1/Create",
                       type: "post",
                       data: JSON.stringify(data.field),
                       dataType: "json",
                       success: function (resp) {
                                layer.close(index2);
                                 if(resp.code == 0){
                                    layer.msg('添加成功', {icon: 1});
									                  //window.location.reload();
                                 }else{
                                    layer.msg(resp.msg,{icon:2});
                                 }
                        }
                      });		  	  
                  },
                  cancel: function(index, layero){ 
                    layer.close(index);
                    layer.close(index2);
		                console.log("不操作");
                  } 
                });
              return false;
        });

    });
</script>
</body>
</html>
5.3.显示yaml配置的html代码

5.3 serviceYaml.html,放到views/front/page/xkube下

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>yaml编辑</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">
		<link
			rel="stylesheet"
			data-name="vs/editor/editor.main"
			href="/monaco-editor/min/vs/editor/editor.main.css"
		/>
    <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">
        <div id="container" style="width: 100%; height:460px; border: 1px solid grey"></div>
        <br>
        <fieldset class="table-search-fieldset">
            <legend></legend>
            <button class="layui-btn layui-btn-sm" id="SaveBtn" >保存更新</button>
            <button class="layui-btn layui-btn-normal layui-btn-sm" id="DownLoadBtn">下载</button>
        </fieldset>   
    </div>
</div>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
	var require = { paths: { vs: '/monaco-editor/min/vs' } };
</script>
<script src="/monaco-editor/min/vs/loader.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.js"></script>
<script>
    layui.use(['form', 'table','code'], function () {
        var $ = layui.jquery;
        var form = layui.form,
            layer = layui.layer;
        
    		var editor = monaco.editor.create(document.getElementById('container'), {
    			value: '',
    			language: 'yaml',
    			//automaticLayout: true,
    			minimap: {enabled: false},
    			wordWrap: 'on',
    			theme: 'vs-dark'
    		});

    		$.ajax({
    		    url: "/svc/v1/Yaml"+location.search,
    		    type: "GET",
    		    success: function (resp) {
    			  //codeyaml = resp;
    			  editor.setValue(resp);
    			}
    		});	

        $('#SaveBtn').on("click",function(){
              var yamlBody = editor.getValue();
              yamlBody = yamlBody.replace(/%/g,"%25");
              layer.confirm('确定修改?', {icon: 3, title:'提示',yes: function(index){
                  $.ajax({
                   url: "/svc/v1/ModifyByYaml"+location.search,
                   type: "POST",
                   data: yamlBody,
                   dataType: "json",
                   success: function (resp) {
                      layer.msg(resp.msg);
                      console.log(resp);
                    }
                  });
                  //console.log(editor.getValue());
                  //layer.msg('ok');
              },
              cancel: function(index, layero){ 
                  layer.close(index);
              } 
            });
        });

        $('#DownLoadBtn').on("click",function(){
              var serviceName = getQueryString("serviceName");
              ExportRaw(serviceName+'.yaml',editor.getValue());
        });
    });
</script>

</body>
</html>

六.完整代码

6.1.控制器service.go的完整代码

6.1 service.go,放到contrillers下

golang 复制代码
// service.go
package controllers

import (
	//"encoding/json"
	//"fmt"
	"log"
	m "myk8s/models"
	"strings"

	beego "github.com/beego/beego/v2/server/web"
)

type SvcController struct {
	beego.Controller
}

func (this *SvcController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	serviceName := this.GetString("serviceName")
	labels := this.GetString("labels")
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}
	svcList, err := m.SvcList(clusterId, nameSpace, serviceName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(svcList)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}

	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &svcList}
	this.ServeJSON()
}

func (this *SvcController) Create() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	//log.Println(string(this.Ctx.Input.RequestBody))
	err := m.SvcCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] configmap Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

func (this *SvcController) ModifyByYaml() {
	clusterId := this.GetString("clusterId")
	//nameSpace := gp.Get("nameSpace").String()
	log.Println(string(this.Ctx.Input.RequestBody))
	bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
	err := m.SvcYamlModify(clusterId, bodyByte)
	if err != nil {
		log.Println(err)
	}
	this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
	this.ServeJSON()
}

func (this *SvcController) Yaml() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	serviceName := this.GetString("serviceName")

	yamlStr, _ := m.GetSvcYaml(clusterId, nameSpace, serviceName)
	this.Ctx.WriteString(yamlStr)
}
6.2.模型serviceModel.go的完整代码

6.2 serviceModel.go,放到models下

golang 复制代码
// serviceModel.go
package models

import (
	"context"
	"fmt"
	"log"
	"myk8s/common"
	"strings"

	"encoding/json"

	"github.com/tidwall/gjson"
	//"k8s.io/apimachinery/pkg/api/errors"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	yamlutil "k8s.io/apimachinery/pkg/util/yaml"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"

	"sigs.k8s.io/yaml"
)

type Service struct {
	ServiceName string `json:"serviceName"` //服务名称
	NameSpace   string `json:"nameSpace"`
	Labels      string `json:"labels"`
	SvcType     string `json:"svcType"`
	SvcIp       string `json:"svcIp"`
	SvcPort     string `json:"svcPort"`
	LanEndpoint string `json:"lanEndpoint"` //内部端点
	WanEndpoint string `json:"wanEndpoint"` //外部端点
	CreateTime  string `json:"createTime"`
}

type ServicePort struct {
	PortName   string `json:"portName"`
	SvcPort    int32  `json:"svcPort"`
	TargetPort int32  `json:"targetPort"`
}

func SvcList(kubeconfig, namespace, serviceName string, labelsKey, labelsValue string) ([]Service, error) {
	clientset := common.ClientSet(kubeconfig)
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
	//var selector labels.Selector

	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
	}

	svcList, err := clientset.CoreV1().Services(namespace).List(context.TODO(), listOptions)
	if err != nil {
		log.Printf("list service error:%v\n", err)
	}

	//fmt.Println("svc count:", len(svcList.Items))
	var bbb = make([]Service, 0)
	for _, svc := range svcList.Items {
		//搜索
		if serviceName != "" {
			if !strings.Contains(svc.Name, serviceName) {
				continue
			}
		}
		//fmt.Printf("name: %s\n", svc.Name)
		var labelsStr string
		for kk, vv := range svc.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		var wanEndPoint string
		svcType := fmt.Sprintf("%s", svc.Spec.Type)
		if svcType == "LoadBalancer" {
			wanEndPoint = fmt.Sprintf("%s:%d", svc.Status.LoadBalancer.Ingress[0].IP, svc.Spec.Ports[0].Port)
		}
		lanEndPoint := ""
		var svcPort string
		if len(svc.Spec.Ports) > 0 {
			vsapp := svc.Spec.Selector["app"]
			if vsapp == "" {
				vsapp = svc.Spec.Selector["k8s-app"]
			}
			for _, vv := range svc.Spec.Ports {
				//lanEndPoint += fmt.Sprintf("%s:%s:%s\n", svc.Spec.Selector["app"], svc.Spec.Ports[0].TargetPort.StrVal, svc.Spec.Ports[0].Protocol)
				//svcPort += fmt.Sprintf("%s:", svc.Spec.Ports[0].Port)
				var vport string
				if vv.TargetPort.Type == 0 {
					vport = fmt.Sprintf("%d", vv.TargetPort.IntVal)
				} else {
					vport = fmt.Sprintf("%s", vv.TargetPort.StrVal)
				}
				lanEndPoint += fmt.Sprintf("%s:%s:%s,", vsapp, vport, vv.Protocol)
				svcPort += fmt.Sprintf("%d,", vv.Port)
			}
			if len(svcPort) > 0 {
				svcPort = svcPort[0 : len(svcPort)-1]
			}
			lanEndPoint = lanEndPoint[0 : len(lanEndPoint)-1]
		}
		Items := &Service{
			ServiceName: svc.Name,
			NameSpace:   svc.Namespace,
			SvcType:     svcType,
			SvcIp:       svc.Spec.ClusterIP,
			Labels:      labelsStr,
			SvcPort:     svcPort,
			LanEndpoint: lanEndPoint,
			WanEndpoint: wanEndPoint,
			CreateTime:  svc.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		bbb = append(bbb, *Items)
	}
	return bbb, err
}

func SvcCreate(kubeconfig string, bodys []byte) error {
	gp := gjson.ParseBytes(bodys)
	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	serviceName := gp.Get("serviceName").String()
	nameSpace := gp.Get("nameSpace").String()
	svcType := gp.Get("svcType").String()
	deployName := gp.Get("deployName").String()
	isHeadless := gp.Get("isHeadless").Str

	var labelsMap = make(map[string]string)
	labelsMap["app"] = serviceName
	for _, vv := range gp.Get("lables").Array() {
		labelsMap[vv.Get("key").Str] = vv.Get("value").Str
	}

	selectApp := serviceName
	if deployName != "" {
		selectApp = deployName
	}

	var serviceType corev1.ServiceType
	switch svcType {
	case "NodePort":
		serviceType = corev1.ServiceTypeNodePort
	case "LoadBalancer":
		serviceType = corev1.ServiceTypeLoadBalancer
	default:
		serviceType = corev1.ServiceTypeClusterIP
	}

	if isHeadless == "on" {
		serviceType = corev1.ServiceTypeClusterIP
	}

	svcInstance := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      serviceName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: corev1.ServiceSpec{
			Selector: map[string]string{
				"app": selectApp,
			},
			Type: serviceType,
		},
	}
	if isHeadless == "on" {
		svcInstance.Spec.ClusterIP = corev1.ClusterIPNone
	}

	ports := gp.Get("ports").Array()
	var svcPorts = make([]corev1.ServicePort, 0)
	for _, vv := range ports {
		var svcProtocol corev1.Protocol
		if vv.Get("protocol").Str == "UDP" {
			svcProtocol = corev1.ProtocolUDP
		} else {
			svcProtocol = corev1.ProtocolTCP
		}
		svcPort := &corev1.ServicePort{
			Name:       vv.Get("portName").Str,
			Port:       int32(vv.Get("svcPort").Int()),
			Protocol:   svcProtocol,
			TargetPort: intstr.FromInt32(int32(vv.Get("targetPort").Int())),
		}
		svcPorts = append(svcPorts, *svcPort)
	}
	svcInstance.Spec.Ports = svcPorts

	clientset := common.ClientSet(kubeconfig)
	_, err := clientset.CoreV1().Services(nameSpace).Create(context.TODO(), svcInstance, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

func SvcYamlModify(kubeconfig string, yamlData []byte) error {
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	service := &corev1.Service{}
	err = json.Unmarshal(data, service)
	if err != nil {
		return err
	}

	namespace := service.ObjectMeta.Namespace
	serviceName := service.ObjectMeta.Name
	clientset := common.ClientSet(kubeconfig)
	_, err = clientset.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	fmt.Println(namespace, serviceName)
	return err
}

func GetSvcYaml(kubeconfig, namespace, serviceName string) (string, error) {
	servicesClient := common.ClientSet(kubeconfig).CoreV1().Services(namespace)
	service, err := servicesClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
	serviceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(service)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(serviceUnstructured)
	if err != nil {
		return "", err
	}
	return string(yamlBytes), nil
}

七.效果图



相关推荐
ifanatic1 小时前
[面试]-golang基础面试题总结
面试·职场和发展·golang
懒是一种态度1 小时前
Golang 调用 mongodb 的函数
数据库·mongodb·golang
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
入 梦皆星河2 小时前
在 Ubuntu/Debian 上安装 Go
ubuntu·golang·debian
凡人的AI工具箱2 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
Python私教2 小时前
ubuntu搭建k8s环境详细教程
linux·ubuntu·kubernetes
O&REO3 小时前
单机部署kubernetes环境下Overleaf-基于MicroK8s的Overleaf应用部署指南
云原生·容器·kubernetes
politeboy4 小时前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
运维小文4 小时前
K8S资源限制之LimitRange
云原生·容器·kubernetes·k8s资源限制
登云时刻4 小时前
Kubernetes集群外连接redis集群和使用redis-shake工具迁移数据(二)
redis·容器·kubernetes