使用 Telegraf 收集运行进程计数

导航至

Photo of the output of ps aux in macOS Terminal

将监控添加到您的堆栈中是快速了解您的应用程序的一种方式,让您能够更快地发现问题,并开始做出关于在哪里投入工程力量的数据驱动决策。开始监控的最直接的事情之一就是您的进程本身——如果没有任何进程在运行,那么 Web 服务器很难提供服务请求,另一方面,如果您运行了比预期更多的程序副本,您会迅速耗尽可用的资源。

在大多数“nix”系统上,可以通过多种方式收集进程信息。根据您正在运行的特定操作系统,您可能能够查看包含有关运行进程和系统一般信息的多个文件的 proc 文件系统,或者您可以使用像 ps 这样的工具,它将有关运行进程的信息输出到命令行。

在这个例子中,我们将使用 Python 和 Ubuntu Linux,但许多概念也可以应用于其他语言、应用程序和操作系统。

在 Linux 上获取进程信息

获取更多系统信息的一个很好的地方是 proc 文件系统,根据 man 页面,“它是一个伪文件系统,提供对内核数据结构的接口。它通常挂载在 /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 time模块中的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命令时,输出包括我们在每个进程启动时提供的参数;在这种情况下,由于Ubuntu有一个别名,它会运行grep --color=auto,所以我们添加了--color=auto,然后是python参数,这是我们正在搜索的字符串。因此,我们正在搜索“python”,这意味着字符串“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脚本之一带到前台,然后使用杀死它,再次统计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,它也满足了我们的所有使用案例要求,但它的通用性不如前者。它允许你搜索所有匹配字符串的进程,并默认返回它们的进程ID。它还接受 -c 参数,输出匹配的数量。

$ pgrep -c python
1

使用 Telegraf 收集进程计数

既然我们知道了如何统计服务器上运行的进程数,我们就需要定期收集这些数据。 Telegraf 允许我们以 exec 输入插件的形式执行在 shell 中使用的相同命令,以收集数据。

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 进程正在运行。

下一步

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

这些指标的视觉呈现将是最基本的;我们可能不需要一个完整的图表,因为我们获取的数据在历史记录上不应该有太大的变化。相反,显示一个单个数字(例如,在 ChronografSingle Stat 面板)应该足以让您相信一切按预期运行。

您如何对这些指标进行警报将取决于您具体监控的内容。也许您总是想有一个进程在运行。您可以创建一个警报,每当进程计数低于 1 时发送电子邮件。但是,在发送了几次警报之后,您的团队可能希望自动化启动新进程,如果您的进程崩溃,因此您需要调整警报,以便在看到指标为 0 和发送第一次警报之间有时间间隔;如果您的自动化系统可以快速启动进程,那么就不需要联系人类。

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

如果您决定将数据发送到 InfluxDB,您可以使用 Chronograf 和 Kapacitor 来可视化和警报您的指标。您可以在它们的相应文档页面上阅读更多关于 创建 Chronograf 仪表板设置 Kapacitor 警报 的信息。