Docker容器监控原理
我们知道 Docker 是基于 Namespace、Cgroups 和联合文件系统实现的。其中 Cgroups 不仅可以用于容器资源的限制,还可以提供容器的资源使用率。无论何种监控方案的实现,底层数据都来源于 Cgroups。
Cgroups 的工作目录为/sys/fs/cgroup,/sys/fs/cgroup目录下包含了 Cgroups 的所有内容。Cgroups包含很多子系统,可以用来对不同的资源进行限制。例如对CPU、内存、PID、磁盘 IO等资源进行限制和监控。
为了更详细的了解 Cgroups 的子系统,我们通过 ls -l 命令查看/sys/fs/cgroup文件夹,可以看到很多目录:
$ sudo ls -l /sys/fs/cgroup/ total 0 dr-xr-xr-x 5 root root 0 Jul 9 19:32 blkio lrwxrwxrwx 1 root root 11 Jul 9 19:32 cpu -> cpu,cpuacct dr-xr-xr-x 5 root root 0 Jul 9 19:32 cpu,cpuacct lrwxrwxrwx 1 root root 11 Jul 9 19:32 cpuacct -> cpu,cpuacct dr-xr-xr-x 3 root root 0 Jul 9 19:32 cpuset dr-xr-xr-x 5 root root 0 Jul 9 19:32 devices dr-xr-xr-x 3 root root 0 Jul 9 19:32 freezer dr-xr-xr-x 3 root root 0 Jul 9 19:32 hugetlb dr-xr-xr-x 5 root root 0 Jul 9 19:32 memory lrwxrwxrwx 1 root root 16 Jul 9 19:32 net_cls -> net_cls,net_prio dr-xr-xr-x 3 root root 0 Jul 9 19:32 net_cls,net_prio lrwxrwxrwx 1 root root 16 Jul 9 19:32 net_prio -> net_cls,net_prio dr-xr-xr-x 3 root root 0 Jul 9 19:32 perf_event dr-xr-xr-x 5 root root 0 Jul 9 19:32 pids dr-xr-xr-x 5 root root 0 Jul 9 19:32 systemd
这些目录代表了 Cgroups 的子系统,Docker 会在每一个 Cgroups 子系统下创建 docker 文件夹。这里如果你对 Cgroups 子系统不了解的话,不要着急,后续我会在第 10 课时对 Cgroups 子系统做详细讲解,这里你只需要明白容器监控数据来源于 Cgroups 即可。
监控系统是如何获取容器的内存限制的?
下面我们以 memory 子系统(memory 子系统是Cgroups 众多子系统的一个,主要用来限制内存使用,Cgroups 会在第十课时详细讲解)为例,讲解一下监控组件是如何获取到容器的资源限制和使用状态的(即容器的内存限制)。
我们首先在主机上使用以下命令启动一个资源限制为 1 核 2G 的 nginx 容器:
$ docker run --name=nginx --cpus=1 -m=2g --name=nginx -d nginx
## 这里输出的是容器 ID
51041a74070e9260e82876974762b8c61c5ed0a51832d74fba6711175f89ede1
注意:如果你已经创建过名称为 nginx 的容器,请先使用 docker rm -f nginx 命令删除已经存在的 nginx 容器。
容器启动后,我们通过命令行的输出可以得到容器的 ID,同时 Docker 会在/sys/fs/cgroup/memory/docker
目录下以容器 ID 为名称创建对应的文件夹。
下面我们查看一下/sys/fs/cgroup/memory/docker
目录下的文件:
$ sudo ls -l /sys/fs/cgroup/memory/docker total 0 drwxr-xr-x 2 root root 0 Sep 2 15:12 51041a74070e9260e82876974762b8c61c5ed0a51832d74fba6711175f89ede1 -rw-r--r-- 1 root root 0 Sep 2 14:57 cgroup.clone_children --w--w--w- 1 root root 0 Sep 2 14:57 cgroup.event_control -rw-r--r-- 1 root root 0 Sep 2 14:57 cgroup.procs -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.failcnt --w------- 1 root root 0 Sep 2 14:57 memory.force_empty -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.failcnt -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.max_usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.slabinfo -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.tcp.failcnt -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.tcp.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.tcp.max_usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.tcp.usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 14:57 memory.kmem.usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.max_usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.memsw.failcnt -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.memsw.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.memsw.max_usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 14:57 memory.memsw.usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.move_charge_at_immigrate -r--r--r-- 1 root root 0 Sep 2 14:57 memory.numa_stat -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.oom_control ---------- 1 root root 0 Sep 2 14:57 memory.pressure_level -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.soft_limit_in_bytes -r--r--r-- 1 root root 0 Sep 2 14:57 memory.stat -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.swappiness -r--r--r-- 1 root root 0 Sep 2 14:57 memory.usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 14:57 memory.use_hierarchy -rw-r--r-- 1 root root 0 Sep 2 14:57 notify_on_release -rw-r--r-- 1 root root 0 Sep 2 14:57 tasks
可以看到 Docker 已经创建了以容器 ID 为名称的目录,我们再使用 ls 命令查看一下该目录的内容:
$ sudo ls -l /sys/fs/cgroup/memory/docker/51041a74070e9260e82876974762b8c61c5ed0a51832d74fba6711175f89ede1 total 0 -rw-r--r-- 1 root root 0 Sep 2 15:21 cgroup.clone_children --w--w--w- 1 root root 0 Sep 2 15:13 cgroup.event_control -rw-r--r-- 1 root root 0 Sep 2 15:12 cgroup.procs -rw-r--r-- 1 root root 0 Sep 2 15:12 memory.failcnt --w------- 1 root root 0 Sep 2 15:21 memory.force_empty -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.failcnt -rw-r--r-- 1 root root 0 Sep 2 15:12 memory.kmem.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.max_usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.slabinfo -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.tcp.failcnt -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.tcp.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.tcp.max_usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.tcp.usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 15:21 memory.kmem.usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:12 memory.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:12 memory.max_usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.memsw.failcnt -rw-r--r-- 1 root root 0 Sep 2 15:12 memory.memsw.limit_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.memsw.max_usage_in_bytes -r--r--r-- 1 root root 0 Sep 2 15:21 memory.memsw.usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.move_charge_at_immigrate -r--r--r-- 1 root root 0 Sep 2 15:21 memory.numa_stat -rw-r--r-- 1 root root 0 Sep 2 15:13 memory.oom_control ---------- 1 root root 0 Sep 2 15:21 memory.pressure_level -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.soft_limit_in_bytes -r--r--r-- 1 root root 0 Sep 2 15:21 memory.stat -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.swappiness -r--r--r-- 1 root root 0 Sep 2 15:12 memory.usage_in_bytes -rw-r--r-- 1 root root 0 Sep 2 15:21 memory.use_hierarchy -rw-r--r-- 1 root root 0 Sep 2 15:21 notify_on_release -rw-r--r-- 1 root root 0 Sep 2 15:21 tasks
由上可以看到,容器 ID 的目录下有很多文件,其中 memory.limit_in_bytes 文件代表该容器内存限制大小,单位为 byte,我们使用 cat 命令(cat 命令可以查看文件内容)查看一下文件内容:
$ sudo cat /sys/fs/cgroup/memory/docker/51041a74070e9260e82876974762b8c61c5ed0a51832d74fba6711175f89ede1/memory.limit_in_bytes
2147483648
这里可以看到memory.limit_in_bytes 的值为2147483648,转换单位后正好为 2G,符合我们启动容器时的内存限制 2G。
通过 memory 子系统的例子,我们可以知道监控组件通过读取 memory.limit_in_bytes 文件即可获取到容器内存的限制值。了解完容器的内存限制我们来了解一下容器的内存使用情况。
监控系统是如何获取容器的内存使用状态的?
内存使用情况存放在 memory.usage_in_bytes 文件里,同样我们也使用 cat 命令查看一下文件内容:
$ sudo /sys/fs/cgroup/memory/docker/51041a74070e9260e82876974762b8c61c5ed0a51832d74fba6711175f89ede1/memory.usage_in_bytes
4259840
可以看到当前内存的使用大小为 4259840 byte,约为 4 M。了解了内存的监控,下面我们来了解下网络的监控数据来源。
监控系统是如何获取网络的监控数据的?
网络的监控数据来源是从 /proc/{PID}/net/dev 目录下读取的,其中 PID 为容器在主机上的进程 ID。下面我们首先使用 docker inspect 命令查看一下上面启动的 nginx 容器的 PID,命令如下:
$ docker inspect nginx |grep Pid "Pid": 27348, "PidMode": "", "PidsLimit": 0,
可以看到容器的 PID 为 27348,使用 cat 命令查看一下 /proc/27348/net/dev 的内容:
$ sudo cat /proc/27348/net/dev Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 eth0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/proc/27348/net/dev 文件记录了该容器里每一个网卡的流量接收和发送情况,以及错误数、丢包数等信息。可见容器的网络监控数据都是定时从这里读取并展示的。
总结一下,容器的监控原理其实就是定时读取 Linux 主机上相关的文件并展示给用户。
本文源自:拉勾教育课程:由浅入深吃透 Docker,讲师:郭少 前 360 高级容器技术专家