【k8s多集群管理平台开发实践】四、client-go实现deployment列表、创建deployment、读取yaml配置、重启deployment
通过client-go、beego实现deployment列表、创建deployment、读取yaml配置、重启deployment这四个功能,再结合layui、layuimini模板实现也没展,功能只要是由控制器、模型、路由、前端代码几部分组成,现将各个模块的功能进行分布讲解。
文章目录
简介
本章节主要讲解通过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"></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="" ο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 }
八.效果图
更多推荐
所有评论(0)