动手搓一个kubernetes管理平台(3)-后端框架

后端框架的选择面比较大,由于不涉及复杂的调度/分布式管理等场景,所以后端选用一个标准的web server即可,比如gin, iris, beego等等,因为正好最近在看iris的一些项目,所以就选用了irsi+corba的框架进行后端开发 。

通过cobra进行初始化的操作就不在赘述,这边先show一下相关的目录结构

cmd # 启动命令
common # 通用组件,如mysql, conf, log等
conf # 配置表
core/server #  核心服务,http server
dao/v1 # 数据层
docs # 文档
http # 接口层
img # readme的截图,架构图
migrate # 首次启动的数据同步 
pkg # 非通用组件,如k8s客户端,websocket等
service/v1 # 业务逻辑的封装
structs/v1 # 所有结构体
main.go # 入口

封装KubeMgrServer的对象

package server

import (
	"fmt"
	"github.com/iris-contrib/swagger/v12"
	"github.com/iris-contrib/swagger/v12/swaggerFiles"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/context"
	"github.com/kataras/iris/v12/sessions"
	"github.com/kataras/iris/v12/sessions/sessiondb/redis"
	cfg "kubemgr/common/configparse"
	log "kubemgr/common/formatlog"
	"kubemgr/common/i18n"
	"kubemgr/common/mysql"
	redis_common "kubemgr/common/redis"
	_ "kubemgr/docs" // docs is generated by Swag CLI, you have to import it.
	"kubemgr/migrate"
	"strings"
	"time"
)

// session管理
const SessionCookieName = "KUBEMGR_SESS"

var SessionMgr *sessions.Sessions

type KubeMgrServer struct {
	app        *iris.Application
	rootRoute  iris.Party
	configPath string
	bind       string
}

var km *KubeMgrServer

// 注册redis
func NewRedis() *redis.Database {
	db := redis.New(redis.Config{
		Network:   "tcp",
		Addr:      cfg.GlobalConf.GetStr("common", "redisbind"),
		Timeout:   time.Duration(30) * time.Second,
		MaxActive: cfg.GlobalConf.GetInt("common", "redismaxactive"),
		Password:  cfg.GlobalConf.GetStr("common", "redispasswd"),
		Database:  cfg.GlobalConf.GetStr("common", "redisdb"),
		Prefix:    cfg.GlobalConf.GetStr("common", "redisprefix"),
		Driver:    redis.GoRedis(), // redis.Radix() can be used instead.
	})
	// defer db.Close()
	return db
}

// 启动服务
func Listen(route func(party iris.Party), confgPath string, serverBindHost string, serverBindPort int) error {
	bind := fmt.Sprintf("%v:%v", serverBindHost, serverBindPort)
	km = NewServer(confgPath, bind)
	route(km.rootRoute)
	return km.app.Run(iris.Addr(bind))
}

// 初始化服务对象
func NewServer(confgPath, bind string) *KubeMgrServer {
	c := &KubeMgrServer{}
	c.app = iris.New()
	c.configPath = confgPath
	c.bind = bind
	return c.bootstrap()
}

// 初始化KubeMgrServer对象,初始化各个模块
func (e *KubeMgrServer) bootstrap() *KubeMgrServer {
	e.setUpRootRoute()
	e.setUpConfig()
	e.setUpLogger()
	e.setUpDB()
	e.setUpSession()
	e.setResultHandler()
	e.setUpErrHandler()
	e.setDBInitial()
	return e
}

// 初始化路由,所有路由的/ 路径,均为kubemgr
func (e *KubeMgrServer) setUpRootRoute() {
	e.app.Any("/", func(ctx *context.Context) {
		ctx.Redirect("/kubemgr")
	})
	c := swagger.Config{
		URL: "/kubemgr/swagger/doc.json",
	}
	e.app.Get("/kubemgr/swagger/{any:path}", swagger.CustomWrapHandler(&c, swaggerFiles.Handler))
	e.rootRoute = e.app.Party("/kubemgr")
}

// 初始化配置文件
func (e *KubeMgrServer) setUpConfig() {
	cfg.GlobalConf.CfgInit(e.configPath)
}

// 初始化日志
func (e *KubeMgrServer) setUpLogger() {
	// logname := cfg.GlobalConf.GetStr("common", "logname")
	loglevel := cfg.GlobalConf.GetStr("common", "loglevel")
	log.InitLog(loglevel)
}

// 初始化DB
func (e *KubeMgrServer) setUpDB() {
	mysql.DB.InitConn()
	redis_common.DB.NewRds()
}

// 初始化session
func (e *KubeMgrServer) setUpSession() {
	SessionMgr = sessions.New(sessions.Config{Cookie: SessionCookieName, AllowReclaim: true, Expires: time.Duration(cfg.GlobalConf.GetInt("common", "sessiontimeout")) * time.Hour})
	db := NewRedis()
	SessionMgr.UseDatabase(db)
	e.rootRoute.Use(SessionMgr.Handler())
}

// 初始化数据库实例
func (e *KubeMgrServer) setDBInitial() {
	DataSourceUrl := fmt.Sprintf("%s%s%s", "mysql://", cfg.GlobalConf.GetStr("mysql", "datasource"), "?multiStatements=true")
	migrate.InitDB(DataSourceUrl, 5*time.Second)
}

// 初始化response
func (e *KubeMgrServer) setResultHandler() {
	e.rootRoute.Use(func(ctx *context.Context) {
		ctx.Next()
		if ctx.GetStatusCode() >= iris.StatusOK && ctx.GetStatusCode() < iris.StatusBadRequest {
			if ctx.Values().Get("token") != nil {
				_, _ = ctx.Write(ctx.Values().Get("token").([]uint8))
			} else {
				resp := iris.Map{
					"success": true,
					"data":    ctx.Values().Get("data"),
				}
				_ = ctx.JSON(resp)
			}
		}
	})
}

// 捕获异常返回
func (e *KubeMgrServer) setUpErrHandler() {
	e.rootRoute.OnAnyErrorCode(func(ctx iris.Context) {
		//如果报错message为空,且errcode为404,则进行错误返回
		if ctx.Values().GetString("message") == "" {
			switch ctx.GetStatusCode() {
			case iris.StatusNotFound:
				ctx.Values().Set("message", "the server could not find the requested resource")
			}
		}

		message := ctx.Values().Get("message")
		if message == nil || message == "" {
			message = ctx.Values().Get("iris.context.error")
		}
		// 默认接口返回错误信息为US
		lang := ctx.Values().GetString("language")
		if lang == "" {
			lang = i18n.LanguageEnUS
		}
		var (
			translateMessage string
			err              error
			originMessage    string
		)

		switch value := message.(type) {
		case string:
			originMessage = message.(string)
			translateMessage, err = i18n.Translate(lang, value)
		case []string:
			originMessage = strings.Join(value, ",")
			if len(value) > 0 {
				translateMessage, err = i18n.Translate(lang, value[0], value[1:])
			}
		case context.ErrPrivate:
			err := message.(context.ErrPrivate)
			translateMessage = err.Error()
		}
		msg := translateMessage
		if err != nil {
			e.app.Logger().Debug(err)
			msg = originMessage
		}
		er := iris.Map{
			"success": false,
			"code":    ctx.GetStatusCode(),
			"message": msg,
		}
		_ = ctx.JSON(er)
	})
}

上述代码对KubeMgrServer的对象进行了初步的初始化,封装了日志/DB/缓存的/异常/返回,在完整基本的封装以后,可以进行接口的封装了,iris或者gin之类的 框架,最大的优势在于有ctx的概念,可以在请求的上下文中进行数据的修改,提取,插入等等,这就很方便了,可以利用这点就行token的提取,参数的抓取 ,甚至于请求头的改写(比如将http请求升级到websocket)。

在完成基本主体的封装后,开始编写接口,首先将接口分个类, 分成对应功能的目录 :

http
├── api
│   └── v1
│       ├── audit
│       │   ├── audit.go
│       │   └── types.go
│       ├── cluster
│       │   ├── cluster.go
│       │   ├── clusterrole.go
│       │   ├── member.go
│       │   └── types.go
│       ├── kubernetes
│       │   ├── prometheus.go
│       │   ├── proxy.go
│       │   └── types.go
│       ├── role
│       │   ├── role.go
│       │   └── types.go
│       ├── session
│       │   ├── session.go
│       │   └── types.go
│       ├── user
│       │   ├── types.go
│       │   └── user.go
│       ├── v1.go
│       └── ws
│           └── ws.go
└── route
    └── route.go

上述目录将不同功能的接口基于不同目录进行了封装,类似如下代码:

package audit

import (
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/context"
	log "kubemgr/common/formatlog"
	v1AuditService "kubemgr/service/v1/audit"
)

type Handler struct {
	auditService v1AuditService.Service
}

func NewHandler() *Handler {
	return &Handler{
		auditService: v1AuditService.NewService(),
	}
}

func (h *Handler) ListPlatFormAuditRecord() iris.Handler {
	return func(ctx *context.Context) {
		pageNum := ctx.URLParamIntDefault("pageNum", 0)
		pageSize := ctx.URLParamIntDefault("pageSize", 10)
		fromTime := ctx.URLParam("fromTime")
		endTime := ctx.URLParam("toTime")
		total, auditLogs, err := h.auditService.ListPlatFormAuditRecord(pageNum, pageSize, fromTime, endTime)
		if err != nil {
			log.Errorf("获取审计日志失败: %v", err.Error())
			ctx.StatusCode(iris.StatusInternalServerError)
			ctx.Values().Set("message", err.Error())
			return
		}

		var allAuditLogs ListAuditLogs
		allAuditLogs.Total = total
		allAuditLogs.AuditLogs = auditLogs

		ctx.StatusCode(iris.StatusOK)
		ctx.Values().Set("data", &allAuditLogs)
	}
}

func Install(parent iris.Party) {
	handler := NewHandler()
	sp := parent.Party("/audit")
	sp.Get("/list", handler.ListPlatFormAuditRecord())
}

然后在v1.go里面,将每个目录的接口接入root_route,并进行基础功能的封装,如日志,认证等等

func AddV1Route(app iris.Party) {
	v1Party := app.Party("/v1")
	session.Install(v1Party)

  // 接口国际化,基于profile内的语言标识,来返回中文/英文
	v1Party.Use(langHandler())
  // websocket请求,将http请求升级到websocket,由于websocket获取token的方式比较特殊,所以不放入统一认证的方法,单独编写
	wss.Install(v1Party)

	// 基础路由的首次封装,添加认证,权限,角色的相关解析操作
	authParty := v1Party.Party("")
	authParty.Use(authHandler())
	authParty.Use(resourceExtractHandler())
	authParty.Use(roleHandler())

	// 将封装好的路由导入平台用户/角色权限管理的相关接口
	user.Install(authParty)
	role.Install(authParty)

	// 将封装好的路由导入平台审计/集群管理的相关接口
	audit.Install(authParty)
	cluster.Install(authParty)
	kubernetes.Install(authParty)
}

至此,可以基于上面的代码看到api的请求路径

请求→v1.go(进行路由封装和分发)→分发到的明细路由处理(例如audit.go)→转发到业务实际处理逻辑,比如调用k8或者crud等

httpRequest
http/api/v1.go
audit...
service
dao/pkg

大致上的后端逻辑就是上面这样了,平台的基本操作用不到pkg,简单的crud即可。这个和其他运维或者中台管理的逻辑大致上没什么区别,主要后面要看对于k8s的操作。

个人公众号, 分享一些日常开发,运维工作中的日常以及一些学习感悟,欢迎大家互相学习,交流

在这里插入图片描述

你可能感兴趣的:(从零开始写一个k8s管理平台,kubernetes,容器,云原生)