在容器化技术广泛应用的场景下,通过Golang实现对容器资源使用情况的监控是很多后端开发者的实际需求。容器本身是通过Linux的cgroup机制实现资源限制的,因此监控容器资源的核心就是读取cgroup相关的文件数据。

容器资源监控的核心原理
Linux系统中,每个容器都会对应一组cgroup目录,这些目录中存储了容器各类资源的使用数据。Golang程序只需要根据容器的cgroup路径,读取对应文件的内容,就可以获取到CPU、内存、磁盘IO等资源的使用情况。不同容器运行时(如Docker、Containerd)的cgroup路径略有差异,但核心的读取逻辑是一致的。
常见资源对应的cgroup文件
| 资源类型 | cgroup文件路径示例 | 文件含义 |
|---|---|---|
| CPU使用总量 | /sys/fs/cgroup/cpu/cpuacct.usage | 容器启动以来CPU使用的总纳秒数 |
| 内存使用量 | /sys/fs/cgroup/memory/memory.usage_in_bytes | 容器当前使用的内存字节数 |
| 内存限制 | /sys/fs/cgroup/memory/memory.limit_in_bytes | 容器被限制的最大内存字节数 |
| 磁盘IO次数 | /sys/fs/cgroup/blkio/blkio.throttle.io_serviced | 容器磁盘IO的服务次数统计 |
实现容器CPU使用率监控
CPU使用率的计算需要获取两个时间点的CPU使用总量,结合时间间隔来计算单位时间内的CPU消耗。下面的示例实现了读取容器CPU使用总量,并计算1秒内的CPU使用率的逻辑。
package main
import (
"fmt"
"io/ioutil"
"strconv"
"time"
)
// 读取cgroup中的CPU使用总纳秒数
func getCpuUsage(cgroupPath string) (uint64, error) {
// 拼接CPU使用文件路径
filePath := cgroupPath + "/cpu/cpuacct.usage"
data, err := ioutil.ReadFile(filePath)
if err != nil {
return 0, err
}
// 转换字符串为无符号整数
usage, err := strconv.ParseUint(string(data[:len(data)-1]), 10, 64)
if err != nil {
return 0, err
}
return usage, nil
}
func main() {
// 这里以Docker容器的cgroup路径为例,实际使用时需要根据容器运行时调整
cgroupBasePath := "/sys/fs/cgroup"
// 第一次获取CPU使用量
firstUsage, err := getCpuUsage(cgroupBasePath)
if err != nil {
fmt.Println("获取第一次CPU使用量失败:", err)
return
}
// 间隔1秒
time.Sleep(1 * time.Second)
// 第二次获取CPU使用量
secondUsage, err := getCpuUsage(cgroupBasePath)
if err != nil {
fmt.Println("获取第二次CPU使用量失败:", err)
return
}
// 计算1秒内的CPU使用纳秒数
usageDiff := secondUsage - firstUsage
// 单核CPU的1秒对应1e9纳秒,这里计算使用率(假设单核场景)
usageRate := float64(usageDiff) / 1e9 * 100
fmt.Printf("容器1秒内CPU使用率: %.2f%%n", usageRate)
}
实现容器内存使用监控
内存监控相对简单,只需要读取当前内存使用量和内存限制量,就可以计算出内存使用占比。下面的示例实现了内存使用数据的读取和占比计算。
package main
import (
"fmt"
"io/ioutil"
"strconv"
)
// 读取cgroup中的内存使用字节数
func getMemoryUsage(cgroupPath string) (uint64, error) {
filePath := cgroupPath + "/memory/memory.usage_in_bytes"
data, err := ioutil.ReadFile(filePath)
if err != nil {
return 0, err
}
usage, err := strconv.ParseUint(string(data[:len(data)-1]), 10, 64)
if err != nil {
return 0, err
}
return usage, nil
}
// 读取cgroup中的内存限制字节数
func getMemoryLimit(cgroupPath string) (uint64, error) {
filePath := cgroupPath + "/memory/memory.limit_in_bytes"
data, err := ioutil.ReadFile(filePath)
if err != nil {
return 0, err
}
limit, err := strconv.ParseUint(string(data[:len(data)-1]), 10, 64)
if err != nil {
return 0, err
}
return limit, nil
}
func main() {
cgroupBasePath := "/sys/fs/cgroup"
usage, err := getMemoryUsage(cgroupBasePath)
if err != nil {
fmt.Println("获取内存使用量失败:", err)
return
}
limit, err := getMemoryLimit(cgroupBasePath)
if err != nil {
fmt.Println("获取内存限制量失败:", err)
return
}
// 转换为MB方便查看
usageMB := float64(usage) / 1024 / 1024
limitMB := float64(limit) / 1024 / 1024
usagePercent := float64(usage) / float64(limit) * 100
fmt.Printf("容器内存使用情况: 已使用%.2fMB, 限制%.2fMB, 使用占比%.2f%%n", usageMB, limitMB, usagePercent)
}
注意事项
- 不同Linux发行版的cgroup版本可能不同,cgroup v1和vgroup v2的文件路径和字段名称有差异,需要根据实际系统调整路径。
- 如果是监控非当前主机的容器,需要先获取目标容器的cgroup路径,比如Docker容器可以通过
docker inspect 容器ID命令查看HostConfig中的cgroup路径。 - 读取cgroup文件需要对应的文件权限,运行Golang程序的用户需要有cgroup目录的读取权限,否则会报权限错误。
- 计算CPU使用率时需要考虑容器的CPU核数限制,多核场景下需要将使用纳秒数除以(核数 * 时间间隔纳秒数)才能得到准确的使用率。
在实际生产环境中,还可以将采集到的资源数据上报到Prometheus等监控系统,配合Grafana实现可视化的容器资源监控面板,更方便地观察容器的运行状态。
Golang容器监控资源使用cgroupcontainer_resource修改时间:2026-06-11 13:45:33