package main import ( "cmii-uav-watchdog-common/wdd_log" "flag" "os" "os/exec" "os/signal" "strings" "sync" "syscall" "time" ) // 是否是Debug模式 var DebugMode = false var ( businessProgramType = flag.String("business-program-type", "", "Type of business program (java or python)") businessProgramPath = flag.String("business-program-path", "", "Path to the business program file") stopRequested bool currentCmd *exec.Cmd mu sync.Mutex ) // java 启动参数 var ( podName = "unknown" imageName = "unknown" imageVersion = "unknown" workpath = "/cmii" custJavaOpts = "-Xms200m -Xmx1500m -Djava.awt.headless=true -Dlog4j2.formatMsgNoLookups=true " envJvmTimezone = "Asia/Shanghai" k8sNamespace = "default" applicationName = "app" nacosRegistry = "helm-nacos:8848" nacosDiscoveryIp = "127.0.0.1" nacosDiscoveryPort = "8080" nacosUsername = "nacos" nacosPassword = "nacos" bizConfigGroup = "wdd-biz" sysConfigGroup = "wdd-sys" ) // 初始化 func init() { // 获取环境变量 特殊设置才能开启DEBUG debugMode := os.Getenv("CMII_DEBUG_MODE") if debugMode == "WDD_DEBUG" { DebugMode = true } wdd_log.Info("DebugMode 是否开启: %v", DebugMode) } // 初始化配置并打印配置信息 func initConfig() { // 获取环境变量 imageName = getEnvOrDefault("IMAGE_NAME", imageName) if imageName != "unknown" { parts := strings.Split(imageName, ":") if len(parts) > 1 { imageVersion = parts[len(parts)-1] } } workpath = getEnvOrDefault("WORKPATH", workpath) custJavaOpts = getEnvOrDefault("CUST_JAVA_OPTS", custJavaOpts) envJvmTimezone = getEnvOrDefault("ENV_JVM_TIMEZONE", envJvmTimezone) k8sNamespace = getEnvOrDefault("K8S_NAMESPACE", k8sNamespace) applicationName = getEnvOrDefault("APPLICATION_NAME", applicationName) nacosRegistry = getEnvOrDefault("NACOS_REGISTRY", nacosRegistry) nacosDiscoveryIp = getEnvOrDefault("NACOS_DISCOVERY_IP", nacosDiscoveryIp) nacosDiscoveryPort = getEnvOrDefault("NACOS_DISCOVERY_PORT", nacosDiscoveryPort) nacosUsername = getEnvOrDefault("NACOS_USERNAME", nacosUsername) nacosPassword = getEnvOrDefault("NACOS_PASSWORD", nacosPassword) // 设置默认配置组 defaultConfigGroup := "default" if imageVersion != "unknown" { parts := strings.Split(imageVersion, "-") if len(parts) > 0 { defaultConfigGroup = parts[0] } } bizConfigGroup = getEnvOrDefault("BIZ_CONFIG_GROUP", bizConfigGroup) if bizConfigGroup == "" { wdd_log.Info("[CONTAINER] As BIZ_CONFIG_GROUP is null, it set default value [%s]", defaultConfigGroup) bizConfigGroup = defaultConfigGroup os.Setenv("BIZ_CONFIG_GROUP", bizConfigGroup) } sysConfigGroup = getEnvOrDefault("SYS_CONFIG_GROUP", sysConfigGroup) if sysConfigGroup == "" { wdd_log.Info("[CONTAINER] As SYS_CONFIG_GROUP is null, it set default value [%s]", defaultConfigGroup) sysConfigGroup = defaultConfigGroup os.Setenv("SYS_CONFIG_GROUP", sysConfigGroup) } // 打印配置信息 wdd_log.Info("[CONTAINER] %s image is running ...", imageName) wdd_log.Info("[CONTAINER] IMAGE_VERSION is %s", imageVersion) wdd_log.Info("[CONTAINER] WORKPATH is %s", workpath) wdd_log.Info("[CONTAINER] CUST_JAVA_OPTS is %s", custJavaOpts) wdd_log.Info("[CONTAINER] JVM_TIMEZONE is %s", envJvmTimezone) wdd_log.Info("[CONTAINER] K8S_NAMESPACE is %s", k8sNamespace) wdd_log.Info("[CONTAINER] APPLICATION_NAME is %s", applicationName) wdd_log.Info("[CONTAINER] NACOS_REGISTRY is %s", nacosRegistry) wdd_log.Info("[CONTAINER] NACOS_DISCOVERY_IP is %s", nacosDiscoveryIp) wdd_log.Info("[CONTAINER] NACOS_DISCOVERY_PORT is %s", nacosDiscoveryPort) wdd_log.Info("[CONTAINER] NACOS_USERNAME is %s", nacosUsername) wdd_log.Info("[CONTAINER] NACOS_PASSWORD is %s", nacosPassword) wdd_log.Info("[CONTAINER] BIZ_CONFIG_GROUP is %s", bizConfigGroup) wdd_log.Info("[CONTAINER] SYS_CONFIG_GROUP is %s", sysConfigGroup) wdd_log.Info("[CONTAINER] starting...") } // 获取环境变量,如果为空则返回默认值 func getEnvOrDefault(key, defaultValue string) string { value := os.Getenv(key) if value == "" { return defaultValue } return value } func startBusinessProcess(programType, programPath string) *exec.Cmd { var cmd *exec.Cmd switch programType { case "java": // 初始化配置 initConfig() // 构建命令参数列表 args := []string{} // 添加Java选项 if custJavaOpts != "" { // 分割CUST_JAVA_OPTS中的多个参数 for _, opt := range splitArgs(custJavaOpts) { if opt != "" { args = append(args, opt) } } } // 添加主JAR文件 args = append(args, "-jar", programPath) // 添加其他参数 args = append(args, []string{ "--user.timezone=" + envJvmTimezone, "-Dfile.encoding=UTF-8", "--spring.main.allow-bean-definition-overriding=true", "--spring.application.name=" + applicationName, "--spring.cloud.nacos.username=" + nacosUsername, "--spring.cloud.nacos.password=" + nacosPassword, "--spring.cloud.nacos.config.server-addr=" + nacosRegistry, "--spring.cloud.nacos.config.extension-configs[0].data-id=" + applicationName + ".yml", "--spring.cloud.nacos.config.extension-configs[0].group=" + bizConfigGroup, "--spring.cloud.nacos.config.extension-configs[0].refresh=true", "--spring.cloud.nacos.config.shared-configs[0].data-id=cmii-backend-system.yml", "--spring.cloud.nacos.config.shared-configs[0].group=" + sysConfigGroup, "--spring.cloud.nacos.config.shared-configs[0].refresh=true", "--spring.cloud.nacos.discovery.server-addr=" + nacosRegistry, "--spring.cloud.nacos.discovery.ip=" + nacosDiscoveryIp, "--spring.cloud.nacos.discovery.port=" + nacosDiscoveryPort, }...) wdd_log.Info("[CONTAINER] java args: %v", args) cmd = exec.Command("java", args...) case "python": cmd = exec.Command("python", programPath) default: wdd_log.Error("不支持的业务程序类型: %s", programType) } return cmd } // 分割命令行参数 func splitArgs(s string) []string { var args []string var inQuote bool var current string var quoteChar rune for _, r := range s { switch { case (r == '"' || r == '\'') && !inQuote: inQuote = true quoteChar = r case r == quoteChar && inQuote: inQuote = false quoteChar = 0 case r == ' ' && !inQuote: if current != "" { args = append(args, current) current = "" } default: current += string(r) } } if current != "" { args = append(args, current) } return args } func main() { // 解析命令行参数 flag.Parse() if *businessProgramType == "" || *businessProgramPath == "" { wdd_log.Error("缺少必要的参数: -business-program-type 和 -business-program-path 必须指定") } // 信号处理 signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) // StartHeartbeatDetection(signalChan) go func() { for sig := range signalChan { wdd_log.Info("接收到信号: %v", sig) mu.Lock() stopRequested = true if currentCmd != nil && currentCmd.Process != nil { // 发送 SIGTERM 给业务进程 if err := currentCmd.Process.Signal(syscall.SIGTERM); err != nil { wdd_log.Error("向进程发送SIGTERM信号失败: %v", err) } // 等待 10 秒后强制杀死进程 time.AfterFunc(10*time.Second, func() { mu.Lock() defer mu.Unlock() if currentCmd != nil && currentCmd.Process != nil { wdd_log.Warn("优雅关闭超时,发送SIGKILL信号") currentCmd.Process.Kill() } }) } mu.Unlock() } }() // 授权检测 go func() { for { StartHeartbeatDetection(signalChan) } }() // 主循环 for { mu.Lock() if stopRequested { mu.Unlock() wdd_log.Info("收到停止请求,正在关闭") os.Exit(0) } mu.Unlock() cmd := startBusinessProcess(*businessProgramType, *businessProgramPath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // 启动业务进程 if err := cmd.Start(); err != nil { wdd_log.Error("启动业务进程失败: %v", err) time.Sleep(5 * time.Second) continue } // 业务进程启动成功 if *businessProgramType == "java" { wdd_log.Info("[CONTAINER] 程序启动成功!") } mu.Lock() currentCmd = cmd mu.Unlock() // 等待业务进程退出 err := cmd.Wait() mu.Lock() currentCmd = nil mu.Unlock() if err != nil { wdd_log.Error("业务进程异常退出: %v", err) } else { wdd_log.Info("业务进程正常退出") } mu.Lock() if stopRequested { mu.Unlock() wdd_log.Info("收到停止请求,正在关闭") os.Exit(0) } mu.Unlock() // 等待 5 秒后重启 wdd_log.Info("5秒后重启业务进程...") time.Sleep(5 * time.Second) } }