0


【云原生开发】K8S集群管理后端开发设计与实现

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:云原生开发
景天的主页:景天科技苑

在这里插入图片描述

文章目录

K8S集群管理后端开发设计与实现

集群管理包含在前端添加,更新,删除,查询所有集群,查询集群详情等功能实现。

1、incluster命名空间的检测与创建

根据之前的架构规划,我们的元数据存储在inCluster这个K8S集群中的krm名称空间,程序启动的时候,先检查krm这个命名空间是否存在,不存在的话我们就创建这个命名空间
在这里插入图片描述

设个默认值,并可以通过环境变量获取
在这里插入图片描述

检查命名空间需要在程序运行前执行,因此我们需要创建个controller,来做初始化检查操作

package initcontroller

import(_"jingtian/krm-backend/config"//调用里面的init函数,将日志格式初始化"jingtian/krm-backend/utils/logs")// 只写个init函数,用来检查配置funcinit(){//这里面需要初始化incluster的kubeconfig,创建客户端。创建元数据的命名空间
    logs.Debug(nil,"初始化incluster数据...")// 1. 通过kubeconfig创建client-go客户端// 2. 检查元数据命名空间是否创建,如有,提醒下元数据命名空间未创建。如没有,就创建命名空间MetadataInit()}

在这里插入图片描述

在initcontroller包里面,创建个initcluster.go,用来检测命名空间是否创建,如未创建,即刻创建。

package initcontroller

import("context""jingtian/krm-backend/config""jingtian/krm-backend/utils/logs"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/client-go/kubernetes""k8s.io/client-go/tools/clientcmd")funcMetadataInit(){
    logs.Debug(nil,"初始化元数据命名空间")// 1. 初始化config实例// masterUrl就是离我们主节点的ip地址和端口号,我们在kubeconfig文件中有了,所以可以省略
    kubeconfig, err := clientcmd.BuildConfigFromFlags("","meta.kubeconfig")//要想正常应用我们的服务,必须能够实例化成功kubeconfig,要不然后面所有的功能都无法使用,所以这里直接报panic即可if err !=nil{
        logs.Error(map[string]interface{}{"msg": err.Error()},"inCluster kubeconfig加载失败")panic(err.Error())}// 2. 创建客户端工具 create the clientset
    clientset, err := kubernetes.NewForConfig(kubeconfig)//这个客户端工具如果生成失败的话,后面的操作也无法完成,所以这里也报panic即可if err !=nil{
        logs.Error(map[string]interface{}{"msg": err.Error()},"inCluster客户端创建失败")panic(err.Error())}// 获取K8S的版本号// ServerVersion() (*version.Info, error)
    inClusterVersion,_:= clientset.Discovery().ServerVersion()// 3.检查命名空间是否存在_, err = clientset.CoreV1().Namespaces().Get(context.TODO(), config.MetadataNamespace, metav1.GetOptions{})if err !=nil{// 不存在元数据命名空间
        logs.Info(nil,"元数据命名空间不存在,准备创建....")// 创建元数据命名空间var metadataNamespace corev1.Namespace
        metadataNamespace.Name = config.MetadataNamespace
        _, err = clientset.CoreV1().Namespaces().Create(context.TODO(),&metadataNamespace, metav1.CreateOptions{})if err !=nil{
            logs.Error(map[string]interface{}{"msg": err.Error()},"元数据命名空间创建失败")panic(err.Error())}

        logs.Info(map[string]interface{}{"Namespace": config.MetadataNamespace,"inCluster版本": inClusterVersion.String()},"元数据命名空间创建成功")}else{// 已经存在namespace
        logs.Info(map[string]interface{}{"Namespace": config.MetadataNamespace,"inCluster版本": inClusterVersion.String()},"元数据命名空间已存在")}}

在这里插入图片描述

运行程序,元数据命名空间创建成功
在这里插入图片描述

在K8S集群查看命名空间,可见命名空间创建成功
在这里插入图片描述

2、集群管理的路由配置

我们得路由要在routers目录下去管理
在这里插入图片描述

并且通过routers.go文件实现路由的注册
在这里插入图片描述

创建集群管理的路由和控制器

routers.go

// Package routers 路由层 管理程序的路由信息package routers

import("github.com/gin-gonic/gin""jingtian/krm-backend/routers/auth""jingtian/krm-backend/routers/cluster")// RegisterRouters 需要将main.go里面的路由引擎r传过来// 写个注册路由的方法funcRegisterRouters(r *gin.Engine){//登录的路由配置//1. 登录: login//2. 登出: loginout//3. 路由分组 /api/auth/login  /api/auth/loginout
    apiGroup := r.Group("/api")
    auth.RegisterSubRouter(apiGroup)
    cluster.RegisterSubRouter(apiGroup)}

在这里插入图片描述

cluster.go

package cluster

import("github.com/gin-gonic/gin""jingtian/krm-backend/controllers/cluster")// 实现添加集群的接口funcadd(authGroup *gin.RouterGroup){//具体逻辑写到控制器controller里面
    authGroup.POST("/add", cluster.Add)}// 实现更新集群的接口funcupdate(authGroup *gin.RouterGroup){//具体逻辑写到控制器controller里面
    authGroup.POST("/update", cluster.Update)}// 删除集群funcdeleteCluster(clusterGroup *gin.RouterGroup){
    clusterGroup.GET("/delete", cluster.DeleteCluster)}// 获取集群信息funcget(clusterGroup *gin.RouterGroup){
    clusterGroup.GET("/get", cluster.Get)}// 列出所有集群funclist(clusterGroup *gin.RouterGroup){
    clusterGroup.GET("/list", cluster.List)}// RegisterSubRouter 认证子路由funcRegisterSubRouter(g *gin.RouterGroup){//配置登录功能路由策略
    clusterGroup := g.Group("/cluster")add(clusterGroup)update(clusterGroup)deleteCluster(clusterGroup)get(clusterGroup)list(clusterGroup)}

在这里插入图片描述

控制器
在这里插入图片描述

由于除了登录和登出的其他接口,都要携带token才能访问,所以我们先登录,生成token,携带token发出请求
在这里插入图片描述

携带token请求测试
在这里插入图片描述

请求能走到添加集群的控制器
在这里插入图片描述

3、实现添加更新集群功能

怎么实现添加集群呢? 前端可能会让我们输入一些集群的信息,比如集群的名字,集群的ID,kubeconfig等。前端把这些信息提交给后端,然后后端再根据这些信息来创建集群
gin框架可以将前端传来的json数据和结构体进行绑定,然后通过结构体进行创建资源等其他操作。

我们先创建个结构体,来声明创建一个集群所需要的字段。我们将结构体的定义放在controller/cluster.go中

package cluster

import("k8s.io/client-go/kubernetes""k8s.io/client-go/tools/clientcmd")// MyClusterInfo 主要用于查询操作type MyClusterInfo struct{
    Id          string`json:"id"`
    DisplayName string`json:"displayName"`//集群的别名
    City        string`json:"city"`
    District    string`json:"district"`}// MyClusterStatus 定义一个结构体,用于描述集群的状态// 集群可用,状态就是Active 不可用,状态就是InActivetype MyClusterStatus struct{
    MyClusterInfo
    Version string`json:"version"`
    Status  string`json:"status"`}// MyClusterConfig 定义一个结构体,用于描述创建集群所用的配置信息type MyClusterConfig struct{
    MyClusterInfo
    Kubeconfig string`json:"kubeconfig"`//集群的配置资源}// GetClusterStatus 结构体的方法,用于判断集群的状态func(cfg *MyClusterConfig)GetClusterStatus()(MyClusterStatus,error){// 判断集群是否是正常
    clusterStatus := MyClusterStatus{}
    clusterStatus.MyClusterInfo = cfg.MyClusterInfo
    //此时,需要检测集群实时状态,所以需要重新连接集群// 创建一个clientset,从前端传来的kubeconfig字符串来连接集群,这也是out-cluster方式创建客户端// func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error)// 接收的参数是字节类型
    restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(cfg.Kubeconfig))if err !=nil{
        clusterStatus.Version ="Unavailable Version"
        clusterStatus.Status ="InActive"return clusterStatus, err
    }
    clientset, err := kubernetes.NewForConfig(restConfig)if err !=nil{
        clusterStatus.Version ="Unavailable Version"
        clusterStatus.Status ="InActive"return clusterStatus, err
    }//客户端工具创建成功后,获取版本信息
    serverVersion, err := clientset.Discovery().ServerVersion()if err !=nil{return clusterStatus, err
    }//集群正常就将集群的版本和状态返回
    clusterVersion := serverVersion.String()
    clusterStatus.Version = clusterVersion
    clusterStatus.Status ="Active"return clusterStatus,nil}

在这里插入图片描述

由于添加和更新集群,所用的参数差不多,所以我们将添加和更新写到一个函数中AddOrUpdate.go,根据传参,来区分是添加还是更新

package cluster

import("context""fmt""github.com/gin-gonic/gin""jingtian/krm-backend/config""jingtian/krm-backend/utils""jingtian/krm-backend/utils/logs"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""net/http")// method: update,  createfuncaddOrUpdate(c *gin.Context, method string){var arg stringif method =="Create"{
        arg ="添加"}elseif method =="Update"{
        arg ="更新"}// 声明一个集群配置
    clusterConfig := MyClusterConfig{}
    returnData := config.NewReturnData()if err := c.ShouldBindJSON(&clusterConfig); err !=nil{
        msg := arg +"集群的配置信息不完整: "+ err.Error()
        returnData.Status =400
        returnData.Msg = msg
        c.JSON(200, returnData)return}// 判断集群是否正常,写个函数去连接集群,如果能获取到集群的版本,说明集群是正常的// 这个函数写在哪呢,应该写在MyClusterConfig这个结构体的方法中
    clusterStatus, err := clusterConfig.GetClusterStatus()if err !=nil{
        msg :="无法获取集群信息: "+ err.Error()
        returnData.Status =400
        returnData.Msg = msg
        c.JSON(http.StatusOK, returnData)
        logs.Error(map[string]interface{}{"error": err.Error()}, arg+"集群失败,无法获取集群信息")return}
    logs.Info(map[string]interface{}{"集群名称": clusterConfig.DisplayName,"集群ID": clusterConfig.Id},"开始"+arg+"集群")// 创建一个集群配置的secret 保存集群信息var clusterConfigSecret corev1.Secret
    clusterConfigSecret.Name = clusterConfig.Id
    clusterConfigSecret.Labels =make(map[string]string)
    clusterConfigSecret.Labels[config.ClusterConfigSecretLabelKey]= config.ClusterConfigSecretLabelValue

    // 添加注释,保存集群的配置信息
    clusterConfigSecret.Annotations =make(map[string]string)// 把集群的状态结构体转成map。我们专门写个工具函数来实现结构体与map的转换
    m, err := utils.Struct2Map(clusterStatus)if err !=nil{
        logs.Error(nil, err.Error())return}
    clusterConfigSecret.Annotations = m

    // 保存kubeconfig,我们保存到StringData里面的。我们点进去secret查看//里面有Data和StringData两种,Data是需要加密的才能使用,StringData我们可以传字符串,当我们查看secret的时候自动加密//Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`//StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,rep,name=stringData"`//如果在 data 和 stringData 中设置了同一个字段,则使用来自 stringData 中的值

    clusterConfigSecret.StringData =make(map[string]string)
    clusterConfigSecret.StringData["kubeconfig"]= clusterConfig.Kubeconfig

    // 创建secretif method =="Create"{_, err = config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Create(context.TODO(),&clusterConfigSecret, metav1.CreateOptions{})}elseif method =="Update"{//更新_, err = config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Update(context.TODO(),&clusterConfigSecret, metav1.UpdateOptions{})}if err !=nil{// 说明创建失败
        logs.Error(map[string]interface{}{"集群ID": clusterConfig.Id,"集群名字:": clusterConfig.DisplayName,"msg": err.Error()},"集群"+arg+"失败")
        msg := arg +"集群失败: "+ err.Error()
        returnData.Msg = msg
        returnData.Status =400
        c.JSON(200, returnData)return}//创建or更新成功//map需要先初始化
    config.ClusterKubeconfig =make(map[string]string)
    config.ClusterKubeconfig[clusterConfig.Id]= clusterConfig.Kubeconfig
    fmt.Println("当前集群配置:", config.ClusterKubeconfig)
    logs.Info(map[string]interface{}{"集群ID": clusterConfig.Id,"集群名字:": clusterConfig.DisplayName},"集群"+arg+"成功")
    returnData.Status =200
    returnData.Msg = arg +"成功"
    c.JSON(200, returnData)}

在这里插入图片描述

add.go中
在这里插入图片描述

update.go中
在这里插入图片描述

4、 结构体转化成map工具包

结构体转换成map工具类utils/utils.go

// Package utils 工具层package utils

import"encoding/json"funcStruct2Map(s interface{})(map[string]string,error){
    j,_:= json.Marshal(s)//先将结构体转换成字节
    m :=make(map[string]string)
    err := json.Unmarshal(j,&m)//将字节转化成mapif err !=nil{returnnil, err
    }return m,nil}

在这里插入图片描述

测试添加集群接口
看下绑定的结构体,来确定前端传的数据
在这里插入图片描述

在这里插入图片描述

postman请求
我们先不传kubeconfig,可以看到检测到集群不正常,无法添加
在这里插入图片描述

我们把集群的~/.kube/config填进去
在这里插入图片描述

注意,需要把换行符改成\n。不然postman无法传参
在这里插入图片描述

再次请求
在这里插入图片描述

k8s集群查看secret,添加成功
在这里插入图片描述

查看详情
在这里插入图片描述

更新集群
只需要在请求处,将URL改成update,然后修改字段即可
在这里插入图片描述

5、删除集群

删除集群,只需要根据集群ID,将对应的secret删除掉即可
删除集群的控制器deleteCluster.go

package cluster

import("context""github.com/gin-gonic/gin""jingtian/krm-backend/config""jingtian/krm-backend/utils/logs"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1")funcDeleteCluster(c *gin.Context){
    logs.Debug(nil,"删除集群")// 1.根据传来的id来删除secret
    clusterId := c.Query("id")// 2.删除secret
    err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Delete(context.TODO(), clusterId, metav1.DeleteOptions{})// 定义响应格式
    returnData := config.NewReturnData()if err !=nil{
        logs.Error(map[string]interface{}{"id": clusterId,"message": err.Error()},"删除失败")// 说明删除集群失败
        msg :="集群删除失败: "+ err.Error()
        returnData.Status =400
        returnData.Msg = msg
    }else{
        logs.Warning(map[string]interface{}{"id": clusterId},"删除成功")// 说明删除成功
        returnData.Status =200
        returnData.Msg ="删除成功"delete(config.ClusterKubeconfig, clusterId)}//响应给前端
    c.JSON(200, returnData)}

在这里插入图片描述

请求
在这里插入图片描述

k8s查看,删除成功
在这里插入图片描述

6、集群列表查询

集群列表查询,就是查询所有的secret列表,将secret列表返回给前端就可以了
我们先通过postman创建几个secret,模拟几个集群
在这里插入图片描述

查看secret
在这里插入图片描述

我们把secret都查询出来,但是我们不要把secret的所有信息都传给前端,因为可能会不安全,所以尽量不要把kubeconfig传给前端
我们只需要传annotations里面的数据就行了
在这里插入图片描述

因此,我们在查询到secret列表时,需要处理下
查询的时候,可以过滤

//根据指定标签,过滤出我们添加的集群
listOptions := metav1.ListOptions{
    LabelSelector: config.ClusterConfigSecretLabelKey +"="+ config.ClusterConfigSecretLabelValue,}
secretList, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions)

ListOptions里面可以根据LabelSelector或者FieldSelector等过滤
在这里插入图片描述

查询集群列表list.go完整代码

package cluster

import("context""github.com/gin-gonic/gin""jingtian/krm-backend/config""jingtian/krm-backend/utils/logs"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1")funcList(c *gin.Context){
    logs.Debug(nil,"列出集群列表")//根据指定标签,过滤出我们添加的集群
    listOptions := metav1.ListOptions{
        LabelSelector: config.ClusterConfigSecretLabelKey +"="+ config.ClusterConfigSecretLabelValue,}
    secretList, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions)
    returnData := config.NewReturnData()if err !=nil{
        logs.Info(map[string]interface{}{"message": err.Error()},"查询集群列表失败")// 查询失败
        msg :="查询失败: "+ err.Error()
        returnData.Status =400
        returnData.Msg = msg
        c.JSON(200, returnData)return}// 优化数据返回的结构,将annotions里面的map以切片的形式传给前端var clusterList []map[string]stringfor_, v :=range secretList.Items {
        anno := v.Annotations
        clusterList =append(clusterList, anno)}
    returnData.Msg ="查询成功"
    returnData.Data["items"]= clusterList
    c.JSON(200, returnData)}

在这里插入图片描述

postman请求,拿到集群列表
在这里插入图片描述

7、获取集群详情

我们查询一个集群的配置的时候,需要用到get,还有就是编辑一个集群的时候,也需要用到查询集群的详情。
我们可以根据集群id来获取到secret,返回给前端

控制器get.go

package cluster

import("context""fmt""github.com/gin-gonic/gin""jingtian/krm-backend/config""jingtian/krm-backend/utils/logs"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""net/http")funcGet(c *gin.Context){
    logs.Debug(nil,"获取集群详情")// 1.根据传来的id来获取集群详情
    clusterId := c.Query("id")
    returnData := config.NewReturnData()// 2.获取集群的详细信息
    secretDetail, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Get(context.TODO(), clusterId, metav1.GetOptions{})if err !=nil{
        logs.Error(map[string]interface{}{"id": clusterId,"message": err.Error()},"获取集群信息失败")
        returnData.Status =400
        returnData.Msg ="获取集群详情失败 "+ err.Error()}else{
        returnData.Msg ="集群详情查询成功"
        fmt.Println("secretDetail是什么", secretDetail)
        clusterConfigMap := secretDetail.Annotations
        // Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"` Data 键是字符串,值是字节// 通过string将值转化成字符串//获取的时候不可以用StringData,因为secret里面没有这个字段。只有Data字段
        clusterConfigMap["kubeconfig"]=string(secretDetail.Data["kubeconfig"])
        returnData.Data["item"]= clusterConfigMap
    }
    c.JSON(http.StatusOK, returnData)}

postman请求,查询到集群详情数据
在这里插入图片描述


本文转载自: https://blog.csdn.net/littlefun591/article/details/143601393
版权归原作者 景天科技苑 所有, 如有侵权,请联系我们删除。

“【云原生开发】K8S集群管理后端开发设计与实现”的评论:

还没有评论