后端: go+gin
后端代码地址GitHub - yunixiangfeng/k8s-platform: K8s管理系统后端: go+gin
5、存储与配置
5.1 ConfigMap
5.2 Secret
5.3 PersistentVolumeClaims
6、工作流
6.1 流程设计
6.2 数据库操作(GORM)
(1)初始化数据库
db/db.go
6.3 Workflow
service/workflow.go
(1)列表
(2)获取Workflow详情
(3)新增Workflow
(4)表数据列表
7、中间件
7.1 什么是中间件
7.2 gin中间件用法
7.2 Cors跨域
7.3 JWT token验证
8、WebShell终端
8.1 kubectl exec原理
8.2 实现思路
8.3 代码实现
9、总结
API开发:存储与配置资源
5、存储与配置
5.1 ConfigMap
(1)列表
(2)获取ConfigMap详情
(3)更新ConfigMap
(4) 删除ConfigMap
5.2 Secret
(1)列表
(2)获取Secret详情
(3)更新Secret
(4) 删除Secret
5.3 PersistentVolumeClaims
(1)列表
(2)获取Pvc详情
(3)更新Pvc
(4) 删除Pvc
API开发:部署工作流
6、工作流
6.1 流程设计
6.2 数据库操作(GORM)
(1)初始化数据库
db/init.go
package db
import (
"fmt"
"k8s-plantform/config"
"time"
"github.com/wonderivan/logger"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
var (
isInit bool
GORM *gorm.DB
err error
)
// DB的初始化函数,与数据库建立连接
func Init() {
// 判断是否已经初始化
if isInit {
return
}
// 组装连接配置
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
config.DbUser,
config.DbPass,
config.DbHost,
config.DbPort,
config.DbName)
GORM, err := gorm.Open(config.DbType, dsn)
if err != nil {
panic("数据库连接失败," + err.Error())
}
// 打印sql语句
GORM.LogMode(config.LogMode)
// 开启连接池
GORM.DB().SetMaxIdleConns(config.MaxIdleConns)
GORM.DB().SetMaxOpenConns(config.MaxOpenConns)
GORM.DB().SetConnMaxLifetime(time.Duration(config.MaxLifeTime))
isInit = true
logger.Info("数据库初始化成功")
}
// 关闭数据库连接
func Close() error {
return GORM.Close()
}
加数据库配置
service/config.go
package config
import "time"
const (
ListenAddr = "0.0.0.0:9090"
KubeConfig = "C:\\Users\\Administrator\\.kube\\config"
// tail的日志行数
// tail -n 2000
PodLogTailLine = 2000
// DB Config
DbType = "mysql"
DbHost = "192.168.204.129"
DbPort = 3306
DbName = "k8s_dashboard"
DbUser = "root"
DbPass = ""
// 打印mysql debug的sql日志
LogMode = false
// 连接池配置
MaxIdleConns = 10 // 最大空闲连接
MaxOpenConns = 100 // 最大连接数
MaxLifeTime = 30 * time.Second // 会话时间
)
SetMaxOpenConns
默认情况下,连接池的最大数量是没有限制的,一般来说,连接数越多,访问数据库的性能越高,但是系统资源不是无限的,数据库的并发能力也不是无限的,因此为了减少系统和数据据库崩溃的风险,可以给并发连接教设置一个上限,这个数值一般不超过进程的最大文件句柄打开数,不超过数据库服务自身支持的并发连接数,比如1000。
SetMaxldleConns
理论上maxldleConns连接的上限越高,也即允许在连接池中的空闲连接最大值越大,可以有效减少连接创建和消毁的次数,提高程序的性能,但是连接对象也是占用内存资源的,而且如果空闲连接越多,存在于连接池内的时间可能越长,连接在经过一段时间后有可能会变得不可用,而这时连接还在连接池内没有回收的话,后续被征用的时候就会出问题,一般建议maxidleConns的值为MaxOpenConns的1/2仅供参考。
SetConnMaxLifetime
设置一个连接被使用的最长时间,即过了一段时间后会被强制回收,理论上这可以有效减少不可用连接出现的概率,当数据库方面也设置了连接的超时时间时,这个值应当不超过数据库的超时参数值。
main.go
初始化
package main
import (
"k8s-platform/config"
"k8s-platform/controller"
"k8s-platform/db"
"k8s-platform/service"
"github.com/gin-gonic/gin"
)
func main() {
// 初始化k8s client
service.K8s.Init() // 可以使用service.K8s.clientset 进行跨包调用
// 初始化数据库
db.Init()
// 初始化gin对象/路由配置
r := gin.Default()
// 初始化路由规则
controller.Router.InitApiRouter(r)
// gin程序启动
r.Run(config.ListenAddr)
// 关闭数据库
db.Close()
}
创建数据库k8s_dashboard
PS C:\Users\Administrator\Desktop\k8s-platform> go run main.go
2023-05-07 10:37:11 [INFO] [C:/Users/Administrator/Desktop/k8s-platform/service/init.go:26] 获取K8s配置成功!
2023-05-07 10:37:11 [INFO] [C:/Users/Administrator/Desktop/k8s-platform/service/init.go:33] 创建K8s client 成功!
2023-05-07 10:37:11 [INFO] [C:/Users/Administrator/Desktop/k8s-platform/db/init.go:44] 数据库初始化成功
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /api/k8s/pods --> k8s-platform/controller.(*pod).GetPods-fm (3 handlers)
[GIN-debug] GET /api/k8s/pod/detail --> k8s-platform/controller.(*pod).GetPodDetail-fm (3 handlers)
[GIN-debug] DELETE /api/k8s/pod/del --> k8s-platform/controller.(*pod).DeletePod-fm (3 handlers)
[GIN-debug] PUT /api/k8s/pod/update --> k8s-platform/controller.(*pod).UpdatePod-fm (3 handlers)
[GIN-debug] GET /api/k8s/pod/container --> k8s-platform/controller.(*pod).GetPodContainer-fm (3 handlers)
[GIN-debug] GET /api/k8s/pod/log --> k8s-platform/controller.(*pod).GetPodLog-fm (3 handlers)
[GIN-debug] GET /api/k8s/pod/numnp --> k8s-platform/controller.(*pod).GetPodNumPerNp-fm (3 handlers)
[GIN-debug] GET /api/k8s/deployments --> k8s-platform/controller.(*deployment).GetDeployments-fm (3 handlers)
[GIN-debug] GET /api/k8s/deployment/detail --> k8s-platform/controller.(*deployment).GetDeploymentDetail-fm (3 handlers)
[GIN-debug] PUT /api/k8s/deployment/scale --> k8s-platform/controller.(*deployment).ScaleDeployment-fm (3 handlers)
[GIN-debug] DELETE /api/k8s/deployment/del --> k8s-platform/controller.(*deployment).DeleteDeployment-fm (3 handlers)
[GIN-debug] PUT /api/k8s/deployment/restart --> k8s-platform/controller.(*deployment).RestartDeployment-fm (3 handlers)
[GIN-debug] PUT /api/k8s/deployment/update --> k8s-platform/controller.(*deployment).UpdateDeployment-fm (3 handlers)
[GIN-debug] GET /api/k8s/deployment/numnp --> k8s-platform/controller.(*deployment).GetDeployNumPerNp-fm (3 handlers)
[GIN-debug] POST /api/k8s/deployment/create --> k8s-platform/controller.(*deployment).CreateDeployment-fm (3 handlers)
[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 0.0.0.0:9090
(2)建立表的映射关系
表结构
model/workflow.go
package model
import "time"
// 定义结构体,属性与mysql表字段对齐
type Workflow struct {
// gorm:"primarykey"用于声明主键
ID uint `json:"id" gorm:"primaryKey"`
CreateAt *time.Time `json:"created_at"`
UpdateAt *time.Time `json:"update_at"`
DeleteAt *time.Time `json:"deleted_at"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Replicas int32 `json:"replicas"`
Deployment string `json:"deployment"`
Service string `json:"service"`
Ingress string `json:"ingress"`
// gorm:"column:type"用于声明mysql中表的字段名
Type string `json:"type" gorm:"column:type"`
}
// 定义TableName方法,返回mysql表名,以次定义mysql中的表名
func (*Workflow) TableName() string {
return "workflow"
}
(3)数据库创建表
db\workflow.sql
CREATE TABLE `workflow` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL,
`namespace` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
`replicas` int DEFAULT NULL,
`deployment` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
`service` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
`ingress` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
`type` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
(4)表数据列表
// 获取workflow列表/获取列表分页查询GetList
func (w *workflow) GetWorkflows(filterName, namespace string, limit, page int) (data *WorkflowResp, err error) {
//定义分页的起始位置
startSet := (page - 1) * limit
//定义数据库查询返回的内容
var (
workflowList []*model.Workflow
total int
)
//数据库查询,Limit方法用于限制条数,Offset方法用于设置起始位置
tx := db.GORM.
Model(&model.Workflow{}).
Where("name like ?", "%"+filterName+"%").
Count(&total).
Limit(limit).
Offset(startSet).
Order("id desc").
Find(&workflowList)
if tx.Error != nil && tx.Error.Error() != "record not found" {
logger.Error("获取Workflow列表失败, " + tx.Error.Error())
return nil, errors.New("获取Workflow列表失败, " + tx.Error.Error())
}
return &WorkflowResp{
Items: workflowList,
Total: total,
}, nil
}
(5)获取单条
// 获取详情
func (w *workflow) GetById(id int) (workflow *model.Workflow, err error) {
workflow = &model.Workflow{}
tx := db.GORM.Where("id = ?", id).First(&workflow)
if tx.Error != nil && tx.Error.Error() != "record not found" {
logger.Error("获取Workflow详情失败, " + tx.Error.Error())
return nil, errors.New("获取Workflow详情失败, " + tx.Error.Error())
}
return workflow, nil
}
(6)表数据新增
// 创建
func (w *workflow) Add(workflow *model.Workflow) (err error) {
tx := db.GORM.Create(&workflow)
if tx.Error != nil && tx.Error.Error() != "record not found" {
logger.Error("创建Workflow失败, " + tx.Error.Error())
return errors.New("创建Workflow失败, " + tx.Error.Error())
}
return nil
}
(7)表数据删除
// 删除
func (w *workflow) DelById(id int) (err error) {
tx := db.GORM.Where("id = ?", id).Delete(&model.Workflow{})
if tx.Error != nil && tx.Error.Error() != "record not found" {
logger.Error("获取Workflow详情失败, " + tx.Error.Error())
return errors.New("获取Workflow详情失败, " + tx.Error.Error())
}
return nil
}
package dao
import (
"errors"
"k8s-platform/db"
"k8s-platform/model"
"github.com/wonderivan/logger"
)
var Workflow workflow
type workflow struct{}
//定义列表的返回内容,Items是workflow元素列表,Total为workflow元素数量
type WorkflowResp struct {
Items []*model.Workflow `json:"items"`
Total int `json:"total"`
}
6.3 Workflow
service/workflow.go
package service
import (
"k8s-platform/dao"
"k8s-platform/model"
)
var Workflow workflow
type workflow struct{}
//定义workflowCreate类型
type WorkflowCreate struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Replicas int32 `json:"replicas"`
Image string `json:"image"`
Label map[string]string `json:"label"`
Cpu string `json:"cpu"`
Memory string `json:"memory"`
ContainerPort int32 `json:"container_port"`
HealthCheck bool `json:"health_check"`
HealthPath string `json:"health_path"`
Type string `json:"type"`
Port int32 `json:"port"`
NodePort int32 `json:"node_port"`
Hosts map[string][]*HttpPath `json:"hosts"`
}
(1)列表
//获取列表分页查询
func(w *workflow) GetList(name, namespace string, page, limit int) (data *dao.WorkflowResp, err error) {
data, err = dao.Workflow.GetWorkflows(name, namespace, page, limit)
if err != nil {
return nil, err
}
return data, nil
}
(2)获取Workflow详情
//查询workflow单条数据
func(w *workflow) GetById(id int) (data *model.Workflow, err error) {
data, err = dao.Workflow.GetById(id)
if err != nil {
return nil, err
}
return data, nil
}
(3)新增Workflow
//创建workflow
func(w *workflow) CreateWorkFlow(data *WorkflowCreate) (err error) {
//定义ingress名字
var ingressName string
if data.Type == "Ingress" {
ingressName = getIngressName(data.Name)
} else {
ingressName = ""
}
//workflow数据落库
workflow := &model.Workflow{
Name: data.Name,
Namespace: data.Namespace,
Replicas: data.Replicas,
Deployment: data.Name,
Service: getServiceName(data.Name),
Ingress: ingressName,
Type: data.Type,
}
err = dao.Workflow.Add(workflow)
if err != nil {
return err
}
//创建k8s资源
err = createWorkflowRes(data)
if err != nil {
return err
}
return err
}
//创建k8s资源 deployment service ingress
func createWorkflowRes(data *WorkflowCreate) (err error) {
//创建deployment
dc := &DeployCreate{
Name: data.Name,
Namespace: data.Namespace,
Replicas: data.Replicas,
Image: data.Image,
Label: data.Label,
Cpu: data.Cpu,
Memory: data.Memory,
ContainerPort: data.ContainerPort,
HealthCheck: data.HealthCheck,
HealthPath: data.HealthPath,
}
err = Deployment.CreateDeployment(dc)
if err != nil {
return err
}
var serviceType string
if data.Type != "Ingress" {
serviceType = data.Type
} else {
serviceType = "ClusterIP"
}
//创建service
sc := &ServiceCreate{
Name: getServiceName(data.Name),
Namespace: data.Namespace,
Type: serviceType,
ContainerPort: data.ContainerPort,
Port: data.Port,
NodePort: data.NodePort,
Label: data.Label,
}
if err := Servicev1.CreateService(sc); err != nil {
return err
}
//创建ingress
var ic *IngressCreate
if data.Type == "Ingress" {
ic = &IngressCreate{
Name: getIngressName(data.Name),
Namespace: data.Namespace,
Label: data.Label,
Hosts: data.Hosts,
}
err = Ingress.CreateIngress(ic)
if err != nil {
return err
}
}
return nil
}
//workflow名字转换成service名字,添加-svc后缀
func getServiceName(workflowName string) (serviceName string) {
return workflowName + "-svc"
}
//workflow名字转换成ingress名字,添加-ing后缀
func getIngressName(workflowName string) (ingressName string) {
return workflowName + "-ing"
}
(4)删除workflow
//删除workflow
func(w *workflow) DelById(id int) (err error) {
//获取数据库数据
workflow, err := dao.Workflow.GetById(id)
if err != nil {
return err
}
//删除k8s资源
err = delWorkflowRes(workflow)
if err != nil {
return err
}
//删除数据库数据
err = dao.Workflow.DelById(id)
if err != nil {
return err
}
return
}
//删除k8s资源 deployment service ingress
func delWorkflowRes(workflow *model.Workflow) (err error) {
err = Deployment.DeleteDeployment(workflow.Name, workflow.Namespace)
if err != nil {
return err
}
err = Servicev1.DeleteService(getServiceName(workflow.Name), workflow.Namespace)
if err != nil {
return err
}
if workflow.Type == "Ingress" {
err = Ingress.DeleteIngress(getIngressName(workflow.Name), workflow.Namespace)
if err != nil {
return err
}
}
return nil
}
controller/workflow.go
package controller
import (
"k8s-platform/service"
"net/http"
"github.com/gin-gonic/gin"
"github.com/wonderivan/logger"
)
var Workflow workflow
type workflow struct{}
// 获取列表分页查询
func (w *workflow) GetList(ctx *gin.Context) {
params := new(struct {
Name string `form:"name"`
Namespace string `form:"namespace"`
Page int `form:"page"`
Limit int `form:"limit"`
})
if err := ctx.Bind(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
data, err := service.Workflow.GetList(params.Name, params.Namespace, params.Limit, params.Page)
if err != nil {
logger.Error("获取Workflow列表失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Workflow列表成功",
"data": data,
})
}
// 查询workflow单条数据
func (w *workflow) GetById(ctx *gin.Context) {
params := new(struct {
ID int `form:"id"`
})
if err := ctx.Bind(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
data, err := service.Workflow.GetById(params.ID)
if err != nil {
logger.Error("查询Workflow单条数据失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "查询Workflow单条数据成功",
"data": data,
})
}
// 创建workflow
func (w *workflow) Create(ctx *gin.Context) {
var (
wc = &service.WorkflowCreate{}
err error
)
if err = ctx.ShouldBindJSON(wc); err != nil {
logger.Error("Bind请求参数dc失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
if err = service.Workflow.CreateWorkFlow(wc); err != nil {
logger.Error("创建Workflow失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "创建Workflow成功",
"data": nil,
})
}
// 删除workflow
func (w *workflow) DelById(ctx *gin.Context) {
params := new(struct {
ID int `json:"id"`
})
if err := ctx.ShouldBindJSON(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
if err := service.Workflow.DelById(params.ID); err != nil {
logger.Error("删除Workflow失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "删除Workflow成功",
"data": nil,
})
}
配置workflow路由
controller/router.go
package controller
import (
"github.com/gin-gonic/gin"
)
// // 初始化router类型对象,首字母大写,用于跨包调用
// var Router router
// // 声明一个router的结构体
// type router struct{}
// func (r *router) InitApiRouter(router *gin.Engine) {
// router.GET("/", Index)
// }
// func Index(ctx *gin.Context) {
// ctx.JSON(200, gin.H{
// "code": 200,
// "msg": "In index",
// })
// }
// 实例化router结构体,可使用该对象点出首字母大写的方法(包外调用)
var Router router
// 创建router的结构体
type router struct{}
// // 初始化路由规则,创建测试api接口
// func (r *router) InitApiRouter(router *gin.Engine) {
// router.GET("/testapi", func(ctx *gin.Context) {
// ctx.JSON(http.StatusOK, gin.H{
// "msg": "testapi success!",
// "data": nil,
// })
// })
// }
// 初始化路由规则
// func (r *router) InitApiRouter(router *gin.Engine) {
// router.
// GET("/api/k8s/pods", Pod.GetPods).
// GET("/api/k8s/pod/detail", Pod.GetPodDetail).
// POST("/api/k8s/pods", Pod.DeletePod).
func (r *router) InitApiRouter(router *gin.Engine) {
router.
// Pods
GET("/api/k8s/pods", Pod.GetPods).
GET("/api/k8s/pod/detail", Pod.GetPodDetail).
DELETE("/api/k8s/pod/del", Pod.DeletePod).
PUT("/api/k8s/pod/update", Pod.UpdatePod).
GET("/api/k8s/pod/container", Pod.GetPodContainer).
GET("/api/k8s/pod/log", Pod.GetPodLog).
GET("/api/k8s/pod/numnp", Pod.GetPodNumPerNp).
//deployment操作
GET("/api/k8s/deployments", Deployment.GetDeployments).
GET("/api/k8s/deployment/detail", Deployment.GetDeploymentDetail).
PUT("/api/k8s/deployment/scale", Deployment.ScaleDeployment).
DELETE("/api/k8s/deployment/del", Deployment.DeleteDeployment).
PUT("/api/k8s/deployment/restart", Deployment.RestartDeployment).
PUT("/api/k8s/deployment/update", Deployment.UpdateDeployment).
GET("/api/k8s/deployment/numnp", Deployment.GetDeployNumPerNp).
POST("/api/k8s/deployment/create", Deployment.CreateDeployment).
// workflows
GET("/api/k8s/workflows", Workflow.GetList).
GET("/api/k8s/workflow/detail", Workflow.GetById).
POST("/api/k8s/workflow/create", Workflow.Create).
DELETE("/api/k8s/workflow/del", Workflow.DelById)
}
测试api接口
7、中间件
7.1 什么是中间件
中间件,英译middleware,顾名思义,放在中间的物件,那么放在谁中间呢?本来,客户端可以直接请求到服务端接口。现在,中间件横插一脚它能在请求到达接口之前拦截请求,做一些特殊处理,比如日志记录,故障处理等
7.2 gin中间件用法
7.2 Cors跨域
7.3 JWT token验证
8、WebShell终端
8.1 kubectl exec原理
8.2 实现思路
8.3 代码实现
(1)处理终端交互
service/terminal.go
9、总结