简介

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

一.k8s的ingress列表

1.1.controllers控制器代码

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

func (this *IngressController) List() {
	clusterId := this.GetString("clusterId")    //集群名称
	nameSpace := this.GetString("nameSpace")    //命名空间
	ingressName := this.GetString("ingressName")    //ingress名称
	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]
	}
	//调用IngList函数
	ingressList, err := m.IngList(clusterId, nameSpace, ingressName, serviceName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(ingressList)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}

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

1.2.models模型代码

先定义一个结构体ingress,通过传递过来的参数,在clientset.NetworkingV1().Ingresses(namespace).List(context.TODO(), listOptions)中读取ingress的信息,然后定义一个结构体数组,并循环读取ingress.Items中的数据并赋值到结构体Ingress,然后再追加到数组中var bbb = make([]Ingress, 0)

type Ingress struct {
	IngressName  string `json:"ingressName"`
	NameSpace    string `json:"nameSpace"`
	Labels       string `json:"labels"`
	IngressClass string `json:"ingressClass"`
	Rules        string `json:"rules"`
	Endpoint     string `json:"endpoint"` //内部端点
	CreateTime   string `json:"createTime"`
}

type KV struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

func IngList(kubeconfig, namespace, ingressName, serviceName string, labelsKey, labelsValue string) ([]Ingress, error) {
	clientset := common.ClientSet(kubeconfig)
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
    //定义标签过滤
	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
	}
    //通过api读取ingress信息
	ingList, err := clientset.NetworkingV1().Ingresses(namespace).List(context.TODO(), listOptions)
	if err != nil {
		log.Printf("list service error:%v\n", err)
	}

	var bbb = make([]Ingress, 0)
	for _, ing := range ingList.Items {
		//搜索
		if ingressName != "" {
			if !strings.Contains(ing.Name, ingressName) {
				continue
			}
		}
        //提取标签
		var labelsStr string
		for kk, vv := range ing.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		//读取ingress中的path规则
		var rulestr string
		var isNeedService bool
		for _, v1 := range ing.Spec.Rules {
			for _, v2 := range v1.HTTP.Paths {
				var portstr string
				if v2.Backend.Service.Port.Number > 0 {
					portstr = fmt.Sprintf(":%d", v2.Backend.Service.Port.Number)
				}
				rulestr += fmt.Sprintf("%s%s-->%s%s,", v1.Host, v2.Path, v2.Backend.Service.Name, portstr)

				//用serviceName来搜索
				if serviceName != "" && v2.Backend.Service.Name == serviceName {
					isNeedService = true
				}
			}
		}
		//用serviceName来搜索
		if serviceName != "" && !isNeedService {
			continue
		}

		var endpointIp string
		if len(ing.Status.LoadBalancer.Ingress) > 0 {
			endpointIp = ing.Status.LoadBalancer.Ingress[0].IP
		}
        //赋值到结构体
		Items := &Ingress{
			IngressName:  ing.Name,
			NameSpace:    ing.Namespace,
			IngressClass: ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"],
			Rules:        rulestr,
			Endpoint:     endpointIp,
			Labels:       labelsStr,
			CreateTime:   ing.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		bbb = append(bbb, *Items)
	}
	return bbb, err
}

二.创建ingress

2.1.controllers控制器代码

将前端的form 表单提交的数据this.Ctx.Input.RequestBody传递到模型函数ingressCreate来进行创建

func (this *IngressController) Create() {
	clusterId := this.GetString("clusterId")
	msg := "success"
	code := 0
	err := m.IngCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

2.2.models模分代码

将控制器Create函数中传递过来的body数据进行json解析,并将解析出来的数据赋值到结构体中,再调用clientset.NetworkingV1().Ingresses(nameSpace).Create(context.TODO(), newIngress, metav1.CreateOptions{})来进行创建.

func IngCreate(kubeconfig string, bodys []byte) error {
    //解析json
	gp := gjson.ParseBytes(bodys)
	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	ingressName := gp.Get("ingressName").String()
	nameSpace := gp.Get("nameSpace").String()
	ingressHost := gp.Get("ingressHost").String()

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

	var annotationsMap = make(map[string]string)
	annotationsMap["kubernetes.io/ingress.class"] = "nginx"
    //提取path规则并赋值
	var ingresPaths []networkingv1.HTTPIngressPath
	paths := gp.Get("paths").Array()
	for _, vv := range paths {
		var pathType networkingv1.PathType
		switch vv.Get("pathType").Str {
		case "ImplementationSpecific":
			pathType = networkingv1.PathTypeImplementationSpecific
		case "Exact":
			pathType = networkingv1.PathTypeExact
		default:
			pathType = networkingv1.PathTypePrefix
		}

		ingresPaths = append(ingresPaths, *&networkingv1.HTTPIngressPath{
			Path:     vv.Get("path").Str,
			PathType: &pathType,
			Backend: networkingv1.IngressBackend{
				Service: &networkingv1.IngressServiceBackend{
					Name: vv.Get("serviceName").Str,
					Port: networkingv1.ServiceBackendPort{
						Number: int32(vv.Get("servicePort").Int()),
					},
				},
			},
		})
	}
    //定义的ingress实例结构体
	newIngress := &networkingv1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:        ingressName,
			Namespace:   nameSpace,
			Labels:      labelsMap,
			Annotations: annotationsMap,
		},
		Spec: networkingv1.IngressSpec{
			Rules: []networkingv1.IngressRule{
				{
					Host: ingressHost,
					IngressRuleValue: networkingv1.IngressRuleValue{
						HTTP: &networkingv1.HTTPIngressRuleValue{
							Paths: ingresPaths,
						},
					},
				},
			},
		},
	}

	tlsCert := gp.Get("tlsCert").String()
	if tlsCert != "" {
		var ingTls = []networkingv1.IngressTLS{
			{
				Hosts:      []string{ingressHost},
				SecretName: tlsCert,
			},
		}
		newIngress.Spec.TLS = ingTls
	}

	// 创建ingress
	clientset := common.ClientSet(kubeconfig)
	_, err := clientset.NetworkingV1().Ingresses(nameSpace).Create(context.TODO(), newIngress, metav1.CreateOptions{})
	return err
}

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

3.1.controllers控制器代码

通过传递集群id、命名空间、ingress名称来读取实例信息,并调用模型代码中ingressYmal函数来读取yaml配置

//读取yaml配置
func (this *IngressController) Yaml() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	ingressName := this.GetString("ingressName")

	yamlStr, _ := m.GetIngYaml(clusterId, namespace, ingressName)
	this.Ctx.WriteString(yamlStr)
}

//更新yaml配置
func (this *IngressController) 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.IngYamlModify(clusterId, bodyByte)
	if err != nil {
		log.Println(err)
	}
	this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
	this.ServeJSON()
}

3.2.models模型代码

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

func GetIngYaml(kubeconfig, namespace, ingressName string) (string, error) {
	ingClient := common.ClientSet(kubeconfig).NetworkingV1().Ingresses(namespace)
	ingress, err := ingClient.Get(context.TODO(), ingressName, metav1.GetOptions{})
	ingresseUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ingress)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(ingresseUnstructured)
	if err != nil {
		return "", err
	}
	return string(yamlBytes), nil
}

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

四.路由设置

4.1.路由设置

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

	beego.Router("/ing/v1/List", &controllers.IngressController{}, "*:List")
	beego.Router("/ing/v1/Create", &controllers.IngressController{}, "*:Create")
	beego.Router("/ing/v1/ModifyByYaml", &controllers.IngressController{}, "*:ModifyByYaml")
	beego.Router("/ing/v1/Yaml", &controllers.IngressController{}, "*:Yaml")

五.前端代码

5.1.列表部分html代码

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ingress列表</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: visible;
    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>创建Ingress</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="rulesTpl">
    {{# if (d.rules != "") { }}
            {{# layui.each(d.rules.split(','), function(index, item){ }}
                {{# if(index == 0) { }}
                        <span>{{ item }}</span>
                {{# }else{ }}
                        <br><span>{{ item }}</span>
                {{# } }}  
            {{# });  }}
    {{# }else{  }}
            <span></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: '/ing/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: 'ingressName',title: '名称', sort: true},
                {field: 'nameSpace',title: '命名空间', sort: true},
                {field: 'ingressClass', title: '类型', sort: true},
                {field: 'rules', width: 400,title: '规则', sort: true,templet: '#rulesTpl'},
                {field: 'endpoint', title: '端点', sort: true},
                {field: 'labels', title: '标签', sort: true,templet: '#TagTpl',hide:true},
                {field: 'createTime', title: '创建时间',hide:true},
                {title: '操作', minWidth: 300,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/ingressCreate.html',
                    //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/ingressYaml.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&ingressName='+data.ingressName,
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
                return false;
            }
        });

    });
</script>
</html>
5.2.创建表单html代码

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

<!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>
                </div>
            </div>

            <div class="layui-form-item">
                <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>
                <label class="layui-form-label required">ingress名称</label>
                <div class="layui-input-inline">
                    <input type="text" name="ingressName" placeholder="不能为空" lay-verify="required" lay-reqtext="不能为空" value="" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">域名</label>
                <div class="layui-input-inline" style="width:500px">  
                    <input type="text" name="ingressHost" placeholder="域名" lay-verify="required" lay-reqtext="不能为空" value="" class="layui-input">
                </div> 
                <div class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>
            </div>
            <div class="ingressTpl">
                <div class="layui-form-item">
                    <label class="layui-form-label">路径</label>
                    <div class="layui-input-inline" style="width:125px">  
                        <input type="text" id="ingress_path0" name="ingress_path[]" placeholder="路径" value="/" class="layui-input">
                    </div>
                    <div class="layui-input-inline" style="width:100px">  
                        <select name="pathType[]" id="pathType0" >
                    			<option value="" selected="">匹配规则</option>
                    			<option value="Prefix">Prefix</option>
                    			<option value="Exact">Exact</option>
                    			<option value="ImplementationSpecific">ImplementationSpecific</option>
        		            </select>
                    </div>
                    <div class="layui-input-inline" style="width:100px">  
                        <input type="text" id="ingress_svcName0" name="ingress_svcName[]" placeholder="服务" lay-verify="required" lay-reqtext="不能为空" value="" class="layui-input">
                    </div>
                    <div class="layui-input-inline" style="width:80px">  
                        <input type="text" id="ingress_svcPort0" name="ingress_svcPort[]" placeholder="端口" lay-verify="required" lay-reqtext="不能为空" value="" class="layui-input">
                    </div>   
                    <div class="layui-input-inline" style="width:80px">  
                        <button class="layui-btn layui-btn-normal" id="ingressaddbtn"><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>SSL配置</legend>
            </fieldset>
              <div class="layui-form-item">
                  <label class="layui-form-label required">证书配置</label>
                  <div class="layui-input-inline" style="width:550px">  
                      <input type="text" id="tlsCert" name="tlsCert" placeholder="保密字典[secret]" value="" class="layui-input">
                  </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>

  <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();
    }

    //tls删除
    var tTplIndex = 0;
    function deltTpl(id) {
      $("#ttpl-"+id).remove();
      tTplIndex--;
    }

    //ingress删除
    var iTplIndex = 0;
    function deliTpl(id) {
      iTplIndex--;
      $("#itpl-"+id).remove();
    }

    layui.use(['form'], function () {
        var form = layui.form,
            layer = layui.layer,
            $ = layui.$;

        //选择集群
        form.on('select(cluster_Id)', function(data){
          //console.log(data.elem); //得到select原始DOM对象
          console.log(data.value); //得到被选中的值
          setCookie('clusterId',data.value,30);
          window.location.reload();
          //console.log(data.othis); //得到美化后的DOM对象
        });

        //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;   
        });

        //ingress增加
        $('#ingressaddbtn').on("click",function(){
          iTplIndex++;
          var addTpl =
                '<div class="layui-form-item" id="itpl-'+iTplIndex+'">' +
                    '<label class="layui-form-label">路径</label>' +  
                    '<div class="layui-input-inline" style="width:125px">' +  
                        '<input type="text" name="ingress_path[]" id="ingress_path'+iTplIndex+'" placeholder="路径" value="/" class="layui-input">' +
                    '</div>' +
                    '<div class="layui-input-inline" style="width:100px">' +  
                        '<select name="pathType[]" id="pathType'+iTplIndex+'">' +  
                    			'<option value="" selected="">匹配规则</option>' +  
                    			'<option value="Prefix">Prefix</option>' +  
                    			'<option value="Exact">Exact</option>' +  
                    			'<option value="ImplementationSpecific">ImplementationSpecific</option>' +  
        		            '</select>' +  
                    '</div>' +
                    '<div class="layui-input-inline" style="width:100px">' +  
                        '<input type="text" name="ingress_svcName[]" id="ingress_svcName'+iTplIndex+'" placeholder="服务" value="" class="layui-input">' +
                    '</div>' +
                    '<div class="layui-input-inline"  style="width:80px">' +  
                        '<input type="text" name="ingress_svcPort[]" id="ingress_svcPort'+iTplIndex+'" placeholder="端口" 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="deliTpl('+iTplIndex+');" />' +
                    '</div>' +              
                '</div>' 
            $('.ingressTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //监听提交
        form.on('submit(saveBtn)', function (data) {
                data.field.ingressName = data.field.ingressName.replace(/^\s*|\s*$/g,""); //替换空格
                data.field.ingressHost = data.field.ingressHost.replace(/^\s*|\s*$/g,""); //替换空格
                //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;
                }

                //ingress 处理
                var ingressArry = [];
                for (var i=0;i<=iTplIndex;i++) {
                  var vv = document.getElementById("ingress_path"+i).value; 
                  var tt = document.getElementById("pathType"+i).value; 
                  var ss = document.getElementById("ingress_svcName"+i).value; 
                  var pp = document.getElementById("ingress_svcPort"+i).value; 
                  if ( kk != "" && vv != "" && tt != "" && ss != "" && pp != "") {
                    ingressArry.push(
                      {
                        path:vv,
                        pathType:tt,
                        serviceName:ss,
                        servicePort:pp,
                      }
                    )
                  }
                }
                if (ingressArry.length > 0) {
                  data.field.paths = ingressArry;
                }
                    
					      console.log(data.field);
			          layer.confirm('确定添加?', {icon: 3, title:'提示',yes: function(index){
                     var index2 = layer.load(0, {shade: false});
                     layer.msg('稍等片刻');
                     $.ajax({
                       url: "/ing/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>
</html>
5.3.显示yaml配置的html代码

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

<!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: "/ing/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: "/ing/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 ingressName = getQueryString("ingressName");
              ExportRaw(ingressName+'.yaml',editor.getValue());
        });
    });
</script>

</body>
</html>

六.完整代码

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

6.1 ingress.go,放到contrillers下

// ingress.go
package controllers

import (
	"log"
	m "myk8s/models"
	"strings"

	"github.com/tidwall/gjson"

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

type IngressController struct {
	beego.Controller
}

func (this *IngressController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	ingressName := this.GetString("ingressName")
	serviceName := this.GetString("serviceName")
	labels := this.GetString("labels")
	if this.Ctx.Input.Method() == "POST" {
		gp := gjson.ParseBytes(this.Ctx.Input.RequestBody)
		ingressName = gp.Get("ingressName").String()
		nameSpace = gp.Get("nameSpace").String()
	}
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}
	ingressList, err := m.IngList(clusterId, nameSpace, ingressName, serviceName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(ingressList)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}

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

func (this *IngressController) Create() {
	clusterId := this.GetString("clusterId")
	msg := "success"
	code := 0
	err := m.IngCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

func (this *IngressController) 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.IngYamlModify(clusterId, bodyByte)
	if err != nil {
		log.Println(err)
	}
	this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
	this.ServeJSON()
}

func (this *IngressController) Yaml() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	ingressName := this.GetString("ingressName")

	yamlStr, _ := m.GetIngYaml(clusterId, namespace, ingressName)
	this.Ctx.WriteString(yamlStr)
}

6.2.模型ingressModel.go的完整代码

6.2 ingressModel.go,放到models下

// ingressModel.go
package models

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

	"github.com/tidwall/gjson"

	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
	"sigs.k8s.io/yaml"
)

type Ingress struct {
	IngressName  string `json:"ingressName"`
	NameSpace    string `json:"nameSpace"`
	Labels       string `json:"labels"`
	IngressClass string `json:"ingressClass"`
	Rules        string `json:"rules"`
	Endpoint     string `json:"endpoint"` //内部端点
	CreateTime   string `json:"createTime"`
}

type KV struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

func IngList(kubeconfig, namespace, ingressName, serviceName string, labelsKey, labelsValue string) ([]Ingress, error) {
	clientset := common.ClientSet(kubeconfig)
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}

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

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

	var bbb = make([]Ingress, 0)
	for _, ing := range ingList.Items {
		//搜索
		if ingressName != "" {
			if !strings.Contains(ing.Name, ingressName) {
				continue
			}
		}

		var labelsStr string
		for kk, vv := range ing.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		var rulestr string
		var isNeedService bool
		for _, v1 := range ing.Spec.Rules {
			for _, v2 := range v1.HTTP.Paths {
				var portstr string
				if v2.Backend.Service.Port.Number > 0 {
					portstr = fmt.Sprintf(":%d", v2.Backend.Service.Port.Number)
				}
				rulestr += fmt.Sprintf("%s%s-->%s%s,", v1.Host, v2.Path, v2.Backend.Service.Name, portstr)

				//用serviceName来搜索
				if serviceName != "" && v2.Backend.Service.Name == serviceName {
					isNeedService = true
				}
			}
		}
		//用serviceName来搜索
		if serviceName != "" && !isNeedService {
			continue
		}

		var endpointIp string
		if len(ing.Status.LoadBalancer.Ingress) > 0 {
			endpointIp = ing.Status.LoadBalancer.Ingress[0].IP
		}

		Items := &Ingress{
			IngressName:  ing.Name,
			NameSpace:    ing.Namespace,
			IngressClass: ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"],
			Rules:        rulestr,
			Endpoint:     endpointIp,
			Labels:       labelsStr,
			CreateTime:   ing.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		bbb = append(bbb, *Items)
	}
	return bbb, err
}

func IngCreate(kubeconfig string, bodys []byte) error {
	gp := gjson.ParseBytes(bodys)
	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	ingressName := gp.Get("ingressName").String()
	nameSpace := gp.Get("nameSpace").String()
	ingressHost := gp.Get("ingressHost").String()

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

	var annotationsMap = make(map[string]string)
	annotationsMap["kubernetes.io/ingress.class"] = "nginx"

	var ingresPaths []networkingv1.HTTPIngressPath
	paths := gp.Get("paths").Array()
	for _, vv := range paths {
		var pathType networkingv1.PathType
		switch vv.Get("pathType").Str {
		case "ImplementationSpecific":
			pathType = networkingv1.PathTypeImplementationSpecific
		case "Exact":
			pathType = networkingv1.PathTypeExact
		default:
			pathType = networkingv1.PathTypePrefix
		}

		ingresPaths = append(ingresPaths, *&networkingv1.HTTPIngressPath{
			Path:     vv.Get("path").Str,
			PathType: &pathType,
			Backend: networkingv1.IngressBackend{
				Service: &networkingv1.IngressServiceBackend{
					Name: vv.Get("serviceName").Str,
					Port: networkingv1.ServiceBackendPort{
						Number: int32(vv.Get("servicePort").Int()),
					},
				},
			},
		})
	}

	newIngress := &networkingv1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:        ingressName,
			Namespace:   nameSpace,
			Labels:      labelsMap,
			Annotations: annotationsMap,
		},
		Spec: networkingv1.IngressSpec{
			Rules: []networkingv1.IngressRule{
				{
					Host: ingressHost,
					IngressRuleValue: networkingv1.IngressRuleValue{
						HTTP: &networkingv1.HTTPIngressRuleValue{
							Paths: ingresPaths,
						},
					},
				},
			},
		},
	}

	tlsCert := gp.Get("tlsCert").String()
	if tlsCert != "" {
		var ingTls = []networkingv1.IngressTLS{
			{
				Hosts:      []string{ingressHost},
				SecretName: tlsCert,
			},
		}
		newIngress.Spec.TLS = ingTls
	}

	// 创建Service
	clientset := common.ClientSet(kubeconfig)
	_, err := clientset.NetworkingV1().Ingresses(nameSpace).Create(context.TODO(), newIngress, metav1.CreateOptions{})
	return err
}

func IngYamlModify(kubeconfig string, yamlData []byte) error {
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	ingress := &networkingv1.Ingress{}
	err = json.Unmarshal(data, ingress)
	if err != nil {
		return err
	}

	namespace := ingress.ObjectMeta.Namespace
	ingressName := ingress.ObjectMeta.Name
	clientset := common.ClientSet(kubeconfig)
	_, err = clientset.NetworkingV1().Ingresses(namespace).Update(context.TODO(), ingress, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	fmt.Println(namespace, ingressName)
	return err
}

func GetIngYaml(kubeconfig, namespace, ingressName string) (string, error) {
	ingClient := common.ClientSet(kubeconfig).NetworkingV1().Ingresses(namespace)
	ingress, err := ingClient.Get(context.TODO(), ingressName, metav1.GetOptions{})
	ingresseUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ingress)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(ingresseUnstructured)
	if err != nil {
		return "", err
	}
	return string(yamlBytes), nil
}

七.效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐