简介

本章节主要讲解通过client-go、beego实现deployment列表、创建deployment、读取yaml配置、重启deployment这四个功能,再结合layui、layuimini模板实现也没展,功能只要是由控制器、模型、路由、前端代码几部分组成,现将各个模块的功能进行分布讲解。

一.读取k8s的deployment列表功能

1.1.controllers控制器代码

控制部分代码通过传递集群ID、命名空间、deploy名称、标签来调用控制器中的DeployList函数来进行过滤查询。

func (this *DeployController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	deployName := this.GetString("deployName")
	labels := this.GetString("labels")
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}

	depList, err := m.DeployList(clusterId, nameSpace, deployName, labelsKey, labelsValue) //查询列表函数
	msg := "success"
	code := 0
	count := len(depList)
	if err != nil {
		msg = err.Error()
		code = -1
	}

	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &depList}
	this.ServeJSON()
}
1.2.models模型代码

该模块功只要是先定义一个需要列表中显示的信息的结构体,然后通过DeploymentClient.List获取deploy的所有资源deplist.Items,然后通过循环对返回的数据进行整理,提取结构体中的信息,然后定义个结构体数组var bbb = make([]Deploy, 0),并将信息不断追加到结构体数组中

//定义一个deploy信息的结构体
type Deploy struct {
	DeployName string `json:"deployName" form:"deployName"` //名称
	NameSpace  string `json:"nameSpace" form:"nameSpace"` //命名空间
	RevisionHistoryLimit int32  `json:"revisionHistoryLimit" form:"historyVersionLimit"` //保留的历史版本
	Replicas             int32  `json:"replicas" form:"replicas"`   //副本数
	AvailableReplicas    int32  `json:"availableReplicas"`  //可用副本数
	PodNumber            string `json:"podNumber"`  //pod数量
	Labels               string `json:"labels" form:"labels"` //标签
	ContainerName        string `form:"containerName"`  //container的名称
	ImageUrl             string `json:"imageUrl" form:"imageUrl"`   //镜像地址
	ContainerPortName    string `json:"-"form:"containerPortName"`  //container端口名称
	ContainerPort        string `form:"containerPort"`  //端口
	HostPort             int32  `form:"hostPort"`   //主机端口
	CreateTime string `json:"createTime"` //创建时间
}


func DeployList(kubeconfig, namespace, deployName string, labelsKey, labelsValue string) ([]Deploy, error) {

	//通过kubeconfig集群认证文件生成一个客户端操作对象clientset
	clientset := common.ClientSet(kubeconfig)

	//创建一个deployment资源的接口对象DeploymentClient,用于操作指定名称空间的deployment资源
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
	DeploymentClient := clientset.AppsV1().Deployments(namespace)

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

	deplist, err := DeploymentClient.List(context.TODO(), listOptions)
	var bbb = make([]Deploy, 0)

	for _, dep := range deplist.Items {
		//搜索
		if deployName != "" {
			if !strings.Contains(dep.Name, deployName) {
				continue
			}
		}
		//用于判断是否有appanme标签,并根据标签记录对应的资源到应用下
		var labelsStr, imgUrlStr, containerNameStr, ContainerPortNameStr, ContainerPortStr string
		for kk, vv := range dep.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}

		for _, v2 := range dep.Spec.Template.Spec.Containers {
			containerNameStr += fmt.Sprintf("%s,", v2.Name)
			imgUrlStr += fmt.Sprintf("%s,", v2.Image)
			if len(v2.Ports) > 0 {
				ContainerPortNameStr += fmt.Sprintf("%s,", v2.Ports[0].Name)
				ContainerPortStr += fmt.Sprintf("%d,", v2.Ports[0].ContainerPort)
			}
		}
		if len(containerNameStr) > 0 {
			containerNameStr = containerNameStr[0 : len(containerNameStr)-1]
		}
		if len(imgUrlStr) > 0 {
			imgUrlStr = imgUrlStr[0 : len(imgUrlStr)-1]
		}
		if len(ContainerPortNameStr) > 0 {
			ContainerPortNameStr = ContainerPortNameStr[0 : len(ContainerPortNameStr)-1]
		}
		if len(ContainerPortStr) > 0 {
			ContainerPortStr = ContainerPortStr[0 : len(ContainerPortStr)-1]
		}

		depItems := &Deploy{
			DeployName: dep.Name,
			NameSpace:  dep.Namespace,
			RevisionHistoryLimit: *dep.Spec.RevisionHistoryLimit,
			Replicas:             *dep.Spec.Replicas,
			AvailableReplicas:    dep.Status.AvailableReplicas,
			PodNumber:            fmt.Sprintf("%d/%d", dep.Status.AvailableReplicas, *dep.Spec.Replicas),
			Labels:               labelsStr,
			ContainerName:        containerNameStr,
			ImageUrl:             imgUrlStr,
			ContainerPortName:    ContainerPortNameStr,
			ContainerPort:        ContainerPortStr,
			CreateTime: dep.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		bbb = append(bbb, *depItems)    //将读取的deploy信息循环追加到结构体数组中
	}
	return bbb, err
}

二.创建deployment功能

2.1.创建deploy控制器代码

2.1该功能主要是通过在页面的表单输入名称、镜像、副本数、端口、标签等信息,然后创建一个deploy,控制器部分代码,直接将获取body内容this.Ctx.Input.RequestBody传递到DeployCreate函数去处理

func (this *DeployController) Create() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	err := m.DeployCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] Deploy Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}
2.2 models模型代码

2.2.通过前端传递的body用gjson进行解析,提取表单数据,然后定义一个结构体appsv1.Deployment,并将表单中的数据对应上,然后调用DeployClient.Create进行创建。

func DeployCreate(kubeconfig string, bodys []byte) error {

	gp := gjson.ParseBytes(bodys)

	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	deployName := gp.Get("deployName").String()
	nameSpace := gp.Get("nameSpace").String()
	containerPort := gp.Get("containerPort").Int()
	replicas := gp.Get("replicas").Int()
	var pullPolicy corev1.PullPolicy
	imagePullPolicy := gp.Get("imagePullPolicy").String()
	switch imagePullPolicy {
	case "Never":
		pullPolicy = corev1.PullNever
	case "IfNotPresent":
		pullPolicy = corev1.PullIfNotPresent
	default:
		pullPolicy = corev1.PullAlways
	}
	imageUrl := gp.Get("imageUrl").String()

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

	deployInstance := &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: "apps/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: int32Ptr(int32(replicas)),
			Selector: &metav1.LabelSelector{
				MatchLabels: labelsMap,
			},
			RevisionHistoryLimit: int32Ptr(10),
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					//Name: "golang-pod",
					Labels: labelsMap,
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						corev1.Container{
							Name:            deployName,
							Image:           imageUrl,
							ImagePullPolicy: pullPolicy,
							Ports: []corev1.ContainerPort{
								corev1.ContainerPort{
									//Name:     ""     ,
									ContainerPort: int32(containerPort),
									//HostPort:      deployment.HostPort,
									Protocol: corev1.ProtocolTCP,
								},
							},
						},
					},
				},
			},
		},
	}

	DeployClient := common.ClientSet(kubeconfig).AppsV1().Deployments(nameSpace)
	//调用DeployClient接口中的create方法,创建deployment资源
	_, err := DeployClient.Create(context.TODO(), deployInstance, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

三.读取deployment的yaml配置功能

3.1.controllers控制器代码

3.1.通过传递集群ID、命名空间、deploy名称读取yaml配置

func (this *DeployController) Yaml() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	deployName := this.GetString("deployName")

	yamlStr, _ := m.GetDeployYaml(clusterId, namespace, deployName)
	this.Ctx.WriteString(yamlStr)
}
3.2.models模型代码

3.2.查询具体的deploy名称,然后将deploy的配置信息进行非结构化转换,并解析成yaml配置返回

func GetDeployYaml(kubeconfig, namespace, deploy string) (string, error) {

	DeploymentClient := common.ClientSet(kubeconfig).AppsV1().Deployments(namespace)
	deployment, err := DeploymentClient.Get(context.TODO(), deploy, metav1.GetOptions{})

	deploymentUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(deployment)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(deploymentUnstructured)
	if err != nil {
		return "", err
	}
	return string(yamlBytes), nil
}

四.重启deployment

4.1.重启deploy的控制器代码

4.1.通过传递的deploy名称并调用DeployRestart函数进行重启

func (this *DeployController) Restart() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	deployName := this.GetString("deployName")

	code := 0
	msg := "success"
	err := m.DeployRestart(clusterId, namespace, deployName)
	if err != nil {
		log.Println(err)
		code = -1
		msg = err.Error()
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

4.2.重启deploy的模型代码

4.2.通过传递过来的deploy名称,然后patch一个申明去触发容器进行滚动更新

func DeployRestart(kubeconfig, namespace, deployName string) error {
	patchOpt := metav1.PatchOptions{FieldManager: "kubectl-rollout"}
	patchInfo := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"%s"}}}}}`, time.Now().Format(time.RFC3339))
	_, err := common.ClientSet(kubeconfig).AppsV1().Deployments(namespace).Patch(context.TODO(), deployName, types.StrategicMergePatchType, []byte(patchInfo), patchOpt)
	return err
}

五.路由配置

url路由配置,在routers/route.go中增加如下路径

	beego.Router("/deploy/v1/List", &controllers.DeployController{}, "*:List")  //列表
	beego.Router("/deploy/v1/Restart", &controllers.DeployController{}, "*:Restart") //重启
	beego.Router("/deploy/v1/Yaml", &controllers.DeployController{}, "*:Yaml") //读取yaml配置
	beego.Router("/deploy/v1/Create", &controllers.DeployController{}, "*:Create") //创建

六.前端部分代码

6.1.前端列表展示的html代码

放在front/page/xkube/deployList.html,加载数据到表格的函数主要是javascript中table.render,然后渲染到currentTableId这个id表格,currentTableId的位置在body内的table这里

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>deploy列表</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;
    max-height: 200px;
    line-height: 15px !important;
    text-overflow: inherit;
    /* overflow: hidden; */
    overflow: ellipsis;
    white-space: normal;
    /*word-wrap: break-word;*/
  }
  .layui-table-cell .layui-table-tool-panel li {
    word-break: break-word;
  }
</style>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <table class="layui-table" id="currentTableId" lay-filter="currentTableFilter"></table>

    </div>
</div>
</body>
<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="createDeploy"><i class="layui-icon">&#xe61f;</i>创建应用</button>
    </div>
</script>

<script type="text/html" id="currentTableBar">
    <a class="layui-btn layui-btn-normal layui-btn-sm" lay-event="viewYaml">yaml编辑</a>
    <a class="layui-btn layui-btn-sm layui-btn-warm" lay-event="restart">重启</a>
</script>

<script type="text/html" id="TagTpl">
    {{# if (d.labels != "") { }}
            {{# layui.each(d.labels.split(','), function(index, item){ }}
                {{# if(index == 0) { }}
                        <span style="background-color: #1E9FFF; border: 0px solid #1E9FFF; color:#FFF;border-radius:3px;">{{ item }}</span>
                {{# }else{ }}
                        <br><br><span style="background-color: #1E9FFF; border: 0px solid #1E9FFF; color:#FFF;border-radius:3px;">{{ 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();

            form.render();

        table.render({
            elem: '#currentTableId',
            //url: '/deploy/v1/List'+location.search+'&nameSpace=zx-app&clusterId='+clusterId,
            url: '/deploy/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"},
                {field: 'deployName',title: '名称'},
                {field: 'nameSpace',title: '命名空间', sort: true},
                {field: 'podNumber',title: '容器', sort: true},
                {field: 'imageUrl', title: '镜像', sort: true},
                {field: 'labes',title: '标签', sort: true,templet: '#TagTpl'},
                {field: 'createTime',title: '创建时间'},
                {field: 'updateTime',title: '更新时间', hide:true},
                {title: '操作', minWidth: 350, toolbar: '#currentTableBar', align: "center"}
            ]],
            //size:'lg',
            limits: [25, 50, 100],
            limit: 25,
            page: true
        });

        table.on('toolbar(currentTableFilter)', function (obj) {
            if (obj.event === 'createDeploy') {  // 监听添加操作
                var index = layer.open({
                    title: '创建应用',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['60%', '90%'],
                    content: '/page/xkube/deployCreate.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/deployYaml.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&deployName='+data.deployName,
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
                return false;
            }  else if (obj.event === 'restart') {
      	          layer.confirm('确定重启?', {icon: 3, title:'提示',yes: function(index){
                       var index2 = layer.load(0, {shade: false});
                       layer.msg('稍等片刻');
                       $.ajax({
                         url: '/deploy/v1/Restart?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&deployName='+data.deployName,
                         type: "get",
                         dataType: "json",
                         success: function (resp) {
                                  layer.close(index2);
                                   if(resp.code == 0){
                                      layer.msg('重启成功', {icon: 1});
                                   }else{
                                      layer.msg(resp.msg,{icon:2});
                                   }
                          }
                        });		  	  
                    },
            		  cancel: function(index, layero){ 
            			    layer.close(index);
                      layer.close(index2);
            			    console.log("不操作");
            		  } 
        		    });                
            }
        });
    });
</script>
</html>
6.2.创建deploy的html

frot/page/xkube/deployCreate.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>
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label required">名称</label>
                <div class="layui-input-inline">
                    <input type="text" name="deployName"  lay-verify="required" lay-reqtext="不能为空"  placeholder="不能为空" value="" class="layui-input">
                </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 class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label required">副本数量</label>
                <div class="layui-input-inline">
                    <input type="text" name="replicas"  lay-verify="required" lay-reqtext="大于0" value="1" class="layui-input">
                </div>

                <label class="layui-form-label required">端口</label>
                <div class="layui-input-inline">
                    <input type="text" name="containerPort"  lay-verify="required" lay-reqtext="不能为空"  placeholder="不能为空" value="80" class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">镜像策略</label>
                  <div class="layui-input-block">
                      <input type="radio" name="imagePullPolicy" value="Always" title="Always" checked="">
                      <input type="radio" name="imagePullPolicy" value="IfNotPresent" title="IfNotPresent">
                      <input type="radio" name="imagePullPolicy" value="Never" title="Never">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label required">镜像地址</label>
                <div class="layui-input-inline" style="width:450px">
                    <input type="text" name="imageUrl"  lay-verify="required" lay-reqtext="不能为空"  placeholder="不能为空" value="" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>
            </div>

            <div class="labelsTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">标签</label>
                  <div class="layui-input-inline">  
                      <input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline">  
                      <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" style="width:100px" id="newaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>
            <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>

//SetDefaultCluster();
//var clusterId = getQueryString("clusterId");
//if (clusterId == null) {
//	clusterId = getCookie("clusterId")
//}

    //标签删除
    var TplIndex = 0;
    function delTpl(id) {
      $("#tpl-"+id).remove();
      TplIndex--;
    }

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

        //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">' +
        	              '<input type="text" name="labels_key[]" id="labels_key'+TplIndex+'" placeholder="key" value="" class="layui-input">' +
                  '</div>' +    
                  '<div class="layui-input-inline">' +  
                      '<input type="text" name="labels_value[]" id="labels_value'+TplIndex+'" placeholder="value" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline">' +  
                      '<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:100px" type="button" id="newDelbtn" value="&#xe616;" οnclick="delTpl('+TplIndex+');" />' +
                  '</div>' +                
              '</div>'
            $('.labelsTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //监听提交
        form.on('submit(saveBtn)', function (data) {
                //data.field.appname = data.field.appname.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;
                }
					      console.log(data.field);
			          layer.confirm('确定添加?', {icon: 3, title:'提示',yes: function(index){
                     var index2 = layer.load(0, {shade: false});
                     layer.msg('稍等片刻');
                     $.ajax({
                       url: "/deploy/v1/Create?clusterId="+data.field.clusterId,
                       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>
6.3.读取yaml配置的html

6.3.frot/page/xkube/deployYaml.html,配置的显示是通过monaco-editor来进行显示,网上下载一个monaco-editor:https://github.com/Microsoft/monaco-editor,国内的镜像地址: https://gitee.com/mirrors/monaco-editor 放到front目录下,

<!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: "/deploy/v1/Yaml"+location.search,
    		    type: "GET",
    		    success: function (resp) {
    			  //codeyaml = resp;
    			  editor.setValue(resp);
    			}
    		});	
        $('#DownLoadBtn').on("click",function(){
              var deployName = getQueryString("deployName");
              ExportRaw(deployName+'.yaml',editor.getValue());
        });

    });
</script>

</body>
</html>

七.完整代码

7.1. deploy.go

7.1. deploy.go

package controllers

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

	//"github.com/tidwall/gjson"

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

type DeployController struct {
	beego.Controller
}

func (this *DeployController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	deployName := this.GetString("deployName")
	labels := this.GetString("labels")
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}

	depList, err := m.DeployList(clusterId, nameSpace, deployName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(depList)
	if err != nil {
		msg = err.Error()
		code = -1
	}

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

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

func (this *DeployController) Yaml() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	deployName := this.GetString("deployName")

	yamlStr, _ := m.GetDeployYaml(clusterId, namespace, deployName)
	this.Ctx.WriteString(yamlStr)
}

func (this *DeployController) Restart() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	deployName := this.GetString("deployName")

	code := 0
	msg := "success"
	err := m.DeployRestart(clusterId, namespace, deployName)
	if err != nil {
		log.Println(err)
		code = -1
		msg = err.Error()
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

7.2 deployModel.go

7.2 deployModel.go,编译前先test,然后在get.

package models

import (
	//"bytes"
	"context"
	//"encoding/json"
	"fmt"

	//"log"
	//"strconv"
	"strings"

	"time"
	//"io/ioutil"
	"myk8s/common"

	//"k8s.io/apimachinery/pkg/api/errors"

	"sigs.k8s.io/yaml"

	//"github.com/ghodss/yaml"

	appsv1 "k8s.io/api/apps/v1"

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

	//"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	//"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"

	//"k8s.io/apimachinery/pkg/api/resource"
	//yamlv2 "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
	//"k8s.io/apimachinery/pkg/util/intstr"
	//yamlutil "k8s.io/apimachinery/pkg/util/yaml"

	//"k8s.io/client-go/kubernetes/scheme"
	"github.com/tidwall/gjson"
	//"k8s.io/client-go/util/retry"

	//"k8s.io/apimachinery/pkg/runtime/serializer/json"
	// "k8s.io/client-go/kubernetes"
	//kubeAppV1 "k8s.io/client-go/kubernetes/typed/apps/v1"
	// "k8s.io/client-go/tools/clientcmd"
	//"k8s.io/klog/v2"
	//"k8s.io/client-go/dynamic"
	//"k8s.io/client-go/restmapper"

	"k8s.io/apimachinery/pkg/types"
)

type Deploy struct {
	DeployName string `json:"deployName" form:"deployName"`
	NameSpace  string `json:"nameSpace" form:"nameSpace"`
	//CreationTimestamp    time.Time `json:"created_time"`
	RevisionHistoryLimit int32  `json:"revisionHistoryLimit" form:"historyVersionLimit"`
	Replicas             int32  `json:"replicas" form:"replicas"`
	AvailableReplicas    int32  `json:"availableReplicas"`
	PodNumber            string `json:"podNumber"`
	Labels               string `json:"labels" form:"labels"`
	ContainerName        string `form:"containerName"`
	ImageUrl             string `json:"imageUrl" form:"imageUrl"`
	ContainerPortName    string `json:"-"form:"containerPortName"`
	ContainerPort        string `form:"containerPort"`
	HostPort             int32  `form:"hostPort"`
	// Resource             string    `json:"resource"`
	// StrategyType         string    `json:"strategyType"`
	CreateTime string `json:"createTime"` //创建时间
}

type DeployStatus struct {
	AvailableReplicas  string `json:"availableReplicas"`
	ObservedGeneration string `json:"observedGeneration"`
	ReadyReplicas      string `json:"readyReplicas"`
	Replicas           string `json:"replicas"`
	UpdatedReplicas    string `json:"updatedReplicas"`
	Conditions         []StatusConditions
}

type StatusConditions struct {
	LastTransitionTime string `json:"lastTransitionTime"`
	LastUpdateTime     string `json:"lastUpdateTime"`
	Message            string `json:"message"`
	Reason             string `json:"reason"`
	Status             string `json:"status"`
	Ctype              string `json:"ctype"`
}

type Replicaset struct {
	ReplicasetName string `json:"replicasetName"`
	ImageUrl       string `json:"imageUrl"`
	CreateTime     string `json:"createTime"`
}

// func ListDeploy(kubeconfig, namespace string) ([]appsv1.Deployment, error) {
func DeployList(kubeconfig, namespace, deployName string, labelsKey, labelsValue string) ([]Deploy, error) {

	//通过kubeconfig集群认证文件生成一个客户端操作对象clientset
	clientset := common.ClientSet(kubeconfig)

	//创建一个deployment资源的接口对象DeploymentClient,用于操作指定名称空间的deployment资源
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
	DeploymentClient := clientset.AppsV1().Deployments(namespace)
	//调用接口对象DeploymentClient中的Get方法,获取相应的deployment资源数据	deploymentInstance,err:=DeploymentClient.Get(context.TODO(),deploymentName,metaV1.GetOptions{})

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

	// // 获取deployments
	// deployments, _ := clientset.AppsV1().Deployments("zx-app").List(listOptions)
	//var listOptions = metav1.ListOptions{}
	//if serviceName != "" {
	//	listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("app=%s", serviceName)}
	//}

	//deplist, err := DeploymentClient.List(context.TODO(), metav1.ListOptions{})
	deplist, err := DeploymentClient.List(context.TODO(), listOptions)
	// if err != nil {
	// 	klog.Errorf("list deployment error, err:%v", err)
	// 	return
	// }

	var bbb = make([]Deploy, 0)
	// jsonStr, err := json.Marshal(deplist.Items)
	// if err != nil {
	// 	fmt.Println("ERROR: simpledetail json.Marshal error")
	// }
	// fmt.Println(string(jsonStr))
	for _, dep := range deplist.Items {
		//搜索
		if deployName != "" {
			if !strings.Contains(dep.Name, deployName) {
				continue
			}
		}
		//用于判断是否有appanme标签,并根据标签记录对应的资源到应用下
		//var appName string

		var labelsStr, imgUrlStr, containerNameStr, ContainerPortNameStr, ContainerPortStr string
		for kk, vv := range dep.ObjectMeta.Labels {
			// if kk == "appname" {
			// 	appName = vv
			// }
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}

		for _, v2 := range dep.Spec.Template.Spec.Containers {
			containerNameStr += fmt.Sprintf("%s,", v2.Name)
			imgUrlStr += fmt.Sprintf("%s,", v2.Image)
			if len(v2.Ports) > 0 {
				ContainerPortNameStr += fmt.Sprintf("%s,", v2.Ports[0].Name)
				ContainerPortStr += fmt.Sprintf("%d,", v2.Ports[0].ContainerPort)
			}
		}
		if len(containerNameStr) > 0 {
			containerNameStr = containerNameStr[0 : len(containerNameStr)-1]
		}
		if len(imgUrlStr) > 0 {
			imgUrlStr = imgUrlStr[0 : len(imgUrlStr)-1]
		}
		if len(ContainerPortNameStr) > 0 {
			ContainerPortNameStr = ContainerPortNameStr[0 : len(ContainerPortNameStr)-1]
		}
		if len(ContainerPortStr) > 0 {
			ContainerPortStr = ContainerPortStr[0 : len(ContainerPortStr)-1]
		}
		//klog.Infof("deploy name:%s, replicas:%d, container image:%s", dep.Name, *dep.Spec.Replicas, dep.Spec.Template.Spec.Containers[0].Image)
		depItems := &Deploy{
			DeployName: dep.Name,
			NameSpace:  dep.Namespace,
			//CreationTimestamp:    dep.CreationTimestamp.Time,
			RevisionHistoryLimit: *dep.Spec.RevisionHistoryLimit,
			Replicas:             *dep.Spec.Replicas,
			AvailableReplicas:    dep.Status.AvailableReplicas,
			PodNumber:            fmt.Sprintf("%d/%d", dep.Status.AvailableReplicas, *dep.Spec.Replicas),
			Labels:               labelsStr,
			ContainerName:        containerNameStr,
			ImageUrl:             imgUrlStr,
			ContainerPortName:    ContainerPortNameStr,
			ContainerPort:        ContainerPortStr,
			//HostPort:             dep.Spec.Template.Spec.Containers[0].Ports[0].HostPort,
			// Resource:			dep.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().
			// StrategyType:
			CreateTime: dep.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		// if appName != "" {
		// 	jsonData, err := json.Marshal(&depItems)
		// 	if err == nil {
		// 		_ = common.HSet(appName, "deploy__"+dep.Name, string(jsonData))
		// 	}
		// }
		bbb = append(bbb, *depItems)
	}
	return bbb, err
}

//func DeployCreate(kubeconfig string, deployment *Deploy) error {
func DeployCreate(kubeconfig string, bodys []byte) error {

	gp := gjson.ParseBytes(bodys)

	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	deployName := gp.Get("deployName").String()
	nameSpace := gp.Get("nameSpace").String()
	containerPort := gp.Get("containerPort").Int()
	replicas := gp.Get("replicas").Int()
	var pullPolicy corev1.PullPolicy
	imagePullPolicy := gp.Get("imagePullPolicy").String()
	switch imagePullPolicy {
	case "Never":
		pullPolicy = corev1.PullNever
	case "IfNotPresent":
		pullPolicy = corev1.PullIfNotPresent
	default:
		pullPolicy = corev1.PullAlways
	}
	imageUrl := gp.Get("imageUrl").String()

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

	deployInstance := &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: "apps/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: int32Ptr(int32(replicas)),
			Selector: &metav1.LabelSelector{
				MatchLabels: labelsMap,
			},
			RevisionHistoryLimit: int32Ptr(10),
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					//Name: "golang-pod",
					Labels: labelsMap,
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						corev1.Container{
							Name:            deployName,
							Image:           imageUrl,
							ImagePullPolicy: pullPolicy,
							Ports: []corev1.ContainerPort{
								corev1.ContainerPort{
									//Name:     ""     ,
									ContainerPort: int32(containerPort),
									//HostPort:      deployment.HostPort,
									Protocol: corev1.ProtocolTCP,
								},
							},
						},
					},
				},
			},
		},
	}

	DeployClient := common.ClientSet(kubeconfig).AppsV1().Deployments(nameSpace)
	//调用DeployClient接口中的create方法,创建deployment资源
	_, err := DeployClient.Create(context.TODO(), deployInstance, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

func GetDeployYaml(kubeconfig, namespace, deploy string) (string, error) {

	DeploymentClient := common.ClientSet(kubeconfig).AppsV1().Deployments(namespace)
	deployment, err := DeploymentClient.Get(context.TODO(), deploy, metav1.GetOptions{})

	deploymentUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(deployment)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(deploymentUnstructured)
	if err != nil {
		return "", err
	}
	return string(yamlBytes), nil
}

//Restart
func DeployRestart(kubeconfig, namespace, deployName string) error {
	patchOpt := metav1.PatchOptions{FieldManager: "kubectl-rollout"}
	patchInfo := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"%s"}}}}}`, time.Now().Format(time.RFC3339))
	_, err := common.ClientSet(kubeconfig).AppsV1().Deployments(namespace).Patch(context.TODO(), deployName, types.StrategicMergePatchType, []byte(patchInfo), patchOpt)
	return err
}

func int32Ptr(i int32) *int32 { return &i }

八.效果图

在这里插入图片描述

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

Logo

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

更多推荐