使用 Telegraf 收集运行进程计数

导航至

Photo of the output of ps aux in macOS Terminal

为您的堆栈添加监控是快速了解您的应用程序的最快方法之一,让您可以更快地发现问题,并开始就工程投入方向做出数据驱动的决策。最直接的监控事项之一是您自己的进程——如果没有进程在运行,Web 服务器很难为请求提供服务;另一方面,如果您运行的程序副本超出预期,可能会迅速耗尽可用资源。

在大多数 ‘nix 系统上,可以通过多种方式收集进程信息。根据您运行的特定操作系统,您或许可以查看 proc 文件系统,其中包含许多文件,其中包含有关运行进程和系统总体信息;或者您可以使用 ps 等工具,该工具将有关运行进程的信息输出到命令行。

在本示例中,我们将使用 Python 和 Ubuntu Linux,但许多概念将适用于其他语言或应用程序以及操作系统。

在 Linux 上获取有关进程的信息

获取有关系统更多信息的一个好地方是 proc 文件系统,根据 手册页,“它是一个伪文件系统,提供内核数据结构的接口。它通常挂载在 /proc。” 如果您访问 Linux 机器上的该目录,您可能会看到类似这样的内容(来自全新安装的 Ubuntu 16.04.3 的输出)

$ cd /proc
$ ls
1     1182  14   18   25   3    399  437  53   60   80   839        bus        driver       kallsyms     locks         partitions   sys            version_signature
10    12    141  19   26   30   4    47   54   61   81   840        cgroups    execdomains  kcore        mdstat        sched_debug  sysrq-trigger  vmallocinfo
1017  1200  15   2    266  31   413  48   544  62   817  890        cmdline    fb           keys         meminfo       schedstat    sysvipc        vmstat
1032  1230  152  20   267  320  414  480  55   66   818  9          consoles   filesystems  key-users    misc          scsi         thread-self    zoneinfo
1033  1231  16   21   277  321  420  49   56   671  820  919        cpuinfo    fs           kmsg         modules       self         timer_list
1095  1243  164  22   278  369  421  5    57   7    828  925        crypto     interrupts   kpagecgroup  mounts        slabinfo     timer_stats
11    126   165  23   28   373  423  50   58   701  831  acpi       devices    iomem        kpagecount   mtrr          softirqs     tty
1174  128   166  24   29   381  425  51   59   79   837  asound     diskstats  ioports      kpageflags   net           stat         uptime
1176  13    17   241  295  397  426  52   6    8    838  buddyinfo  dma        irq          loadavg      pagetypeinfo  swaps        version

那里有很多内容!您首先会注意到一系列编号的目录;这些目录对应于正在运行的进程,每个目录都以该进程的“进程 ID”或 PID 命名。您还会看到许多其他目录和文件,其中包含从内核参数和加载的模块到 CPU 信息、网络统计信息和系统正常运行时间等各种信息。在每个进程的目录中,您会找到几乎同样多的关于每个单独进程的信息——对于我们的用例来说太多了。毕竟,我们只想监控进程是否正在运行,或许还有多少副本正在运行。

当系统管理员登录服务器以验证进程是否正在运行时,他们不太可能首先转向 /proc。相反,他们可能会使用 ps 等工具,该工具也提供有关运行进程的信息。您可能会使用几个不同版本的 ps,但对于 Ubuntu 上的版本,您可以使用以下命令获取有关服务器上所有运行进程的信息

$ ps aux

我们将使用 Python 创建几个进程以进行测试。由于我们实际上不需要这些进程执行任何工作,因此我们将编写一个简单的程序,其中包含无限循环和对 Python 时间模块中 sleep 函数的调用,以避免使用不必要的 CPU 周期。通过在命令行输入以下命令,确保您已安装 Python

$ python --version
Python 2.7.12

由于我们的程序非常简单,因此它适用于 Python 2 或 Python 3。接下来,使用文本编辑器创建一个名为 loop.py 的文件,其中包含以下内容

#!/usr/bin/env python

import time

while True:
    time.sleep(5)

第一行告诉操作系统使用哪个解释器来执行脚本。如果此程序更复杂,或者使用了 Python 2 和 Python 3 之间不同的功能,我们希望指定我们正在使用的 Python 版本,而不是仅仅说 python

从文件所在的同一目录运行此命令,使脚本可执行

$ chmod 744 loop.py

然后运行该程序,在命令末尾附加 & 字符(它告诉 Linux 在后台运行进程),以便我们仍然可以访问 shell

$ ./loop.py &
[1] 1886

使用 & 字符运行命令后,正在运行的进程的 PID 将在输出中列出。如果您再次运行 ps aux,您现在应该在结果列表中看到 PID 为 1886 的 Python 进程。

在我使用的 Ubuntu 服务器上,此命令返回了 100 多个结果,手动搜索该列表效率太低。我们可以使用另一个命令 grep,以及 Linux 的一些内置功能来缩小结果范围。grep 命令的作用类似于搜索过滤器,我们可以使用 Linux “管道”字符 | 将来自 ps aux 命令输出的数据发送到 grep 命令的输入。让我们试用一下

$ ps aux | grep python
noah      1886  0.0  0.2  24512  6000 pts/0    S    20:14   0:00 python ./loop.py
noah      1960  0.0  0.0  14224  1084 pts/0    S+   20:56   0:00 grep --color=auto python

首先,我们获取有关所有运行进程的信息,然后我们将该数据“管道”到 grep 命令中,该命令正在搜索任何包含字符串 python 的行。果然,我们的 Python 进程 1886 出现在结果的第一行中。但是第二行呢?

当我们运行 ps 命令时,输出包括我们在启动每个进程时提供的参数;在本例中,添加了 --color=auto,因为 Ubuntu 有一个别名,当您键入 grep 时,它会运行 grep --color=auto,然后是 python 参数,这是我们正在搜索的字符串。因此,我们正在搜索“python”,这意味着对于 grep 进程,字符串“python”将包含在 ps 的输出中,因此 grep 将始终与自身匹配,因为它包含它正在搜索的字符串。

解决此问题的常用方法是搜索正则表达式“[p]ython”而不是字符串“python”。这将导致 grep 匹配以方括号内的任何字母开头的任何字符串,在我们的例子中,仅匹配“p”,后跟字母“ython”。当我们这样做时,grep 仍然匹配单词“python”,因为它以“p”开头并以“ython”结尾,但它不匹配自身,因为“[p]ython”与该模式不匹配。试一试

$ ps aux | grep [p]ython
noah      1886  0.0  0.2  24512  6000 pts/0    S    20:14   0:00 python ./loop.py

让我们启动另一个 Python 进程,看看会得到什么

$ ./loop.py &
[2] 1978
$ ps aux | grep [p]ython
noah      1886  0.0  0.2  24512  6000 pts/0    S    20:14   0:00 python ./loop.py
noah      1978  0.0  0.2  24512  6004 pts/0    S    21:13   0:00 python ./loop.py

两个 Python 进程,两个结果。如果我们想验证是否正在运行特定数量的进程,我们应该只需计算命令输出的行数;幸运的是,为 grep 提供 -c 参数正是这样做的

$ ps aux | grep -c [p]ython
2

让我们使用 fg 命令将两个 Python 脚本中最新的一个调到前台,然后使用 <Ctrl+C> 杀死它,并再次计算 Python 进程的数量

$ fg
./loop.py
^CTraceback (most recent call last):
  File "./loop.py", line 6, in 
    time.sleep(5)
KeyboardInterrupt
$ ps aux | grep -c [p]ython
1

完美!一个是我们要查找的数字。

还有另一个命令 pgrep,它也满足我们用例的所有要求,但它不如通常有用。它允许您搜索与字符串匹配的所有进程,并默认返回其 PID。它还接受 -c 参数,该参数输出匹配数量的计数

$ pgrep -c python
1

使用 Telegraf 收集进程计数

现在我们知道如何计算服务器上运行的进程数,我们需要开始定期收集该数据。Telegraf 为我们提供了一种执行与我们在 shell 中使用的相同命令的方法,以便以 exec 输入插件的形式收集数据。

exec 插件将在 Telegraf 的每个收集间隔期间运行一次,执行来自您的配置文件中的命令并收集其输出。输出可以是多种格式,包括任何受支持的输入格式,这意味着如果您已经有以 JSON 或其他受支持格式之一输出某种指标数据的脚本,则可以使用 exec 插件快速开始使用 Telegraf 收集这些指标。

如果您尚未安装 Telegraf,您可以参考此处的安装文档。按照 Ubuntu 的说明操作后,您应该找到位于 /etc/telegraf/telegraf.conf 的配置文件。

对于本示例,我们将把输出写入文件,因此我们希望编辑配置的 [[outputs.file]] 部分,如下所示

# # Send telegraf metrics to file(s)
[[outputs.file]]
  ## Files to write to, "stdout" is a specially handled file.
  files = ["/tmp/metrics.out"]
  ## Data format to output.
  data_format = "influx"

我们将通过重启 Telegraf 来应用这些更改,然后检查指标是否正在写入 /tmp/metrics.out。从包管理器安装 Telegraf 时,默认情况下会启用 system 输入插件,因此我们应该立即开始看到指标

$ sudo systemctl restart telegraf
$ tail -n 2 /tmp/metrics.out
diskio,name=dm-0,host=demo writes=7513i,read_bytes=422806528i,write_bytes=335978496i,write_time=23128i,io_time=9828i,iops_in_progress=0i,reads=9111i,read_time=23216i,weighted_io_time=46344i 1519701100000000000
diskio,name=dm-1,host=demo write_time=0i,io_time=108i,weighted_io_time=116i,read_time=116i,writes=0i,read_bytes=3342336i,write_bytes=0i,iops_in_progress=0i,reads=137i 1519701100000000000

遗憾的是,exec 插件不知道如何处理多个命令,就像我们在上面所做的那样,因此我们需要将它们放入一个简单的 bash 脚本中。首先,在您的主目录中创建一个名为 pyprocess_count 的文件,其中包含以下文本

#!/bin/sh

count=$(ps aux | grep -c [p]ython)

echo $count

除了允许我们使用 exec 插件执行管道命令外,此脚本还服务于次要目标——如果 grep -c 返回零结果,则它将以状态代码 1 退出,表示错误。这会导致 Telegraf 忽略命令的输出,并发出自己的错误。通过将命令的结果存储在 count 变量中,然后使用 echo 输出它,我们可以确保脚本以状态代码 0 退出。小心不要在文件名中包含“python”,否则当脚本运行时,grep 将与该字符串匹配。创建文件后,设置其权限,以便任何人都可以执行它并进行测试

$ chmod 755 pyprocess_count
$ ./pyprocess_count

然后将其移动到 /usr/local/bin

$ sudo mv pyprocess_count /usr/local/bin

接下来,我们需要配置 exec 输入插件以执行脚本。编辑 [[inputs.exec]] 文件,使其如下所示

# # Read metrics from one or more commands that can output to stdout
[[inputs.exec]]
  ## Commands array
  commands = [
    "/usr/bin/local/pyprocess_count"
  ]

  ## Timeout for each command to complete.
  timeout = "5s"

  name_override = "python_processes"

  ## Data format to consume.
  ## Each data format has its own unique set of configuration options, read
  ## more about them here:
  ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
  data_format = "value"

我们已将命令直接添加到命令数组,因此 Telegraf 将在每个收集间隔执行一次该命令。我们还将 data_format 设置为 "value",因为该命令将输出一个数字,并且我们使用 name_override 为指标命名。

再次重启 Telegraf,然后查看 metrics.out 文件,查看我们的新指标是否正在显示。我们可以再次使用 grep 搜索任何包含“python”的行,而不是用肉眼搜索文件

$ grep python < /tmp/metrics.out
python_processes,host=demo value=1i 1519703250000000000
python_processes,host=demo value=1i 1519703260000000000
python_processes,host=demo value=1i 1519703270000000000

我们正在使用 < 字符将指标文件的内容发送到 grep 命令(这是另一个 Linux 功能),作为回报,我们得到几行 InfluxDB 行协议的指标,其中包含指标名称、Telegraf 添加的主机标签、值(带有“i”表示它是整数)和时间戳。

如果我们启动另一个 Python 进程,我们应该看到输出中的值发生变化

$ ./loop.py &
[2] 2468
$ grep python < /tmp/metrics.out
python_processes,host=demo value=1i 1519703250000000000
python_processes,host=demo value=1i 1519703260000000000
python_processes,host=demo value=1i 1519703270000000000
python_processes,host=demo value=1i 1519703280000000000
python_processes,host=demo value=1i 1519703290000000000
python_processes,host=demo value=2i 1519703300000000000

就这样!最终指标显示正在运行两个 Python 进程。

后续步骤

将指标写入磁盘并不是一种非常有用的做法,但它对于确保您的设置正在收集您期望的数据很有用。为了使其可操作,您需要将收集的数据发送到某处的中央存储,以便您可以对其进行可视化和告警。

这些指标的可视化将是最小的;我们可能不需要完整的图形,因为我们获得的需要从历史上查看的数据不应有太多变化。相反,显示单个数字(例如,Chronograf 中的 单值统计 面板)应该足以让您对事物按预期工作充满信心。

如何针对这些指标发出告警将取决于您究竟在监控什么。也许您始终希望运行一个进程副本。您可以创建一个告警,在每次进程计数降至 1 以下时发送电子邮件。但是,在最初几次告警之后,您的团队可能希望在进程崩溃时自动启动新进程,因此您需要调整告警,以便在看到指标变为 0 到发送第一个告警之间需要经过一段时间;如果您的自动化系统可以快速启动进程,则无需联系人员。

或者,您可能有一个定期生成新进程并杀死旧进程的系统,但在给定时间永远不应运行超过 X 个进程。您可能希望设置与上述类似的告警,只不过不是在指标从 0 降至 1 时发出告警,而是在指标大于或小于 X 时发出告警。您可能还想为此告警提供一个时间窗口;也许在杀死和启动新进程时,您的系统运行 X+1 或 X-1 个进程短时间是可以接受的。

如果您决定将数据发送到 InfluxDB,则可以使用 Chronograf 和 Kapacitor 对指标进行可视化和告警。您可以在各自的文档页面上阅读有关创建 Chronograf 仪表板设置 Kapacitor 告警的更多信息。