Skip to main content
Glama
lua_inspection.go6.43 kB
package lua import ( "bytes" "context" "encoding/json" "fmt" "os" "strings" "time" "github.com/weibaohui/k8m/internal/dao" "github.com/weibaohui/k8m/pkg/comm/utils" "github.com/weibaohui/k8m/pkg/constants" "github.com/weibaohui/k8m/pkg/models" "github.com/weibaohui/kom/kom" lua "github.com/yuin/gopher-lua" "gorm.io/gorm" "k8s.io/klog/v2" ) type Inspection struct { Cluster string // 集群名称 lua *lua.LState Schedule *models.InspectionSchedule // 巡检计划ID } func NewLuaInspection(schedule *models.InspectionSchedule, cluster string) *Inspection { instance := &Inspection{ Cluster: cluster, Schedule: schedule, lua: lua.NewState(), } instance.registerKubectlFunc() return instance } // 调用方法可参考pkg/models/lua_scripts_builtin.go中的示例 func (p *Inspection) registerKubectlFunc() { p.lua.SetGlobal("log", p.lua.NewFunction(logFunc)) k := kom.Cluster(p.Cluster) if k == nil { klog.Errorf("巡检 集群【%s】,但是该集群未连接,巡检结果为失败", p.Cluster) } ud := p.lua.NewUserData() ud.Value = &Kubectl{k} p.lua.SetGlobal("kubectl", ud) // 设置元方法 mt := p.lua.NewTypeMetatable("kubectl") p.lua.SetField(mt, "__index", p.lua.SetFuncs(p.lua.NewTable(), map[string]lua.LGFunction{ "GVK": gvkFunc, // kubectl.GVK(group, version, kind) Kind首字母大写 "WithLabelSelector": withLabelSelectorFunc, "Name": withNameFunc, "Namespace": withNamespaceFunc, "AllNamespace": withAllNamespaceFunc, "Cache": withCacheFunc, "List": listResource, "Doc": getDoc, "Get": getResource, "GetLogs": getLogs, "GetPodResourceUsage": getPodResourceUsage, })) p.lua.SetMetatable(ud, mt) } func (p *Inspection) Start() []CheckResult { // 初始化 Lua 状态 defer p.lua.Close() params := &dao.Params{ PerPage: 10000000, } klog.V(6).Infof("p.Schedule.ScriptCodes: %v", utils.ToJSON(p.Schedule.ScriptCodes)) // 执行所有 Lua 检查脚本并收集结果 var results []CheckResult // 从数据库读取内置检查脚本 script := models.InspectionLuaScript{} list, _, err := script.List(params, func(db *gorm.DB) *gorm.DB { return db.Where("script_code in ?", strings.Split(p.Schedule.ScriptCodes, ",")) }) if err != nil { fmt.Println("无法从数据库读取检查脚本:", err) return results } for _, item := range list { result := p.runLuaCheck(item) results = append(results, result) } // 打印所有检测结果 for _, res := range results { fmt.Printf("\n--- 检查: %s ---\n", res.Name) fmt.Printf("开始时间: %s\n结束时间: %s\n", res.StartTime.Format(time.RFC3339), res.EndTime.Format(time.RFC3339)) if res.LuaRunError != nil { fmt.Printf("错误: %v\n", res.LuaRunError) } fmt.Printf("输出:\n%s\n", res.LuaRunOutput) if len(res.Events) > 0 { fmt.Println("结构化检测失败事件:") for _, evt := range res.Events { if evt.Status != string(constants.LuaEventStatusNormal) { b, _ := json.Marshal(evt) fmt.Println(string(b)) } } } else { fmt.Println("无结构化检测事件(未调用 check_event)") } } return results } // runLuaCheck 执行单个Lua脚本检查,支持超时控制和脚本中断 func (p *Inspection) runLuaCheck(item *models.InspectionLuaScript) CheckResult { var buf bytes.Buffer origStdout := os.Stdout r, w, _ := os.Pipe() os.Stdout = w defer func() { os.Stdout = origStdout }() var events []CheckEvent p.registerCheckEvent(&events, item) // 获取超时时间,如果未设置或为0,则使用默认60秒 timeoutSeconds := item.TimeoutSeconds if timeoutSeconds <= 0 { timeoutSeconds = 60 } timeout := time.Duration(timeoutSeconds) * time.Second start := time.Now() // 创建可取消的上下文 ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // 为 Lua 状态设置上下文,使其能够响应取消信号 p.lua.SetContext(ctx) // 使用channel来处理超时 type result struct { err error } resultChan := make(chan result, 1) // 在goroutine中执行Lua脚本 go func() { defer func() { // 捕获可能的 panic,防止程序崩溃 if r := recover(); r != nil { klog.Warningf("Lua脚本 [%s] 执行时发生panic: %v", item.Name, r) resultChan <- result{err: fmt.Errorf("脚本执行发生panic: %v", r)} } }() err := p.lua.DoString(item.Script) resultChan <- result{err: err} }() var err error // 等待脚本执行完成或超时 select { case res := <-resultChan: err = res.err case <-ctx.Done(): // 上下文被取消(超时或手动取消) if ctx.Err() == context.DeadlineExceeded { err = fmt.Errorf("脚本执行超时(%d秒)", timeoutSeconds) klog.Warningf("Lua脚本 [%s] 执行超时,超时时间: %d秒,脚本已被中断", item.Name, timeoutSeconds) } else { err = fmt.Errorf("脚本执行被取消: %v", ctx.Err()) klog.Warningf("Lua脚本 [%s] 执行被取消: %v", item.Name, ctx.Err()) } } _ = w.Close() os.Stdout = origStdout _, _ = buf.ReadFrom(r) _ = r.Close() // 读取完立即关闭 output := buf.String() end := time.Now() return CheckResult{ Name: item.Name, StartTime: start, EndTime: end, LuaRunOutput: output, LuaRunError: err, Events: events, } } // 注册 check_event 到 Lua,自动补充上下文 func (p *Inspection) registerCheckEvent(events *[]CheckEvent, item *models.InspectionLuaScript) { p.lua.SetGlobal("check_event", p.lua.NewFunction(func(L *lua.LState) int { status := L.CheckString(1) msg := L.CheckString(2) var extra map[string]any if L.GetTop() >= 3 { extraVal := L.CheckAny(3) if tbl, ok := extraVal.(*lua.LTable); ok { extra = lValueToGoValue(tbl).(map[string]any) } } var name, namespace string if v, ok := extra["name"]; ok { name, _ = v.(string) } if v, ok := extra["namespace"]; ok { namespace, _ = v.(string) } *events = append(*events, CheckEvent{ Name: name, Namespace: namespace, Status: status, Msg: msg, Extra: extra, ScriptName: item.Name, // 检测脚本名称 Kind: item.Kind, // 检查的资源类型 CheckDesc: item.Description, // 检查脚本内容描述 }) return 0 })) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/weibaohui/k8m'

If you have feedback or need assistance with the MCP directory API, please join our Discord server