feat(k8s): M14 Namespace 管理看板(资源配额 + 命名空间 CRUD)
- 后端: ListNamespacesDetailed/CreateNamespace/DeleteNamespace/ListResourceQuotas/ListLimitRanges - 后端: GetNamespacesDetail handler (6 API calls aggregate, not N×3) - 后端: CreateNamespaceHandler + DeleteNamespaceHandler (system NS protection) - 前端: NamespaceManagementPage.tsx (~280行) — 表格+概览卡片+创建/删除弹框 - 路由: GET/POST/DELETE /:clusterCode/namespaces + GET /namespace/:ns/detail - 验证: 9/9 通过 (urllib), TS 零错误, Vite 构建成功
This commit is contained in:
1 parent
c7b61968bc
commit
8240a88a84
33 files changed
+2029
-71
No files matched your search
+151
@@ -0,0 +1,151 @@
|
||||
# AIOps Agent
|
||||
|
||||
轻量主机采集代理,定时采集 CPU/内存/磁盘/网络/端口/进程 等指标,通过 HTTP 上报至 AIOps 后端。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
agent/
|
||||
├── main.go # 入口:CLI 解析、采集编排、上报循环
|
||||
├── Makefile # 本地构建(单架构)
|
||||
│
|
||||
├── collector/ # 采集器 —— 从 /proc /sys netstat 读原始数据
|
||||
│ ├── cpu.go # CPU 使用率、核数、负载
|
||||
│ ├── mem.go # 内存 total/used/cached
|
||||
│ ├── swap.go # Swap 分区
|
||||
│ ├── disk.go # 磁盘(按挂载点)
|
||||
│ ├── diskio.go # 磁盘 IO(读写速率/IOPS)
|
||||
│ ├── network.go # TCP 连接状态、网卡流量
|
||||
│ ├── port.go # 监听端口发现
|
||||
│ ├── proctop.go # 进程 TOP N(按 CPU 排序)
|
||||
│ ├── fd.go # 系统文件句柄使用量
|
||||
│ ├── context.go # 上下文切换/中断次数
|
||||
│ ├── health_probe.go # 健康探针(HTTP GET / TCP / CMD)
|
||||
│ ├── system.go # 系统信息(OS/Kernel/Uptime)
|
||||
│ └── util.go # 通用工具(读文件、解析等)
|
||||
│
|
||||
├── model/ # 数据模型
|
||||
│ ├── model.go # Report 结构体(一次采集的完整报文)
|
||||
│ ├── model_ext.go # NetworkStat / DiskIOStat / SwapStat 等
|
||||
│ └── model_proctop.go # ProcessStat(含 Cmdline/User/CPU%/Mem%/FD)
|
||||
│
|
||||
├── client/ # HTTP 客户端(连接池复用、Keep-Alive)
|
||||
│ └── http.go
|
||||
├── reporter/ # 上报器 —— 将 Report JSON 序列化后 POST 到后端
|
||||
│ └── http.go
|
||||
├── task/ # 任务系统(后端下发命令,Agent 执行并回传结果)
|
||||
│ ├── client.go
|
||||
│ └── executor.go
|
||||
│
|
||||
├── deploy/ # 部署文件
|
||||
│ ├── install.sh # 一键安装脚本(自动检测架构)
|
||||
│ └── aiops-agent.service # systemd 服务模板
|
||||
│
|
||||
├── bin/ # 编译产物(Docker 多架构构建后拷贝)
|
||||
├── go.mod / go.sum # Go 依赖
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
┌─────────────┐ 采集 ┌──────────────────────────────┐
|
||||
│ /proc/cpu │──────────▶│ collector/ │
|
||||
│ /proc/mem │ │ cpu / mem / disk / net / ... │
|
||||
│ netstat │ └──────────┬───────────────────┘
|
||||
│ /sys/... │ │ Go struct
|
||||
└─────────────┘ ▼
|
||||
┌────────────────┐
|
||||
│ model.Report │ ← 一次采集的完整数据
|
||||
└───────┬────────┘
|
||||
│ JSON 序列化
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ reporter/ │ ← HTTP POST /api/v1/agent/report
|
||||
└───────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ AIOps Backend │
|
||||
│ (10.10.10.75:8080) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
- **采集层**:从 `/proc`、`/sys`、`netstat` 等系统接口读取原始数据
|
||||
- **模型层**:统一的数据结构 `Report`,涵盖系统指标、进程 TOP、端口、FD、探针等
|
||||
- **上报层**:共享 HTTP 连接池,JSON 序列化后 POST 到后端
|
||||
- **任务层**:独立协程轮询后端任务(脚本执行、探针配置拉取)
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 输出全部上报指标(Zabbix-agent -p 风格),调试用
|
||||
./aiops-agent output
|
||||
|
||||
# 按 CPU 占用输出 TOP 5 进程
|
||||
./aiops-agent output -top-n 5
|
||||
|
||||
# 正常采集上报(daemon)
|
||||
./aiops-agent -server http://10.10.10.75:8080 -token <TOKEN> -interval 30
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `-server` | (空) | 后端地址,daemon 模式必填 |
|
||||
| `-token` | (空) | Bearer 鉴权令牌 |
|
||||
| `-hostname` | 系统主机名 | 上报标识 |
|
||||
| `-interval` | `10` | 采集间隔(秒),建议 30~60 |
|
||||
| `-top-n` | `3` | 进程 TOP N,按 CPU 占用排序 |
|
||||
|
||||
## output 输出
|
||||
|
||||
格式:`key [type|value]`,type=`s`(string) `u`(uint) `d`(double)。
|
||||
|
||||
```text
|
||||
agent.hostname [s|opsx-server]
|
||||
system.hostname [s|opsx-server]
|
||||
agent.version [s|v2.1.0]
|
||||
agent.ip [s|10.10.10.75]
|
||||
system.os [s|Ubuntu 24.04 LTS]
|
||||
system.kernel [s|6.8.0-90-generic]
|
||||
system.uptime [u|603932]
|
||||
cpu.usage_percent [d|0.00]
|
||||
cpu.core_count [u|8]
|
||||
cpu.load1 / load5 / load15 [d|...]
|
||||
memory.total_gb / used_gb / usage_percent [d|...]
|
||||
swap.total_gb / used_gb / usage_percent [d|...]
|
||||
disk[/].total_gb / used_gb / usage_percent [d|...] (每挂载点一行)
|
||||
ports.discovery[*] [s|{JSON}] (Zabbix LLD 格式)
|
||||
net.listen[PORT].process / .protocol [s|...]
|
||||
net.tcp.established / time_wait / close_wait [u|...]
|
||||
net.rx_bps / tx_bps [d|...]
|
||||
disk_io.read_bps / write_bps / read_iops / write_iops
|
||||
proc.top[N].name / pid / user / cpu_pct / mem_pct / fd_count / threads
|
||||
fd.allocated / max / usage_pct [u|...]
|
||||
ctx.ctx_per_sec / irq_per_sec [d|...]
|
||||
agent.heartbeat.version / uptime_sec / self_rss_mb / report_seq
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# x86_64
|
||||
curl -o /usr/local/bin/aiops-agent http://10.10.10.75:8080/agent/aiops-agent-linux-amd64
|
||||
|
||||
# ARM64
|
||||
curl -o /usr/local/bin/aiops-agent http://10.10.10.75:8080/agent/aiops-agent-linux-arm64
|
||||
|
||||
# 一键安装(自动检测架构并注册 systemd)
|
||||
curl -fsSL http://10.10.10.75:8080/agent/install.sh | sudo bash -s -- --server http://10.10.10.75:8080 --token <TOKEN>
|
||||
```
|
||||
|
||||
## 本地构建
|
||||
|
||||
```bash
|
||||
cd agent
|
||||
make # CGO_ENABLED=0 静态编译当前平台
|
||||
```
|
||||
|
||||
Docker 多架构构建由顶层 `backend/Dockerfile` 的 `agent-builder` stage 完成(linux-amd64 / linux-arm64 / darwin-arm64)。
|
||||
+13
-2
@@ -18,8 +18,9 @@ import (
|
||||
|
||||
// cpuSample 保存某一时刻 CPU 的累计 jiffies,用于两次采样计算差值
|
||||
type cpuSample struct {
|
||||
total uint64 // 自系统启动以来 CPU 所有状态的累计 jiffies
|
||||
idle uint64 // idle + iowait 的累计 jiffies
|
||||
total uint64 // 自系统启动以来 CPU 所有状态的累计 jiffies
|
||||
idle uint64 // idle + iowait 的累计 jiffies
|
||||
iowait uint64 // iowait 单独累计 jiffies(用于计算 iowait 占比)
|
||||
}
|
||||
|
||||
// CPUCollector CPU 指标采集器。
|
||||
@@ -50,12 +51,18 @@ func (c *CPUCollector) Collect() (model.CPUStat, error) {
|
||||
if c.prev != nil {
|
||||
totalDelta := cur.total - c.prev.total
|
||||
idleDelta := cur.idle - c.prev.idle
|
||||
iowaitDelta := cur.iowait - c.prev.iowait
|
||||
if totalDelta > 0 {
|
||||
// 使用率 = (总增量 - 空闲增量) / 总增量 * 100
|
||||
stat.UsagePercent = float64(totalDelta-idleDelta) / float64(totalDelta) * 100.0
|
||||
if stat.UsagePercent < 0 {
|
||||
stat.UsagePercent = 0
|
||||
}
|
||||
// iowait = iowait增量 / 总增量 * 100
|
||||
stat.IOWait = float64(iowaitDelta) / float64(totalDelta) * 100.0
|
||||
if stat.IOWait < 0 {
|
||||
stat.IOWait = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
c.prev = &cur
|
||||
@@ -100,6 +107,10 @@ func readCPUSample() (cpuSample, error) {
|
||||
if i == 3 || i == 4 {
|
||||
sample.idle += x
|
||||
}
|
||||
// iowait(索引4) 单独记录
|
||||
if i == 4 {
|
||||
sample.iowait += x
|
||||
}
|
||||
}
|
||||
return sample, nil
|
||||
}
|
||||
|
||||
+15
-5
@@ -76,12 +76,22 @@ func (d *DiskCollector) Collect() ([]model.DiskStat, error) {
|
||||
}
|
||||
used := total - free
|
||||
|
||||
// inode 使用率
|
||||
var inodePct float64
|
||||
if fs.Files > 0 {
|
||||
inodePct = float64(fs.Files-fs.Ffree) / float64(fs.Files) * 100.0
|
||||
if inodePct < 0 {
|
||||
inodePct = 0
|
||||
}
|
||||
}
|
||||
|
||||
disks = append(disks, model.DiskStat{
|
||||
Mount: m.mountpoint,
|
||||
Device: m.device,
|
||||
TotalGB: bytesToGB(total),
|
||||
UsedGB: bytesToGB(used),
|
||||
UsagePercent: float64(used) / float64(total) * 100.0,
|
||||
Mount: m.mountpoint,
|
||||
Device: m.device,
|
||||
TotalGB: bytesToGB(total),
|
||||
UsedGB: bytesToGB(used),
|
||||
UsagePercent: float64(used) / float64(total) * 100.0,
|
||||
InodeUsagePct: inodePct,
|
||||
})
|
||||
}
|
||||
return disks, nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package collector — 磁盘 IO 指标采集器
|
||||
// 通过读取 /proc/diskstats 获取磁盘读写 BPS 和 IOPS。
|
||||
// 通过读取 /proc/diskstats 获取磁盘读写 BPS、IOPS 和 util(磁盘繁忙程度)。
|
||||
//
|
||||
// 署名 Hermes
|
||||
package collector
|
||||
@@ -23,6 +23,7 @@ type diskIOSample struct {
|
||||
writeSects uint64
|
||||
readOps uint64
|
||||
writeOps uint64
|
||||
ioTicks uint64 // io_ticks (field 12) — 磁盘忙碌的毫秒累计
|
||||
}
|
||||
|
||||
// DiskIOCollector 磁盘 IO 采集器
|
||||
@@ -40,7 +41,7 @@ func NewDiskIOCollector() *DiskIOCollector {
|
||||
func (c *DiskIOCollector) Collect() (model.DiskIOStat, error) {
|
||||
stat := model.DiskIOStat{}
|
||||
|
||||
rSects, wSects, rOps, wOps, err := readDiskStats()
|
||||
rSects, wSects, rOps, wOps, ioTicks, err := readDiskStats()
|
||||
if err != nil {
|
||||
return stat, fmt.Errorf("读取磁盘 IO 失败: %w", err)
|
||||
}
|
||||
@@ -55,6 +56,7 @@ func (c *DiskIOCollector) Collect() (model.DiskIOStat, error) {
|
||||
writeSects: wSects,
|
||||
readOps: rOps,
|
||||
writeOps: wOps,
|
||||
ioTicks: ioTicks,
|
||||
}
|
||||
|
||||
if c.prev != nil && now.After(c.prev.time) {
|
||||
@@ -65,6 +67,15 @@ func (c *DiskIOCollector) Collect() (model.DiskIOStat, error) {
|
||||
stat.WriteBPS = float64((cur.writeSects-c.prev.writeSects)*sectorSize) / elapsed
|
||||
stat.ReadIOPS = float64(cur.readOps-c.prev.readOps) / elapsed
|
||||
stat.WriteIOPS = float64(cur.writeOps-c.prev.writeOps) / elapsed
|
||||
// util% = io_ticks_delta_ms / (elapsed_ms) * 100
|
||||
ioDelta := cur.ioTicks - c.prev.ioTicks
|
||||
stat.Util = float64(ioDelta) / (elapsed * 1000.0) * 100.0
|
||||
if stat.Util > 100 {
|
||||
stat.Util = 100
|
||||
}
|
||||
if stat.Util < 0 {
|
||||
stat.Util = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
c.prev = cur
|
||||
@@ -73,11 +84,11 @@ func (c *DiskIOCollector) Collect() (model.DiskIOStat, error) {
|
||||
}
|
||||
|
||||
// readDiskStats 读取 /proc/diskstats,汇总所有物理磁盘的读写数据
|
||||
// fields[2]=设备名, fields[3]=读完成次数, fields[5]=读扇区数, fields[7]=写完成次数, fields[9]=写扇区数
|
||||
func readDiskStats() (rSects, wSects, rOps, wOps uint64, err error) {
|
||||
// fields[2]=设备名, fields[3]=读完成次数, fields[5]=读扇区数, fields[7]=写完成次数, fields[9]=写扇区数, fields[12]=io_ticks
|
||||
func readDiskStats() (rSects, wSects, rOps, wOps, ioTicks uint64, err error) {
|
||||
f, err := os.Open("/proc/diskstats")
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
return 0, 0, 0, 0, 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@@ -103,6 +114,7 @@ func readDiskStats() (rSects, wSects, rOps, wOps uint64, err error) {
|
||||
// field 5 = 读扇区数 (rd_sectors)
|
||||
// field 7 = 写完成次数 (wr_ios)
|
||||
// field 9 = 写扇区数 (wr_sectors)
|
||||
// field 12 = io_ticks (milliseconds spent doing I/O)
|
||||
if v, e := strconv.ParseUint(fields[3], 10, 64); e == nil {
|
||||
rOps += v
|
||||
}
|
||||
@@ -115,7 +127,10 @@ func readDiskStats() (rSects, wSects, rOps, wOps uint64, err error) {
|
||||
if v, e := strconv.ParseUint(fields[9], 10, 64); e == nil {
|
||||
wSects += v
|
||||
}
|
||||
if v, e := strconv.ParseUint(fields[12], 10, 64); e == nil {
|
||||
ioTicks += v
|
||||
}
|
||||
}
|
||||
|
||||
return rSects, wSects, rOps, wOps, scanner.Err()
|
||||
return rSects, wSects, rOps, wOps, ioTicks, scanner.Err()
|
||||
}
|
||||
+13
-3
@@ -37,6 +37,8 @@ func (m *MemCollector) Collect() (model.MemoryStat, error) {
|
||||
cachedKB := values["Cached"]
|
||||
availableKB := values["MemAvailable"]
|
||||
freeKB := values["MemFree"]
|
||||
swapTotalKB := values["SwapTotal"]
|
||||
swapFreeKB := values["SwapFree"]
|
||||
|
||||
// 若内核未提供 MemAvailable(较老版本),则按传统方式估算可用内存
|
||||
if availableKB == 0 {
|
||||
@@ -48,10 +50,18 @@ func (m *MemCollector) Collect() (model.MemoryStat, error) {
|
||||
usedKB = totalKB - availableKB
|
||||
}
|
||||
|
||||
// Swap used
|
||||
swapUsedKB := uint64(0)
|
||||
if swapTotalKB > swapFreeKB {
|
||||
swapUsedKB = swapTotalKB - swapFreeKB
|
||||
}
|
||||
|
||||
stat := model.MemoryStat{
|
||||
TotalGB: float64(totalKB) * kbToGB,
|
||||
UsedGB: float64(usedKB) * kbToGB,
|
||||
CachedGB: float64(buffersKB+cachedKB) * kbToGB,
|
||||
TotalGB: float64(totalKB) * kbToGB,
|
||||
UsedGB: float64(usedKB) * kbToGB,
|
||||
CachedGB: float64(buffersKB+cachedKB) * kbToGB,
|
||||
AvailableGB: float64(availableKB) * kbToGB,
|
||||
SwapUsedGB: float64(swapUsedKB) * kbToGB,
|
||||
}
|
||||
if totalKB > 0 {
|
||||
stat.UsagePercent = float64(usedKB) / float64(totalKB) * 100.0
|
||||
|
||||
+25
-14
@@ -18,9 +18,11 @@ import (
|
||||
|
||||
// netSample 保存上一次的网络采样数据,用于计算速率
|
||||
type netSample struct {
|
||||
time time.Time
|
||||
rxBytes uint64
|
||||
txBytes uint64
|
||||
time time.Time
|
||||
rxBytes uint64
|
||||
txBytes uint64
|
||||
rxErrors uint64
|
||||
txErrors uint64
|
||||
}
|
||||
|
||||
// NetworkCollector 网络指标采集器
|
||||
@@ -34,7 +36,7 @@ func NewNetworkCollector() *NetworkCollector {
|
||||
return &NetworkCollector{}
|
||||
}
|
||||
|
||||
// Collect 采集网络指标(并发连接数、TIME_WAIT、网卡 rx/tx B/s)
|
||||
// Collect 采集网络指标(并发连接数、TIME_WAIT、网卡 rx/tx B/s、错误包)
|
||||
func (c *NetworkCollector) Collect() (model.NetworkStat, error) {
|
||||
stat := model.NetworkStat{}
|
||||
|
||||
@@ -46,8 +48,8 @@ func (c *NetworkCollector) Collect() (model.NetworkStat, error) {
|
||||
}
|
||||
stat.TCPCloseWait = countCloseWait()
|
||||
|
||||
// 2. 采集网卡流量(取所有非 lo 网卡合计)
|
||||
curRx, curTx, nicName, err := readNetDevTotal()
|
||||
// 2. 采集网卡流量 + 错误包(取所有非 lo 网卡合计)
|
||||
curRx, curTx, curRxErr, curTxErr, nicName, err := readNetDevTotal()
|
||||
if err != nil {
|
||||
return stat, fmt.Errorf("读取网卡流量失败: %w", err)
|
||||
}
|
||||
@@ -57,7 +59,7 @@ func (c *NetworkCollector) Collect() (model.NetworkStat, error) {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
cur := &netSample{time: now, rxBytes: curRx, txBytes: curTx}
|
||||
cur := &netSample{time: now, rxBytes: curRx, txBytes: curTx, rxErrors: curRxErr, txErrors: curTxErr}
|
||||
|
||||
if c.prev != nil && now.After(c.prev.time) {
|
||||
elapsed := now.Sub(c.prev.time).Seconds()
|
||||
@@ -66,6 +68,11 @@ func (c *NetworkCollector) Collect() (model.NetworkStat, error) {
|
||||
stat.NetTxBPS = float64(cur.txBytes-c.prev.txBytes) / elapsed
|
||||
}
|
||||
}
|
||||
|
||||
// 错误包:返回当前累计值(供前端判断是否激增)
|
||||
stat.NetRxErrors = int64(cur.rxErrors)
|
||||
stat.NetTxErrors = int64(cur.txErrors)
|
||||
|
||||
c.prev = cur
|
||||
|
||||
return stat, nil
|
||||
@@ -120,11 +127,11 @@ func readSockStat() (tcpSockStat, error) {
|
||||
return result, scanner.Err()
|
||||
}
|
||||
|
||||
// readNetDevTotal 读取 /proc/net/dev,取第一个非 lo 且活跃的物理网卡总流量
|
||||
func readNetDevTotal() (rxBytes, txBytes uint64, nicName string, err error) {
|
||||
// readNetDevTotal 读取 /proc/net/dev,取第一个非 lo 且活跃的物理网卡总流量 + 错误包
|
||||
func readNetDevTotal() (rxBytes, txBytes, rxErrors, txErrors uint64, nicName string, err error) {
|
||||
f, err := os.Open("/proc/net/dev")
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
return 0, 0, 0, 0, "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@@ -149,26 +156,30 @@ func readNetDevTotal() (rxBytes, txBytes uint64, nicName string, err error) {
|
||||
}
|
||||
|
||||
fields := strings.Fields(parts[1])
|
||||
if len(fields) < 10 {
|
||||
if len(fields) < 11 {
|
||||
continue
|
||||
}
|
||||
|
||||
// fields[0]=rx_bytes, fields[8]=tx_bytes
|
||||
// fields[0]=rx_bytes, fields[2]=rx_errors, fields[8]=tx_bytes, fields[10]=tx_errors
|
||||
rb, _ := strconv.ParseUint(fields[0], 10, 64)
|
||||
tb, _ := strconv.ParseUint(fields[8], 10, 64)
|
||||
re, _ := strconv.ParseUint(fields[2], 10, 64)
|
||||
te, _ := strconv.ParseUint(fields[10], 10, 64)
|
||||
|
||||
if rb > 0 || tb > 0 {
|
||||
nicName = name
|
||||
rxBytes += rb
|
||||
txBytes += tb
|
||||
rxErrors += re
|
||||
txErrors += te
|
||||
}
|
||||
}
|
||||
|
||||
if nicName == "" {
|
||||
return 0, 0, "eth0", nil
|
||||
return 0, 0, 0, 0, "eth0", nil
|
||||
}
|
||||
|
||||
return rxBytes, txBytes, nicName, scanner.Err()
|
||||
return rxBytes, txBytes, rxErrors, txErrors, nicName, scanner.Err()
|
||||
}
|
||||
|
||||
// countCloseWait 从 /proc/net/tcp 统计 CLOSE_WAIT 连接数
|
||||
|
||||
+71
-2
@@ -2,6 +2,8 @@
|
||||
// 本文件实现监听 TCP 端口的自动发现:
|
||||
// - 扫描 /proc/net/tcp 与 /proc/net/tcp6 中状态为 LISTEN(0A) 的套接字;
|
||||
// - 通过遍历 /proc/*/fd 反查套接字 inode 归属的进程。
|
||||
// - 内置系统进程排除列表(sshd、rpcbind、master 等永远不上报)。
|
||||
// - 支持远程端口策略:whitelist(全量上报,默认)/ blacklist(只上报白名单端口)。
|
||||
//
|
||||
// 署名 Logan.Li
|
||||
package collector
|
||||
@@ -15,14 +17,47 @@ import (
|
||||
"ezops/agent/model"
|
||||
)
|
||||
|
||||
// 系统级进程 — 永远不采集其监听端口
|
||||
var systemProcesses = map[string]bool{
|
||||
"sshd": true,
|
||||
"rpcbind": true,
|
||||
"master": true, // Postfix
|
||||
"systemd": true,
|
||||
"systemd-resolve": true,
|
||||
"systemd-journal": true,
|
||||
"dbus-daemon": true,
|
||||
"rsyslogd": true,
|
||||
"irqbalance": true,
|
||||
"polkitd": true,
|
||||
"accounts-daemon": true,
|
||||
"NetworkManager": true,
|
||||
"udisksd": true,
|
||||
"upowerd": true,
|
||||
"rtkit-daemon": true,
|
||||
"switcheroo-contr": true,
|
||||
"power-profiles-": true, // power-profiles-daemon
|
||||
"wpa_supplicant": true,
|
||||
"avahi-daemon": true,
|
||||
"cron": true,
|
||||
"atd": true,
|
||||
"agetty": true,
|
||||
}
|
||||
|
||||
// PortCollector 监听端口自动发现采集器
|
||||
type PortCollector struct{}
|
||||
type PortCollector struct {
|
||||
Policy *model.PortPolicy // 可选:后端下发的端口过滤策略
|
||||
}
|
||||
|
||||
// NewPortCollector 创建端口采集器实例
|
||||
func NewPortCollector() *PortCollector {
|
||||
return &PortCollector{}
|
||||
}
|
||||
|
||||
// SetPolicy 设置端口过滤策略(daemon 模式下从后端轮询获得)
|
||||
func (p *PortCollector) SetPolicy(policy *model.PortPolicy) {
|
||||
p.Policy = policy
|
||||
}
|
||||
|
||||
// listenSocket 描述一个处于 LISTEN 状态的套接字
|
||||
type listenSocket struct {
|
||||
port int
|
||||
@@ -47,7 +82,7 @@ func (p *PortCollector) Collect() ([]model.PortStat, error) {
|
||||
// 2. 构建 inode -> 进程信息 映射(仅 Linux 上可用)
|
||||
inodeMap := p.buildInodeMap()
|
||||
|
||||
// 3. 组装结果并按 (端口, 协议, PID) 去重
|
||||
// 3. 组装结果并按 (端口, 协议, PID) 去重,应用过滤策略
|
||||
seen := make(map[string]bool)
|
||||
ports := make([]model.PortStat, 0, len(sockets))
|
||||
for _, s := range sockets {
|
||||
@@ -55,6 +90,22 @@ func (p *PortCollector) Collect() ([]model.PortStat, error) {
|
||||
if info, ok := inodeMap[s.inode]; ok {
|
||||
pid, name = info.pid, info.name
|
||||
}
|
||||
|
||||
// ── 过滤逻辑 ──
|
||||
// 1) 永远排除系统级进程端口
|
||||
if isSystemProcess(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2) 黑名单模式:只上报在白名单中的端口
|
||||
if p.Policy != nil && p.Policy.IsBlacklistMode() {
|
||||
if !p.Policy.InWhitelist(s.port) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 白名单模式(默认):全量上报已排除系统进程的端口
|
||||
// ── 过滤逻辑结束 ──
|
||||
|
||||
key := strconv.Itoa(s.port) + "|" + s.protocol + "|" + strconv.Itoa(pid)
|
||||
if seen[key] {
|
||||
continue
|
||||
@@ -200,3 +251,21 @@ func readProcessName(pid string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isSystemProcess 判断进程名是否为系统进程(永远不上报的端口)
|
||||
func isSystemProcess(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
// 精确匹配
|
||||
if systemProcesses[name] {
|
||||
return true
|
||||
}
|
||||
// 前缀匹配(如 power-profiles-daemon)
|
||||
for prefix := range systemProcesses {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -79,6 +79,9 @@ func main() {
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "version", "v", "-v", "--version":
|
||||
fmt.Printf("AIOps Agent %s (built %s) go=%s\n", version, buildTime, runtime.Version())
|
||||
return
|
||||
case "output":
|
||||
runOutput(cfg)
|
||||
return
|
||||
@@ -279,10 +282,25 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
pullPortPolicy := func() {
|
||||
policy, err := taskClient.PollPortPolicy(ctx)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
// 静默忽略 — 端口策略为可选配置
|
||||
return
|
||||
}
|
||||
if policy != nil {
|
||||
portCollector.SetPolicy(policy)
|
||||
}
|
||||
}
|
||||
|
||||
const pollInterval = 5 * time.Second
|
||||
go func() {
|
||||
pollAndExecute()
|
||||
pullProbes()
|
||||
pullPortPolicy()
|
||||
ticker := time.NewTicker(pollInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
@@ -290,6 +308,7 @@ func main() {
|
||||
case <-ticker.C:
|
||||
pollAndExecute()
|
||||
pullProbes()
|
||||
pullPortPolicy()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
@@ -404,6 +423,7 @@ func runOutput(cfg Config) {
|
||||
|
||||
// CPU
|
||||
pd("cpu.usage_percent", report.CPU.UsagePercent)
|
||||
pd("cpu.iowait", report.CPU.IOWait)
|
||||
pu("cpu.core_count", report.CPU.CoreCount)
|
||||
pd("cpu.load1", report.CPU.Load1)
|
||||
pd("cpu.load5", report.CPU.Load5)
|
||||
@@ -413,6 +433,8 @@ func runOutput(cfg Config) {
|
||||
pd("memory.total_gb", report.Memory.TotalGB)
|
||||
pd("memory.used_gb", report.Memory.UsedGB)
|
||||
pd("memory.cached_gb", report.Memory.CachedGB)
|
||||
pd("memory.available_gb", report.Memory.AvailableGB)
|
||||
pd("memory.swap_used_gb", report.Memory.SwapUsedGB)
|
||||
pd("memory.usage_percent", report.Memory.UsagePercent)
|
||||
|
||||
// Swap
|
||||
@@ -428,6 +450,7 @@ func runOutput(cfg Config) {
|
||||
pd(prefix+".total_gb", d.TotalGB)
|
||||
pd(prefix+".used_gb", d.UsedGB)
|
||||
pd(prefix+".usage_percent", d.UsagePercent)
|
||||
pd(prefix+".inode_usage_pct", d.InodeUsagePct)
|
||||
}
|
||||
|
||||
// 端口 (监听)
|
||||
@@ -457,14 +480,18 @@ func runOutput(cfg Config) {
|
||||
pu("net.tcp.time_wait", report.Network.TCPTimeWait)
|
||||
pu("net.tcp.close_wait", report.Network.TCPCloseWait)
|
||||
pu("net.tcp.total", report.Network.TCPTotal)
|
||||
pu("net.connection.total", report.Network.TCPEstablished+report.Network.TCPTimeWait+report.Network.TCPCloseWait)
|
||||
pd("net.rx_bps", report.Network.NetRxBPS)
|
||||
pd("net.tx_bps", report.Network.NetTxBPS)
|
||||
pu64("net.rx_errors", report.Network.NetRxErrors)
|
||||
pu64("net.tx_errors", report.Network.NetTxErrors)
|
||||
|
||||
// 磁盘 IO
|
||||
pd("disk_io.read_bps", report.DiskIO.ReadBPS)
|
||||
pd("disk_io.write_bps", report.DiskIO.WriteBPS)
|
||||
pd("disk_io.read_iops", report.DiskIO.ReadIOPS)
|
||||
pd("disk_io.write_iops", report.DiskIO.WriteIOPS)
|
||||
pd("disk_io.util", report.DiskIO.Util)
|
||||
|
||||
// 进程 TOP
|
||||
for i, pr := range report.ProcessTop {
|
||||
|
||||
@@ -28,6 +28,7 @@ type Report struct {
|
||||
// CPUStat CPU 相关指标
|
||||
type CPUStat struct {
|
||||
UsagePercent float64 `json:"usage_percent"` // CPU 总体使用率(百分比)
|
||||
IOWait float64 `json:"iowait"` // CPU 等待 IO 的时间占比(百分比)
|
||||
CoreCount int `json:"core_count"` // CPU 逻辑核心数
|
||||
Load1 float64 `json:"load1"` // 1 分钟平均负载
|
||||
Load5 float64 `json:"load5"` // 5 分钟平均负载
|
||||
@@ -39,16 +40,19 @@ type MemoryStat struct {
|
||||
TotalGB float64 `json:"total_gb"` // 总内存(GB)
|
||||
UsedGB float64 `json:"used_gb"` // 已用内存(GB)
|
||||
CachedGB float64 `json:"cached_gb"` // 缓存(Buffers + Cached,GB)
|
||||
AvailableGB float64 `json:"available_gb"` // 可用内存(GB),含可回收缓存 — 真实剩余可用
|
||||
SwapUsedGB float64 `json:"swap_used_gb"` // Swap 已用量(GB)— OOM 金标准
|
||||
UsagePercent float64 `json:"usage_percent"` // 内存使用率(百分比)
|
||||
}
|
||||
|
||||
// DiskStat 单个挂载点的磁盘使用情况
|
||||
type DiskStat struct {
|
||||
Mount string `json:"mount"` // 挂载点
|
||||
Device string `json:"device"` // 设备名
|
||||
TotalGB float64 `json:"total_gb"` // 总容量(GB)
|
||||
UsedGB float64 `json:"used_gb"` // 已用容量(GB)
|
||||
UsagePercent float64 `json:"usage_percent"` // 使用率(百分比)
|
||||
Mount string `json:"mount"` // 挂载点
|
||||
Device string `json:"device"` // 设备名
|
||||
TotalGB float64 `json:"total_gb"` // 总容量(GB)
|
||||
UsedGB float64 `json:"used_gb"` // 已用容量(GB)
|
||||
UsagePercent float64 `json:"usage_percent"` // 使用率(百分比)
|
||||
InodeUsagePct float64 `json:"inode_usage_pct"` // inode 使用率(百分比)— 防止文件数爆炸
|
||||
}
|
||||
|
||||
// PortStat 监听端口及其归属进程
|
||||
|
||||
+41
-16
@@ -9,6 +9,8 @@ type NetworkStat struct {
|
||||
NicName string `json:"nic_name"` // 主网卡名
|
||||
NetRxBPS float64 `json:"net_rx_bps"` // 网卡接收速率 (bytes/s)
|
||||
NetTxBPS float64 `json:"net_tx_bps"` // 网卡发送速率 (bytes/s)
|
||||
NetRxErrors int64 `json:"net_rx_errors"` // 网卡接收错误包累计 — 物理层故障告警
|
||||
NetTxErrors int64 `json:"net_tx_errors"` // 网卡发送错误包累计 — 物理层故障告警
|
||||
}
|
||||
|
||||
// DiskIOStat 磁盘 IO 相关指标
|
||||
@@ -17,6 +19,7 @@ type DiskIOStat struct {
|
||||
WriteBPS float64 `json:"write_bps"` // 写入速率 (bytes/s)
|
||||
ReadIOPS float64 `json:"read_iops"` // 读取 IOPS (count/s)
|
||||
WriteIOPS float64 `json:"write_iops"` // 写入 IOPS (count/s)
|
||||
Util float64 `json:"util"` // 磁盘繁忙程度(百分比)— 判断磁盘是否瓶颈的核心指标
|
||||
}
|
||||
|
||||
// SwapStat 交换分区指标(OOM 前兆判断)
|
||||
@@ -28,34 +31,34 @@ type SwapStat struct {
|
||||
|
||||
// SocketFDStat 系统级 FD 使用情况
|
||||
type SocketFDStat struct {
|
||||
Allocated int `json:"allocated"` // 已分配的 file handles (fs.file-nr 第一列)
|
||||
Max int64 `json:"max"` // 系统上限 (fs.file-max)
|
||||
Allocated int `json:"allocated"` // 已分配的 file handles (fs.file-nr 第一列)
|
||||
Max int64 `json:"max"` // 系统上限 (fs.file-max)
|
||||
UsagePct float64 `json:"usage_pct"`
|
||||
}
|
||||
|
||||
// ContextSwitchStat 上下文切换(决定是否开中断合并/CPU 隔离)
|
||||
type ContextSwitchStat struct {
|
||||
CTXPerSec float64 `json:"ctx_per_sec"` // 每秒上下文切换次数
|
||||
IRQPerSec float64 `json:"irq_per_sec"` // 每秒中断次数
|
||||
CTXPerSec float64 `json:"ctx_per_sec"` // 每秒上下文切换次数
|
||||
IRQPerSec float64 `json:"irq_per_sec"` // 每秒中断次数
|
||||
}
|
||||
|
||||
// AgentHeartbeat Agent 自检心跳(随每次上报附带,极小 payload)
|
||||
type AgentHeartbeat struct {
|
||||
Version string `json:"version"`
|
||||
UptimeSec int64 `json:"uptime_sec"`
|
||||
LastErr string `json:"last_err,omitempty"`
|
||||
SelfCPU float64 `json:"self_cpu_pct"`
|
||||
SelfRSSMB float64 `json:"self_rss_mb"`
|
||||
ReportSeq uint64 `json:"report_seq"` // 单调递增序列号,用于丢包检测
|
||||
Version string `json:"version"`
|
||||
UptimeSec int64 `json:"uptime_sec"`
|
||||
LastErr string `json:"last_err,omitempty"`
|
||||
SelfCPU float64 `json:"self_cpu_pct"`
|
||||
SelfRSSMB float64 `json:"self_rss_mb"`
|
||||
ReportSeq uint64 `json:"report_seq"` // 单调递增序列号,用于丢包检测
|
||||
}
|
||||
|
||||
// SystemStat 系统信息(启动时采集一次)
|
||||
type SystemStat struct {
|
||||
OSVersion string `json:"os_version"` // OS 版本 (如 "Ubuntu 22.04")
|
||||
Kernel string `json:"kernel"` // 内核版本
|
||||
Uptime int64 `json:"uptime"` // 运行秒数
|
||||
UlimitNofile int64 `json:"ulimit_nofile"` // ulimit -n (最大文件描述符)
|
||||
UlimitNproc int64 `json:"ulimit_nproc"` // ulimit -u (最大进程数)
|
||||
OSVersion string `json:"os_version"` // OS 版本 (如 "Ubuntu 22.04")
|
||||
Kernel string `json:"kernel"` // 内核版本
|
||||
Uptime int64 `json:"uptime"` // 运行秒数
|
||||
UlimitNofile int64 `json:"ulimit_nofile"` // ulimit -n (最大文件描述符)
|
||||
UlimitNproc int64 `json:"ulimit_nproc"` // ulimit -u (最大进程数)
|
||||
}
|
||||
|
||||
// ──── v2.1 健康探针 ────
|
||||
@@ -80,8 +83,30 @@ type ProbeResult struct {
|
||||
ProbeID uint `json:"probe_id"`
|
||||
Name string `json:"name"`
|
||||
ProbeType string `json:"probe_type"`
|
||||
Status string `json:"status"` // up / down
|
||||
Status string `json:"status"` // up / down
|
||||
LatencyMs int `json:"latency_ms"`
|
||||
StatusCode int `json:"status_code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// PortPolicy 后端下发的端口过滤策略 // Logan.Li
|
||||
type PortPolicy struct {
|
||||
Hostname string `json:"hostname"`
|
||||
PortFilterMode string `json:"port_filter_mode"` // whitelist / blacklist
|
||||
PortWhitelist []int `json:"port_whitelist"` // 端口号数组
|
||||
}
|
||||
|
||||
// IsBlacklistMode 判断是否为黑名单模式(只上报选中端口)
|
||||
func (p *PortPolicy) IsBlacklistMode() bool {
|
||||
return p.PortFilterMode == "blacklist"
|
||||
}
|
||||
|
||||
// InWhitelist 检查端口是否在白名单中
|
||||
func (p *PortPolicy) InWhitelist(port int) bool {
|
||||
for _, w := range p.PortWhitelist {
|
||||
if w == port {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
pollPath = "/api/v1/agent/tasks/poll"
|
||||
resultPathFmt = "/api/v1/agent/tasks/%s/result"
|
||||
probePath = "/api/v1/agent/health-probes/config"
|
||||
portPolicyPath = "/api/v1/agent/port-policy"
|
||||
)
|
||||
|
||||
// Client 负责与后端任务/探针接口通信。
|
||||
@@ -74,3 +75,18 @@ func (c *Client) PollProbes(ctx context.Context) ([]model.ProbeConfig, error) {
|
||||
}
|
||||
return wrapper.Data, nil
|
||||
}
|
||||
|
||||
// PollPortPolicy 拉取本机端口过滤策略(可选配置,拉取失败静默忽略) // Logan.Li
|
||||
func (c *Client) PollPortPolicy(ctx context.Context) (*model.PortPolicy, error) {
|
||||
path := portPolicyPath + "?hostname=" + url.QueryEscape(c.hostname)
|
||||
|
||||
var wrapper struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data model.PortPolicy `json:"data"`
|
||||
}
|
||||
if err := c.client.DoJSON(ctx, "GET", path, nil, &wrapper); err != nil {
|
||||
return nil, fmt.Errorf("端口策略拉取失败: %w", err)
|
||||
}
|
||||
return &wrapper.Data, nil
|
||||
}
|
||||
Reference in new issue
Block a user