cadvisor数据获取 源码分析

介绍

cAdvisor(Container Advisor):可以对节点上的资源及容器进行监控与数据的收集,如:cpu、memory、net等

manager

manager是cAdvisor的资源管理器,包含containerData数组,InMemoryCache、与系统文件系统的接口sysFs、监听器以及httpClient等等

type manager struct {
//所有受监控容器的具体操作和信息
containers               map[namespacedContainerName]*containerData
//缓存在内存中的数据,主要是容器相关的一些信息
memoryCache              *memory.InMemoryCache
//host机器的文件系统的相关信息
fsInfo                   fs.FsInfo
//文件系统接口
sysFs                    sysfs.SysFs
//节点的信息
machineInfo              info.MachineInfo
//cadvisor运行在哪个容器
cadvisorContainer        string
//对event相关操作的封装
eventHandler             events.EventManager
//manager的启动时间
startupTime              time.Time
//搜集更新容器信息的时间间隔
maxHousekeepingInterval  time.Duration
...
}

其中InMemoryCache是一个内存存储器,包含的containerCacheMap为容器数据缓存的map,storageDriver控制数据会被持久化存储到哪里,maxAge是数据在内存保留多长时间

sysFs是与系统文件系统的接口,获取文件系统对应的信息

maxHousekeepingInterval 是搜集更新容器信息的时间间隔

cAdvisor启动后就会有上面这些内容

Manager接口

Manager接口中定义了获取容器进程节点数据的方法

Container info
//  获取指定 container 的 info 数据
DockerContainer(containerName string, query *info.ContainerInfoRequest) (info.ContainerInfo, error)

1、参数有

containerName 容器名
ContainerInfoRequest(指定了用户想要获得多少关于容器的数据,即要返回的最大统计数、开始时间和结束时间)

2、返回值ContainerInfo中包含

ContainerReference 是唯一标识容器信息的结构体(容器id 名称 别名 命名空间)

Subcontainers []ContainerReference 是当前容器的直接子容器

ContainerSpec 是容器信息,创建时间 cpu使用情况 内存使用情况 使用的镜像等等

Stats []*ContainerStats 是从容器中收集的历史数据的统计,cpu 内存 io 文件系统

3、函数实现

3.1、DockerContainer通过容器名获取ContainerData( 通过manager中map[namespacedContainerName]*containerData 查找ContainerData )

container, err := m.getDockerContainer(containerName)

containerData包含 提供对容器的实际操作以及containerInfo,对InMemoryCache中的数据的实际操作都由他进行

type containerData struct {
   oomEvents                uint64
   handler                  container.ContainerHandler
   info                     containerInfo
   memoryCache              *memory.InMemoryCache
   housekeepingInterval     time.Duration
   maxHousekeepingInterval  time.Duration
   allowDynamicHousekeeping bool
   // 更新perf_event cgroup控制器的统计信息
   perfCollector stats.Collector
   // 更新resctrl控制器的统计信息
   resctrlCollector stats.Collector
   ...
}

3.2、获取到ContainerInfo (query后的ContainerInfo)(ContainerData中就有ContainerInfo)

m.containerDataToContainerInfo(container, query)  (参数为ContainerData和ContainerInfoRequest)

3.2.1、先通过containerData的GetInfo(…)方法

cinfo, err := cont.GetInfo(true)

将containerData中ContainerInfo的ContainerReference、子容器和ContainerSpec 赋值给要返回的ContainerInfo中
GetInfo()方法中

// 间隔超过5s就会更新spec和子容器信息
if cd.clock.Since(cd.infoLastUpdatedTime) > 5*time.Second || shouldUpdateSubcontainers {
   err := cd.updateSpec()

3.2.2、通过manager的getAdjustedSpec(info),将spec.Memory.Limit从默认值设置为实际值

spec.Memory.Limit = uint64(m.machineInfo.MemoryCapacity)

3.2.3、最后获取到stats

// memoryCache为InMemoryCache类型,包含containerCacheMap 容器数据缓存的map
stats, err := m.memoryCache.RecentStats(cinfo.Name, query.Start, query.End, query.NumStats)

获取stats的具体函数为通过containerCacheMap[name]中获取到containerCache,然后通过containerCache的RecentStats(start, end, maxStats)获取到stats

4、manage资源管理器会定时读取Linux 上相关的文件,数据都来源于Cgroups,Cgroups 的目录为/sys/fs/cgroup,该目录下包含了 Cgroups 的各个子系统 subSystem。subSystem可以用来对不同的资源(如CPU、内存、PID、磁盘 IO)进行限制 (如:cpu超出限制会将其cpu使用率降到设置的值附近,内存超出限制可以在文件中设置选择 将其杀掉)以及采集各个资源的使用数据。

(cgroup中的概念:

1、任务:task,一个任务就是一个进程

2、控制组:cgroup,cgroup资源控制方式就是以控制组的方式,控制组中配置资源的限制,一个cgroup中的 tasks 中包含多个task

3、层级树:hierarchy,描述控制组的层级关系,层级树上的一个节点就是一个cgroup,子节点的控制信息继承父节点的控制信息

4、子系统:subSystem,资源控制器,各个子系统控制各自的资源,一个子系统加入到伯格层级树,该层级树中的所有节点,都会被这个子系统控制,比如:在子系统下创建文件夹,该文件夹就会拥有这些控制文件,这个文件夹就是一个节点,即一个控制组,也就表示在该层级树上创建了一个节点

5、更新ContainerSpec
-----通过containerData 去 updateSpec()

// updateSpec()方法会调用下面两个方法
spec, err := cd.handler.GetSpec()
customMetrics, err := cd.collectorManager.GetSpec() //自定义指标

数据由cgroup提供,读取的 cgroup 文件如下:

cpu文件:cpu.weight、cpu.max、cpu.shares、cpu.cfs_period_us、cpu.cfs_quota_us等等

cpuacct文件: cpuacct.usage、cpuacct.stat、cpuacct.usage_percpu

cpuset文件:cpuset.cpus.effective、cpuset.cpus等等

memory文件:memory.max、memory.high、memory.max、memory.swap.max、memory.limit_in_bytes、memory.memsw.limit_in_bytes、memory.soft_limit_in_bytes等等

blkio文件:blkio.time、blkio.avg_queue_size、blkio.dequeue、blkio.io_service_bytes等等

GetSpec() 中调用getSpecInternal() 方法,通过cgroupPaths 和 对应的系统文件 获取到machine info( machineInfoFactory.GetMachineInfo() )、CPU、Cpu Mask、Memory、Hugepage、Processes数据

func getSpecInternal(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem, cgroup2UnifiedMode bool) (info.ContainerSpec, error)

getSpecInternal() 方法实现( 通过cgroup获取数据 ) (非cgroup2UnifiedMode)

(1) Get machine info

mi, err := machineInfoFactory.GetMachineInfo()
if err != nil { return spec, err }

(2) Get cpu info

spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares")
spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us")
// 将读出的quota字符串转化为十进制,quota不为空且不等于-1
quota := readString(cpuRoot, "cpu.cfs_quota_us")
if quota != "" && quota != "-1" {
   val, err := strconv.ParseUint(quota, 10, 64)
   if err != nil {
      klog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err)
   } else {
      spec.Cpu.Quota = val
   }
}

(3) Get Cpu Mask info ( cgroup 中 cpuset )

mask = readString(cpusetRoot, "cpuset.cpus")
// mi 是(1)中获取的 MachineInfo
spec.Cpu.Mask = utils.FixCpuMask(mask, mi.NumCores)

(4) Get Memory info

spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes")
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes")
spec.Memory.Reservation = readUInt64(memoryRoot, "memory.soft_limit_in_bytes")

(5) Get Hugepage info

hugepageRoot, ok := cgroupPaths["hugetlb"]
if ok {
   if utils.FileExists(hugepageRoot) {
      spec.HasHugetlb = true
   }
}

(6) Get pids info ( 直接从pids路径读取它的值 )

pidsRoot, ok := GetControllerPath(cgroupPaths, "pids", cgroup2UnifiedMode)
if ok {
   if utils.FileExists(pidsRoot) {
      spec.HasProcesses = true
      spec.Processes.Limit = readUInt64(pidsRoot, "pids.max")
   }
}

(7) Network、Filesystm、DiskIo

(hasNetwork、hasFilesystem是方法的参数)

spec.HasNetwork = hasNetwork
spec.HasFilesystem = hasFilesystem

ioControllerName := "blkio"
if blkioRoot, ok := cgroupPaths[ioControllerName]; ok && utils.FileExists(blkioRoot) {
   spec.HasDiskIo = true
}

6、获取指定容器的cgroup和网络统计信息

func (h *Handler) GetStats() (*info.ContainerStats, error)

方法实现:

/proc/cgroups —当前内核支持的 cgroup 子系统

cgroupStats, err := h.cgroupManager.GetStats()
libcontainerStats := &libcontainer.Stats{
   CgroupStats: cgroupStats,
}
stats := newContainerStats(libcontainerStats, h.includedMetrics)

如果我们知道pid,那么从/proc//net/dev获取网络统计信息

...
// If we know the pid then get network stats from /proc/<pid>/net/dev
if h.pid > 0 {
   if h.includedMetrics.Has(container.NetworkUsageMetrics) {
      netStats, err := networkStatsFromProc(h.rootFs, h.pid)
      if err != nil {
         klog.V(4).Infof("Unable to get network stats from pid %d: %v", h.pid, err)
      } else {
         stats.Network.Interfaces = append(stats.Network.Interfaces, netStats...)
      }
   }
   if h.includedMetrics.Has(container.NetworkTcpUsageMetrics) {
      t, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp")
      if err != nil {
         klog.V(4).Infof("Unable to get tcp stats from pid %d: %v", h.pid, err)
      } else {
         stats.Network.Tcp = t
...		 

7、获取 fs 相关信息

func NewFsInfo(context Context) (FsInfo, error)

方法实现

/proc/self/mountinfo —当前运行进程的挂载信息

...
fileReader, err := os.Open("/proc/self/mountinfo")
mounts, err := mount.GetMountsFromReader(fileReader, nil)
...
for _, mnt := range mounts {
   fsInfo.mounts[mnt.Mountpoint] = *mnt
}
...
fsInfo.addDockerImagesLabel(context, mounts)
fsInfo.addCrioImagesLabel(context, mounts)
fsInfo.addSystemRootLabel(mounts)
...

8、启动一个docker容器,cgroup就会在对应子系统下创建节点

cpu:/sys/fs/cgroup/cpu/docker/容器ID/一些限制文件及tasks文件
memory:/sys/fs/cgroup/memory/docker/容器ID/一些限制文件及tasks文件

当容器有多个进程,进程ID就会加入到tasks中,一个容器在各层级树中对应一个cgroup,对这个cgroup数据的采集与限制,就是对这个容器中所有进程的采集与限制

9、Cadvisor的架构是一个event机制事件监听层(监听linux系统发生的事件)和事件处理层

事件监听层 ContainerAdd事件(watchForNewContainers函数)OOM事件(watchForNewOoms函数)

rawWatcher直接监控系统的cgroup根目录,发生事件由事件处理层进行数据的更新处理

Machine Info

方法GetMachineInfo()用来获取节点机器信息

func GetCPUVendorID(procInfo []byte) string     //返回“vendor_id ”,读取/proc/cpuinfo文件

func GetPhysicalCores(procInfo []byte) int      //返回读取/proc/cpuinfo文件的CPU核心数

func GetSockets(procInfo []byte) int       //返回读取/proc/cpuinfo文件的CPU套接字数量

func GetClockSpeed(procInfo []byte) (uint64, error)        //返回CPU时钟速度

func GetMachineMemoryCapacity() (uint64, error)      //从/proc/meminfo返回机器的总内存

func GetMachineMemoryByType(edacPath string) (map[string]*info.MemoryInfo, error)  //返回内存容量和DIMMs数量

func GetMachineSwapCapacity() (uint64, error)     //从/proc/meminfo返回机器的总交换空间

通过读取文件获取数据

out, err := ioutil.ReadFile("/proc/cpuinfo") //获取cpuinfo

clockSpeed, err := machine.GetClockSpeed(cpuinfo) // 获取时钟速率

out, err := ioutil.ReadFile("/proc/meminfo") //memoryInfo

对应文件:

/proc/cpuinfo //MumCores、CpuFrequency等

/var/lib/dbus/machine-id //machine_id

/proc/sys/kernel/random/boot_id //boot_id

/proc/diskstats //disk

/sys/block //获取磁盘设备信息

/sys/class/net //netDevices

/sys/class/dmi/id/product_uuid //system UUID

ProcessList
func (m *manager) GetProcessList(containerName string, options v2.RequestOptions) ([]v2.ProcessInfo, error) 

函数中

1、获取到requestedContainers, 返回ContainerData数组

//conts为 map[string]*containerData
conts, err := m.getRequestedContainers(containerName, options)

2、对ContainerData数组遍历,获取所有容器的ps信息

ps, err = cont.GetProcessList(m.cadvisorContainer, m.inHostNamespace)

2.1、

out, err := cd.getPsOutput(inHostNamespace, format)

此函数中cAdvisor 首先判断自己是否运行在 container 中,如果是则 chroot /rootfs (cAdvisor 在 container 中运行会通过 volume 方式把系统 / mount 到 /rootfs 下)再执行

ps -e -o user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,psr,cgroup

具体函数实现

func (cd *containerData) getPsOutput(inHostNamespace bool, format string) ([]byte, error) {
   args := []string{}
   command := "ps"
   if !inHostNamespace {
      command = "/usr/sbin/chroot"
      args = append(args, "/rootfs", "ps")
   }
   args = append(args, "-e", "-o", format)
   out, err := exec.Command(command, args...).Output()
   if err != nil {
      return nil, fmt.Errorf("failed to execute %q command: %v", command, err)
   }
   return out, err
}

否则直接执行获取, 如果是获取全局而非某个 container 的 processlist,则 containername 默认为 “/”

2.2、接着

func (cd *containerData) parseProcessList(cadvisorContainer string, inHostNamespace bool, out []byte) ([]v2.ProcessInfo, error) {

对上面的out []byte进行每行读取、格式化,将其转化为ProcessInfo类型返回

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AloneDrifters

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值