运行时指标
预计阅读时间:19分钟
Docker统计
您可以使用该docker stats
命令实时流式传输容器的运行时指标。该命令支持CPU,内存使用率,内存限制和网络IO指标。
以下是docker stats
命令的示例输出
$ docker stats redis1 redis2
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
redis1 0.07% 796 KB / 64 MB 1.21% 788 B / 648 B 3.568 MB / 512 KB
redis2 0.07% 2.746 MB / 64 MB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B
该泊坞窗统计参考页有关于更多的细节docker stats
命令。
对照组
Linux容器依赖于控制组 ,这些控制组不仅跟踪进程组,而且还公开有关CPU,内存和块I / O使用情况的指标。您可以访问这些指标并获取网络使用率指标。这与“纯” LXC容器以及Docker容器有关。
控制组通过伪文件系统公开。在最近的发行版中,您应该在下找到该文件系统/sys/fs/cgroup
。在该目录下,您可以看到多个子目录,分别称为devices,freezer,blkio等。每个子目录实际上对应于不同的cgroup层次结构。
在较旧的系统上,控制组可能安装在上/cgroup
,而没有明显的层次结构。在那种情况下,您没有看到子目录,而是看到该目录中的一堆文件,甚至可能看到一些与现有容器相对应的目录。
要弄清楚控制组的安装位置,可以运行:
$ grep cgroup /proc/mounts
列举cgroups
在v1和v2之间,cgroup的文件布局明显不同。
如果/sys/fs/cgroup/cgroup.controllers
系统上存在,则说明您使用的是v2,否则您使用的是v1。请参阅与您的cgroup版本相对应的小节。
笔记
截至2020年,Fedora是唯一默认使用cgroup v2的知名Linux发行版。从Fedora 31开始,Fedora默认使用cgroup v2。
cgroup v1
您可以查看/proc/cgroups
以了解系统已知的不同控制组子系统,它们所属的层次结构以及它们包含多少个组。
您也可以查看/proc/<pid>/cgroup
进程属于哪个控制组。控制组显示为相对于层次结构安装点的根的路径。/
表示该进程尚未分配给组,而/lxc/pumpkin
表示该进程是名为的容器的成员pumpkin
。
cgroup v2
在cgroup v2主机上,的内容/proc/cgroups
没有意义。请参阅/sys/fs/cgroup/cgroup.controllers
可用的控制器。
更改cgroup版本
更改cgroup版本需要重新引导整个系统。
在基于systemd的系统上,可以通过添加systemd.unified_cgroup_hierarchy=1
到内核cmdline来启用cgroup v2 。要将cgroup版本还原为v1,您需要进行设置systemd.unified_cgroup_hierarchy=0
。
如果grubby
您的系统上有命令可用(例如在Fedora上),则可以按以下方式修改cmdline:
$ sudo grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=1"
如果grubby
命令不可用,请在中编辑该GRUB_CMDLINE_LINUX
行/etc/default/grub
并运行sudo update-grub
。
在cgroup v2上运行Docker
自Docker 20.10起,Docker就支持cgroup v2。在cgroup v2上运行Docker还需要满足以下条件:
- 容器化:v1.4或更高版本
- runc:v1.0.0-rc91或更高版本
- 内核:v4.15或更高版本(建议使用v5.2或更高版本)
请注意,cgroup v2模式的行为与cgroup v1模式略有不同:
- 默认的cgroup驱动程序(
dockerd --exec-opt native.cgroupdriver
)在v2上为“ systemd”,在v1上为“ cgroupfs”。 - 默认的cgroup命名空间模式(
docker run --cgroupns
)在v2上为“专用”,在v1上为“主机”。 - 该
docker run
标志--oom-kill-disable
和--kernel-memory
被丢弃在V2。
查找给定容器的cgroup
对于每个容器,在每个层次结构中创建一个cgroup。在具有较旧版本的LXC用户界面工具的较旧系统上,cgroup的名称为容器的名称。随着LXC工具的最新版本,cgroup是lxc/<container_name>.
对于使用cgroup的Docker容器,容器名称是容器的完整ID或长ID。如果容器显示为ae836c95b4c3 docker ps
,则其长ID可能类似于
ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79
。您可以使用docker inspect
或查找它docker ps --no-trunc
。
将所有内容放在一起以查看Docker容器的内存指标,请查看以下路径:
/sys/fs/cgroup/memory/docker/<longid>/
在cgroup v1上,cgroupfs
驱动程序/sys/fs/cgroup/memory/system.slice/docker-<longid>.scope/
在cgroup v1上,systemd
驱动程序/sys/fs/cgroup/docker/<longid/>
在cgroup v2上,cgroupfs
驱动程序/sys/fs/cgroup/system.slice/docker-<longid>.scope/
在cgroup v2上,systemd
驱动程序
来自cgroup的指标:内存,CPU,块I / O
笔记
此部分尚未针对cgroup v2更新。有关cgroup v2的更多信息,请参阅内核文档。
对于每个子系统(内存,CPU和块I / O),存在一个或多个伪文件并包含统计信息。
内存指标: memory.stat
内存指标可在“内存” cgroup中找到。内存控制组会增加一点开销,因为它可以非常精细地计算主机上的内存使用情况。因此,许多发行版选择默认情况下不启用它。通常,要启用它,您要做的就是添加一些内核命令行参数:
cgroup_enable=memory swapaccount=1
。
指标位于伪文件中memory.stat
。看起来是这样的:
cache 11492564992
rss 1930993664
mapped_file 306728960
pgpgin 406632648
pgpgout 403355412
swap 0
pgfault 728281223
pgmajfault 1724
inactive_anon 46608384
active_anon 1884520448
inactive_file 7003344896
active_file 4489052160
unevictable 32768
hierarchical_memory_limit 9223372036854775807
hierarchical_memsw_limit 9223372036854775807
total_cache 11492564992
total_rss 1930993664
total_mapped_file 306728960
total_pgpgin 406632648
total_pgpgout 403355412
total_swap 0
total_pgfault 728281223
total_pgmajfault 1724
total_inactive_anon 46608384
total_active_anon 1884520448
total_inactive_file 7003344896
total_active_file 4489052160
total_unevictable 32768
前半部分(无total_
前缀)包含与cgroup中的进程相关的统计信息,不包括子cgroup。后半部分(带有total_
前缀)也包括子cgroup。
一些指标是“量表”,或者可以增加或减少的值。例如,
swap
是cgroup成员使用的交换空间量。其他一些是“计数器”或只能上升的值,因为它们表示特定事件的发生。例如,pgfault
表示自创建cgroup以来的页面错误数。
公制 | 描述 |
---|---|
快取 | 该控制组的进程使用的内存量,可以与块设备上的一个块精确关联。当您读取和写入磁盘上的文件时,此数量会增加。这样的话,如果你使用的“传统” I / O( ,,系统调用),以及映射文件(含)。尽管原因尚不清楚,但它也考虑了挂载使用的内存。open read write mmap tmpfs |
rss | 与磁盘上的任何内容都不对应的内存量:堆栈,堆和匿名内存映射。 |
映射文件 | 指示控制组中的进程映射的内存量。它不会为您提供有关已使用多少内存的信息。而是告诉您如何使用它。 |
pgfault,pgmajfault | 指示cgroup的进程分别触发“页面错误”和“主要错误”的次数。当进程访问其不存在或未受保护的虚拟内存空间的一部分时,将发生页面错误。如果进程有错误并尝试访问无效的地址(可能会发送一个SIGSEGV 信号,通常会用著名的地址将其杀死),则前者可能会发生Segmentation fault 信息)。当进程从已换出或对应于映射文件的内存区域读取数据时,可能会发生后者:在这种情况下,内核从磁盘加载页面,并让CPU完成内存访问。当进程将数据写入写时复制内存区域时,也会发生这种情况:同样,内核会抢占进程,复制内存页面,并在该进程自己的页面副本上恢复写操作。当内核实际需要从磁盘读取数据时,就会发生“重大”故障。当它仅复制一个现有页面或分配一个空页面时,这是一个常规(或“次要”)错误。 |
交换 | 此cgroup中的进程当前使用的交换量。 |
active_anon,inactive_anon | 内核已识别出的匿名内存量分别处于活动状态和非活动状态。“匿名”内存是未链接到磁盘页面的内存。换句话说,这相当于上述的rss计数器。实际上,rss计数器的定义是active_anon + inactive_anon - tmpfs(其中tmpfs是由tmpfs 该控制组挂载的文件系统)。现在,“活动”和“非活动”有什么区别?页面最初是“活动的”;并定期以内核间隔扫描内存,并将某些页面标记为“非活动”。每当再次访问它们时,它们都会立即被重新标记为“活动”。当内核几乎快用完内存并且需要换出到磁盘时,内核会交换“非活动”页面。 |
active_file,inactive_file | 高速缓存存储器,有活动和无活动状态,与上面的匿名内存类似。确切的公式是cache = active_file + inactive_file + tmpfs。内核用于在活动集和非活动集之间移动内存页面的确切规则与用于匿名内存的规则不同,但是一般原理是相同的。当内核需要回收内存时,从此池中回收干净(=未修改)的页面会比较便宜,因为它可以立即回收(而匿名页面和脏/修改后的页面需要首先写入磁盘)。 |
不可战胜的 | 无法回收的内存量;通常,它说明已被“锁定”的内存mlock 。加密框架通常使用它来确保秘密密钥和其他敏感材料永远不会交换到磁盘上。 |
memory_limit,memsw_limit | 这些并不是真正的指标,而是对应用于此cgroup的限制的提醒。第一个表示该控制组的进程可以使用的最大物理内存量;第二个表示RAM + swap的最大数量。 |
计算页面缓存中的内存非常复杂。如果不同控制组中的两个进程都读取相同的文件(最终依赖于磁盘上的相同块),则在控制组之间分配相应的内存费用。很好,但这也意味着当一个cgroup终止时,它可能会增加另一个cgroup的内存使用量,因为它们不再为这些内存页面分配成本了。
CPU指标: cpuacct.stat
既然我们已经介绍了内存指标,那么其他所有内容相比之下都是简单的。CPU指标位于
cpuacct
控制器中。
对于每个容器,一个伪文件cpuacct.stat
包含由容器的方法中,分解成累积的CPU使用率user
和
system
时间。区别是:
user
time是进程直接控制CPU,执行进程代码的时间。system
time是内核代表进程执行系统调用的时间。
那些时间以1/100秒的滴答声表示,也称为“用户抖动”。每秒有USER_HZ
“跳动次数”,在x86系统上
USER_HZ
是100。从历史上看,它精确地映射到每秒的调度程序“跳动次数”,但是更高的频率调度和
无跳动内核使跳动次数无关紧要。
块I / O指标
块I / O在blkio
控制器中计算。不同的指标分散在不同的文件中。虽然您可以在
内核文档的blkio-controller文件中找到详细的详细信息,但以下是最相关的简短列表:
公制 | 描述 |
---|---|
blkio.sectors | 包含cgroup的进程成员逐设备读取和写入的512字节扇区数。读写合并在一个计数器中。 |
blkio.io_service_bytes | 指示cgroup读取和写入的字节数。每个设备有4个计数器,因为对于每个设备,它区分同步I / O与异步I / O,以及读写I / O。 |
blkio.io_serviced | 不论其大小如何,执行的I / O操作数。每个设备还具有4个计数器。 |
blkio.io_queued | 指示当前为此cgroup排队的I / O操作数。换句话说,如果cgroup不执行任何I / O,则该值为零。相反的说法是不正确的。换句话说,如果没有I / O排队,则并不意味着cgroup处于空闲状态(I / O方式)。它可以在其他静态设备上进行纯同步读取,因此可以立即处理它们而无需排队。同样,虽然找出哪个cgroup给I / O子系统带来了压力是有帮助的,但请记住,这是一个相对数量。即使进程组不执行更多的I / O,其队列大小也会增加,这仅仅是因为其他设备会增加设备负载。 |
网络指标
控制组未直接公开网络指标。对此有一个很好的解释:网络接口存在于网络名称空间的上下文中。内核可能会累积有关一组进程发送和接收的数据包和字节的度量,但是这些度量不是很有用。您需要每个接口的指标(因为本地lo
接口上发生的流量实际上并不重要)。但是由于单个cgroup中的进程可以属于多个网络名称空间,所以这些度量标准将更难解释:多个网络名称空间意味着多个lo
接口,可能是多个eth0
接口,等等。因此,这就是为什么没有简单的方法可以与控制组收集网络指标的原因。
相反,我们可以从其他来源收集网络指标:
IP表
IPtables(或者说iptables只是一个接口的netfilter框架)可以做一些认真的工作。
例如,您可以设置一个规则来说明Web服务器上的出站HTTP流量:
$ iptables -I OUTPUT -p tcp --sport 80
没有-j
或-g
标志,因此该规则仅对匹配的数据包进行计数,然后转到以下规则。
以后,您可以使用以下方法检查计数器的值:
$ iptables -nxvL OUTPUT
从技术上讲,-n
这不是必需的,但它可以防止iptables执行DNS反向查找,这在这种情况下可能没有用。
计数器包括数据包和字节。如果要像这样设置容器流量的度量标准,则可以执行for
循环以iptables
在FORWARD
链中为每个容器IP地址添加两个规则(每个方向一个)。这仅对通过NAT层的流量进行计量。您还需要添加通过Userland代理的流量。
然后,您需要定期检查这些计数器。如果您碰巧使用collectd
,则有一个不错的插件
可以自动执行iptables计数器集合。
接口级计数器
由于每个容器都有一个虚拟以太网接口,因此您可能需要直接检查此接口的TX和RX计数器。每个容器都与主机中的虚拟以太网接口相关联,名称类似于vethKk8Zqi
。不幸的是,找出哪个接口对应于哪个容器是困难的。
但是目前,最好的方法是从容器中检查指标。为此,可以使用ip-netns magic在容器的网络名称空间中的主机环境中运行可执行文件。
该ip-netns exec
命令允许您在当前进程可见的任何网络名称空间内执行任何程序(存在于主机系统中)。这意味着您的主机可以输入容器的网络名称空间,但是您的容器不能访问主机或其他对等容器。不过,容器可以与其子容器进行交互。
该命令的确切格式为:
$ ip netns exec <nsname> <command...>
例如:
$ ip netns exec mycontainer netstat -i
ip netns
通过使用名称空间伪文件查找“ mycontainer”容器。每个进程都属于一个网络名称空间,一个PID名称空间,一个mnt
名称空间等,这些名称空间在下实现
/proc/<pid>/ns/
。例如,PID 42的网络名称空间由伪文件实现
/proc/42/ns/net
。
运行时ip netns exec mycontainer ...
,它/var/run/netns/mycontainer
应该是这些伪文件之一。(接受符号链接。)
换句话说,要在容器的网络名称空间内执行命令,我们需要:
- 找出我们要调查的容器中任何进程的PID;
- 从创建一个符号链接
/var/run/netns/<somename>
到/proc/<thepid>/ns/net
- 执行
ip netns exec <somename> ....
查看枚举Cgroup,以了解如何找到您要测量其网络使用率的容器内进程的cgroup。在此处,您可以检查名为的伪文件
tasks
,其中包含cgroup(因此包含在容器中)的所有PID。选择任何一个PID。
将所有内容放在一起,如果容器的“短ID”保存在环境变量中$CID
,则可以执行以下操作:
$ TASKS=/sys/fs/cgroup/devices/docker/$CID*/tasks
$ PID=$(head -n 1 $TASKS)
$ mkdir -p /var/run/netns
$ ln -sf /proc/$PID/ns/net /var/run/netns/$CID
$ ip netns exec $CID netstat -i
高性能指标收集的提示
每次您想要更新指标时都要运行一个新流程(相对)很昂贵。如果要以高分辨率和/或通过大量容器(在单个主机上考虑1000个容器)来收集指标,则不想每次都派生一个新进程。
这是从单个过程中收集指标的方法。您需要用C(或任何可以进行低级系统调用的语言)编写指标收集器。您需要使用特殊的系统调用,setns()
该调用
可让当前进程输入任意的命名空间。但是,它需要为命名空间伪文件提供一个打开的文件描述符(请记住:这是中的伪文件
/proc/<pid>/ns/net
)。
但是,有一个陷阱:您一定不能保持此文件描述符为打开状态。如果这样做,则在控制组的最后一个进程退出时,名称空间不会被破坏,并且其网络资源(如容器的虚拟接口)将永远存在(或直到您关闭该文件描述符)。
正确的方法是跟踪每个容器的第一个PID,并每次都重新打开名称空间伪文件。
容器退出时收集指标
有时,您并不关心实时度量标准收集,但是当容器退出时,您想知道它已使用了多少CPU,内存等。
Docker使得这一点变得困难,因为它依赖于lxc-start
,它会自己仔细清理。通常更容易定期收集度量标准,这就是collectd
LXC插件的工作方式。
但是,如果您仍然想在容器停止时收集统计信息,请按以下步骤操作:
对于每个容器,开始收集过程,然后通过将其PID写入cgroup的任务文件中,将其移至要监视的控制组。收集过程应定期重新读取任务文件,以检查它是否是控制组的最后一个过程。(如果您还希望按照上一节中的说明收集网络统计信息,则还应该将过程移至适当的网络名称空间。)
容器退出后,lxc-start
尝试删除控制组。由于控制组仍在使用中,因此失败。没关系 现在,您的进程应检测到它是该组中唯一剩余的进程。现在是收集所需所有指标的最佳时机!
最后,您的进程应将自身移回根控制组,并删除容器控制组。要删除控制组,只需删除
rmdir
其目录。它与rmdir
目录不符,
因为它仍然包含文件。但是请记住,这是一个伪文件系统,因此通常的规则不适用。清理完成后,收集过程可以安全退出。