✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:云原生开发
景天的主页:景天科技苑
文章目录
- namespace管理的后端开发设计与实现
- 1、设计理念
- 2、对增删改查的逻辑进行实现
- 3、创建命名空间
- 4、删除命名空间
- 5、查询命名空间列表
- 6、查询命名空间详情
- 7、更新namespace
namespace管理的后端开发设计与实现
1、设计理念
名称空间的增删改查,逻辑和集群的差不多,我们直接将集群的复制代码过来,改改
启动程序,通过postman先测试下接口
接口可以跑通
2、对增删改查的逻辑进行实现
如果对集群中的namespace进行管理,和上一章我们做的集群管理还是有区别的。
- 首先要知道对哪个集群中的哪个namespace进行管理,我们在管理集群的时候,我们需要接收clusterid的参数,管理namespace也同样需要clusterid
- kubeconfig的获取,之前我们把集群的信息都放到secret中,因此我们需要根据clusterid查出来是哪个secret,然后通过secret的data字段,得到kubeconfig,然后通过kubeconfig实例化出一个clientset。然后通过clientset就可以对集群的资源进行操作了。
- 但是,我们每次接收到clusterid的时候,都要经过第二步的整个流程,这个步骤是个需要重复使用的步骤,如果我们把kubeconfig存放在一个map[string]string变量中,{clusterId:xxx, name: xxxx, namespace:xxx}
我们拿到clusterid之后,直接就可以拿到kubeconfig文件了。 - 创建clientset
- 操作namespace
所以,经过上述分析,我们在config.go中创建个ClusterKubeconfig变量
我们希望程序启动时,就初始化该变量
//程序启动时,就初始化初始化ClusterKubeconfig
config.ClusterKubeconfig = make(map[string]string)
// 查询当前已经存在集群配置
listOptions := metav1.ListOptions{
LabelSelector: config.ClusterConfigSecretLabelKey + "=" + config.ClusterConfigSecretLabelValue,
}
secretList, _ := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions)
for _, secret := range secretList.Items {
// clusterId
clusterId := secret.Name
kubeconfig := secret.Data["kubeconfig"] //得到的是字节,需要转换成字符串
// clusterid作为key,kubeconfig作为value,存到config.ClusterKubeconfig之中
config.ClusterKubeconfig[clusterId] = string(kubeconfig)
}
// 打印一个debug
//fmt.Println("当前集群配置:", config.ClusterKubeconfig)
logs.Debug(map[string]interface{}{"当前已存在的集群": config.ClusterKubeconfig}, "展示已存在的集群配置")
然后在添加,更新,删除集群时,都要针对这个变量做相应的操作
添加,更新集群
删除集群
3、创建命名空间
我们想象一下,如果想要前端操作创建一个命名空间,需要传递给后端什么数据呢?
是不是类似这样 {clusterId:xxx, name: xxxx, namespace:xxx…}
因此,我们需要创建个结构体来接收前端传来的数据,不管我们操作namespace、pod、deployment、daemonset等等,貌似都需要这些数据。
所以说,我们可以定义一个最基础的信息basicInfo来存放一些公用字段。这些公共信息可以在很多控制器中使用。
我们将这些公共信息与方法定义在controllers.go中
// Package controllers 控制器层,实现路由的逻辑处理
package controllers
import (
"errors"
"github.com/gin-gonic/gin"
"jingtian/krm-backend/config"
"jingtian/krm-backend/utils/logs"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// BasicInfo 定义全局的数据结构
type BasicInfo struct {
//使用form标签指定参数名,以便正确地绑定参数 get请求使用功能form标签
ClusterId string `json:"clusterId" form:"clusterId"`
Namespace string `json:"namespace" form:"namespace"`
Name string `json:"name" form:"name"`
Item interface{} `json:"item"`
DeleteList []string `json:"deleteList"`
}
// BasicInit 这个函数用来返回通用的clientset,集群信息和error
func BasicInit(c *gin.Context, item interface{}) (clientset *kubernetes.Clientset, basicInfo BasicInfo, err error) {
basicInfo = BasicInfo{}
basicInfo.Item = item
// 首先获取请求的类型,根据不同的请求类型来使用不同的方式绑定数据
requestMethod := c.Request.Method
if requestMethod == "GET" {
err = c.ShouldBindQuery(&basicInfo)
} else if requestMethod == "POST" {
err = c.ShouldBindJSON(&basicInfo)
} else {
err = errors.New("不支持的请求类型")
}
logs.Debug(map[string]interface{}{"basicInfo": basicInfo}, "数据绑定结果")
if err != nil {
msg := "请求出错: " + err.Error()
return nil, basicInfo, errors.New(msg)
}
if basicInfo.Namespace == "" {
basicInfo.Namespace = "default"
}
// 获取kubeconfig
kubeconfig := config.ClusterKubeconfig[basicInfo.ClusterId]
restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig))
if err != nil {
msg := "解析kubeconfig错误: " + err.Error()
return nil, basicInfo, errors.New(msg)
}
//创建客户端
clientset, err = kubernetes.NewForConfig(restConfig)
if err != nil {
msg := "创建clientset失败: " + err.Error()
return nil, basicInfo, errors.New(msg)
}
return clientset, basicInfo, nil
}
postman请求创建namespace
创建成功
4、删除命名空间
删除命名空间的危险系数是非常高的,要是在生产中,稍不留意删除了K8S系统自带的一些命名空间,将会造成无法挽回的损失。
因此我们删除namespace一定要谨慎,我们在开发时,也可以做一些规避,避免用户不小心删除掉关键命名空间。
我们做一些删除保护,当用户要删除kube-system或者krm时,禁止执行该操作,防止对集群造成不必要的风险。
删除namespace的时候,后端需要接收前端传来的clusterid和name。
delete.go
package namespace
import (
"context"
"github.com/gin-gonic/gin"
"jingtian/krm-backend/config"
"jingtian/krm-backend/controllers"
"jingtian/krm-backend/utils/logs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Delete 删除命名空间
func Delete(c *gin.Context) {
//删除namespace是非常危险的操作,一定要谨慎
logs.Debug(nil, "删除namespace")
clientset, basicInfo, err := controllers.BasicInit(c, nil)
returnData := config.NewReturnData()
if err != nil {
returnData.Msg = err.Error()
returnData.Status = 400
c.JSON(200, returnData)
return
}
//规避删除kube-system系统命名空间
if basicInfo.Name == "kube-system" || basicInfo.Name == "krm" {
returnData.Msg = "禁止删除系统命名空间kube-system,元数据命名空间krm"
returnData.Status = 400
c.JSON(200, returnData)
logs.Error(nil, "系统命名空间kube-system不允许删除")
return
}
//执行删除操作
err = clientset.CoreV1().Namespaces().Delete(context.TODO(), basicInfo.Name, metav1.DeleteOptions{})
if err != nil {
msg := "删除Namespace失败: " + err.Error()
returnData.Msg = msg
returnData.Status = 400
logs.Error(map[string]interface{}{"message": msg}, basicInfo.Name+" 删除失败")
} else {
returnData.Msg = "删除成功"
logs.Info(map[string]interface{}{"message": "namespace " + basicInfo.Name + " 删除成功"}, "删除成功")
}
c.JSON(200, returnData)
}
postman发送删除请求,删除成功
当用户要删除krm命名空间,提示用户不能删除
5、查询命名空间列表
查询命名空间列表,也是需要先拿到clientset
package namespace
import (
"context"
"github.com/gin-gonic/gin"
"jingtian/krm-backend/config"
"jingtian/krm-backend/controllers"
"jingtian/krm-backend/utils/logs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
)
func List(c *gin.Context) {
logs.Debug(nil, "列出namespace列表")
clientset, _, err := controllers.BasicInit(c, nil)
returnData := config.NewReturnData()
if err != nil {
returnData.Msg = err.Error()
returnData.Status = 400
c.JSON(http.StatusOK, returnData)
return
}
//执行查询操作
namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
msg := "查询Namespace失败: " + err.Error()
returnData.Msg = msg
returnData.Status = 400
} else {
returnData.Msg = "查询成功"
returnData.Data["items"] = namespaceList.Items
//如果只想查询命名空间的名字,可以如下循环获得
//var nameList []string
//for _, namespace := range namespaceList.Items {
// namestr := namespace.Name
// nameList = append(nameList, namestr)
//}
//returnData.Data["items"] = nameList
}
c.JSON(200, returnData)
}
查询的时候,需要传参clusterId,查询哪个集群的命名空间
6、查询命名空间详情
package namespace
import (
"context"
"github.com/gin-gonic/gin"
"jingtian/krm-backend/config"
"jingtian/krm-backend/controllers"
"jingtian/krm-backend/utils/logs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
)
func Get(c *gin.Context) {
logs.Debug(nil, "获取namespace详情")
clientset, basicInfo, err := controllers.BasicInit(c, nil)
returnData := config.NewReturnData()
if err != nil {
returnData.Msg = err.Error()
returnData.Status = 400
c.JSON(http.StatusOK, returnData)
return
}
namespace, err := clientset.CoreV1().Namespaces().Get(context.TODO(), basicInfo.Name, metav1.GetOptions{})
if err != nil {
msg := "获取Namespace详情失败: " + err.Error()
returnData.Msg = msg
returnData.Status = 400
} else {
returnData.Msg = "获取成功"
returnData.Data["item"] = namespace
}
c.JSON(200, returnData)
}
postman请求,获取一个不存在的namespace
获取一个存在的namespace
7、更新namespace
更新命名空间一般是更新labels或者annotations,其他字段一般也不需要更新
后端接收一个namespace实例,做下update操作即可。
更新namespace和create namespace差不多
我们在BasicInfo中,定义一个变量Item用来接收前端传来的用于更新namespace的json串
这里为什么用空接口类型呢?因为后续我们更新deployment,pod,statefulset等都可以用到该字段,所以不能写死
package namespace
import (
"context"
"github.com/gin-gonic/gin"
"jingtian/krm-backend/config"
"jingtian/krm-backend/controllers"
"jingtian/krm-backend/utils/logs"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Update(c *gin.Context) {
logs.Debug(nil, "更新namespace")
//先定义一个namespace变量
var ns corev1.Namespace
//创建clientset对象,将namespace指针传进去
clientset, _, err := controllers.BasicInit(c, &ns)
returnData := config.NewReturnData()
if err != nil {
returnData.Msg = err.Error()
returnData.Status = 400
c.JSON(200, returnData)
return
}
//执行更新操作
//更新直接用basicinfo中的Item是不行的,因为它是interface类型的,
_, err = clientset.CoreV1().Namespaces().Update(context.TODO(), &ns, metav1.UpdateOptions{})
if err != nil {
msg := "更新Namespace失败: " + err.Error()
returnData.Msg = msg
returnData.Status = 400
} else {
returnData.Msg = "更新成功"
}
c.JSON(200, returnData)
}
发送更新的请求,将需要更新的namespace字段放入到item中传进去
添加一些标签,和annotations
"item": {
"metadata": {
"name":"jingtian",
"labels": {
"kubernetes.io/metadata.name": "jingtian",
"ceshi":"ceshilabel"
},
"annotations":{
"kubernetes.io/metadata.name": "true"
}
}
}
k8s集群查看更新成功
更新的时候,与其他几个在增删查方法不同的地方在于,需要将更新的资源传递给basicInfo,然后接收前端传参给item。再执行更新操作。