mirror of
https://github.com/TeaOSLab/EdgeAPI.git
synced 2025-11-07 02:20:24 +08:00
API节点启动失败后记录相关问题和处理建议
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/apps"
|
"github.com/TeaOSLab/EdgeAPI/internal/apps"
|
||||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"github.com/iwind/gosock/pkg/gosock"
|
"github.com/iwind/gosock/pkg/gosock"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -21,12 +23,12 @@ func main() {
|
|||||||
if !Tea.IsTesting() {
|
if !Tea.IsTesting() {
|
||||||
Tea.Env = "prod"
|
Tea.Env = "prod"
|
||||||
}
|
}
|
||||||
app := apps.NewAppCmd()
|
var app = apps.NewAppCmd()
|
||||||
app.Version(teaconst.Version)
|
app.Version(teaconst.Version)
|
||||||
app.Product(teaconst.ProductName)
|
app.Product(teaconst.ProductName)
|
||||||
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon]")
|
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon|issues]")
|
||||||
app.On("setup", func() {
|
app.On("setup", func() {
|
||||||
setupCmd := setup.NewSetupFromCmd()
|
var setupCmd = setup.NewSetupFromCmd()
|
||||||
err := setupCmd.Run()
|
err := setupCmd.Run()
|
||||||
result := maps.Map{}
|
result := maps.Map{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -122,6 +124,43 @@ func main() {
|
|||||||
fmt.Println("prepared statements count: " + types.String(count))
|
fmt.Println("prepared statements count: " + types.String(count))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
app.On("issues", func() {
|
||||||
|
var flagSet = flag.NewFlagSet("issues", flag.ExitOnError)
|
||||||
|
var formatJSON = false
|
||||||
|
flagSet.BoolVar(&formatJSON, "json", false, "")
|
||||||
|
_ = flagSet.Parse(os.Args[2:])
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(Tea.LogFile("issues.log"))
|
||||||
|
if err != nil {
|
||||||
|
if formatJSON {
|
||||||
|
fmt.Print("[]")
|
||||||
|
} else {
|
||||||
|
fmt.Println("no issues yet")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var issueMaps = []maps.Map{}
|
||||||
|
err = json.Unmarshal(data, &issueMaps)
|
||||||
|
if err != nil {
|
||||||
|
if formatJSON {
|
||||||
|
fmt.Print("[]")
|
||||||
|
} else {
|
||||||
|
fmt.Println("no issues yet")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if formatJSON {
|
||||||
|
fmt.Print(string(data))
|
||||||
|
} else {
|
||||||
|
if len(issueMaps) == 0 {
|
||||||
|
fmt.Println("no issues yet")
|
||||||
|
} else {
|
||||||
|
for i, issue := range issueMaps {
|
||||||
|
fmt.Println("issue " + types.String(i+1) + ": " + issue.GetString("message"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.Run(func() {
|
app.Run(func() {
|
||||||
nodes.NewAPINode().Start()
|
nodes.NewAPINode().Start()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package nodes
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/accesslogs"
|
"github.com/TeaOSLab/EdgeAPI/internal/accesslogs"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc"
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/setup"
|
"github.com/TeaOSLab/EdgeAPI/internal/setup"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/dbs"
|
"github.com/iwind/TeaGo/dbs"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
@@ -35,6 +37,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -51,12 +54,18 @@ type APINode struct {
|
|||||||
sock *gosock.Sock
|
sock *gosock.Sock
|
||||||
|
|
||||||
isStarting bool
|
isStarting bool
|
||||||
|
|
||||||
|
issues []*StartIssue
|
||||||
|
issuesFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPINode() *APINode {
|
func NewAPINode() *APINode {
|
||||||
return &APINode{
|
return &APINode{
|
||||||
serviceInstanceMap: map[string]interface{}{},
|
serviceInstanceMap: map[string]interface{}{},
|
||||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||||
|
|
||||||
|
issues: []*StartIssue{},
|
||||||
|
issuesFile: Tea.LogFile("issues.log"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +74,27 @@ func (this *APINode) Start() {
|
|||||||
|
|
||||||
logs.Println("[API_NODE]start api node, pid: " + strconv.Itoa(os.Getpid()))
|
logs.Println("[API_NODE]start api node, pid: " + strconv.Itoa(os.Getpid()))
|
||||||
|
|
||||||
// 检查数据库连接
|
// 保存启动过程中的问题,以便于查看
|
||||||
err := this.checkDB()
|
defer func() {
|
||||||
if err != nil {
|
this.saveIssues()
|
||||||
logs.Println("[API_NODE]" + err.Error())
|
}()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 本地Sock
|
// 本地Sock
|
||||||
logs.Println("[API_NODE]listening sock ...")
|
logs.Println("[API_NODE]listening sock ...")
|
||||||
err = this.listenSock()
|
err := this.listenSock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("[API_NODE]" + err.Error())
|
var errString = "start local sock failed: " + err.Error()
|
||||||
|
logs.Println("[API_NODE]" + errString)
|
||||||
|
this.addStartIssue("sock", errString, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查数据库连接
|
||||||
|
err = this.checkDB()
|
||||||
|
if err != nil {
|
||||||
|
var errString = "check database connection failed: " + err.Error()
|
||||||
|
logs.Println("[API_NODE]" + errString)
|
||||||
|
this.addStartIssue("db", errString, this.dbIssueSuggestion(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +102,9 @@ func (this *APINode) Start() {
|
|||||||
logs.Println("[API_NODE]auto upgrading ...")
|
logs.Println("[API_NODE]auto upgrading ...")
|
||||||
err = this.autoUpgrade()
|
err = this.autoUpgrade()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("[API_NODE]auto upgrade failed: " + err.Error())
|
var errString = "auto upgrade failed: " + err.Error()
|
||||||
|
logs.Println("[API_NODE]" + errString)
|
||||||
|
this.addStartIssue("db", errString, this.dbIssueSuggestion(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +125,9 @@ func (this *APINode) Start() {
|
|||||||
logs.Println("[API_NODE]reading api config ...")
|
logs.Println("[API_NODE]reading api config ...")
|
||||||
config, err := configs.SharedAPIConfig()
|
config, err := configs.SharedAPIConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("[API_NODE]start failed: " + err.Error())
|
var errString = "read api config failed: " + err.Error()
|
||||||
|
logs.Println("[API_NODE]" + errString)
|
||||||
|
this.addStartIssue("config", errString, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sharedAPIConfig = config
|
sharedAPIConfig = config
|
||||||
@@ -113,15 +135,23 @@ func (this *APINode) Start() {
|
|||||||
// 校验
|
// 校验
|
||||||
apiNode, err := models.SharedAPINodeDAO.FindEnabledAPINodeWithUniqueIdAndSecret(nil, config.NodeId, config.Secret)
|
apiNode, err := models.SharedAPINodeDAO.FindEnabledAPINodeWithUniqueIdAndSecret(nil, config.NodeId, config.Secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("[API_NODE]start failed: read api node from database failed: " + err.Error())
|
var errString = "start failed: read api node from database failed: " + err.Error()
|
||||||
|
logs.Println("[API_NODE]" + errString)
|
||||||
|
this.addStartIssue("db", errString, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if apiNode == nil {
|
if apiNode == nil {
|
||||||
logs.Println("[API_NODE]can not start node, wrong 'nodeId' or 'secret'")
|
var errString = "can not start node, wrong 'nodeId' or 'secret'"
|
||||||
|
logs.Println("[API_NODE]" + errString)
|
||||||
|
this.addStartIssue("config", errString, "请在api.yaml配置文件中填写正确的`nodeId`和`secret`,如果数据库或者管理节点或API节点是从别的服务器迁移过来的,请将老的系统配置拷贝到当前节点配置下")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.SetNumberId(int64(apiNode.Id))
|
config.SetNumberId(int64(apiNode.Id))
|
||||||
|
|
||||||
|
// 清除上一次启动错误
|
||||||
|
// 这个错误文件可能不存在,不需要处理错误
|
||||||
|
_ = os.Remove(this.issuesFile)
|
||||||
|
|
||||||
// 设置rlimit
|
// 设置rlimit
|
||||||
_ = utils.SetRLimit(1024 * 1024)
|
_ = utils.SetRLimit(1024 * 1024)
|
||||||
|
|
||||||
@@ -138,10 +168,12 @@ func (this *APINode) Start() {
|
|||||||
// 监听RPC服务
|
// 监听RPC服务
|
||||||
remotelogs.Println("API_NODE", "starting RPC server ...")
|
remotelogs.Println("API_NODE", "starting RPC server ...")
|
||||||
|
|
||||||
isListening := this.listenPorts(apiNode)
|
var isListening = this.listenPorts(apiNode)
|
||||||
|
|
||||||
if !isListening {
|
if !isListening {
|
||||||
remotelogs.Error("API_NODE", "the api node require at least one listening address")
|
var errString = "the api node require at least one listening address"
|
||||||
|
remotelogs.Error("API_NODE", errString)
|
||||||
|
this.addStartIssue("config", errString, "请给当前API节点设置一个监听端口")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,9 +186,8 @@ func (this *APINode) Start() {
|
|||||||
|
|
||||||
// Daemon 实现守护进程
|
// Daemon 实现守护进程
|
||||||
func (this *APINode) Daemon() {
|
func (this *APINode) Daemon() {
|
||||||
path := os.TempDir() + "/" + teaconst.ProcessName + ".sock"
|
var path = os.TempDir() + "/" + teaconst.ProcessName + ".sock"
|
||||||
isDebug := lists.ContainsString(os.Args, "debug")
|
var isDebug = lists.ContainsString(os.Args, "debug")
|
||||||
isDebug = true
|
|
||||||
for {
|
for {
|
||||||
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -199,14 +230,14 @@ func (this *APINode) Daemon() {
|
|||||||
|
|
||||||
// InstallSystemService 安装系统服务
|
// InstallSystemService 安装系统服务
|
||||||
func (this *APINode) InstallSystemService() error {
|
func (this *APINode) InstallSystemService() error {
|
||||||
shortName := teaconst.SystemdServiceName
|
var shortName = teaconst.SystemdServiceName
|
||||||
|
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
|
var manager = utils.NewServiceManager(shortName, teaconst.ProductName)
|
||||||
err = manager.Install(exe, []string{})
|
err = manager.Install(exe, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -242,21 +273,33 @@ func (this *APINode) checkDB() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTries := 600
|
// 第一次测试连接
|
||||||
for i := 0; i <= maxTries; i++ {
|
_, err = db.Exec("SELECT 1")
|
||||||
_, err := db.Exec("SELECT 1")
|
if err != nil {
|
||||||
if err != nil {
|
var errString = "check database connection failed: " + err.Error()
|
||||||
if i == maxTries-1 {
|
logs.Println("[API_NODE]" + errString)
|
||||||
return err
|
this.addStartIssue("db", errString, this.dbIssueSuggestion(errString))
|
||||||
} else {
|
|
||||||
if i%10 == 0 { // 这让提示不会太多
|
// 多次尝试
|
||||||
logs.Println("[API_NODE]reconnecting to database (" + fmt.Sprintf("%.1f", float32(i*100)/float32(maxTries+1)) + "%) ...")
|
var maxTries = 600
|
||||||
|
if Tea.IsTesting() {
|
||||||
|
maxTries = 600
|
||||||
|
}
|
||||||
|
for i := 0; i <= maxTries; i++ {
|
||||||
|
_, err := db.Exec("SELECT 1")
|
||||||
|
if err != nil {
|
||||||
|
if i == maxTries-1 {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
if i%10 == 0 { // 这让提示不会太多
|
||||||
|
logs.Println("[API_NODE]reconnecting to database (" + fmt.Sprintf("%.1f", float32(i*100)/float32(maxTries+1)) + "%) ...")
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Second)
|
} else {
|
||||||
|
logs.Println("[API_NODE]database connected")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logs.Println("[API_NODE]database connected")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +313,7 @@ func (this *APINode) autoUpgrade() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行SQL
|
// 执行SQL
|
||||||
config := &dbs.Config{}
|
var config = &dbs.Config{}
|
||||||
configData, err := ioutil.ReadFile(Tea.ConfigFile("db.yaml"))
|
configData, err := ioutil.ReadFile(Tea.ConfigFile("db.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("read database config file failed: " + err.Error())
|
return errors.New("read database config file failed: " + err.Error())
|
||||||
@@ -681,3 +724,62 @@ func (this *APINode) unaryInterceptor(ctx context.Context, req interface{}, info
|
|||||||
}
|
}
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加启动相关的Issue
|
||||||
|
func (this *APINode) addStartIssue(code string, message string, suggestion string) {
|
||||||
|
this.issues = append(this.issues, NewStartIssue(code, message, suggestion))
|
||||||
|
this.saveIssues()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增加数据库建议
|
||||||
|
func (this *APINode) dbIssueSuggestion(errString string) string {
|
||||||
|
// 数据库配置
|
||||||
|
db, err := dbs.Default()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
config, err := db.Config()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var dsn = config.Dsn
|
||||||
|
dsnConfig, err := mysql.ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var addr = dsnConfig.Addr
|
||||||
|
|
||||||
|
// 配置文件位置
|
||||||
|
var dbConfigPath = Tea.ConfigFile("db.yaml")
|
||||||
|
|
||||||
|
// 连接被拒绝
|
||||||
|
if strings.Contains(errString, "connection refused") {
|
||||||
|
// 本机
|
||||||
|
if strings.HasPrefix(addr, "127.0.0.1:") || strings.HasPrefix(addr, "localhost:") {
|
||||||
|
return "试图连接到数据库被拒绝,请检查:1)本地数据库服务是否已经启动;2)数据库IP和端口(" + addr + ")是否正确;(当前数据库配置为:" + dsn + ",配置文件位置:" + dbConfigPath + ")。"
|
||||||
|
} else {
|
||||||
|
return "试图连接到数据库被拒绝,请检查:1)数据库服务是否已经启动;2)数据库IP和端口(" + addr + ")是否正确;3)防火墙设置;(当前数据库配置为:" + dsn + ",配置文件位置:" + dbConfigPath + ")。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限错误
|
||||||
|
if strings.Contains(errString, "Error 1045") {
|
||||||
|
return "使用的用户和密码没有权限连接到指定数据库,请检查:数据库配置文件中的用户名(" + dsnConfig.User + ")和密码(" + dsnConfig.Passwd + ")是否正确;(当前数据库配置为:" + dsn + ",配置文件位置:" + dbConfigPath + ")。"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库名称错误
|
||||||
|
if strings.Contains(errString, "Error 1049") {
|
||||||
|
return "数据库名称配置错误,请检查:数据库配置文件中数据库名称(" + dsnConfig.DBName + ")是否正确;(当前数据库配置为:" + dsn + ",配置文件位置:" + dbConfigPath + ")。"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存issues
|
||||||
|
func (this *APINode) saveIssues() {
|
||||||
|
issuesJSON, err := json.Marshal(this.issues)
|
||||||
|
if err == nil {
|
||||||
|
_ = ioutil.WriteFile(this.issuesFile, issuesJSON, 0666)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
17
internal/nodes/start_issue.go
Normal file
17
internal/nodes/start_issue.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package nodes
|
||||||
|
|
||||||
|
type StartIssue struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Suggestion string `json:"suggestion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStartIssue(code string, message string, suggestion string) *StartIssue {
|
||||||
|
return &StartIssue{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Suggestion: suggestion,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user