浅谈k8s+docker 资源监控

简介: 写在前面 最近在研究docker集群(kubernetes)的监控,为了彻底弄清楚,简单看了一点源码。这里分享一下我学到的东西。 docker api: stats 首先是docker的api,stats的具体使用场景如: http://$dockerip:2375/containers/$con.

写在前面

最近在研究docker集群(kubernetes)的监控,为了彻底弄清楚, 简单看了一点源码。这里分享一下我学到的东西。

docker api: stats

首先是docker的api,stats的具体使用场景如:

http://$dockerip:2375/containers/$containerid/stats 

可以获取docker机器上某一个容器的状态,该请求的response会持续的写响应报文回来(每秒一次)

http://$dockerip:2375/containers/$containerid/stats?stream=false 

参数stream默认是true,设为false后,response只写一次。

docker中该api对应的操作,就相当于docker stats $CID 这条命令,它调用到了ContainerStats()方法(位于docker项目目录的/daemon/stats.go 第19行),函数中启动了一个收集器:

daemon.SubscribeToContainerStats(name)

收集器定时写数据到update变量中。
然后启动一个协程读数据,获取数据的途径包括:

update := v.(*execdriver.ResourceStats)

nwStats, err := daemon.getNetworkStats(name)

可以看到网络状态是另外读的,而cpu,内存状态在哪读呢?我们一步步跳转,看到在这里:
/daemon/execdriver/driver_linux.go 112行:

func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) 

在这个函数里我们看得到,其实所谓的获取容器状态,就是读文件而已。我们举一个已经在docker运行的容器来说:

 cat /run/docker/execdriver/native/$containerID/state.json

这里面记录了一个cgroup_paths字段,他的值是一个路径,通过cstats, err := mgr.GetStats()程序才真正拿到了监控数据,如cpu的状态信息,存储在一个如下的路径中:

cd /sys/fs/cgroup/cpu,cpuacct/system.slice/docker-9b479ceaf3bef1caee2c37dfdc982a968144700a2c42991b238b938788a8a91f.scope

又比如内存的大致信息存在:

cat /sys/fs/cgroup/memory/system.slice/docker-9b479ceaf3bef1caee2c37dfdc982a968144700a2c42991b238b938788a8a91f.scope/memory.stat

具体里面放了什么数据,大家就自己看咯。

还剩下一个数据,也是我们讨论的重点:网络IO。

我们回到/daemon/stats.go
看看函数getNetworkStats(name string):
每个docker容器创建后都会又docker创建一个网卡,网卡名以veth开头,后面是一串随机生成的十六进制码。
我们只要找到该网卡,然后,像上文的cpu,内存状态获取的方法一样,去找文件就行了。
然而这个网卡名似乎是存在内存中,至少我没有看到docker将这个网卡名存到别的地方。
代码中:
nw, err := daemon.netController.NetworkByID(c.NetworkSettings.NetworkID)
可以看出获取了网卡(network),尔后有一个Statistics()方法,我们继续跟踪会发现,docker调用了一个组件包:
github.com/docker/libcontainer
其中有statistics方法,并执行了一个cat的命令(该组件包的/sandbox/interface_linux.go 第174行):
data, err := exec.Command("cat", netStatsFile).Output()

是的,又是读文件。
这次读的文件在:/sys/class/net/目录下,
我们进入该目录可以看到有许多子目录,其中就包括了docker启动容器时生成的网卡。

个人猜测:docker在内存中保存了容器到网卡的映射关系。通过这个映射关系可以找到网卡的统计数据(数据由内核生成,记录在机器的文件中)
我们可以看到如下的数据:

6a9be8e8f74a48cebcce9d746d83c6aa3c84d3a1

将这些数据统一起来,就是docker stats 这条命令干的事。

kubernetes如何调用上述的监控功能

kubernetes的监控采用了cAdvisor组件。因为kubernetes中记录了容器的信息(但是没有记录容器-网卡的映射关系),所以运行在节点上的cAdvisor不需要通过docker stats去获取容器的cpu和内存使用数据。
而网络IO数据呢?

我们知道k8s部署运行一个容器是会先生成一个pause容器。
是的,网络IO都记录在pause容器中。这里大家可以在自己的k8s环境中验证。

所以只要我们获取某容器对应的pause容器的containerID,我们就可以用如上的方式去抓到网络IO。

因为cAdvisor并不是为k8s专门设计的,不会特地在获取网络IO时去遍历查找容器对应的pause容器。所以当前的cAdvisor没有办法获取容器的网络IO。

所以如果在使用k8s集群,想要加入网络IO监控功能的时候,可以从k8s自带的cAdvisor入手。

cAdvisor

上面讲到cAdvisor,那么cAdvisor是如何获取网络的IO的呢?
首先,在k8s(1.0.6版本)的/pkg/kubelet/cadvisor/cadvisor_linux.go中51行,New(port int)方法,这是kubelet调用cAdvisor的入口。实例化一个cAdvisorClient,并执行他的Start()方法,我们可以进入cAdvisor项目中找到该方法(github.com/google/cadvisor/manager/manager.go 195行)。

我们看到有一句

err := docker.Register(self, self.fsInfo)

先mark之。继续看:

glog.Infof("Starting recovery of all containers")
err = self.detectSubcontainers("/")

这里程序程序检查所有在运行的容器,同内存中记录的容器作比较,有新的容器就新建一个相关的处理机制:一个Container的结构(有减少的,就删除),然后执行cont.Start() (github.com/google/cadvisor/manager/manager.go 766行)

持续追踪我们就可以知道每秒它收集一次监控信息,收集方法即

stats, statsErr := c.handler.GetStats()

(/cadvisor/manager/container.go 401行)
handler是一个接口。我们得知道当时是如何给他赋值的。这里不赘述了,我们直接找关键的方法:

func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error)

(cadvisor/container/docker/handler.go 第305行)

它又调用了:

func GetStats(cgroupManager cgroups.Manager, networkInterfaces []string) (*info.ContainerStats, error)

(cadvisor/container/libcontainer/helpers.go 第77行)

这里就是真正获取监控信息的地方,它引入了libcontainer包中的同名方法,

最终是导入了"github.com/docker/libcontainer/cgroups"这个第三方包中的方法。是不是很熟悉?对就是上文中提到的docker stats获取网络IO用到的包。我们到这个包里找到函数:
(/libcontainer/cgroups/fs/apply_raw.go 第148行)

func (m *Manager) GetStats() (*cgroups.Stats, error) {
 m.mu.Lock()
 defer m.mu.Unlock()
 stats := cgroups.NewStats()
 for name, path := range m.Paths {
 //m.Paths中包括了cpu,memory,network等字段,根据这些字段找到相应的目录
 sys, ok := subsystems[name]
 if !ok || !cgroups.PathExists(path) {
 continue
 }
 //读目录里的文件获取监控信息 if err := sys.GetStats(path, stats); err != nil {
 return nil, err
 }
 }

 return stats, nil
}

我们可以看到libcontainer/cgroups/fs目录下有cpu.go,memory.go等文件,每一个文件中都有一个集成了GetStats()的结构,我们可以获取到任何数据。

我们再回到cadvisor项目中cadvisor/container/libcontainer/helpers.go 第77行,

往下看:

// TODO(rjnagal): Use networking stats directly from libcontainer.
 stats.Network.Interfaces = make([]info.InterfaceStats, len(networkInterfaces))
 for i := range networkInterfaces {
 interfaceStats, err := sysinfo.GetNetworkStats(networkInterfaces[i])
 if err != nil {
 return stats, err
 }
 stats.Network.Interfaces[i] = interfaceStats
 }

这里官方还用了别的手段再去找network的数据,虽然不知道是处于什么理由,也不管这里是去找哪个系统文件看状态,但是这样依然是拿不到数据的。因为根本没有找到容器对应的网卡。这里找网卡的方法在cadvisor/container/docker/handler.go 第311行:

var networkInterfaces []string if len(config.Networks) > 0 {
 // ContainerStats only reports stat for one network device. // TODO(vmarmol): Handle multiple physical network devices. for _, n := range config.Networks {
 // Take the first non-loopback. if n.Type != "loopback" {
 networkInterfaces = []string{n.HostInterfaceName}
 break
 }
 }
 }
 stats, err := containerLibcontainer.GetStats(self.cgroupManager, networkInterfaces)

很显然这里是要改进的(在k8s的环境下)。

注:随着版本更新,新版本的cAdvisor采用github.com/opencontainers包。详见:github.com/opencontainers/runc/libcontainer/container_linux.go line 151:
另外,cadvisor获取网络监控数据的正确途径应该是:
监控pause容器,在其Pid对应的/proc/$pid/net/dev文件中即可得到容器内部网络监控数据

应用

说了这么多,其实给大家在实现k8s容器网络IO监控这个需求时提供一点想法:
1.从cadvisor入手,找到对应pause容器的containerID,那么就可以用libcontainer包去获取到数据(也即用docker stats的方式)。
2.从docker入手,创建容器时记录网卡信息,能否提供一个接口,根据容器名找到对应网卡名,那么cadvisor只要通过这个docker API得到网卡名,就可以自己读文件获取网络IO。

本文转自中文社区-浅谈k8s+docker 资源监控

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
23天前
|
运维 Kubernetes 监控
揭秘高效运维:如何用kubectl top命令实时监控K8s资源使用情况?
揭秘高效运维:如何用kubectl top命令实时监控K8s资源使用情况?
28 0
|
1月前
|
运维 Kubernetes 监控
构建高效自动化运维体系:基于Docker和Kubernetes的实践指南
【2月更文挑战第30天】 在当今快速发展的云计算时代,传统的IT运维模式已难以满足业务的敏捷性和稳定性需求。本文深入探讨了如何通过Docker容器化技术和Kubernetes集群管理工具构建一个高效、可靠的自动化运维体系。文章首先概述了容器化技术和微服务架构的基本概念,随后详细阐述了基于Docker的应用打包、部署流程,以及Kubernetes在自动化部署、扩展和管理容器化应用中的关键作用。最后,文中通过案例分析,展示了如何在实际场景中利用这些技术优化运维流程,提高系统的整体效率和可靠性。
|
1月前
|
存储 Kubernetes 负载均衡
Kubernetes的“厨房”:架构是菜谱,组件是厨具,资源对象是食材(下)
本文深入探讨了Kubernetes(K8s)的架构、核心组件以及资源对象。Kubernetes作为一个开源的容器编排系统,通过其独特的架构设计和丰富的组件,实现了对容器化应用程序的高效管理和扩展。通过本文的介绍,读者可以深入了解Kubernetes的架构、核心组件以及资源对象,从而更好地应用和管理容器化应用程序。Kubernetes的灵活性和可扩展性使得它成为容器编排领域的领先者,为企业提供了强大的容器运行环境。
|
1月前
|
开发者 Docker Python
深入浅出:使用Docker容器化部署Python Web应用
在当今快速发展的软件开发领域,Docker作为一个开放平台,为开发者提供了将应用打包在轻量级、可移植的容器中的能力,从而简化了部署和管理应用程序的复杂性。本文将通过一个简单的Python Web应用示例,引导读者理解Docker的基本概念、容器化的优势以及如何使用Docker来容器化部署Python Web应用。我们将从零开始,逐步探索创建Dockerfile、构建镜像、运行容器等关键步骤,旨在为读者提供一个清晰、易于理解的指南,帮助他们掌握使用Docker容器化部署应用的技能。
|
1月前
|
Prometheus 监控 Kubernetes
Kubernetes 集群监控与日志管理实践
【2月更文挑战第29天】 在微服务架构日益普及的当下,Kubernetes 已成为容器编排的事实标准。然而,随着集群规模的扩大和业务复杂度的提升,有效的监控和日志管理变得至关重要。本文将探讨构建高效 Kubernetes 集群监控系统的策略,以及实施日志聚合和分析的最佳实践。通过引入如 Prometheus 和 Fluentd 等开源工具,我们旨在为运维专家提供一套完整的解决方案,以保障系统的稳定性和可靠性。
|
1天前
|
存储 运维 Kubernetes
Kubernetes 集群的监控与维护策略
【4月更文挑战第23天】 在微服务架构日益盛行的当下,容器编排工具如 Kubernetes 成为了运维工作的重要环节。然而,随着集群规模的增长和复杂性的提升,如何确保 Kubernetes 集群的高效稳定运行成为了一大挑战。本文将深入探讨 Kubernetes 集群的监控要点、常见问题及解决方案,并提出一系列切实可行的维护策略,旨在帮助运维人员有效管理和维护 Kubernetes 环境,保障服务的持续可用性和性能优化。
|
6天前
|
存储 运维 监控
构建高效稳定的Docker容器监控体系
【4月更文挑战第18天】 在现代微服务架构中,Docker容器已成为部署和运行应用的标准环境。随之而来的挑战是如何有效监控这些容器的性能与健康状况,确保系统的稳定性和可靠性。本文将探讨构建一个高效稳定的Docker容器监控体系的关键技术和方法,包括日志管理、性能指标收集以及异常检测机制,旨在为运维人员提供实用的指导和建议。
11 0
|
23天前
|
消息中间件 Kubernetes Kafka
Terraform阿里云创建资源1分钟创建集群一键发布应用Terraform 创建 Kubernetes 集群
Terraform阿里云创建资源1分钟创建集群一键发布应用Terraform 创建 Kubernetes 集群
16 0
|
25天前
|
运维 Kubernetes 持续交付
构建高效自动化运维体系:基于Docker和Kubernetes的最佳实践
在现代云计算环境中,自动化运维成为保障系统稳定性与提升效率的关键。本文深入探讨了如何利用Docker容器化技术和Kubernetes容器编排工具构建一个高效、可靠的自动化运维体系。文中不仅介绍了相关的技术原理,还结合具体案例分析了实施过程中的常见问题及解决方案,为读者提供了一套行之有效的最佳实践指南。
|
27天前
|
运维 监控 云计算
构建高效稳定的Docker容器监控体系
随着微服务架构的普及,Docker容器作为其核心承载单元,在系统运维中扮演着日益重要的角色。本文旨在探讨如何构建一个高效且稳定的Docker容器监控体系,以确保容器运行的可靠性和系统的高可用性。文章首先分析了容器监控的必要性,接着详细介绍了监控体系的设计理念和组件选择,最后提供了实施过程中的关键步骤与最佳实践。通过本文,读者将掌握构建和维护Docker容器监控体系的有效方法。