Skip to main content
实战阶段 II:大脑(Master)已经就绪,现在我们需要编写派往各处的“斥候”——Agent。这个 Go 程序将运行在业务服务器上。它必须满足运维的三大铁律:占用低(不能干扰业务)、零依赖(部署时不许安装 Runtime)、自愈强(Master 挂了不能崩)。

学习内容 (30 mins)

如何获取 CPU 使用率?
  • Linux 原理: 读取 /proc/stat 文件,计算时间片差值。
  • Go 原理: 我们不需要自己解析文件。gopsutil 库封装了底层系统调用 (syscall),能跨平台(Linux/Windows/Mac)一致地获取信息。
  • 注意事项: 获取 CPU 使用率需要采样时间。比如采样 1 秒,意味着函数会阻塞 1 秒来计算这一秒内的平均负载。
为什么不用 net/http 标准库?
  • 标准库: 功能太基础,设置超时、重试、JSON 序列化都要自己写很多代码。
  • Resty: 类似 Python 的 Requests 库。它内置了 Automatic Retries (自动重试) 和 Connection Pooling (连接池),这对 Agent 这种需要长期运行的网络程序至关重要。
DevOps 的魔法
  • 场景: 你在 Mac 上开发,但生产服务器是 Linux,甚至还有几台 Windows。
  • 做法: 只需要在编译时指定 GOOS=linuxGOARCH=amd64
  • 结果: Go 编译器会直接生成目标平台机器码。这也是 Go 在运维领域吊打 Python 的核心原因——分发极其容易

开发任务 (90 mins)

1

步骤 1: 初始化项目与依赖

确保你已经初始化了 Go 项目并拉取了库。
mkdir -p ~/final-project/agent && cd ~/final-project/agent

# 1. 初始化模块
go mod init github.com/akria/monitor-agent

# 2. 获取系统信息采集库
go get github.com/shirou/gopsutil/v3

# 3. 获取 HTTP 客户端库
go get github.com/go-resty/resty/v2
2

步骤 2: 编写采集与上报逻辑

单文件 main.go 搞定一切。我们将结构体定义、采集逻辑、上报逻辑写在一起。
package main

import (
    "fmt"
    "os"
    "time"

    "github.com/go-resty/resty/v2"
    "github.com/shirou/gopsutil/v3/cpu"
    "github.com/shirou/gopsutil/v3/disk"
    "github.com/shirou/gopsutil/v3/mem"
)

// Data Model: 必须与 Python Master 的 Pydantic Schema 一致
// json tag 指定了序列化后的字段名
type MetricPayload struct {
    Hostname string  `json:"hostname"`
    IP       string  `json:"ip"`
    CPU      float64 `json:"cpu"`
    Mem      float64 `json:"mem"`
    Disk     float64 `json:"disk"`
}

// 配置常量
const MasterURL = "http://127.0.0.1:8000/api/v1/report"

// CollectAndReport 执行一次完整的 "采集 -> 上报" 流程
func CollectAndReport(client *resty.Client) {
    // --- 1. 采集阶段 ---
    host, _ := os.Hostname()
    
    // CPU: 采样 1秒。注意这会阻塞当前协程 1秒
    // 返回的是一个 slice,因为可能是多核,我们取第一个(总平均)
    cpuPercent, _ := cpu.Percent(1*time.Second, false)
    
    // Mem & Disk
    memStats, _ := mem.VirtualMemory()
    diskStats, _ := disk.Usage("/") // 采集根分区

    // --- 2. 组装阶段 ---
    payload := MetricPayload{
        Hostname: host,
        IP:       "192.168.1.10", // 暂写死,拓展任务里教怎么获取
        CPU:      cpuPercent[0],
        Mem:      memStats.UsedPercent,
        Disk:     diskStats.UsedPercent,
    }

    // --- 3. 上报阶段 ---
    fmt.Printf("📤 Reporting: %+v ... ", payload)
    
    // Chain API 调用风格
    resp, err := client.R().
        SetBody(payload).
        Post(MasterURL)

    if err != nil {
        fmt.Printf("❌ Failed: %v\n", err)
        return
    }
    
    // 检查业务状态码
    if resp.StatusCode() == 201 {
        fmt.Println("✅ Sent.")
    } else {
        fmt.Printf("⚠️ Server Error: %s\n", resp.Status())
    }
}

func main() {
    fmt.Println("🚀 Agent version 1.0.0 is running...")
    
    // 1. 初始化 HTTP Client (复用连接池)
    client := resty.New()
    client.SetTimeout(2 * time.Second) // 务必设置超时,防止卡死
    client.SetRetryCount(3)            // 自动重试 3 次

    // 2. 立即执行一次 (启动时自检)
    CollectAndReport(client)

    // 3. 启动定时器 (Ticker)
    // 类似于 Python 的 while True + sleep,但更精准
    ticker := time.NewTicker(5 * time.Second)
    
    // 4. 阻塞主线程,不断响应 Ticker 事件
    for range ticker.C {
        CollectAndReport(client)
    }
}
代码解释
  • cpu.Percent(1*time.Second, false):采样 1 秒计算 CPU 使用率,会阻塞 1 秒
  • resty.New():创建 HTTP 客户端,支持连接池和自动重试
  • time.NewTicker(5 * time.Second):定时器,每 5 秒触发一次
  • for range ticker.C:阻塞主线程,响应定时器事件
Agent 的设计哲学
  • 轻量级:资源占用小,不影响业务
  • 自愈能力:Master 挂了不崩溃,自动重试
  • 零依赖:编译后是单个二进制,无需运行时
验证步骤:
  1. 启动 Master: 确保 Pt.1 开发的 Master 服务正在运行
  2. 运行 Agent: go run main.go
  3. 观察终端:
    • Agent 端显示: 📤 Reporting... ✅ Sent.
    • Master 端显示: POST /api/v1/report 201
  4. 断在 Master: 关掉 Master 服务。Agent 应该报错 Connection Refused,但不会崩溃退出,而是等待下一次 Ticker。这就是健壮性
  5. 恢复 Master: 重新启动 Master,Agent 应该自动恢复上报
常见错误
  • CPU 始终为 0: 因为 cpu.Percent(0, false) 没给采样时间。必须给 1*time.Second 或更多。
  • Panic: index out of range: 如果系统极度繁忙导致获取不到 CPU 数据,cpuPercent 可能是空的。生产代码需要 check len(cpuPercent) > 0
3

步骤 3: 跨平台编译实战

假设公司的服务器是 Linux 的。
# 编译成 Linux 64位 可执行文件
# CGO_ENABLED=0: 禁用 CGO,确保生成纯静态二进制,不依赖系统 libc
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o monitor_agent_linux main.go

# 检查产物
ls -lh monitor_agent_linux
# 你会发现它只有 7-10MB 左右

拓展任务 (30 mins)

配置化改造

任务:目前 MasterURL 是写死的。挑战:改造代码,优先读取环境变量 MASTER_URL。如果没有环境变量,再读取同目录下 config.json 文件。这是标准的云原生应用配置加载顺序。

IP 获取难题

任务:如何获取机器的真实 Outbound IP?提示:不要遍历网卡(可能会拿到虚拟 IP)。
// 原理:假装要连 Google DNS,看系统给分配了哪个本地 IP
conn, _ := net.Dial("udp", "8.8.8.8:80")
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()

今日产出物

  • monitor_agent_linux - 可直接 scp 到服务器运行的二进制文件
  • main.go - 具备重试和超时控制的 Agent 源码

参考代码

查看参考代码

GitHub 完整代码仓库

Resty 文档

Go 最流行的 HTTP 客户端库

实际应用场景

Sidecar 模式

  • 在 K8s 中,Agent 也可以作为 Sidecar 容器和业务容器跑在一个 Pod 里。
  • 虽然 Agent 是 Go 写的二进制,但依然可以封装进极小的 Docker 镜像(使用 scratchalpine 基础镜像)。
Next Step: 左手 Python Master,右手 Go Agent。现在,我们要把它们装进 Docker 容器里,完成终极交付 (Pt.3)

上一阶段: Master 开发

Day 30 | Pt.1 后端服务开发

下一阶段: 部署交付

Day 30 | Pt.3 毕业交付与总结