【k8s多集群管理平台开发实践】八、client-go实现service读取列表、创建service、读取yaml配置并更新
本章节主要讲解通过client-go实现service的列表读取和界面创建service,sevice的yaml配置文件读取和修改,并通过layui实现界面操作,其中包含控制器这部分的代码,模型这部分代码,以及前端的html代码。
文章目录
简介
本章节主要讲解通过client-go实现service的列表读取和界面创建service,sevice的yaml配置文件读取和修改,并通过layui实现界面操作,其中包含控制器这部分的代码,模型这部分代码,以及前端的html代码。
一.k8s的service列表
1.1.controllers控制器代码
通过传递集群id、命名空间、服务名称并调用模型代码中serviceList函数来读取列表
func (this *SvcController) List() {
clusterId := this.GetString("clusterId") //集群名称
nameSpace := this.GetString("nameSpace") //命名空间
serviceName := this.GetString("serviceName") //服务名称
labels := this.GetString("labels") //标签:labels=key:value
labelsKV := strings.Split(labels, ":")
var labelsKey, labelsValue string
if len(labelsKV) == 2 {
labelsKey = labelsKV[0]
labelsValue = labelsKV[1]
}
//根据传递的参数进行过滤返回服务列表
svcList, err := m.SvcList(clusterId, nameSpace, serviceName, labelsKey, labelsValue)
msg := "success"
code := 0
count := len(svcList)
if err != nil {
log.Println(err)
msg = err.Error()
code = -1
}
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &svcList}
this.ServeJSON()
}
1.2.models模型代码
先定义一个结构体Service,通过传递过来的参数,在
svcList.Items
中循环读取service的信息,然后赋值结构体Service,并追加到定义一个结构体数组var bbb = make([]Service, 0)
。
type Service struct {
ServiceName string `json:"serviceName"` //服务名称
NameSpace string `json:"nameSpace"` //命名空间
Labels string `json:"labels"` //标签
SvcType string `json:"svcType"` //service的类型
SvcIp string `json:"svcIp"` //服务IP
SvcPort string `json:"svcPort"`
LanEndpoint string `json:"lanEndpoint"` //内部端点
WanEndpoint string `json:"wanEndpoint"` //外部端点
CreateTime string `json:"createTime"`
}
type ServicePort struct {
PortName string `json:"portName"`
SvcPort int32 `json:"svcPort"`
TargetPort int32 `json:"targetPort"`
}
func SvcList(kubeconfig, namespace, serviceName string, labelsKey, labelsValue string) ([]Service, error) {
//获取链接
clientset := common.ClientSet(kubeconfig)
if namespace == "" { //判断namespace是否为空,空则读取全部命名空间
namespace = corev1.NamespaceAll
}
//定义标签过滤条件
var listOptions = metav1.ListOptions{}
if labelsKey != "" && labelsValue != "" {
listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
}
//读取service信息
svcList, err := clientset.CoreV1().Services(namespace).List(context.TODO(), listOptions)
if err != nil {
log.Printf("list service error:%v\n", err)
}
//fmt.Println("svc count:", len(svcList.Items))
//循环items并追加到结构体数组bbb上
var bbb = make([]Service, 0)
for _, svc := range svcList.Items {
//搜索
if serviceName != "" {
if !strings.Contains(svc.Name, serviceName) {
continue
}
}
//提取标签
var labelsStr string
for kk, vv := range svc.ObjectMeta.Labels {
labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
}
if len(labelsStr) > 0 {
labelsStr = labelsStr[0 : len(labelsStr)-1]
}
//提取所有映射的端口
var wanEndPoint string
svcType := fmt.Sprintf("%s", svc.Spec.Type)
if svcType == "LoadBalancer" {
wanEndPoint = fmt.Sprintf("%s:%d", svc.Status.LoadBalancer.Ingress[0].IP, svc.Spec.Ports[0].Port)
}
lanEndPoint := ""
var svcPort string
if len(svc.Spec.Ports) > 0 {
vsapp := svc.Spec.Selector["app"]
if vsapp == "" {
vsapp = svc.Spec.Selector["k8s-app"]
}
for _, vv := range svc.Spec.Ports {
//lanEndPoint += fmt.Sprintf("%s:%s:%s\n", svc.Spec.Selector["app"], svc.Spec.Ports[0].TargetPort.StrVal, svc.Spec.Ports[0].Protocol)
//svcPort += fmt.Sprintf("%s:", svc.Spec.Ports[0].Port)
var vport string
if vv.TargetPort.Type == 0 {
vport = fmt.Sprintf("%d", vv.TargetPort.IntVal)
} else {
vport = fmt.Sprintf("%s", vv.TargetPort.StrVal)
}
lanEndPoint += fmt.Sprintf("%s:%s:%s,", vsapp, vport, vv.Protocol)
svcPort += fmt.Sprintf("%d,", vv.Port)
}
if len(svcPort) > 0 {
svcPort = svcPort[0 : len(svcPort)-1]
}
lanEndPoint = lanEndPoint[0 : len(lanEndPoint)-1]
}
//赋值到结构体
Items := &Service{
ServiceName: svc.Name,
NameSpace: svc.Namespace,
SvcType: svcType,
SvcIp: svc.Spec.ClusterIP,
Labels: labelsStr,
SvcPort: svcPort,
LanEndpoint: lanEndPoint,
WanEndpoint: wanEndPoint,
CreateTime: svc.CreationTimestamp.Format("2006-01-02 15:04:05"),
}
//追加到数组bbb
bbb = append(bbb, *Items)
}
return bbb, err
}
二.创建service
2.1.controllers控制器代码
将前端的form 表单提交的json数据传递到模型函数serviceCreate来进行解析
func (this *SvcController) Create() {
clusterId := this.GetString("clusterId") //集群ID
code := 0
msg := "success"
//log.Println(string(this.Ctx.Input.RequestBody))
//将传递过来的json 二进制 body直接传递到模型中的函数SvcCreate处理
err := m.SvcCreate(clusterId, this.Ctx.Input.RequestBody)
if err != nil {
code = -1
msg = err.Error()
log.Printf("[ERROR] configmap Create Fail:%s\n", err)
}
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
this.ServeJSON()
}
2.2.models模分代码
将控制器Create函数中传递过来的body数据进行json解析,创建一个service实例结构体
svcInstance := &corev1.Service
,并将解析json出来的数据赋值到结构体中,再调用clientset.CoreV1().Services(nameSpace).Create
来进行创建.
func SvcCreate(kubeconfig string, bodys []byte) error {
//解析json并提取数据
gp := gjson.ParseBytes(bodys)
clusterId := gp.Get("clusterId").String()
if kubeconfig == "" {
kubeconfig = clusterId
}
serviceName := gp.Get("serviceName").String() //服务名称
nameSpace := gp.Get("nameSpace").String() //命名空间
svcType := gp.Get("svcType").String() //服务类型
deployName := gp.Get("deployName").String() //关联的负载名称
isHeadless := gp.Get("isHeadless").Str //是否是无头服务
var labelsMap = make(map[string]string) //提取标签
labelsMap["app"] = serviceName
for _, vv := range gp.Get("lables").Array() {
labelsMap[vv.Get("key").Str] = vv.Get("value").Str
}
selectApp := serviceName //默认将service的名称和deployment的名称一致,deployName不为空时,按照deployName的名称创建
if deployName != "" {
selectApp = deployName
}
var serviceType corev1.ServiceType //赋值服务类型
switch svcType {
case "NodePort":
serviceType = corev1.ServiceTypeNodePort
case "LoadBalancer":
serviceType = corev1.ServiceTypeLoadBalancer
default:
serviceType = corev1.ServiceTypeClusterIP
}
if isHeadless == "on" { //判断是否是无头服务
serviceType = corev1.ServiceTypeClusterIP
}
//将json解析的数据赋值到service实例结构体上
svcInstance := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: nameSpace,
Labels: labelsMap,
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": selectApp,
},
Type: serviceType,
},
}
if isHeadless == "on" {
svcInstance.Spec.ClusterIP = corev1.ClusterIPNone
}
ports := gp.Get("ports").Array()
var svcPorts = make([]corev1.ServicePort, 0)
for _, vv := range ports {
var svcProtocol corev1.Protocol
if vv.Get("protocol").Str == "UDP" {
svcProtocol = corev1.ProtocolUDP
} else {
svcProtocol = corev1.ProtocolTCP
}
svcPort := &corev1.ServicePort{
Name: vv.Get("portName").Str,
Port: int32(vv.Get("svcPort").Int()),
Protocol: svcProtocol,
TargetPort: intstr.FromInt32(int32(vv.Get("targetPort").Int())),
}
svcPorts = append(svcPorts, *svcPort)
}
svcInstance.Spec.Ports = svcPorts
//根据传递的集群ID进行创建service
clientset := common.ClientSet(kubeconfig)
_, err := clientset.CoreV1().Services(nameSpace).Create(context.TODO(), svcInstance, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
三.读取和更新service的yaml配置
3.1.controllers控制器代码
通过传递集群id、命名空间、服务名称来进行读取service,并调用模型代码中GetSvcYaml函数来读取yaml配置
//读取yaml配置
func (this *SvcController) Yaml() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
serviceName := this.GetString("serviceName")
yamlStr, _ := m.GetSvcYaml(clusterId, nameSpace, serviceName)
this.Ctx.WriteString(yamlStr)
}
//更新yaml配置
func (this *SvcController) ModifyByYaml() {
clusterId := this.GetString("clusterId")
//nameSpace := gp.Get("nameSpace").String()
//log.Println(string(this.Ctx.Input.RequestBody))
//提取body并替换掉%,否则解析出错
bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
err := m.SvcYamlModify(clusterId, bodyByte)
if err != nil {
log.Println(err)
}
this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
this.ServeJSON()
}
3.2.models模型代码
先读取service实例信息,然后将实例信息解析成解构,然后解构的字节数据进行yaml序列化处理,并转成字符串进行返回
//读取yaml配置
func GetSvcYaml(kubeconfig, namespace, serviceName string) (string, error) {
//先通过传递的信息读取service实例
servicesClient := common.ClientSet(kubeconfig).CoreV1().Services(namespace)
service, err := servicesClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
//将读取的service进行解构
serviceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(service)
if err != nil {
return "", err
}
//将结构的service数据进行序列号成yaml的bytes
yamlBytes, err := yaml.Marshal(serviceUnstructured)
if err != nil {
return "", err
}
//以字符串的形式返回
return string(yamlBytes), nil
}
//更新yaml配置
func SvcYamlModify(kubeconfig string, yamlData []byte) error {
//转成json格式
data, err := yamlutil.ToJSON(yamlData)
if err != nil {
return err
}
//反序列化到service结构体实例
service := &corev1.Service{}
err = json.Unmarshal(data, service)
if err != nil {
return err
}
//提取出名称和命名空间
namespace := service.ObjectMeta.Namespace
serviceName := service.ObjectMeta.Name
//调用更新api进行更新
clientset := common.ClientSet(kubeconfig)
_, err = clientset.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
if err != nil {
return err
}
fmt.Println(namespace, serviceName)
return err
}
四.路由设置
4.1.路由设置
将以下代码放到routers/route.go中
beego.Router("/svc/v1/List", &controllers.SvcController{}, "*:List") //读取service列表
beego.Router("/svc/v1/Create", &controllers.SvcController{}, "*:Create") //创建service
beego.Router("/svc/v1/ModifyByYaml", &controllers.SvcController{}, "*:ModifyByYaml") //更新yaml配置
beego.Router("/svc/v1/Yaml", &controllers.SvcController{}, "*:Yaml") //读取yaml配置
五.前端代码
5.1.列表部分html代码
5.1 serviceList.html,放到views/front/page/xkube下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>service列表</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="/css/public.css" media="all">
<script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script src="/js/xkube.js?v=1" charset="utf-8"></script>
<script src="/js/lay-config.js?v=1.0.4" charset="utf-8"></script>
<style type="text/css">
.layui-table-cell {
height: auto;
line-height: 22px !important;
text-overflow: inherit;
overflow:ellipsis;
white-space: normal;
}
.layui-table-cell .layui-table-tool-panel li {
word-break: break-word;
}
</style>
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="create"><i class="layui-icon"></i>创建service</button>
</div>
</script>
<table class="layui-table" id="currentTableId" lay-filter="currentTableFilter"></table>
<script type="text/html" id="currentTableBar">
<a class="layui-btn layui-btn-normal layui-btn-sm" lay-event="viewYaml">yaml编辑</a>
</script>
</div>
</div>
</body>
<script type="text/html" id="TagTpl">
{{# if (d.labels != "") { }}
{{# layui.each(d.labels.split(','), function(index, item){ }}
{{# if(index == 0) { }}
<span>{{ item }}</span>
{{# }else{ }}
<br><span>{{ item }}</span>
{{# } }}
{{# }); }}
{{# }else{ }}
<span></span>
{{# } }}
</script>
<script type="text/html" id="ipPortTpl">
<span>{{ d.svcIp }}:{{ d.svcPort }}</span>
</script>
<script>
var clusterId = getQueryString("clusterId");
if (clusterId == null) {
clusterId = getCookie("clusterId")
}
layui.use(['form', 'table','miniTab'], function () {
var $ = layui.jquery,
form = layui.form,
table = layui.table;
miniTab = layui.miniTab,
miniTab.listen();
table.render({
elem: '#currentTableId',
url: '/svc/v1/List?clusterId='+clusterId,
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', {
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips'
}],
parseData: function(res) { //实现加载全部数据后再分页
if(this.page.curr) {
result=res.data.slice(this.limit*(this.page.curr-1),this.limit*this.page.curr);
}else{
result=res.data.slice(0,this.limit);
}
return {
"code": res.code,
"msg":'',
"count":res.count,
"data":result
};
},
cols: [[
//{type: "checkbox", width: 50},
{field: 'serviceName',title: '名称', sort: true},
{field: 'nameSpace',title: '命名空间', sort: true},
{field: 'svcType', title: '类型', sort: true},
{field: 'svcIp', title: 'IP', sort: true,hide:true},
{field: 'svcPort', title: '端口', sort: true,hide:true},
{field: '', width:150,title: 'ip端口', sort: true,templet: '#ipPortTpl'},
{field: 'lanEndpoint',title: '内部端点', sort: true},
{field: 'wanEndpoint', title: '外部端点', sort: true,hide:true},
{field: 'labels', title: '标签', sort: true,templet: '#TagTpl'},
{field: 'createTime', title: '创建时间'},
{title: '操作', minWidth: 250, toolbar: '#currentTableBar', align: "center"}
]],
//size:'lg',
limits: [25, 50, 100],
limit: 25,
page: true
});
/**
* toolbar监听事件
*/
table.on('toolbar(currentTableFilter)', function (obj) {
if (obj.event === 'create') { // 监听添加操作
var index = layer.open({
title: '创建',
type: 2,
shade: 0.2,
maxmin:true,
shadeClose: true,
area: ['60%', '90%'],
content: '/page/xkube/serviceCreate.html?v=1',
//end: function(){
// window.parent.location.reload();//关闭open打开的页面时,刷新父页面
//}
});
$(window).on("resize", function () {
layer.full(index);
});
}
});
table.on('tool(currentTableFilter)', function (obj) {
var data = obj.data;
if (obj.event === 'viewYaml') {
var index = layer.open({
title: 'yaml',
type: 2,
shade: 0.2,
maxmin:true,
shadeClose: true,
area: ['45%', '92%'],
content: '/page/xkube/serviceYaml.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&serviceName='+data.serviceName,
});
$(window).on("resize", function () {
layer.full(index);
});
return false;
}
});
});
</script>
</body>
</html>
5.2.创建表单html代码
5.2 serviceCreate.html,放到views/front/page/xkube下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>创建</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="/css/public.css" media="all">
<script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script src="/js/xkube.js?v=1" charset="utf-8"></script>
<style>
body {
background-color: #ffffff;
}
</style>
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">当前集群</label>
<div class="layui-input-inline">
<select name="clusterId" lay-filter="cluster_Id" lay-search="" id="cluster_Id">
<option value="">请选择集群</option>
</select>
</div>
<label class="layui-form-label">命名空间</label>
<div class="layui-input-inline">
<select name="nameSpace" lay-filter="name_Space" lay-search="" id="name_Space">
</select>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">svc名称</label>
<div class="layui-input-inline">
<input type="text" name="serviceName" placeholder="不能为空" value="" class="layui-input">
</div>
<label class="layui-form-label">关联</label>
<div class="layui-input-inline">
<input type="text" name="deployName" placeholder="关联的deploy或sts" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">svc类型</label>
<div class="layui-input-inline">
<select name="svcType" lay-filter="svcType" lay-search="" id="svcType">
<option value="ClusterIp" selected="">ClusterIp</option>
<option value="NodePort">NodePort</option>
<option value="LoadBalancer">LoadBalancer</option>
</select>
</div>
<div id="isHeadlessTpl">
<div class="layui-input-inline">
<input type="checkbox" name="isHeadless" lay-skin="primary" lay-filter="isHeadless" title="实例间服务发现(Headless Service)">
</div>
</div>
</div>
<div class="serviceTpl">
<div class="layui-form-item">
<label class="layui-form-label">svc端口</label>
<div class="layui-input-inline" style="width:120px">
<input type="text" id="svc_portsName0" name="svc_portsName[]" placeholder="名称" value="" class="layui-input">
</div>
<div class="layui-input-inline" style="width:120px">
<input type="text" id="svc_port0" name="svc_port[]" placeholder="服务端口" value="" class="layui-input">
</div>
<div class="layui-input-inline" style="width:120px">
<input type="text" id="svc_targetPort0" name="svc_targetPort[]" placeholder="容器端口" value="" class="layui-input">
</div>
<div class="layui-input-inline" style="width:70px">
<select id="svc_protocol0" name="svc_protocol[]">
<option value="TCP" selected="">TCP</option>
<option value="UDP">UDP</option>
</select>
</div>
<div class="layui-input-inline" style="width:80px">
<button class="layui-btn layui-btn-normal" id="svcaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
</div>
</div>
</div>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>标签与注解</legend>
</fieldset>
<div class="labelsTpl">
<div class="layui-form-item">
<label class="layui-form-label">标签</label>
<div class="layui-input-inline" style="width:150px">
<input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
</div>
<div class="layui-input-inline" style="width:150px">
<input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
</div>
<div class="layui-input-inline" style="width:80px">
<button class="layui-btn layui-btn-normal" id="newaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
</div>
</div>
</div>
<!--
<div class="annotationTpl">
<div class="layui-form-item">
<label class="layui-form-label">注解</label>
<div class="layui-input-inline" style="width:240px">
<input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
</div>
<div class="layui-input-inline" style="width:240px">
<input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
</div>
<div class="layui-input-inline">
<button class="layui-btn layui-btn-normal" id="annotateaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
</div>
</div>
</div>
-->
<br>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
</div>
</div>
</form>
</div>
</div>
</body>
<script>
//标签删除
var TplIndex = 0;
function delTpl(id) {
TplIndex--;
$("#tpl-"+id).remove();
}
//服务删除
var sTplIndex = 0;
function delsTpl(id) {
sTplIndex--;
$("#stpl-"+id).remove();
}
layui.use(['form'], function () {
var form = layui.form,
layer = layui.layer,
$ = layui.$;
$("#isHeadlessTpl").show();
form.on('select(svcType)', function (data) {
if (data.value == "ClusterIp") {
$("#isHeadlessTpl").show();
}else{
$("#isHeadlessTpl").hide();
}
});
//labes add
$('#newaddbtn').on("click",function(){
TplIndex++;
var addTpl =
'<div class="layui-form-item" id="tpl-'+TplIndex+'">' +
'<label class="layui-form-label">标签</label>' +
'<div class="layui-input-inline" style="width:150px">' +
'<input type="text" name="labels_key[]" id="labels_key'+TplIndex+'" placeholder="key" value="" class="layui-input">' +
'</div>' +
'<div class="layui-input-inline" style="width:150px">' +
'<input type="text" name="labels_value[]" id="labels_value'+TplIndex+'" placeholder="value" value="" class="layui-input">' +
'</div>' +
'<div class="layui-input-inline" style="width:80px">' +
'<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:60px" type="button" id="newDelbtn" value="" οnclick="delTpl('+TplIndex+');" />' +
'</div>' +
'</div>'
$('.labelsTpl').append(addTpl);
form.render();
return false;
});
//service增加
$('#svcaddbtn').on("click",function(){
sTplIndex++;
var addTpl =
'<div class="layui-form-item" id="stpl-'+sTplIndex+'">' +
'<label class="layui-form-label">svc端口</label>' +
'<div class="layui-input-inline" style="width:120px">' +
'<input type="text" name="svc_portsName[]" id="svc_portsName'+sTplIndex+'" placeholder="名称" value="" class="layui-input">' +
'</div>' +
'<div class="layui-input-inline" style="width:120px">' +
'<input type="text" name="svc_port[]" id="svc_port'+sTplIndex+'" placeholder="服务端口" value="" class="layui-input">' +
'</div>' +
'<div class="layui-input-inline" style="width:120px">' +
'<input type="text" name="svc_targetPort[]" id="svc_targetPort'+sTplIndex+'" placeholder="容器端口" value="" class="layui-input">' +
'</div>' +
'<div class="layui-input-inline" style="width:70px">' +
'<select name="svc_protocol[]" id="svc_protocol'+sTplIndex+'" >' +
'<option value="TCP" selected="">TCP</option>' +
'<option value="UDP">UDP</option>' +
'</select>' +
'</div>' +
'<div class="layui-input-inline" style="width:80px">' +
'<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:60px" type="button" value="" οnclick="delsTpl('+sTplIndex+');" />' +
'</div>' +
'</div>'
$('.serviceTpl').append(addTpl);
form.render();
return false;
});
//监听提交
form.on('submit(saveBtn)', function (data) {
data.field.serviceName = data.field.serviceName.replace(/^\s*|\s*$/g,""); //替换空格
if (data.field.svcType != "ClusterIp") {
delete data.field.isHeadless;
}
//lables 处理
var labelsArry = [];
for (var i=0;i<=TplIndex;i++) {
//delete data.field.lables_key[i];
//delete data.field.labels_value[i];
var kk = document.getElementById("labels_key"+i).value;
var vv = document.getElementById("labels_value"+i).value;
if ( kk != "" && vv != "") {
labelsArry.push(
{
key:kk,
value:vv,
}
)
}
}
if (labelsArry.length > 0) {
data.field.lables = labelsArry;
}
//service 处理
var svcArry = [];
for (var i=0;i<=sTplIndex;i++) {
var kk = document.getElementById("svc_portsName"+i).value;
var vv = document.getElementById("svc_port"+i).value;
var ss = document.getElementById("svc_targetPort"+i).value;
var tt = document.getElementById("svc_protocol"+i).value;
if ( kk != "" && vv != "" && ss != "") {
svcArry.push(
{
portName:kk,
svcPort:vv,
targetPort:ss,
protocol:tt,
}
)
}
}
if (svcArry.length > 0) {
data.field.ports = svcArry;
}
console.log(data.field);
layer.confirm('确定添加?', {icon: 3, title:'提示',yes: function(index){
var index2 = layer.load(0, {shade: false});
layer.msg('稍等片刻');
$.ajax({
url: "/svc/v1/Create",
type: "post",
data: JSON.stringify(data.field),
dataType: "json",
success: function (resp) {
layer.close(index2);
if(resp.code == 0){
layer.msg('添加成功', {icon: 1});
//window.location.reload();
}else{
layer.msg(resp.msg,{icon:2});
}
}
});
},
cancel: function(index, layero){
layer.close(index);
layer.close(index2);
console.log("不操作");
}
});
return false;
});
});
</script>
</body>
</html>
5.3.显示yaml配置的html代码
5.3 serviceYaml.html,放到views/front/page/xkube下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>yaml编辑</title>
<meta name="renderer" content="webkit">
<link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="/css/public.css" media="all">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link
rel="stylesheet"
data-name="vs/editor/editor.main"
href="/monaco-editor/min/vs/editor/editor.main.css"
/>
<script src="/js/xkube.js?v=1" charset="utf-8"></script>
<script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<div id="container" style="width: 100%; height:460px; border: 1px solid grey"></div>
<br>
<fieldset class="table-search-fieldset">
<legend></legend>
<button class="layui-btn layui-btn-sm" id="SaveBtn" >保存更新</button>
<button class="layui-btn layui-btn-normal layui-btn-sm" id="DownLoadBtn">下载</button>
</fieldset>
</div>
</div>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
var require = { paths: { vs: '/monaco-editor/min/vs' } };
</script>
<script src="/monaco-editor/min/vs/loader.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.js"></script>
<script>
layui.use(['form', 'table','code'], function () {
var $ = layui.jquery;
var form = layui.form,
layer = layui.layer;
var editor = monaco.editor.create(document.getElementById('container'), {
value: '',
language: 'yaml',
//automaticLayout: true,
minimap: {enabled: false},
wordWrap: 'on',
theme: 'vs-dark'
});
$.ajax({
url: "/svc/v1/Yaml"+location.search,
type: "GET",
success: function (resp) {
//codeyaml = resp;
editor.setValue(resp);
}
});
$('#SaveBtn').on("click",function(){
var yamlBody = editor.getValue();
yamlBody = yamlBody.replace(/%/g,"%25");
layer.confirm('确定修改?', {icon: 3, title:'提示',yes: function(index){
$.ajax({
url: "/svc/v1/ModifyByYaml"+location.search,
type: "POST",
data: yamlBody,
dataType: "json",
success: function (resp) {
layer.msg(resp.msg);
console.log(resp);
}
});
//console.log(editor.getValue());
//layer.msg('ok');
},
cancel: function(index, layero){
layer.close(index);
}
});
});
$('#DownLoadBtn').on("click",function(){
var serviceName = getQueryString("serviceName");
ExportRaw(serviceName+'.yaml',editor.getValue());
});
});
</script>
</body>
</html>
六.完整代码
6.1.控制器service.go的完整代码
6.1 service.go,放到contrillers下
// service.go
package controllers
import (
//"encoding/json"
//"fmt"
"log"
m "myk8s/models"
"strings"
beego "github.com/beego/beego/v2/server/web"
)
type SvcController struct {
beego.Controller
}
func (this *SvcController) List() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
serviceName := this.GetString("serviceName")
labels := this.GetString("labels")
labelsKV := strings.Split(labels, ":")
var labelsKey, labelsValue string
if len(labelsKV) == 2 {
labelsKey = labelsKV[0]
labelsValue = labelsKV[1]
}
svcList, err := m.SvcList(clusterId, nameSpace, serviceName, labelsKey, labelsValue)
msg := "success"
code := 0
count := len(svcList)
if err != nil {
log.Println(err)
msg = err.Error()
code = -1
}
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &svcList}
this.ServeJSON()
}
func (this *SvcController) Create() {
clusterId := this.GetString("clusterId")
code := 0
msg := "success"
//log.Println(string(this.Ctx.Input.RequestBody))
err := m.SvcCreate(clusterId, this.Ctx.Input.RequestBody)
if err != nil {
code = -1
msg = err.Error()
log.Printf("[ERROR] configmap Create Fail:%s\n", err)
}
this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
this.ServeJSON()
}
func (this *SvcController) ModifyByYaml() {
clusterId := this.GetString("clusterId")
//nameSpace := gp.Get("nameSpace").String()
log.Println(string(this.Ctx.Input.RequestBody))
bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
err := m.SvcYamlModify(clusterId, bodyByte)
if err != nil {
log.Println(err)
}
this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
this.ServeJSON()
}
func (this *SvcController) Yaml() {
clusterId := this.GetString("clusterId")
nameSpace := this.GetString("nameSpace")
serviceName := this.GetString("serviceName")
yamlStr, _ := m.GetSvcYaml(clusterId, nameSpace, serviceName)
this.Ctx.WriteString(yamlStr)
}
6.2.模型serviceModel.go的完整代码
6.2 serviceModel.go,放到models下
// serviceModel.go
package models
import (
"context"
"fmt"
"log"
"myk8s/common"
"strings"
"encoding/json"
"github.com/tidwall/gjson"
//"k8s.io/apimachinery/pkg/api/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/yaml"
)
type Service struct {
ServiceName string `json:"serviceName"` //服务名称
NameSpace string `json:"nameSpace"`
Labels string `json:"labels"`
SvcType string `json:"svcType"`
SvcIp string `json:"svcIp"`
SvcPort string `json:"svcPort"`
LanEndpoint string `json:"lanEndpoint"` //内部端点
WanEndpoint string `json:"wanEndpoint"` //外部端点
CreateTime string `json:"createTime"`
}
type ServicePort struct {
PortName string `json:"portName"`
SvcPort int32 `json:"svcPort"`
TargetPort int32 `json:"targetPort"`
}
func SvcList(kubeconfig, namespace, serviceName string, labelsKey, labelsValue string) ([]Service, error) {
clientset := common.ClientSet(kubeconfig)
if namespace == "" {
//namespace = corev1.NamespaceDefault
namespace = corev1.NamespaceAll
}
//var selector labels.Selector
var listOptions = metav1.ListOptions{}
if labelsKey != "" && labelsValue != "" {
listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
}
svcList, err := clientset.CoreV1().Services(namespace).List(context.TODO(), listOptions)
if err != nil {
log.Printf("list service error:%v\n", err)
}
//fmt.Println("svc count:", len(svcList.Items))
var bbb = make([]Service, 0)
for _, svc := range svcList.Items {
//搜索
if serviceName != "" {
if !strings.Contains(svc.Name, serviceName) {
continue
}
}
//fmt.Printf("name: %s\n", svc.Name)
var labelsStr string
for kk, vv := range svc.ObjectMeta.Labels {
labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
}
if len(labelsStr) > 0 {
labelsStr = labelsStr[0 : len(labelsStr)-1]
}
var wanEndPoint string
svcType := fmt.Sprintf("%s", svc.Spec.Type)
if svcType == "LoadBalancer" {
wanEndPoint = fmt.Sprintf("%s:%d", svc.Status.LoadBalancer.Ingress[0].IP, svc.Spec.Ports[0].Port)
}
lanEndPoint := ""
var svcPort string
if len(svc.Spec.Ports) > 0 {
vsapp := svc.Spec.Selector["app"]
if vsapp == "" {
vsapp = svc.Spec.Selector["k8s-app"]
}
for _, vv := range svc.Spec.Ports {
//lanEndPoint += fmt.Sprintf("%s:%s:%s\n", svc.Spec.Selector["app"], svc.Spec.Ports[0].TargetPort.StrVal, svc.Spec.Ports[0].Protocol)
//svcPort += fmt.Sprintf("%s:", svc.Spec.Ports[0].Port)
var vport string
if vv.TargetPort.Type == 0 {
vport = fmt.Sprintf("%d", vv.TargetPort.IntVal)
} else {
vport = fmt.Sprintf("%s", vv.TargetPort.StrVal)
}
lanEndPoint += fmt.Sprintf("%s:%s:%s,", vsapp, vport, vv.Protocol)
svcPort += fmt.Sprintf("%d,", vv.Port)
}
if len(svcPort) > 0 {
svcPort = svcPort[0 : len(svcPort)-1]
}
lanEndPoint = lanEndPoint[0 : len(lanEndPoint)-1]
}
Items := &Service{
ServiceName: svc.Name,
NameSpace: svc.Namespace,
SvcType: svcType,
SvcIp: svc.Spec.ClusterIP,
Labels: labelsStr,
SvcPort: svcPort,
LanEndpoint: lanEndPoint,
WanEndpoint: wanEndPoint,
CreateTime: svc.CreationTimestamp.Format("2006-01-02 15:04:05"),
}
bbb = append(bbb, *Items)
}
return bbb, err
}
func SvcCreate(kubeconfig string, bodys []byte) error {
gp := gjson.ParseBytes(bodys)
clusterId := gp.Get("clusterId").String()
if kubeconfig == "" {
kubeconfig = clusterId
}
serviceName := gp.Get("serviceName").String()
nameSpace := gp.Get("nameSpace").String()
svcType := gp.Get("svcType").String()
deployName := gp.Get("deployName").String()
isHeadless := gp.Get("isHeadless").Str
var labelsMap = make(map[string]string)
labelsMap["app"] = serviceName
for _, vv := range gp.Get("lables").Array() {
labelsMap[vv.Get("key").Str] = vv.Get("value").Str
}
selectApp := serviceName
if deployName != "" {
selectApp = deployName
}
var serviceType corev1.ServiceType
switch svcType {
case "NodePort":
serviceType = corev1.ServiceTypeNodePort
case "LoadBalancer":
serviceType = corev1.ServiceTypeLoadBalancer
default:
serviceType = corev1.ServiceTypeClusterIP
}
if isHeadless == "on" {
serviceType = corev1.ServiceTypeClusterIP
}
svcInstance := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: nameSpace,
Labels: labelsMap,
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": selectApp,
},
Type: serviceType,
},
}
if isHeadless == "on" {
svcInstance.Spec.ClusterIP = corev1.ClusterIPNone
}
ports := gp.Get("ports").Array()
var svcPorts = make([]corev1.ServicePort, 0)
for _, vv := range ports {
var svcProtocol corev1.Protocol
if vv.Get("protocol").Str == "UDP" {
svcProtocol = corev1.ProtocolUDP
} else {
svcProtocol = corev1.ProtocolTCP
}
svcPort := &corev1.ServicePort{
Name: vv.Get("portName").Str,
Port: int32(vv.Get("svcPort").Int()),
Protocol: svcProtocol,
TargetPort: intstr.FromInt32(int32(vv.Get("targetPort").Int())),
}
svcPorts = append(svcPorts, *svcPort)
}
svcInstance.Spec.Ports = svcPorts
clientset := common.ClientSet(kubeconfig)
_, err := clientset.CoreV1().Services(nameSpace).Create(context.TODO(), svcInstance, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
func SvcYamlModify(kubeconfig string, yamlData []byte) error {
data, err := yamlutil.ToJSON(yamlData)
if err != nil {
return err
}
service := &corev1.Service{}
err = json.Unmarshal(data, service)
if err != nil {
return err
}
namespace := service.ObjectMeta.Namespace
serviceName := service.ObjectMeta.Name
clientset := common.ClientSet(kubeconfig)
_, err = clientset.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
if err != nil {
return err
}
fmt.Println(namespace, serviceName)
return err
}
func GetSvcYaml(kubeconfig, namespace, serviceName string) (string, error) {
servicesClient := common.ClientSet(kubeconfig).CoreV1().Services(namespace)
service, err := servicesClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
serviceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(service)
if err != nil {
return "", err
}
yamlBytes, err := yaml.Marshal(serviceUnstructured)
if err != nil {
return "", err
}
return string(yamlBytes), nil
}
七.效果图
更多推荐
所有评论(0)