- Go agent: CPU/mem/disk/port collectors, HTTP report, static binary - Cross-compile: linux/amd64 + linux/arm64 (statically linked) - One-line install script for all Linux distros - Backend: POST /api/v1/agent/report (no JWT) + GET /agent/latest,history (JWT) - CloudBilling.tsx: fixed syntax errors, stats/trends/records/alerts - K8s menu renamed to Kubernetes管理 Signed-off-by: Logan.Li
137 lines
3.6 KiB
Go
137 lines
3.6 KiB
Go
// Package main 是 AIOps Agent 的入口。
|
|
// Agent 定时采集 CPU、内存、磁盘及监听端口指标,并通过 HTTP 上报至 AIOps 后端。
|
|
//
|
|
// 以下变量可在编译时通过 -ldflags "-X main.version=... -X main.buildTime=..." 注入。
|
|
//
|
|
// 署名 Logan.Li
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"ezops/agent/collector"
|
|
"ezops/agent/model"
|
|
"ezops/agent/reporter"
|
|
)
|
|
|
|
// 编译时可注入的版本信息
|
|
var (
|
|
version = "dev"
|
|
buildTime = "unknown"
|
|
)
|
|
|
|
func main() {
|
|
server := flag.String("server", "http://localhost:8080", "AIOps 后端上报地址(含 scheme 与端口)")
|
|
interval := flag.Int("interval", 10, "采集与上报间隔,单位:秒")
|
|
token := flag.String("token", "", "Bearer 鉴权令牌")
|
|
hostnameFlag := flag.String("hostname", "", "上报主机名,默认使用系统主机名")
|
|
flag.Parse()
|
|
|
|
// 统一日志格式:带微秒时间戳,便于排查问题
|
|
logger := log.New(os.Stdout, "[aiops-agent] ", log.LstdFlags|log.Lmicroseconds)
|
|
|
|
// 解析主机名:优先使用 -hostname,否则回退到 os.Hostname()
|
|
hostname := *hostnameFlag
|
|
if hostname == "" {
|
|
if h, err := os.Hostname(); err == nil {
|
|
hostname = h
|
|
} else {
|
|
hostname = "unknown"
|
|
}
|
|
}
|
|
|
|
// 参数校验
|
|
if *server == "" {
|
|
logger.Fatal("参数 -server 不能为空")
|
|
}
|
|
if *interval < 1 {
|
|
logger.Fatalf("参数 -interval 必须 >= 1,当前为 %d", *interval)
|
|
}
|
|
|
|
logger.Printf("AIOps Agent 启动: version=%s buildTime=%s", version, buildTime)
|
|
logger.Printf("配置: server=%s interval=%ds hostname=%q token=%s",
|
|
*server, *interval, hostname, maskToken(*token))
|
|
|
|
// 初始化各采集器与上报器
|
|
cpuCollector := collector.NewCPUCollector()
|
|
memCollector := collector.NewMemCollector()
|
|
diskCollector := collector.NewDiskCollector()
|
|
portCollector := collector.NewPortCollector()
|
|
rep := reporter.New(*server, *token, logger)
|
|
|
|
// 监听 SIGINT / SIGTERM,支持优雅退出
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
ticker := time.NewTicker(time.Duration(*interval) * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
// collectAndReport 执行单次采集并上报;各项采集独立进行,单项失败不影响整体上报
|
|
collectAndReport := func() {
|
|
report := &model.Report{
|
|
Hostname: hostname,
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
if stat, err := cpuCollector.Collect(); err != nil {
|
|
logger.Printf("CPU 采集异常: %v", err)
|
|
} else {
|
|
report.CPU = stat
|
|
}
|
|
|
|
if stat, err := memCollector.Collect(); err != nil {
|
|
logger.Printf("内存采集异常: %v", err)
|
|
} else {
|
|
report.Memory = stat
|
|
}
|
|
|
|
if disks, err := diskCollector.Collect(); err != nil {
|
|
logger.Printf("磁盘采集异常: %v", err)
|
|
} else {
|
|
report.Disks = disks
|
|
}
|
|
|
|
if ports, err := portCollector.Collect(); err != nil {
|
|
logger.Printf("端口采集异常: %v", err)
|
|
} else {
|
|
report.Ports = ports
|
|
}
|
|
|
|
if err := rep.Send(ctx, report); err != nil {
|
|
logger.Printf("上报失败: %v", err)
|
|
return
|
|
}
|
|
logger.Printf("上报成功: cpu=%.2f%% mem=%.2f%% disks=%d ports=%d",
|
|
report.CPU.UsagePercent, report.Memory.UsagePercent, len(report.Disks), len(report.Ports))
|
|
}
|
|
|
|
// 启动后立即执行一次,再按 ticker 周期执行
|
|
collectAndReport()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
collectAndReport()
|
|
case <-ctx.Done():
|
|
logger.Println("收到退出信号,Agent 正在关闭...")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// maskToken 对令牌做脱敏处理,仅用于日志输出
|
|
func maskToken(token string) string {
|
|
if token == "" {
|
|
return "(empty)"
|
|
}
|
|
if len(token) <= 4 {
|
|
return "****"
|
|
}
|
|
return token[:4] + "****"
|
|
}
|