【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
}

七.效果图



相关推荐
蝎子莱莱爱打怪3 天前
Centos7中一键安装K8s集群以及Rancher安装记录
运维·后端·kubernetes
花酒锄作田3 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
阿里云云原生4 天前
Kubernetes 官方再出公告,强调立即迁移 Ingress NGINX
kubernetes
至此流年莫相忘4 天前
Kubernetes实战篇之配置与存储
云原生·容器·kubernetes
qwfys2004 天前
How to install golang 1.26.0 to Ubuntu 24.04
ubuntu·golang·install
至此流年莫相忘4 天前
Kubernetes实战篇之服务发现
容器·kubernetes·服务发现
only_Klein4 天前
Kubernetes 版本升级
容器·kubernetes·upgrade
codeejun4 天前
每日一Go-25、Go语言进阶:深入并发模式1
开发语言·后端·golang
石牌桥网管4 天前
Go 泛型(Generics)
服务器·开发语言·golang
sanyii3131314 天前
k8s核心资源Pod-主容器之存活性探测
云原生·容器·kubernetes