# scaffold **Repository Path**: bigberg/scaffold ## Basic Information - **Project Name**: scaffold - **Description**: 项目脚手架,生成基础项目配置框架 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-08-07 - **Last Updated**: 2024-07-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

工程化目录

``` 这是一个脚手架项目,可以根据这个项目生成一个基础的框架 ``` ### 一、目录结构 main.go: 程序入口 config : 配置层,存放配置文件 controllers: 控制器层,实现路由的逻辑处理 routers: 路由层,管理程序的路由信息 middlewares: 中间件层 model: 模型层 utils: 工具层 ### 二、JWT JSON Web Token(JSON Web令牌),是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。 JWT作用: - 授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。 - 信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥/私钥对),因此可确保发件人。由于签名是使用标头和有效负载计算的,因此还可验证内容是否被篡改。 #### 2.1 封装生成JWT的函数 下载jtw go get -u github.com/golang-jwt/jwt/v5 在config.go 定义一些配置信息 ``` var ( JwtSignKey string JwtExpireTime int64 // jwt token 过期时间,单位: 分钟 ) func init() { // 设置jwt环境变量 viper.SetDefault("JWT_SIGN_KEY", "Bigberg") JwtSignKey = viper.GetString("JWT_SIGN_KEY") // 设置jwt过期时间,默认值120分钟 viper.SetDefault("JWT_EXPIRE_TIME", 120) JwtExpireTime = viper.GetInt64("JWT_EXPIRE_TIME") } ``` 在utils中定义token封装函数 ``` package jwtutils import ( "kms-backend/config" "time" "github.com/golang-jwt/jwt/v5" ) // 定义一个byte类型的变量 var jwtSignKey = []byte(config.JwtSignKey) // 1.自定义声明类型 type MyCustomClaims struct { Username string `json:"username"` jwt.RegisteredClaims } // 2. 封装生成token的函数 func GenToken(username string) (string, error) { claims := MyCustomClaims{ "bar", jwt.RegisteredClaims{ // A usual scenario is to set the expiration time relative to the current time // 生成时间加上一个设置的分钟数为过期时间 ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * time.Duration(config.JwtExpireTime))), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "bergcom", // 颁发机构 Subject: "Bigberg", }, } // 生成token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ss, err := token.SignedString(jwtSignKey) return ss, err } ``` 在main函数中调用封装函数,生成token。 ``` func main() { r := gin.Default() logs.Info(nil, "程序启动成功!") // 测试生成jwt的 token 是否可用 ss, _ := jwtutils.GenToken("bigberg") logs.Info(nil, ss) r.Run(config.Port) } ``` 输出 ``` {"file":"E:/code/kms-backend/utils/logs/logs.go:14","func":"kms-backend/utils/logs.Info","level":"info","msg":"程序启动成功!","time":"2023-09-11 10:24:13"} {"file":"E:/code/kms-backend/utils/logs/logs.go:14","func":"kms-backend/utils/logs.Info","level":"info","msg":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJhciIsImlzcyI6ImJlcmdjb20iLCJzdWIiOiJCaWdiZXJnIiwiZXhwIjoxNjk0NDA2MjUzLCJuYmYiOjE2OTQzOTkwNTMsImlhdCI6MTY5NDM5OTA1M30.944BRCSVApjo3iYTQyEsl0J8V6pZ9VAuXS7Mg8nQUoc","time":"2023-09-11 10:24:13"} [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. [GIN-debug] Listening and serving HTTP on :8888 ``` #### 2.2 校验Token 生成token后,还需要校验token是否合法,在utils中定义token解析函数 ``` // 3. 解析token func ParseToker(ss string) (*MyCustomClaims, error) { token, err := jwt.ParseWithClaims(ss, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { return jwtSignKey, nil }) if err != nil { // 解析失败 logs.Error(nil, "token解析失败") } if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { // token合法 return claims, nil } else { // token不合法 logs.Warning(nil, "Token 不合法") return nil, errors.New("Token不合法: invalid token") } } ``` 验证,main函数中添加 ``` claim, err := jwtutils.ParseToker(ss) if err != nil { fmt.Println("解析token失败:", err.Error()) } else { fmt.Println(claim) } ``` ### 三、client-go #### 3.1 安装client-go ``` go git k8s.io/client-go@latest ``` #### 3.2 client-go 初体验 `kms-backend/client-go.go` ``` package main import ( "context" "fmt" "kms-backend/utils/logs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) func main() { // 1. 初始化config实例 config, err := clientcmd.BuildConfigFromFlags("", "./config/cls-ik7lvowm-config") if err != nil { panic((err.Error())) } // 2. 创建客户端工具,clientset clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } // 3. 操作集群 // Pods("default") 查询 namespace default 下的pod数量 // 如果为空,查询所有namespace下pods总数 // 查询api-resource // kubectl api-resources | grep deployment // clientset.AppsV1() // clientset.NetworkingV1() pods, err := clientset.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{}) if err != nil { logs.Error(nil, "查询Pods失败") } else { fmt.Printf("There are %d pods in th cluster\n", len(pods.Items)) } // 查询deployment deployments, _ := clientset.AppsV1().Deployments("saas").List(context.TODO(), metav1.ListOptions{}) deploymentsItems := deployments.Items for _, deploy := range deploymentsItems { fmt.Printf("当前deployment名称是: %s\n", deploy.Name) } } ``` #### 3.3 Get方法 ``` ... ... func main() { ... // Get方法 // Get方法中需要传入一个名称参数 podDetail, _ := clientset.CoreV1().Pods("default").Get(context.TODO(), "nginx-deployment-77dd77c74d-69gzc", metav1.GetOptions{}) // 输出pod中第一个容器镜像的名称 fmt.Printf("pod中第一个镜像名称: %s", podDetail.Spec.Containers[0].Image) // 获取namespace详情 namespaceDetail, _ := clientset.CoreV1().Namespaces().Get(context.TODO(), "saas", metav1.GetOptions{}) fmt.Print(namespaceDetail) } ``` #### 3.4 Update更新操作 ``` import ( "context" "fmt" "kms-backend/utils/logs" appsv1 "k8s.io/api/apps/v1" apiCorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) func main(){ ... ... // 更新操作 // 获取deployment并更新 deploymentDetail, _ := clientset.AppsV1().Deployments("default").Get(context.TODO(), "nginx-deployment", metav1.GetOptions{}) fmt.Print("该denployment的名称是:", deploymentDetail.Name) // 获取当前label // labels := deploymentDetail.Labels // labels["newlabel"] = "new-label" // 修改annotination // deploymentDetail.Annotations["new-anno"] = "new-anno" // 如果labels 和annotination 原本是空,会报空指针错误,需要提前初始化map // 修改副本数量 // replicas 需要一个指针类型 // newReplicas := int32(2) // deploymentDetail.Spec.Replicas = &newReplicas // 修改镜像版本 deploymentDetail.Spec.Template.Spec.Containers[0].Image = "nginx:1.22.0" _, err = clientset.AppsV1().Deployments("default").Update(context.TODO(), deploymentDetail, metav1.UpdateOptions{}) if err != nil { fmt.Print("deployment更新失败: ", err.Error()) } } ``` #### 3.5 Delete删除操作 ``` import ( "context" "fmt" "kms-backend/utils/logs" appsv1 "k8s.io/api/apps/v1" apiCorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) func main(){ ... // 删除资源 err = clientset.AppsV1().Deployments("default").Delete(context.TODO(), "nginx-deployment", metav1.DeleteOptions{}) if err != nil { fmt.Print("删除资源失败: ", err.Error()) } } ``` #### 3.6 创建资源 ``` import ( "context" "fmt" "kms-backend/utils/logs" appsv1 "k8s.io/api/apps/v1" apiCorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) func main(){ ... // 创建资源 // 创建一个Namespace // 声明一个namespace的类型 // var newNamespace apiCorev1.Namespace // newNamespace.Name = "mytest" // _, err = clientset.CoreV1().Namespaces().Create(context.TODO(), &newNamespace, metav1.CreateOptions{}) // if err != nil { // fmt.Print("创建资源失败: ", err.Error()) // } // 创建一个deployment // kubect create deployment nginx --image=nginx --dry-run -oymal // 上述命令可以导出一个创建deploymnet必备的参数文档 var newDeployment appsv1.Deployment newDeployment.Name = "nginx" newDeployment.Namespace = "mytest" // 初始化一个label,并将其赋值给 selector Matchlabels 和 deployment 本身metadata中的labels label := make(map[string]string) label["app"] = "nginx" label["version"] = "v1" // selector 需要进行初始化 newDeployment.Spec.Selector = &metav1.LabelSelector{} newDeployment.Spec.Selector.MatchLabels = label newDeployment.Labels = label // 此处更改pod的标签 newDeployment.Spec.Template.Labels = label // 初始化容器 var newContainers []apiCorev1.Container // 声明一个容器 var container apiCorev1.Container container.Image = "nginx" container.Name = "nginx" newContainers = append(newContainers, container) // 再创建一个容器 container.Image = "redis" container.Name = "redis" newContainers = append(newContainers, container) newDeployment.Spec.Template.Spec.Containers = newContainers _, err = clientset.AppsV1().Deployments("mytest").Create(context.TODO(), &newDeployment, metav1.CreateOptions{}) if err != nil { fmt.Print("创建Deployment资源失败: ", err.Error()) } } ``` #### 3.7 通过json串创建资源 ``` import ( "context" "encoding/json" "fmt" "kms-backend/utils/logs" appsv1 "k8s.io/api/apps/v1" // apiCorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) fun main() { // 通过json创建资源 // kubectl create deploy redis --image=redis --dry-run=client -ojson deployJson := `{ "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "app": "redis" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "redis" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "app": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": "redis", "resources": {} } ] } }, "strategy": {} }, "status": {} }` // 声明创建k8s的类型 var newDeployment2 appsv1.Deployment // 把json转换成string err = json.Unmarshal([]byte(deployJson), &newDeployment2) if err != nil { fmt.Print("json转换失败: ", err.Error()) } fmt.Print("json转换struct后配置详情: ", newDeployment2) // 创建 _, err = clientset.AppsV1().Deployments("default").Create(context.TODO(), &newDeployment2, metav1.CreateOptions{}) if err != nil { fmt.Print("创建Deployment资源失败: ", err.Error()) } } ``` ### 四、K8s多集群管理设计 #### 4.1 定义元数据命名空间 `config/config.go` ``` var MetadataNamespace string func init(){ ... // 元数据的metadatanamespace viper.SetDefault("METADATA_NAMESPACE", "kms") MetadataNamespace = viper.GetString("METADATA_NAMESPACE") } ``` #### 4.2 初始化元数据命名空间 在controller目录下 `controller/initcontroller/initcontroller.go` ``` package initcontroller import ( "kms-backend/utils/logs" ) func init() { logs.Debug(nil, "初始化基本数据") // 1. 通过kubeconfig 文件去创建client-go的客户端 // 2. 检查元数据明明空间师傅创建 // a. 已经创建: 元数据空间已经存在 // b. 未创建: 创建命名空间 metadataInit() } ``` 具体实现功能在incluster.go中实现 `controller/initcontroller/incluster.go` ``` package initcontroller import ( "context" "kms-backend/config" "kms-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" ) func metadataInit() { logs.Debug(nil, "初始化元数据命名空间") // 1. 初始化config实例 kmsConfig, err := clientcmd.BuildConfigFromFlags("", "config/cls-ik7lvowm-config") if err != nil { logs.Error(map[string]interface{}{"msg": err.Error()}, "加载incluster kubeconfig配置文件失败") panic((err.Error())) } // 2. 创建客户端工具,clientset clientset, err := kubernetes.NewForConfig(kmsConfig) if err != nil { logs.Error(map[string]interface{}{"msg": err.Error()}, "incluster创建客户端clientSet失败") panic(err.Error()) } // 3. 检查元数据空间是否存在 _, err = clientset.CoreV1().Namespaces().Get(context.TODO(), config.MetadataNamespace, metav1.GetOptions{}) if err != 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(nil, "元数据命名空间创建成功") } else { // 已经存在元数据空间 logs.Info(map[string]interface{}{"Namespace": config.MetadataNamespace}, "元数据空间已经存在") } } ``` #### 4.3 集群路由配置 配置路由 `routers/cluster/cluster.go` ``` package cluster import ( "kms-backend/controllers/cluster" "github.com/gin-gonic/gin" ) // 新增 func add(clusterGroup *gin.RouterGroup) { clusterGroup.POST("/add", cluster.Add) } // 更新 func update(clusterGroup *gin.RouterGroup) { clusterGroup.POST("/update", cluster.Update) } // 详情 func detail(clusterGroup *gin.RouterGroup) { clusterGroup.GET("/detail", cluster.Detail) } func list(clusterGroup *gin.RouterGroup) { clusterGroup.GET("/list", cluster.List) } func delete(clusterGroup *gin.RouterGroup) { clusterGroup.GET("/delete", cluster.Delete) } func RegisterSubRouter(g *gin.RouterGroup) { // 配置路由策略 clusterGroup := g.Group("/cluster") add(clusterGroup) update(clusterGroup) detail(clusterGroup) list(clusterGroup) delete(clusterGroup) } ``` 注册路由 `routers/routers.go` ``` import ( "kms-backend/routers/auth" "kms-backend/routers/cluster" "github.com/gin-gonic/gin" ) // 注册路由的方法 func RegisterRouters(r *gin.Engine) { apiGroup := r.Group("/api") auth.RegisterSubRouter(apiGroup) cluster.RegisterSubRouter(apiGroup) } ``` ### 五、集群路由功能的具体实现 #### 5.1 新增集群功能 新增集群就是添加集群的一些信息,这些信息会存储在元数据空间的`Secret`中,主要包括`kubeconfig`里面的内容 定义数据结构体 `controller/cluster/cluster.go` ``` package cluster type ClusterInfo struct { Id string `json:"id"` DisplayName string `json:"displayName"` City string `json:"city"` District string `json:"district"` } // 定义一个结构体,用于描述创建集群的配置信息 type ClusterConfing struct { ClusterInfo kubeconfig string `json:"kubeconfig"` } ``` 添加集群功能 `controller/cluster/add.go` ``` package cluster import ( "context" "kms-backend/config" "kms-backend/utils/logs" "github.com/gin-gonic/gin" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Add(r *gin.Context) { logs.Debug(nil, "添加集群") returnData := config.NewReturnData() clusterConfig := ClusterConfing{} if err := r.ShouldBindJSON(&clusterConfig); err != nil { msg := "添加集群失败,集群信息不完整" + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(200, returnData) return } logs.Info(map[string]interface{}{"集群名称": clusterConfig.DisplayName, "集群ID": clusterConfig.Id}, "开始添加集群") // 创建一个存储集群配置的secret var clusterConfigSecret corev1.Secret clusterConfigSecret.Name = clusterConfig.Id // labels clusterConfigSecret.Labels = make(map[string]string) clusterConfigSecret.Labels["kubeasy.com/cluster.metadata"] = "true" // annotinations, 添加注释,保存集群配置信息 clusterConfigSecret.Annotations = make(map[string]string) clusterConfigSecret.Annotations["displayName"] = clusterConfig.DisplayName clusterConfigSecret.Annotations["city"] = clusterConfig.City clusterConfigSecret.Annotations["district"] = clusterConfig.District // 保存kubeconfig 信息 clusterConfigSecret.StringData = make(map[string]string) clusterConfigSecret.StringData["kubeconfig"] = clusterConfig.kubeconfig // 调用clientSet 创建Secret // config配置文件中定义一个clientSet类型的变量,controller 中init 方法中创建的clientSet赋值给该变量 _, err = config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Create(context.TODO(), &clusterConfigSecret, metav1.CreateOptions{}) if err != nil { logs.Error(map[string]interface{}{"集群ID": clusterConfig.Id, "集群名称": clusterConfig.DisplayName, "msg": err.Error()}, "添加集群Secret失败") msg := "添加集群失败" + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(200, returnData) return } logs.Info(map[string]interface{}{"集群ID": clusterConfig.Id, "集群名称": clusterConfig.DisplayName}, "添加集群成功") returnData.Message = "集群添加成功" returnData.Status = 200 r.JSON(200, returnData) } ``` #### 5.2 验证新增的集群状态 如果添加的集群状态有问题,则不会新增成功,这需要判断集群状态 先给`clusterConfig`定义一个结构体的方法 `controller/cluster/cluster.go` ``` // 描述集群的状态 type ClusterStatus struct { ClusterInfo Version string `json:"version"` Status string `json:"status"` } // 结构体的方法,用于判断集群的状态 func (c *ClusterConfing) getClusterStatus() (ClusterStatus, error) { // 判断集群是不是正常 ClusterStatus := ClusterStatus{} ClusterStatus.ClusterInfo = c.ClusterInfo // 创建一个clientSet // 通过字符串形式接收kubeconfig restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(c.kubeconfig)) if err != nil { return ClusterStatus, err } clientset, err := kubernetes.NewForConfig(restConfig) if err != nil { return ClusterStatus, err } serverVersion, err := clientset.Discovery().ServerVersion() if err != nil { return ClusterStatus, err } // 能够正常获取集群信息,代表集群是正常的 ClusterStatus.Version = serverVersion.String() ClusterStatus.Status = "Active" return ClusterStatus, nil } ``` 在添加集群的功能中应用该方法 `controller/cluster/add.go` ``` ... ... // 判断集群是否正常, 在集群添加前判断该集群是否正常 clusterStatus, err := clusterConfig.getClusterStatus() if err != nil { msg := "无法获取集群信息: " + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(http.StatusOK, returnData) logs.Error(map[string]interface{}{"error": err.Error()}, "添加集群失败,无法获取集群信息") return } logs.Info(map[string]interface{}{"集群名称": clusterConfig.DisplayName, "集群ID": clusterConfig.Id}, "开始添加集群") ``` #### 5.3 优化annotations的添加代码 新增集群功能中,`clusterConfigSecret.Annotations`是一个map类型,而它的数据在`clusterStatus`结构体中都存在,所以只要将`clusterStatus`结构体转换成`map`,再赋值给`clusterConfigSecret.Annotations`就可以了 先实现一个`struct`转换成`map`的功能 `utils/tools/structmap.go` ``` package tools import "encoding/json" func StructToMap(s interface{}) map[string]string { j, _ := json.Marshal(s) m := make(map[string]string) json.Unmarshal(j, &m) return m } ``` 修改原来`clusterConfigSecret.Annotations`赋值的代码 `controller/cluster/add.go` ``` ... ... // annotations, 添加注释,保存集群配置信息 clusterConfigSecret.Annotations = make(map[string]string) // clusterConfigSecret.Annotations["displayName"] = clusterConfig.DisplayName // clusterConfigSecret.Annotations["city"] = clusterConfig.City // clusterConfigSecret.Annotations["district"] = clusterConfig.District // 优化以上代码 // 把集群状态结构体转换成map m := tools.StructToMap(clusterStatus) clusterConfigSecret.Annotations = m ``` 以上新增集群功能完成 #### 5.4 删除集群功能 `controller/cluster/delete.go` ``` package cluster import ( "context" "kms-backend/config" "kms-backend/utils/logs" "net/http" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Delete(r *gin.Context) { logs.Debug(nil, "删除集群") // 1. 获取集群Id // 前端传递参数clusterId clusterId := r.Query("clusterId") returnData := config.ReturnData{} // 2. 删除集群,即删除Secrect err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Delete(context.TODO(), clusterId, metav1.DeleteOptions{}) if err != nil { // 删除失败 logs.Error(nil, "集群删除失败: "+err.Error()) msg := "删除集群失败: " + err.Error() returnData.Message = msg returnData.Status = 400 } else { // 删除成功 logs.Info(map[string]interface{}{"当前被删除集群Id": clusterId}, "集群删除成功") returnData.Message = "集群删除成功" returnData.Status = 200 } r.JSON(http.StatusOK, returnData) } ``` #### 5.5 集群列表展示功能 `controller/cluster/list.go` ``` package cluster import ( "context" "kms-backend/config" "kms-backend/utils/logs" "net/http" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func List(r *gin.Context) { logs.Debug(nil, "展示集群") // namespace中有很多其他的Secret // ListOptions可以根据LabelSelector 筛选出我们需要的Secret returnData := config.ReturnData{} listOptions := metav1.ListOptions{ LabelSelector: "kubeasy.com/cluster.metadata=true", } secretList, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions) if err != nil { // 查询失败 logs.Error(nil, "查询集群列表失败: "+err.Error()) msg := "查询失败: " + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(http.StatusOK, returnData) return } else { logs.Info(nil, "查询集群列表成功") returnData.Status = 200 returnData.Message = "查询集群列表成功" // 将secretList存入retrunData.Data 返回给前端 // Secret 里面的信息也只需要Annotations的内容 clusterList := []map[string]string{} for _, v := range secretList.Items { anno := v.Annotations clusterList = append(clusterList, anno) } returnData.Data = make(map[string]interface{}) returnData.Data["items"] = clusterList r.JSON(200, returnData) } } ``` #### 5.6 更新集群信息 新增和更新集群部分代码重复,可以将两个功能合并来写 `controller/cluster/addorupadte.go` ``` package cluster import ( "context" "kms-backend/config" "kms-backend/utils/logs" "kms-backend/utils/tools" "net/http" "github.com/gin-gonic/gin" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // 新增和更新集群信息的功能相识,可以统一使用 func addOrUpdate(r *gin.Context, method string) { var flag string if method == "Create" { flag = "添加" } else { flag = "更新" } logs.Debug(nil, flag+"集群") returnData := config.NewReturnData() clusterConfig := ClusterConfing{} if err := r.ShouldBindJSON(&clusterConfig); err != nil { msg := flag + "集群失败,集群信息不完整" + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(200, returnData) return } // 判断集群是否正常 clusterStatus, err := clusterConfig.getClusterStatus() if err != nil { msg := "无法获取集群信息: " + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(http.StatusOK, returnData) logs.Error(map[string]interface{}{"error": err.Error()}, flag+"集群失败,无法获取集群信息") return } logs.Info(map[string]interface{}{"集群名称": clusterConfig.DisplayName, "集群ID": clusterConfig.Id}, "开始"+flag+"集群") // 创建一个存储集群配置的secret var clusterConfigSecret corev1.Secret clusterConfigSecret.Name = clusterConfig.Id // labels clusterConfigSecret.Labels = make(map[string]string) clusterConfigSecret.Labels["kubeasy.com/cluster.metadata"] = "true" // annotations, 添加注释,保存集群配置信息 clusterConfigSecret.Annotations = make(map[string]string) // clusterConfigSecret.Annotations["displayName"] = clusterConfig.DisplayName // clusterConfigSecret.Annotations["city"] = clusterConfig.City // clusterConfigSecret.Annotations["district"] = clusterConfig.District // 优化以上代码 // 把集群状态结构体转换成map m := tools.StructToMap(clusterStatus) clusterConfigSecret.Annotations = m // 保存kubeconfig 信息 clusterConfigSecret.StringData = make(map[string]string) clusterConfigSecret.StringData["kubeconfig"] = clusterConfig.KubeConfig // 调用clientSet 创建Secret // config配置文件中定义一个clientSet类型的变量,controller 中init 方法中创建的clientSet赋值给该变量 if method == "Create" { _, err = config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Create(context.TODO(), &clusterConfigSecret, metav1.CreateOptions{}) } else { _, 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()}, flag+"集群Secret失败") msg := flag + "集群失败" + err.Error() returnData.Message = msg returnData.Status = 400 r.JSON(200, returnData) return } logs.Info(map[string]interface{}{"集群ID": clusterConfig.Id, "集群名称": clusterConfig.DisplayName}, flag+"集群成功") returnData.Message = "集群" + flag + "成功" returnData.Status = 200 r.JSON(200, returnData) } ``` 原先`add.go`的代码 ``` package cluster import ( "github.com/gin-gonic/gin" ) func Add(r *gin.Context) { addOrUpdate(r, "Create") } ``` `update.go`的内容 ``` package cluster import ( "github.com/gin-gonic/gin" ) func Update(r *gin.Context) { addOrUpdate(r, "Update") } ``` #### 5.7 获取集群详情 `controller/cluster/detail.go` ``` package cluster import ( "context" "kms-backend/config" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Detail(r *gin.Context) { logs.Debug(nil, "获取集群详情") clusterId := r.Query("clusterId") returnData := config.ReturnData{} // 获取集群配置Secret clusterSecret, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Get(context.TODO(), clusterId, metav1.GetOptions{}) if err != nil { // 获取集群信息失败 logs.Error(nil, "获取集群详情失败: "+err.Error()) msg := "获取集群详情失败" returnData.Message = msg returnData.Status = 400 } else { logs.Info(map[string]interface{}{"集群Id": clusterId}, "查询集群详情成功") returnData.Status = 200 returnData.Message = "查询集群详情成功" returnData.Data = make(map[string]interface{}) // 使用一个map获取所需详情信息 clusterConfigMap := clusterSecret.Annotations clusterConfigMap["kubeconfig"] = string(clusterSecret.Data["kubeconfig"]) returnData.Data["item"] = clusterConfigMap } r.JSON(200, returnData) } ``` ### 六、命名空间管理 #### 6.1 新增指向命名空间的路由 可以拷贝`controller/cluster`的目录为`controller/namespace`,修改文件中的`package cluster`为`package namespace`。拷贝目录`router/cluster`为`routers/namespace`,修改关键字`cluster`为`namespace`。再`routers/routers.go`中新增`namespace`的路由。 `routers/namespace/namespace.go` 新增路由 ``` package namespace import ( "kms-backend/controllers/namespace" "github.com/gin-gonic/gin" ) // 新增 func create(namespaceGroup *gin.RouterGroup) { namespaceGroup.POST("/create", namespace.Create) } // 更新 func update(namespaceGroup *gin.RouterGroup) { namespaceGroup.POST("/update", namespace.Update) } func detail(namespaceGroup *gin.RouterGroup) { namespaceGroup.GET("/detail", namespace.Detail) } func list(namespaceGroup *gin.RouterGroup) { namespaceGroup.GET("/list", namespace.List) } func delete(namespaceGroup *gin.RouterGroup) { namespaceGroup.GET("/delete", namespace.Delete) } func RegisterSubRouter(g *gin.RouterGroup) { // 配置路由策略 namespaceGroup := g.Group("/namespace") create(namespaceGroup) update(namespaceGroup) detail(namespaceGroup) list(namespaceGroup) delete(namespaceGroup) } ``` 注册路由 `routers/routers.go` ``` func RegisterRouters(r *gin.Engine) { apiGroup := r.Group("/api") auth.RegisterSubRouter(apiGroup) cluster.RegisterSubRouter(apiGroup) namespace.RegisterSubRouter(apiGroup) } ``` #### 6.2 如何获取现有集群的kubeconfig ``` 1. 首先需要知道对哪个集群中的哪个Namespace进行管理 2. 获取具体Namespace的信息 a. 通过kubeconfig 获取MetadataNamespace中的Secret, b. 通过该Secert获取clusterId,即获取到具体集群信息 c. 通过该集群信息中kubeconfig 字段获取 kubeconfig信息 d. 通过kubeconfig信息创建clientSet 3. kubeconfig 信息可以存在一个变量当中,因为会一直用到,这样就不用每次创建clientset时就去获取kubeconfig 4. 创建clientset,操作Namespace ``` 我们需要在初始化的时候,获取所有集群的信息,将这些已经存在的集群Id 和kubeconfig存储在一个map中。这样我们就可以通过集群Id确定具体的集群,并 通过该集群的kubeconfig来创建clientset客户端工具 定义一个`ClusterKubeConfig`的变量 `config/config.go` ``` ClusterKubeConfig map[string]string // 存储集群kubeconfig ``` 获取ClusterKubeConfig `controller/initcontroller/incluster.go` ``` package initcontroller import ( "context" "kms-backend/config" "kms-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" ) func metadataInit() { ... ... // 初始化ClusterkubeConfig config.ClusterKubeConfig = make(map[string]string) // 查询当前已经存在的集群配置 listOptions := metav1.ListOptions{ LabelSelector: config.ClusterConfingSecretKey + "=" + config.ClusterConfingSecretValue, } secretList, _ := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions) for _, secret := range secretList.Items { // 获取集群ID clusterId := secret.Name kubeconfig := secret.Data["kubeconfig"] config.ClusterKubeConfig[clusterId] = string(kubeconfig) } } ``` 新增集群时,我们需要在ClusterKubeConfig中加入新集群的clusterId和kubeconfig `controller/cluster/addorupdate.go` ``` ... ... // 初始化中获取的集群信息中,加入新增的集群 config.ClusterKubeConfig[clusterConfig.Id] = clusterConfig.KubeConfig // 测试输出config.ClusterKubeConfig的信息 // fmt.Print(config.ClusterKubeConfig) logs.Info(map[string]interface{}{"集群ID": clusterConfig.Id, "集群名称": clusterConfig.DisplayName}, flag+"集群成功") returnData.Message = "集群" + flag + "成功" returnData.Status = 200 r.JSON(200, returnData) ``` 删除集群时,需要在ClusterKubeConfig中删除该集群的clusterId `controller/cluster/delte.go` ``` ... ... else { // 删除成功 logs.Info(map[string]interface{}{"当前被删除集群Id": clusterId}, "集群删除成功") returnData.Message = "集群删除成功" returnData.Status = 200 // 删除初始化时获取的集群信息中现在删除的集群 delete(config.ClusterKubeConfig, clusterId) // 测试输出config.ClusterKubeConfig的信息 // fmt.Print(config.ClusterKubeConfig) } r.JSON(http.StatusOK, returnData) ``` #### 6.3 创建namespace 定义一个全局基础数据结构,接收前端页面传递的基础数据 `controller/controllers.go` ``` package controllers // 定义全局的数据结构 type BasicInfo struct { ClusterId string `json:"clusterid"` Namespace string `json:"namespace"` Name string `json:"name"` } ``` 具体实现逻辑 `controller/namespace/create.go` ``` package namespace import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" 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" ) func Create(r *gin.Context) { // 新增 logs.Debug(nil, "创建命名空间") // 初始化数据 returnData := config.ReturnData{} basicInfo := controllers.BasicInfo{} if err := r.ShouldBindJSON(&basicInfo); err != nil { msg := "请求出错" + err.Error() logs.Error(nil, msg) returnData.Message = msg returnData.Status = 400 r.JSON(200, returnData) return } // 获取kubeconfig kubeconfig := config.ClusterKubeConfig[basicInfo.ClusterId] restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig)) if err != nil { msg := "解析kubeconfig出错" + err.Error() logs.Error(nil, msg) returnData.Status = 400 returnData.Message = msg r.JSON(200, returnData) return } // 创建clientset客户端工具 clientSet, err := kubernetes.NewForConfig(restConfig) if err != nil { msg := "创建clientset失败" + err.Error() logs.Error(nil, msg) returnData.Status = 400 returnData.Message = msg r.JSON(200, returnData) return } // 操作资源 var namespace corev1.Namespace namespace.Name = basicInfo.Name _, err = clientSet.CoreV1().Namespaces().Create(context.TODO(), &namespace, metav1.CreateOptions{}) if err != nil { msg := "命名空间创建失败" + err.Error() logs.Error(nil, "集群"+basicInfo.ClusterId+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "创建namespace成功" logs.Info(nil, "集群"+basicInfo.ClusterId+"中命名空间"+namespace.Name+"创建成功") returnData.Message = msg returnData.Status = 200 } r.JSON(200, returnData) } ``` #### 6.4 优化namespace生成clientset的方法 在操作`namespace`资源时,都需要生成`clientSet`客户端,可以将这个生成功能独立出来 `controllers/controllers.go` ``` func BasicInit(r *gin.Context) (clientSet *kubernetes.Clientset, basicInfo BasicInfo, err error) { // 初始化数据 if err := r.ShouldBindJSON(&basicInfo); err != nil { msg := "绑定basicInfo时请求出错" + err.Error() logs.Error(nil, msg) return nil, basicInfo, err } // 获取kubeconfig kubeconfig := config.ClusterKubeConfig[basicInfo.ClusterId] restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig)) if err != nil { msg := "解析kubeconfig出错" + err.Error() logs.Error(nil, msg) return nil, basicInfo, err } // 创建clientset客户端工具 clientSet, err = kubernetes.NewForConfig(restConfig) if err != nil { msg := "创建clientset失败" + err.Error() logs.Error(nil, msg) return nil, basicInfo, err } return clientSet, basicInfo, nil } ``` 创建新命名空间功能修改 `controllers/namespace/create.go` ``` package namespace import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Create(r *gin.Context) { // 新增 logs.Debug(nil, "创建命名空间") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 操作资源 var namespace corev1.Namespace namespace.Name = basicInfo.Name _, err = clientSet.CoreV1().Namespaces().Create(context.TODO(), &namespace, metav1.CreateOptions{}) if err != nil { msg := "命名空间创建失败" + err.Error() logs.Error(nil, "集群"+basicInfo.ClusterId+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "创建namespace成功" logs.Info(nil, "集群"+basicInfo.ClusterId+"中命名空间"+namespace.Name+"创建成功") returnData.Message = msg returnData.Status = 200 } r.JSON(200, returnData) } ``` #### 6.5 删除namespace `controllers/namespace/delete.go` ``` package namespace import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Delete(r *gin.Context) { logs.Debug(nil, "开始删除命名空间") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 删除操作 err = clientSet.CoreV1().Namespaces().Delete(context.TODO(), basicInfo.Name, metav1.DeleteOptions{}) if err != nil { msg := "删除命名空间失败" + err.Error() logs.Error(nil, "集群"+basicInfo.ClusterId+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "删除命名空间成功" logs.Info(nil, "集群"+basicInfo.ClusterId+"中命名空间"+basicInfo.Name+"删除成功") returnData.Message = msg returnData.Status = 200 } r.JSON(200, returnData) } ``` 在绑定前端传递的数据时,因为删除操作是`GET`方法,而新建namespace使用的是`POST`方法,所以绑定数据的功能需要优化 `controllers/controllers.go` ``` package controllers import ( "errors" "kms-backend/config" "kms-backend/utils/logs" "github.com/gin-gonic/gin" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) // 定义全局的数据结构 // 绑定Get方法传递的数据,需要使用form形式 type BasicInfo struct { ClusterId string `json:"clusterid" form:"clusterid"` Namespace string `json:"namespace" form:"namespace"` Name string `json:"name" form:"name"` } // namespace在操作时,都需要生成一个clientSet来具体实施 // 所有可以实现一个基础功能,来生成clientSet func BasicInit(r *gin.Context) (clientSet *kubernetes.Clientset, basicInfo BasicInfo, err error) { // 初始化数据 // 绑定前端传递的json数据,也需要根据调用方法来区别,因为绑定方法不一样 requestMethod := r.Request.Method if requestMethod == "GET" { err = r.ShouldBindQuery(&basicInfo) } else if requestMethod == "POST" { err = r.ShouldBindJSON(&basicInfo) } else { err = errors.New("不支持该请求方式") } if err != nil { msg := "绑定basicInfo时请求出错" + err.Error() logs.Error(nil, msg) return nil, basicInfo, err } // 获取kubeconfig kubeconfig := config.ClusterKubeConfig[basicInfo.ClusterId] restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig)) if err != nil { msg := "解析kubeconfig出错" + err.Error() logs.Error(nil, msg) return nil, basicInfo, err } // 禁止删除的namespace if basicInfo.Name == "kube-system" { returnData.Message = "禁止删除kube-system" returnData.Status = 400 r.JSON(200, returnData) return } // 创建clientset客户端工具 clientSet, err = kubernetes.NewForConfig(restConfig) if err != nil { msg := "创建clientset失败" + err.Error() logs.Error(nil, msg) return nil, basicInfo, err } return clientSet, basicInfo, nil } ``` #### 6.6 展示集群中的命名空间 `controllers/namespace/list.go` ``` package namespace import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func List(r *gin.Context) { logs.Debug(nil, "展示命名空间") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 获取namespace列表 namespaceList, err := clientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) if err != nil { msg := "获取集群中命名空间失败" + err.Error() logs.Error(nil, "集群"+basicInfo.ClusterId+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "获取集群中命名空间成功" logs.Info(nil, "集群"+basicInfo.ClusterId+"中命名空间获取成功") returnData.Message = msg returnData.Status = 200 returnData.Data = make(map[string]interface{}) returnData.Data["items"] = namespaceList.Items } r.JSON(200, returnData) } ``` #### 6.7 获取命名空间详情 `controllers/namespace/detail.go` ``` package namespace import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Detail(r *gin.Context) { logs.Debug(nil, "获取详情") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 获取namespace列表 namespace, err := clientSet.CoreV1().Namespaces().Get(context.TODO(), basicInfo.Name, metav1.GetOptions{}) if err != nil { msg := "获取命名空间详情失败" + err.Error() logs.Error(nil, "集群"+basicInfo.ClusterId+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "获取命名空间详情成功" logs.Info(nil, "集群"+basicInfo.ClusterId+"中命名空间详情获取成功") returnData.Message = msg returnData.Status = 200 returnData.Data = make(map[string]interface{}) returnData.Data["item"] = namespace } r.JSON(200, returnData) } ``` #### 6.8 更新命名空间信息 ``` // 更新操作主要是更新namespace的 labels 和 annotations // 更新操作时Update方法中需要传入一个*v1.Namespace的指针对象,这个对象内容是前端传入的数据 // 所以函数BasicInit中需要加入一个参数,这个参数是interface{}类型,即不仅能接收namespace对象, // 也可以接收其他比如deployment、statefulset等类型 ``` 优化`controllers.go`的内容 ``` package controllers import ( "errors" "kms-backend/config" "kms-backend/utils/logs" "github.com/gin-gonic/gin" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) // 定义全局的数据结构 // 绑定Get方法传递的数据,需要使用form形式 type BasicInfo struct { ClusterId string `json:"clusterid" form:"clusterid"` Namespace string `json:"namespace" form:"namespace"` Name string `json:"name" form:"name"` Item interface{} `json:"item"` } // namespace在操作时,都需要生成一个clientSet来具体实施 // 所有可以实现一个基础功能,来生成clientSet func BasicInit(r *gin.Context, item interface{}) (clientSet *kubernetes.Clientset, basicInfo BasicInfo, err error) { // 初始化数据 // BasicInit接收一个item参数,可以是任意类型,并将其赋值给basicInfo.Item // 前端将具体数据绑定到basicInfo.Item上,如果是namespace类型,item里面就是namespace的json格式的数据 basicInfo = BasicInfo{} basicInfo.Item = item ... ... } ``` 更新操作的功能 ``` package namespace import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Update(r *gin.Context) { logs.Debug(nil, "更新命名空间") var namespace corev1.Namespace returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r, &namespace) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 操作资源 _, err = clientSet.CoreV1().Namespaces().Update(context.TODO(), &namespace, metav1.UpdateOptions{}) if err != nil { msg := "命名空间更新失败" + err.Error() logs.Error(nil, "集群"+basicInfo.ClusterId+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "更新namespace成功" logs.Info(nil, "集群"+basicInfo.ClusterId+"中命名空间"+namespace.Name+"更新成功") returnData.Message = msg returnData.Status = 200 } r.JSON(200, returnData) } ``` 其他namespace功能代码修改,它们都缺失一个参数 ``` clientSet, basicInfo, err := controllers.BasicInit(r, nil) ``` ### 七、Pod的管理 和`namespace`操作一致,先配置路由和controller里面的内容,可以复制`namespace`的代码文件稍作修改 #### 7.1 Pod路由配置 `routers/pod/pod.go` ``` package pod import ( "kms-backend/controllers/pod" "github.com/gin-gonic/gin" ) // 新增 func create(podGroup *gin.RouterGroup) { podGroup.POST("/create", pod.Create) } // 更新 func update(podGroup *gin.RouterGroup) { podGroup.POST("/update", pod.Update) } func detail(podGroup *gin.RouterGroup) { podGroup.GET("/detail", pod.Detail) } func list(podGroup *gin.RouterGroup) { podGroup.GET("/list", pod.List) } func delete(podGroup *gin.RouterGroup) { podGroup.GET("/delete", pod.Delete) } func RegisterSubRouter(g *gin.RouterGroup) { // 配置路由策略 podGroup := g.Group("/pod") create(podGroup) update(podGroup) detail(podGroup) list(podGroup) delete(podGroup) } ``` 注册路由 `routers/routers.go` ``` package routers import ( "kms-backend/routers/auth" "kms-backend/routers/cluster" "kms-backend/routers/namespace" "kms-backend/routers/pod" "github.com/gin-gonic/gin" ) // 注册路由的方法 func RegisterRouters(r *gin.Engine) { apiGroup := r.Group("/api") auth.RegisterSubRouter(apiGroup) cluster.RegisterSubRouter(apiGroup) namespace.RegisterSubRouter(apiGroup) pod.RegisterSubRouter(apiGroup) } ``` #### 7.2 创建pod的功能 `controllers/pod/create.go` ``` package pod import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Create(r *gin.Context) { // 新增 logs.Debug(nil, "创建Pod") var pod corev1.Pod returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r, &pod) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 操作资源 _, err = clientSet.CoreV1().Pods(basicInfo.Namespace).Create(context.TODO(), &pod, metav1.CreateOptions{}) if err != nil { msg := "Pod创建失败" + err.Error() logs.Error(nil, basicInfo.ClusterId+"集群中"+basicInfo.Namespace+"命名空间下"+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "Pod创建成功" logs.Info(nil, basicInfo.ClusterId+"集群中"+basicInfo.Namespace+"命名空间下"+"Pod创建成功") returnData.Message = msg returnData.Status = 200 } r.JSON(200, returnData) } ``` #### 7.3 删除单个Pod功能 `controllers/pod/delete.go` ``` package pod import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Delete(r *gin.Context) { logs.Debug(nil, "开始删除Pod") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r, nil) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 删除操作 err = clientSet.CoreV1().Pods(basicInfo.Namespace).Delete(context.TODO(), basicInfo.Name, metav1.DeleteOptions{}) if err != nil { msg := "Pod删除失败" + err.Error() logs.Error(nil, basicInfo.ClusterId+"集群中"+basicInfo.Namespace+"命名空间下"+basicInfo.Name+" "+msg) returnData.Message = msg returnData.Status = 400 } else { msg := "Pod删除成功" logs.Info(nil, basicInfo.ClusterId+"集群中"+basicInfo.Namespace+"命名空间下"+basicInfo.Name+" "+msg) returnData.Message = msg returnData.Status = 200 } r.JSON(200, returnData) } ``` #### 7.4 同时删除多个Pod 删除多个Pod就不能使用`GET`方法,需要修改为`POST`方法。同时`BasicInfo`中需要新增一个`DeleteList`的切片 新增`DeleteList` `controllers/controllers.go` ``` type BasicInfo struct { 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"` } ``` 修改`GET`方法为`POST` `routers/pod/pod.go` ``` func delete(podGroup *gin.RouterGroup) { podGroup.POST("/delete", pod.Delete) } ``` 修改delete功能 `controllers/pod/delete.go` ``` package pod import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Delete(r *gin.Context) { logs.Debug(nil, "开始删除Pod") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r, nil) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 删除操作 // 多个pod删除 returnData.Data = make(map[string]interface{}) failPod := []string{} for _, v := range basicInfo.DeleteList { err = clientSet.CoreV1().Pods(basicInfo.Namespace).Delete(context.TODO(), v, metav1.DeleteOptions{}) if err != nil { failPod = append(failPod, v) } } returnData.Data["FailPods"] = failPod if len(failPod) > 0 { returnData.Message = "存在Pod删除失败" returnData.Status = 400 r.JSON(200, returnData) return } else { msg := "Pod删除成功" logs.Info(nil, basicInfo.ClusterId+"集群中"+basicInfo.Namespace+"命名空间下"+msg) returnData.Message = msg returnData.Status = 200 r.JSON(200, returnData) } } ``` #### 7.5 展示命名空间中pods `controllers/pods/list.go` ``` package pod import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func List(r *gin.Context) { logs.Debug(nil, "展示Pod") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r, nil) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 获取pod列表 if basicInfo.Namespace == "" { basicInfo.Namespace = "default" } podsList, err := clientSet.CoreV1().Pods(basicInfo.Namespace).List(context.TODO(), metav1.ListOptions{}) if err != nil { msg := "获取Pods失败" + err.Error() logs.Error(nil, "获取"+basicInfo.ClusterId+"集群中Pods失败") returnData.Message = msg returnData.Status = 400 } else { msg := "获取集群Pods成功" logs.Info(nil, "获取"+basicInfo.ClusterId+"集群中"+basicInfo.Namespace+"命名空间下Pods成功") returnData.Message = msg returnData.Status = 200 returnData.Data = make(map[string]interface{}) returnData.Data["items"] = podsList.Items } r.JSON(200, returnData) } ``` #### 7.6 获取pod详情 `controllers/pod/detail.go` ``` package pod import ( "context" "kms-backend/config" "kms-backend/controllers" "kms-backend/utils/logs" "github.com/gin-gonic/gin" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Detail(r *gin.Context) { logs.Debug(nil, "获取Pod详情") returnData := config.ReturnData{} // 生成clientSet, 基础数据basicInfo clientSet, basicInfo, err := controllers.BasicInit(r, nil) if err != nil { returnData.Message = err.Error() returnData.Status = 400 r.JSON(200, returnData) return } // 获取pod pod, err := clientSet.CoreV1().Pods(basicInfo.Namespace).Get(context.TODO(), basicInfo.Name, metav1.GetOptions{}) if err != nil { msg := "获取Pod详情失败" + err.Error() logs.Error(nil, "获取集群"+basicInfo.ClusterId+"命名空间"+basicInfo.Namespace+"中"+basicInfo.Name+"详情失败") returnData.Message = msg returnData.Status = 400 } else { msg := "获取Pod详情成功" logs.Info(nil, "获取集群"+basicInfo.ClusterId+"命名空间"+basicInfo.Namespace+"中"+basicInfo.Name+"详情成功") returnData.Message = msg returnData.Status = 200 returnData.Data = make(map[string]interface{}) returnData.Data["item"] = pod } r.JSON(200, returnData) } ``` ### 八、优化代码 #### 8.1 下载接口工具 使用接口工具简化创建、更新、删除等操作 ``` go get github.com/dotbalo/kubeutils ``` #### 8.2 构造函数获取kubeconfig kubeutils有个kubeconfig的返回值,可以使用一个构造函数获取kubeconfig `controllers/controllers.go` ``` ... ... type BasicInfo struct { 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"` } type Info struct { BasicInfo ReturnData config.ReturnData } // 优化代码使用的接口工具kubeutils需要一个kubeconfig,此处用构造函数获取kubeconfig func NewInfo(r *gin.Context, info *Info, returnDataMsg string) (kubeconfig string) { // 绑定前端传递的json数据,也需要根据调用方法来区别,因为绑定方法不一样 requestMethod := r.Request.Method var err error info.ReturnData.Message = returnDataMsg info.ReturnData.Status = 200 if requestMethod == "GET" { err = r.ShouldBindQuery(&info) } else if requestMethod == "POST" { err = r.ShouldBindJSON(&info) } else { err = errors.New("不支持该请求方式") } if err != nil { msg := "绑定basicInfo时请求出错" + err.Error() info.ReturnData.Message = msg info.ReturnData.Status = 400 logs.Error(nil, msg) r.JSON(http.StatusOK, info.ReturnData) return } // 默认去default命名空间 if info.Namespace == "" { info.Namespace = "default" } // 获取kubeconfig kubeconfig = config.ClusterKubeConfig[info.ClusterId] return kubeconfig } ``` #### 8.3 用结构体方法生成创建功能 `controllers/controllers.go` ``` ... ... // 创建方法 func (c *Info) Create(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { err := kubeUtilsInterface.Create(c.Namespace) if err != nil { msg := "创建失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } r.JSON(200, c.ReturnData) } ``` #### 8.4 修改创建pod的功能 `controllers/pod/create.go` ``` package pod import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" corev1 "k8s.io/api/core/v1" ) func Create(r *gin.Context) { // 新增 logs.Debug(nil, "开始创建Pod") var pod corev1.Pod info := controllers.Info{} info.Item = &pod kubeconfig := controllers.NewInfo(r, &info, "创建Pod成功") // 使用kubeutils接口创建pod var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewPod(kubeconfig, &pod) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Create(r, kubeUtilser) } ``` #### 8.5 使用结构体方法生成其他功能 `controllers/controllers.go` ``` package controllers import ( "errors" "kms-backend/config" "kms-backend/utils/logs" "net/http" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) // 定义全局的数据结构 // 绑定Get方法传递的数据,需要使用form形式 type BasicInfo struct { 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"` } type Info struct { BasicInfo ReturnData config.ReturnData LabelSelector string `json:"labelselector" form:"labelselector"` FieldSelector string `json:"fieldSelector" form:"fieldSelector"` // 判断是否强制删除 Force bool `json:"force" form:"force"` } // 创建方法 func (c *Info) Create(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { err := kubeUtilsInterface.Create(c.Namespace) if err != nil { msg := "创建失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } r.JSON(200, c.ReturnData) } // 更新方法 func (c *Info) Update(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { err := kubeUtilsInterface.Update(c.Namespace) if err != nil { msg := "更新失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } r.JSON(200, c.ReturnData) } // List方法 func (c *Info) List(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { // List需要3个参数:namespace、lableselector、fieldselector // 直接返回items 和error items, err := kubeUtilsInterface.List(c.Namespace, c.LabelSelector, c.FieldSelector) if err != nil { msg := "查询失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } else { c.ReturnData.Data = make(map[string]interface{}) c.ReturnData.Data["items"] = items } r.JSON(200, c.ReturnData) } // Get方法 func (c *Info) Get(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { item, err := kubeUtilsInterface.Get(c.Namespace, c.Name) if err != nil { msg := "查询失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } else { c.ReturnData.Data = make(map[string]interface{}) c.ReturnData.Data["items"] = item } r.JSON(200, c.ReturnData) } // Delete方法 func (c *Info) Delete(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { var gracePeriodSeconds int64 if c.Force { // 强制删除, 0 表示立即删除 var s int64 = 0 gracePeriodSeconds = s } // 需要3个参数: namespace name int64 // int64 表示是否强制删除的参数 err := kubeUtilsInterface.Delete(c.Namespace, c.Name, &gracePeriodSeconds) if err != nil { msg := "删除失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } r.JSON(200, c.ReturnData) } // Delete列表方法 func (c *Info) DeleteList(r *gin.Context, kubeUtilsInterface kubeutils.KubeUtilser) { var gracePeriodSeconds int64 if c.Force { // 强制删除, 0 表示立即删除 var s int64 = 0 gracePeriodSeconds = s } // 需要3个参数: namespace name int64 // int64 表示是否强制删除的参数 err := kubeUtilsInterface.DeleteList(c.Namespace, c.BasicInfo.DeleteList, &gracePeriodSeconds) if err != nil { msg := "删除失败: " + err.Error() c.ReturnData.Message = msg c.ReturnData.Status = 400 logs.Error(nil, msg) } r.JSON(200, c.ReturnData) } ``` #### 8.6 改造Delete功能 `controllers/pod/delete.go` ``` package pod import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" ) func Delete(r *gin.Context) { logs.Debug(nil, "开始删除Pod") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "删除Pod成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewPod(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Delete(r, kubeUtilser) } func DeleteList(r *gin.Context) { logs.Debug(nil, "开始删除Pod") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "删除Pod成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewPod(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.DeleteList(r, kubeUtilser) } ``` #### 8.7 改造List功能 `controllers/pod/list.go` ``` package pod import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" ) func List(r *gin.Context) { logs.Debug(nil, "开始查询Pod列表") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "查询pod列表详情成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewPod(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.List(r, kubeUtilser) } ``` #### 8.8 改造detail功能 `controllers/pod/detail.go` ``` package pod import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" ) func Detail(r *gin.Context) { logs.Debug(nil, "获取Pod详情") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "获取Pod详情成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewPod(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Get(r, kubeUtilser) } ``` #### 8.8 更新功能 `controllers/pod/update.go` ``` package pod import ( "kms-backend/config" "kms-backend/utils/logs" "github.com/gin-gonic/gin" ) func Update(r *gin.Context) { logs.Debug(nil, "更新Pod") var returnData config.ReturnData returnData.Message = "Pod暂不支持更新操作" returnData.Status = 200 r.JSON(200, returnData) } ``` #### 8.9 新增一个DeleteList路由 `routers/pod/pod.go` ``` ... ... func deleteList(podGroup *gin.RouterGroup) { podGroup.POST("/deleteList", pod.DeleteList) } func RegisterSubRouter(g *gin.RouterGroup) { // 配置路由策略 podGroup := g.Group("/pod") create(podGroup) update(podGroup) detail(podGroup) list(podGroup) delete(podGroup) deleteList(podGroup) ``` ### 九、Deployment #### 9.1 生成Deployment路由 复制`routers/pod`的内容为`routers/deployment`,修改`pod`字段为`deployment`。新增`deployment`的路由 `routers/routers.go` ``` deployment.RegisterSubRouter(apiGroup) ``` #### 9.2 Deployment 控制器功能 同上操作`controllers/pod`目录 #### 9.2 Deployment具体实现功能 `controllers/deployment/create.go` ``` package deployment import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" appsv1 "k8s.io/api/apps/v1" ) func Create(r *gin.Context) { // 新增 logs.Debug(nil, "开始创建deployment") var deployment appsv1.Deployment info := controllers.Info{} info.Item = &deployment kubeconfig := controllers.NewInfo(r, &info, "创建deployment成功") // 使用kubeutils接口创建deployment var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewDeployment(kubeconfig, &deployment) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Create(r, kubeUtilser) } ``` `controllers/deployment/update.go` ``` package deployment import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" appsv1 "k8s.io/api/apps/v1" ) func Update(r *gin.Context) { logs.Debug(nil, "更新deployment") var deployment appsv1.Deployment info := controllers.Info{} info.Item = &deployment kubeconfig := controllers.NewInfo(r, &info, "更新deployment成功") // 使用kubeutils接口创建deployment var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewDeployment(kubeconfig, &deployment) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Update(r, kubeUtilser) } ``` `controllers/deployment/list.go` ``` package deployment import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" ) func List(r *gin.Context) { logs.Debug(nil, "开始查询deployment列表") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "查询deployment列表详情成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewDeployment(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.List(r, kubeUtilser) } ``` `controllers/deployment/detail.go` ``` package deployment import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" ) func Detail(r *gin.Context) { logs.Debug(nil, "获取deployment详情") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "获取deployment详情成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewDeployment(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Get(r, kubeUtilser) } ``` `controllers/deployment/delete.go` ``` package pod import ( "kms-backend/controllers" "kms-backend/utils/logs" "github.com/dotbalo/kubeutils/kubeutils" "github.com/gin-gonic/gin" ) func Delete(r *gin.Context) { logs.Debug(nil, "开始删除Pod") info := controllers.Info{} kubeconfig := controllers.NewInfo(r, &info, "删除Pod成功") // 使用kubeutils接口创建一个实例 var kubeUtilser kubeutils.KubeUtilser instance := kubeutils.NewPod(kubeconfig, nil) // 把实例赋值给kubeUtilser kubeUtilser = instance // 使用kubeUtilser创建 info.Delete(r, kubeUtilser) } ``` ### 十、StatefuSet 同`Deployment`的操作 ### 十一 DaemonSet 同上 ### 十二、CronJob 同上 ``` kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" --dry-run=client -o json ``` ### 十三、ReplicaSet 在`Deployment`等回滚的时候,要查询以前的版本,这就需要`ReplicaSet`的查询功能 同上操作但是只需要`get`和`list`功能 ### Service 同上 ### Ingress 同上 获取ingress的json格式 ``` kubectl create ingress annotated --class=default --rule="foo.com/bar=svc:port" --annotation ingress.annotation1=foo --annotation ingress.annotation2=bla --dry-run=client -o json ``` ### ConfigMap 同上 ### Secret 同上 ``` kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret --dry-run=client -ojson ``` ### pv 同上 ### pvc 同上 ### StorageClass 只需要`list`功能