一、背景
在过去的IT日常支持场景中,因为服务的用户、终端、系统等等因业务而异,往往会遇到以下类似这些问题或需求:
- IT工程师定位终端问题跨越不同的平台或系统,低效繁琐
- 用户想要获取一些个人相关的IT环境信息,只能咨询IT部门
- 电脑终端的软硬件资源类性能消耗无法集中宏观监控、数字化管理
- 主动预判终端问题的客观依据和快速定位能力有差异化
通过建设一套“IT用户终端信息一体化管理平台”,采集用户信息、机器信息、软件信息、权限信息、网络信息等等,实现数据入库、实时更新、可视化。具备提供一站式查询管理、缩短case定位时间、集中数字化管理、提前预警终端软硬件风险等能力。
其中针对电脑及性能数据的采集,面临着指标多样性、数据实时性存储、系统差异性等高要求,下面针对电脑终端信息采集的Quark 2.0体系进行详细的介绍。
二、架构
Quark 2.0体系是为“IT用户终端信息一体化管理平台”建立的一套计算机性能监控体系,作为IT资产管理的延伸,帮助IT人员完成日常维护、故障排查和资源统计等工作。
该体系架构示意图如下:
安装在办公电脑的Agent按单位时间一次的心跳频率,采集计算机的CPU、内存、磁盘、网络、电池、进程占用等信息上报给Master集群,并把历史数据打点存入InfluxDB。支持Windows和MacOS两种主流的办公电脑操作系统。Master集群接收到Agent上报的心跳数据,将其存入Redis,并通过Etcd进行节点注册和健康检测。Registry集群作为IT一体化自助查询平台的后端服务器集群,承担了多个数据来源的集中查询、日志记录、任务调度等功能。
Agent客户端支持采集的数据类型如下:
心跳上报:实时获取最新数据,新数据会覆盖旧数据
数据打点:存储最新数据,保留旧数据
类型 | 指标 | 采集方式 | 备注 |
系统 | 操作系统 | 心跳上报 | Windows/MacOS,包括具体版本号 |
计算机名 | 心跳上报 | ||
内核架构 | 心跳上报 | 例:x86_64 | |
硬件 | 序列号 | 心跳上报 | |
制造厂商 | 心跳上报 | ||
产品型号 | 心跳上报 | ||
CPU | 型号 | 心跳上报 | |
厂商 | 心跳上报 | ||
核数 | 心跳上报 | ||
占用核数 | 数据打点 | ||
频率 | 心跳上报 | ||
使用率 | 数据打点 | user、sys、iowait、idle、busy | |
温度 | 数据打点 | ||
内存 | 容量 | 心跳上报/数据打点 | 单位:GiB |
使用量 | 心跳上报/数据打点 | 单位:GiB | |
频率 | 心跳上报 | ||
磁盘 | 总空间 | 心跳上报/数据打点 | 单位:GiB |
已使用空间 | 心跳上报/数据打点 | 单位:GiB | |
分区 | 心跳上报/数据打点 | 单位:GiB | |
驱动 | 心跳上报 | 包括型号、类型、状态 | |
读写字节数 | 数据打点 | 单位:GiB | |
读写速度 | 数据打点 | 单位:KiB/s | |
读写次数 | 数据打点 | ||
网络 | 网络名 | 心跳上报 | |
MTU | 心跳上报 | ||
Mac地址 | 心跳上报 | ||
IP地址 | 心跳上报 | ||
驱动 | 心跳上报 | 包括名称、描述、厂商、产品号 | |
上下行字节数 | 数据打点 | 单位:GiB | |
上下行数据包总数 | 数据打点 | ||
上下行速度 | 数据打点 | 单位:KiB/s | |
电池 | 状态 | 心跳上报/数据打点 | |
状态码 | 心跳上报/数据打点 | 例:有无电池、使用电池/AC电源、是否满电 | |
剩余电量 | 心跳上报/数据打点 | ||
剩余使用时间 | 心跳上报/数据打点 | 部分系统版本不支持此指标 | |
进程 | 占用CPU进程 | 数据打点 | 记录前五个 |
占用内存进程 | 数据打点 | ||
占用IO进程 | 数据打点 | ||
WiFi | ESSID | 数据打点 | |
BSSID | 数据打点 | ||
信号强度 | 数据打点 | ||
联网情况 | ping内网 | 数据打点 | min_rtt、max_rtt、avg_rtt、std_dev_rtt、loss |
ping外网 | 数据打点 | ||
ping网关 | 数据打点 | ||
DNS解析 | 数据打点 | 是否能解析特定域名 |
三、数据采集
Agent客户端基于Go语言开发,通过gopsutil库、wmic等采集计算机数据。对于MacOS,则以解析ioreg、system_profiler等指令获取对应指标。由于采集指标众多,下面仅以电池和WiFi作为示例,讲解具体的数据采集原理。
电池信息
对于Windows,通过wmic指令调用Win32_Batteryapi,获取电脑的状态、状态码、剩余电量等信息。
//go:build windows
// +build windows
package battery
import (
"errors"
"math"
"git.ppd.com/quark/pkg/logger"
"git.ppd.com/quark/pkg/metrics"
"github.com/shopspring/decimal"
"github.com/yusufpapurcu/wmi"
)
type batteryInfo struct {
Availability aStatus
BatteryStatus bStatus
Status string
EstimatedChargeRemaining uint16
EstimatedRunTime uint32
DesignCapacity uint32
FullChargeCapacity uint32
}
func win32BatteryInfo() (*batteryInfo, error) {
var batteryInfo []batteryInfo
err := wmi.Query("SELECT * FROM Win32_Battery", &batteryInfo)
if err != nil {
return nil, err
}
if len(batteryInfo) > 0 {
return &batteryInfo[0], nil
}
return nil, errors.New("empty battery info")
}
func BatteryInfo() metrics.BatteryInfo {
battery, err := win32BatteryInfo()
if err != nil {
logger.Errorf("get BatteryInfo error: %s", err.Error())
return metrics.BatteryInfo{Status: "NoBattery"}
}
var estimatedRunTime float64
if battery.EstimatedRunTime == uint32(math.Pow(2, 32)/60) {
estimatedRunTime = 0
} else {
tmpRunTime := decimal.NewFromFloat(float64(battery.EstimatedRunTime) / 60)
estimatedRunTime, _ = tmpRunTime.Round(1).Float64()
}
return metrics.BatteryInfo{
Status: battery.Status,
Availability: metrics.BatStatus{
Code: uint16(battery.Availability),
Desc: battery.Availability.String(),
},
BatteryStatus: metrics.BatStatus{
Code: uint16(battery.BatteryStatus),
Desc: battery.BatteryStatus.String(),
},
CurrentCap: float64(battery.FullChargeCapacity),
DesignCap: float64(battery.DesignCapacity),
EstimatedTime: estimatedRunTime,
EstimatedPower: int64(battery.EstimatedChargeRemaining),
}
}
对于MacOS,则通过解析system_profiler SPPowerDataType和pmset -g batt指令的返回获取电池的对应信息。
WiFi信息
对于Windows,通过解析netsh wlan show interfaces指令的返回获取WiFi的BSSID、ESSID和信号强度。
对于MacOS,则通过析/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I和ioreg -l -n AirPortDriver | perl -lne print $1 if $_ =~ /IO80211BSSID.*<(.*)>/指令获取WiFi的对应信息。
四、心跳上报
QRPC组件
Master集群与Agent客户端之间基于自研的QRPC组件进行长连接通信。与市面上常见的开源RPC组件相比,QRPC组件具有通信简单、连接管理透明、完全可控等优点。
名称 | 开发者 | 简述 | 优点 | 缺点 |
GRPC | | 功能强大,是一款非常完善的rpc框架 | 功能强大完善;多语言支持;序列化效率高 | 学习、维护的成本较高;引入大量第三方库,存在潜在风险 |
net/rpc(标准库) | GO Team | 内置RPC包 | 标准库内置,无需引入新库 | TCP网络连接管理缺失;序列化(GO内置)效率较低 |
QRPC | 自研 | 用于Master集群与Agent客户端的通信 | 轻量化框架;通信简单;连接管理透明 | 适用性较窄 |
通信过程
首次心跳上报时,Agent需要先从Etcd中获取一个可用的Master节点,并采用Hash算法保证Master集群的负载均衡。获取到Master节点之后,建立长连接并写入Agent配置。Master节点接收到连接请求之后完成握手连接,并将心跳数据存储到Redis中,完成本次通信。如果发生意外导致连接中断或Master节点挂掉,Agent会自动重新获取新的可用节点。
获取Master节点实现如下:
import (
"encoding/json"
"fmt"
"hash/fnv"
"git.ppd.com/quark/agent_v2/config"
"git.ppd.com/quark/agent_v2/stats"
"git.ppd.com/quark/pkg/client/etcd"
"git.ppd.com/quark/pkg/logger"
"git.ppd.com/quark/pkg/metrics"
)
func (a *Agent) getMasterIP() (string, error) {
// 获取本机IP
localIP := stats.IP()
if localIP == "" {
return "", fmt.Errorf("get local_ip failed: local_ip is <nil>")
}
// 获取所有可用的Master节点
// 要求:当前节点负载低于预设的最大负载
var assignAddrs []string
maxLoad := config.Config().MasterConfig.MaxLoad
masterInfo := registryMasterNode()
for _, v := range masterInfo {
if v.ReportNum < maxLoad {
assignAddrs = append(assignAddrs, v.IP)
}
}
// Hash算法获取Master节点
hashValue := hash(localIP)
index := int(hashValue) % len(assignAddrs)
masterNode := assignAddrs[index]
return masterNode, nil
}
// 获取已注册的Master节点
func registryMasterNode() map[string]*metrics.MasterHeartBeat {
tmp := make(map[string]*metrics.MasterHeartBeat, 0)
// etcd初始化
cfg := config.Config().EtcdConfig
etcd.Init(cfg.EtcdAddr)
defer func() {
if err := etcd.EClient.Close(); err != nil {
logger.Errorf("close etcd client failed: %s", err.Error())
}
}()
resp, err := etcd.EClient.Get(cfg.MasterPath, clientv3.WithPrefix())
if err != nil {
logger.Errorf("从etcd获取master节点列表失败: %s", err.Error())
return tmp
}
for k, v := range resp.Kvs {
master := &metrics.MasterHeartBeat{}
err := json.Unmarshal(v.Value, &master)
if err != nil {
logger.Errorf("master节点%d序列化失败: %s", k, err.Error())
continue
}
tmp[master.IP] = master
}
return tmp
}
// Hash: string to int
func hash(key string) uint32 {
h := fnv.New32a()
h.Write([]byte(key))
return h.Sum32()
}
Agent在线状态管理
Master获取Agent在线状态,主要是通过Redis的键过期订阅机制。预先设置Agent键的过期时间,每次心跳上报时更新值,同时重置初始时间。超过过期时间仍未更新,则视为Agent已离线。
五、历史数据打点
InfluxDB
InfluxDB是一个用于存储和分析时间序列数据的开源数据库,主要特性如下:
- 内置HTTP接口,使用方便
- 数据可以打标记,查询很灵活
- 类SQL的查询语句
- 安装管理简单,并且读写数据高效
- 支持实时查询
Quark 2.0体系基于InfluxDB v1的HTTP POST方式,对Agent采集的历史数据进行打点存储。
InfluxDB-Relay
由于InfluxDB v1开源版不支持集群模式,故采用官方推荐的社区开源高可用方案InfluxDB-Relay。
InfluxDB-Relay为InfluxDB提供双写能力,确保其中一个节点挂掉后数据不会丢失。注意InfluxDB-Relay只代理写流量,查询数据时直接访问InfluxDB。
influxdb-relay.toml 配置如下:
[[http]]
name = "influx-http"
bind-addr = "127.0.0.1:9096"
output = [
{ name="db1", location = "http://XX.XXX.XX.XXX:8086/write" },
{ name="db2", location = "http://XX.XXX.XX.XXX:8086/write" },
{ name="db3", location = "http://XX.XXX.XX.XXX:8086/write" },
]
页面交互
“IT用户终端信息一体化管理平台”支持根据域账号和计算机名查询对应信息,包括用户的基本信息、名下资产和所在群组、办公电脑的IT资产信息、心跳数据和历史数据等。
六、未来展望
在后续的平台建设中,“IT用户终端信息一体化管理平台”还将完善接入更完整的其他用户信息,例如各相关系统权限、入网认证各环节状态、虚拟资产等信息,进行一体化关联,为实现终端智能化管理夯实基础。