CPU 根因:每个 Agent 请求都做一次 bcrypt 校验(实测 72ms), 30 台主机 18 req/s × 72ms ≈ 1.35 核,实测容器 CPU 135%。 - agentauth: 校验结果按 Token 哈希缓存(TTL 5min,轮换即失效), last_used_at 写入按 60s 节流。实测 CPU 135% → 1.6%,请求延迟 103ms → 1.6ms - metricagg: 新增后台逐级降采样聚合 raw→1m/5m→1h→6h/1d,取代前端手动触发 - metrics_aggregate: 补 uniq_metric_agg 唯一索引(原 idx_agg 非唯一导致 upsert 插重复行) - agent 合并配置接口 GET /agent/config:一次返回任务+探针+端口策略+频率, 配置轮询 5s→30s;服务端可下发 report_interval,Agent 热更新 ticker 无需重装 - retention: agent_reports 保留期 7d→1d(趋势由聚合表承担) - redis: 增加 maxmemory 512mb + volatile-lru - fix(upgrade): 升级脚本改为 fail-fast,修复下载失败仍报 UPGRADE_OK 的静默谎报 // Logan.Li
147 lines
4.1 KiB
Go
147 lines
4.1 KiB
Go
package router
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type agentStaticPaths struct {
|
|
binDir string
|
|
deployDir string
|
|
}
|
|
|
|
var agentBinaryFiles = map[string]string{
|
|
"linux-amd64": "aiops-agent-linux-amd64",
|
|
"linux-arm64": "aiops-agent-linux-arm64",
|
|
"windows-amd64": "aiops-agent-windows-amd64.exe",
|
|
}
|
|
|
|
// registerAgentDownloads 注册 Agent 安装脚本和二进制下载路由,避免维护 backend/agent 重复目录。 // Logan.Li
|
|
func registerAgentDownloads(r *gin.Engine, log *zap.Logger) {
|
|
paths := resolveAgentStaticPaths(log)
|
|
|
|
r.GET("/agent/arch", func(c *gin.Context) {
|
|
base := requestBaseURL(c)
|
|
if base == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "request host is required"})
|
|
return
|
|
}
|
|
downloads := availableAgentDownloads(paths.binDir)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"version": "1.0.0",
|
|
"downloads": downloads,
|
|
"install": fmt.Sprintf("curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token <token>", base, base),
|
|
})
|
|
})
|
|
|
|
r.GET("/agent/install.sh", func(c *gin.Context) {
|
|
serveAgentStaticFile(c, paths.deployDir, "install.sh")
|
|
})
|
|
r.GET("/agent/install.ps1", func(c *gin.Context) {
|
|
serveAgentStaticFile(c, paths.deployDir, "install.ps1")
|
|
})
|
|
for _, fileName := range agentBinaryFiles {
|
|
name := fileName
|
|
r.GET("/agent/"+name, func(c *gin.Context) {
|
|
serveAgentStaticFile(c, paths.binDir, name)
|
|
})
|
|
}
|
|
r.GET("/agent/bin/:file", func(c *gin.Context) {
|
|
serveAgentStaticFile(c, paths.binDir, c.Param("file"))
|
|
})
|
|
r.GET("/api/agent/bin/:file", func(c *gin.Context) {
|
|
serveAgentStaticFile(c, paths.binDir, c.Param("file"))
|
|
})
|
|
}
|
|
|
|
// resolveAgentStaticPaths 按部署形态寻找唯一 Agent 资源目录:容器优先,其次项目根目录。 // Logan.Li
|
|
func resolveAgentStaticPaths(log *zap.Logger) agentStaticPaths {
|
|
paths := agentStaticPaths{
|
|
binDir: firstExistingDir([]string{
|
|
"/app/agent/bin",
|
|
"./agent/bin",
|
|
"../agent/bin",
|
|
}),
|
|
deployDir: firstExistingDir([]string{
|
|
"/app/agent/deploy",
|
|
"./agent/deploy",
|
|
"../agent/deploy",
|
|
}),
|
|
}
|
|
if log != nil {
|
|
log.Info("Agent 静态资源目录已解析",
|
|
zap.String("bin_dir", paths.binDir),
|
|
zap.String("deploy_dir", paths.deployDir),
|
|
)
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func firstExistingDir(candidates []string) string {
|
|
for _, candidate := range candidates {
|
|
info, err := os.Stat(candidate)
|
|
if err == nil && info.IsDir() {
|
|
return candidate
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func availableAgentDownloads(binDir string) gin.H {
|
|
downloads := gin.H{}
|
|
for platform, fileName := range agentBinaryFiles {
|
|
if binDir == "" {
|
|
continue
|
|
}
|
|
fullPath := filepath.Join(binDir, fileName)
|
|
if info, err := os.Stat(fullPath); err == nil && !info.IsDir() {
|
|
downloads[platform] = "/agent/" + fileName
|
|
}
|
|
}
|
|
return downloads
|
|
}
|
|
|
|
func requestBaseURL(c *gin.Context) string {
|
|
scheme := "http"
|
|
if c.Request.TLS != nil {
|
|
scheme = "https"
|
|
}
|
|
if proto := c.GetHeader("X-Forwarded-Proto"); proto != "" {
|
|
// 多级代理可能传逗号列表,首项是客户端最初使用的协议。 // Logan.Li
|
|
scheme = strings.TrimSpace(strings.Split(proto, ",")[0])
|
|
}
|
|
host := c.Request.Host
|
|
if forwardedHost := c.GetHeader("X-Forwarded-Host"); forwardedHost != "" {
|
|
// 多级代理可能传逗号列表,首项是客户端最初访问的域名。 // Logan.Li
|
|
host = strings.TrimSpace(strings.Split(forwardedHost, ",")[0])
|
|
}
|
|
if host == "" {
|
|
// HTTP/1.1 正常请求必须带 Host,缺失时不再伪造本机地址。 // Logan.Li
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%s://%s", scheme, host)
|
|
}
|
|
|
|
func serveAgentStaticFile(c *gin.Context, dir string, fileName string) {
|
|
if dir == "" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "agent static directory not found"})
|
|
return
|
|
}
|
|
if strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") || fileName == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent file"})
|
|
return
|
|
}
|
|
fullPath := filepath.Join(dir, fileName)
|
|
if info, err := os.Stat(fullPath); err != nil || info.IsDir() {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "agent file not found"})
|
|
return
|
|
}
|
|
c.File(fullPath)
|
|
}
|