获取 Docker container 中的资源使用情况(转)
我在使用docker时也发现了这个问题,看来在docker内执行top/free命令的确有问题
原文:https://zhuanlan.zhihu.com/p/35914450
作者:两片
Docker 是一种对程序环境进行封装并实现资源的隔离技术,可秒级启动,可瞬间启动千千万万个实例,这些优点让它在出现之初就受到极大的关注,其火爆程度就如今天的深度学习一般。人们拿到了锤子就到处找钉子,什么都想套上 Docker,连生产数据库都想用 Docker 来运行,少不了碰到很多钉子,磕磕碰碰才形成今天这个比较合理的各个使用场景。
在一个完善的系统中,我们总需要知道自己的服务使用了多少CPU,内存资源,想知道磁盘读写多少,网络流量如何。如果在 Docker 容器中执行 top,free 等命令会发现我们能看到CPU所有核的使用情况以及宿主机的内存占用,这并不是我们需要的,我们需要的是这个容器被限制了多少 CPU,内存,当前容器内的进程使用了多少。
要明白为何 top,free 显示的是宿主机的情况,以及如何获得容器的资源限制需要先理解 Docker 的两项基础技术:Namespace 和 cgroup。两者在 CoolShell 的博客里都已经讲解得非常清楚,这里只简单说明一下。Namespace 解决了环境隔离的问题,它将进程的PID,Network,IPC等和其它进程隔离开来,结合 chroot,让进程仿佛运行在一个独占的操作系统中。cgroup 则对进程能够使用的资源作限制,如在一台48核256G的机器上只让容器使用2核2G。
容器中CPU,内存,磁盘资源都是被 cgroup 限制和统计的,所有信息都放在 /sys/fs/cgroup
这个虚拟文件夹里,在容器里运行 mount
命令可以看到这些挂载记录
...
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (ro,nosuid,nodev,noexec,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
...
CPU
获取CPU信息需要用到 cpuset, cpu, cpuacct
- cpuset 提供一个机制指定 cgroup 可以使用哪些CPU核哪些内存节点(an on-line node that contains memory)
- cpu 设置CPU的使用配额/份额
- cpuacct 提供 cgroup CPU 使用时长的统计信息
把上面三个文件夹的文件都看了一遍后,会发现 cgroup 里的提供的信息比物理机上的资源使用状况信息少很多,这其实是非常合理的,因为两者本质上就是不同的东西。比如物理机只有10%的时间在运行任务,那剩下90%时间物理机是真正称得上空闲,而 cgroup 限制组内进程最多只能用50%的CPU,而组内进程只用了10%的CPU,但不能说还有40%或者80%是空闲的,在 cgroup 内根本没有空闲这个概念,因此也就没法从中得到常见的 idle 指标。
另外,cgroup 内没有统计 iowait,中断,这些也没法拿到。不过没关系,细化的指标可以在进程里找,系统信息足够反映负载状况即可。这里介绍一下我比较关心的3个指标的获取方式。
LimitedCores: cgroup 限制可以使用的 CPU 核数
cgroup 有三种方式限制 CPU 的使用
- share,对应文件
cpu/cpu.shares
,是系统内多个 cgroup 的进程同时运行时他们的CPU使用上限占比,比如只有两个cgroup: [cgroup1.shares: 1024, cgroup2.shares: 512],那 cgroup1 可以用 2/3 的 CPU。 - quota,对应文件
cpu/cpu.cfs_quota_us
cpu/cpu.cfs_period_us
,表示在每个 period(时间间隔)内 cgroup 可以使用的 CPU 时间。文件里数字的单位是微秒,假设 {quota: 200000, period: 100000},意思是每 100ms 可以使用 200ms CPU 时间,相当于可以使用两个核。quota 为 -1 的时候表示无限制。 - cpuset,对应文件
cpuset/cpuset.cpus
, 表示 cgroup 可以用那几个 CPU,比如0,3-7,12
可以使用 7 个核。
对于 share,我们没法知道有多少邻居以及邻居的值为多少,所以忽略这个限制。考虑到 cpuset 在生产环境极少使用,同时用 quota 和 cpuset 的就更少了,所以我们的策略是:先看 quota,如果 quota 有限制则返回,否则再看 cpuset。
Usage:cgroup 的 CPU 占用率,占了物理机的多少CPU
上面我们拿到了能用多少个核,自然知道 cgroup 的占用上限,只要知道 cgroup 用了物理机的多少 CPU 就可以知道饱和程度了。要获取这个首先需要知道一段时间内物理机用了多少CPU时间,然后获得 cgroup 用了多少CPU时间,最后相除。
- 物理机使用的CPU时间从 /proc/stat 里获取,将里面 cpu 那一行的数字相加并且乘以物理机CPU核数即可得到从开机到现在用的CPU总时间。可以设置一秒的时间间隔,求差即可得到这一秒内用的CPU时间。
- cgroup 使用的CPU时间可以从
cpuacct/cpuacct.usage
中获得,也是求一段时间的差即可。
特别需要注意的是 /proc/stat 里单位是纳秒,而 cpuacct.usage 里的是 Clock Tick,一般是 100纳秒/tick,准确数字可以通过 getconf CLK_TCK
命令获得。
Throttled:cgroup 的 CPU 使用被限制的次数
如果被频繁限制的话,说明很可能分配的CPU不够用了。可以从 cpu/cpu.stat
的字段获得。
内存
cgroup 对内存的使用做了比较多的统计,但是内存使用率本身就是一个糊涂账,因为很多内存是为了提高性能从磁盘映射过来的需要时可以清理掉。cgroup 里的内存信息和定义推荐看 Kernal Doc cgroup-v1 memory
需要额外提一下的是 cache 和 mapped_file,cache 是 page cache memory 也成为 disk cache 是磁盘映射到内存的内存,而 mapped_file 也是一样意思,但实际中常常会发现 cache 比 mapped_file 大很多,这其实是 mapped_file 是还被进程引用着的,而 cache 则包括曾经被进程用过但现在已经没有任何进程使用的映射,也就是 unmapped_file。
对于内存,可以重点关注以下几个指标(没有特别注明,所有指标都从 memory/memory.stat
中取:
- Total: cgroup 被限制可以使用多少内存,可以从文件里的 hierarchical_memory_limit 获得,但不是所有 cgroup 都限制内存,没有限制的话会获得 2^64-1 这样的值,我们还需要从
/proc/meminfo
中获得MemTotal
,取两者最小。 - RSS: Resident Set Size 实际物理内存使用量,在
memory/memory.stat
的 rss 只是 anonymous and swap cache memory,文档里也说了如果要获得真正的 RSS 还需要加上 mapped_file。 - Cached:
memory/memory.stat
中的 cache - MappedFile:
memory/memory.stat
中的 mapped_file - SwapTotal: 限制的 swap 大小,(hierarchical_memsw_limit - hierarchical_memory_limit) 同样会遇到内存没有限制的情况。
- SwapUsed:
memory/memory.stat
中的 total_swap
磁盘
cgroup 通过 blkio 子系统实现对磁盘读写控制。当前有两种限制磁盘的策略,CFQ(就是各个cgroup平分磁盘使用时间)和 Throttling。
如果是 CFQ,可从 blkio/blkio.io_service_bytes_recursive
获得各个磁盘的 IO 统计
如果是 Throttling,可从 blkio/blkio.throttle.io_service_bytes
获得各个磁盘的 IO 统计
文件里会将 IO 分为 Read/Write, Sync/Async,将所有磁盘的 Read/Write相加即可得到磁盘读写量。
网络
网络统计信息放在 /sys/class/net/<ethX>/statistics
中,在容器中只能看到自己用到的网络接口,但网络接口的名字常常不确定,可以先通过命令 ip -o -4 route show to default | awk '{print $5}'
获得默认网络接口的名字。
然后就可以从 rx_bytes 获得接收字节数,从 tx_bytes 获得发出字节数了。
至此,比较重要的信息都已经拿到,有时还需要提防一下宿主机超卖的情况,有时出了问题并不是自己容器用资源太多,而是资源都被同主机的其它主机占用了,这时可以从 /proc 里拿额外的信息来判断。
package
发现目前没有专门针对 container 的指标获取,于是写了个 Go 的,放在 github 上,地址:container-metrics