文章目录
- 简介
- 一.k8s的ingress列表
- 1.1.controllers控制器代码
- 1.2.models模型代码
- 二.创建ingress
- 2.1.controllers控制器代码
- 2.2.models模分代码
- 三.读取和更新ingress的yaml配置
- 3.1.controllers控制器代码
- 3.2.models模型代码
- 四.路由设置
- 4.1.路由设置
- 五.前端代码
- 5.1.列表部分html代码
- 5.2.创建表单html代码
- 5.3.显示yaml配置的html代码
- 六.完整代码
- 6.1.控制器ingress.go的完整代码
- 6.2.模型ingressModel.go的完整代码
- 七.效果图
简介
本章节主要讲解通过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"></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="" ο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="" ο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
}
七.效果图