【云原生开发】namespace管理的后端开发设计与实现
✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,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 {// clusterIdclusterId := secret.Namekubeconfig := 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 controllersimport ("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.Methodif 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"}// 获取kubeconfigkubeconfig := 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 namespaceimport ("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 = 400c.JSON(200, returnData)return}//规避删除kube-system系统命名空间if basicInfo.Name == "kube-system" || basicInfo.Name == "krm" {returnData.Msg = "禁止删除系统命名空间kube-system,元数据命名空间krm"returnData.Status = 400c.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 = msgreturnData.Status = 400logs.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 namespaceimport ("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 = 400c.JSON(http.StatusOK, returnData)return}//执行查询操作namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})if err != nil {msg := "查询Namespace失败: " + err.Error()returnData.Msg = msgreturnData.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 namespaceimport ("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 = 400c.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 = msgreturnData.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 namespaceimport ("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 = 400c.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 = msgreturnData.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。再执行更新操作。