在前一章中,我们使用了lsof
和strace
等工具来确定应用问题的根本原因。
在本章中,我们将再次确定应用相关问题的根本原因。 然而,我们还将重点学习和理解 Linux 用户和内核的限制。
就像前一章的所关注的自定义应用的问题一样,今天的问题也来自相同的自定义应用。
今天,我们将处理一个应用支持团队报告的问题。 然而,这一次支持团队能够为我们提供相当多的信息。
我们在第 9 章、使用系统工具对应用进行故障排除中所处理的应用,现在通过port 25
接收消息并将其存储在队列目录中。 一个作业周期性地运行以处理这些排队的消息,但是作业似乎不再工作。
应用支持团队注意到队列中积压了相当多的消息。 然而,尽管他们已经尽可能地解决了问题,他们还是卡住了,需要我们的帮助。
由于报告的问题是一个预定的工作不工作,我们应该首先关注工作本身。 在这个场景中,我们的应用支持团队可以回答任何问题。 所以,让我们了解更多关于这项工作的细节。
以下是问题的快速列表,应该可以帮助您提供额外的信息:
- 作业是如何运行的?
- 如果需要,我们可以手动运行该工作吗?
- 这个作业执行什么?
这三个问题可能看起来很基本,但它们很重要。 让我们先来看看应用团队提供的答案:
-
How is the job run?
作业以 cron 作业的形式执行。
-
Can we run the job manually if we need to?
是的,可以根据需要经常手动执行作业。
-
What does this job execute?
作业作为流浪用户执行/opt/myapp/bin/processor 命令。
前面的三个问题非常重要,因为它们将为我们节省大量的故障排除时间。 第一个问题关注的是如何执行作业。 由于报告的问题是作业不工作,我们还不知道问题是由于作业没有运行,还是作业正在执行但由于某种原因失败。
第一个问题的答案告诉我们作业是由crond
执行的,它是运行在 Linux 上的cron 守护进程。 这很有用,因为我们可以使用该信息来确定作业是否正在执行。 通常,有许多方法用于执行调度作业。 有时,执行计划作业的软件运行在不同的系统上,有时它运行在相同的本地系统上。
在这种情况下,作业正在同一台服务器上由crond
执行。
第二个问题也很重要。 就像在上一章中我们必须手动启动应用一样,对于报告的问题,我们可能也需要执行这个故障排除步骤。 根据答案,似乎我们可以根据需要任意多次执行该命令。
第三个问题很有用,因为它不仅告诉我们正在执行什么命令,而且还告诉我们要寻找哪个作业。 Cron 作业是一种非常常见的任务调度方法。 对于一个系统来说,调度许多 cron 作业是很常见的。
因为我们知道作业正在由crond
执行,所以我们应该首先检查作业是否正在执行。 为此,我们可以检查相关服务器上的 cron 日志。 例如,考虑以下日志:
# ls -la /var/log/cron*
-rw-r--r--. 1 root root 30792 Jun 10 18:05 /var/log/cron
-rw-r--r--. 1 root root 28261 May 18 03:41 /var/log/cron-20150518
-rw-r--r--. 1 root root 6152 May 24 21:12 /var/log/cron-20150524
-rw-r--r--. 1 root root 42565 Jun 1 15:50 /var/log/cron-20150601
-rw-r--r--. 1 root root 18286 Jun 7 16:22 /var/log/cron-20150607
具体来说,在 Red Hat 的 Linux 系统上,我们可以检查/var/log/cron
日志文件。 我在前面的句子中指定了“基于 Red Hat”,因为在非基于 Red Hat 的系统上,cron 日志可能位于不同的日志文件中。 例如,基于 debian 的系统默认为/var/log/syslog
。
如果我们不知道哪个日志文件包含 cron 日志,那么有一个简单的技巧可以找到它。 只需运行以下命令行:
# grep -ic cron /var/log/* | grep -v :0
/var/log/cron:400
/var/log/cron-20150518:379
/var/log/cron-20150524:86
/var/log/cron-20150601:590
/var/log/cron-20150607:248
/var/log/messages:1
/var/log/secure:1
前面的命令将使用grep
来搜索/var/log
中的所有日志文件以查找字符串cron
。 该命令还将搜索Cron
、CRON
、cRon
等,因为我们将–i
(不敏感)标志添加到grep
命令中。 这告诉grep
在不区分大小写的模式下搜索。 从本质上说,这意味着将找到单词“cron”的任何匹配,即使该单词是大写或大小写混合的。 我们还在grep
命令中添加了–c
(count)标志,这将使它计算找到的实例数:
/var/log/cron:400
如果我们查看第一个结果,可以看到grep
在/var/log/cron
中找到了 400 个单词“cron”的实例。
最后,我们将结果重定向到另一个带有–v
标志和:0
的grep
命令。 这个grep
将接受第一次执行的结果,并省略(-v
)包含字符串:0
的任何行。 这对于将结果限制为只包含cron
字符串的文件非常有用。
从前面的结果中,我们可以看到文件/var/log/cron
中包含单词“cron”的最多实例。 这个事实本身就很好地说明了/var/log/cron
是crond
守护进程的日志文件。
现在我们知道了哪个日志文件包含我们要查找的日志消息,我们可以查看该日志文件的内容。 由于这个日志文件非常大,我们将使用less
命令来读取这个文件:
# less /var/log/cron
由于这个日志中有相当多的信息,我们将只关注有助于解释这个问题的日志条目。 下面的段是一组有趣的日志消息,它们可以回答我们的作业是否在运行:
Jun 10 18:01:01 localhost CROND[2033]: (root) CMD (run-parts /etc/cron.hourly)
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2033]: starting 0anacron
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2042]: finished 0anacron
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2033]: starting 0yum-hourly.cron
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2048]: finished 0yum-hourly.cron
Jun 10 18:05:01 localhost CROND[2053]: (vagrant) CMD (/opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml > /dev/null)
Jun 10 18:10:01 localhost CROND[2086]: (root) CMD (/usr/lib64/sa/sa1 1 1)
Jun 10 18:10:01 localhost CROND[2087]: (vagrant) CMD (/opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml > /dev/null)
Jun 10 18:15:01 localhost CROND[2137]: (vagrant) CMD (/opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml > /dev/null)
Jun 10 18:20:01 localhost CROND[2147]: (root) CMD (/usr/lib64/sa/sa1 1 1)
前面的日志消息显示了相当多的行。 让我们分解这些日志,以便更好地理解正在执行的内容。 考虑以下几行:
Jun 10 18:01:01 localhost CROND[2033]: (root) CMD (run-parts /etc/cron.hourly)
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2033]: starting 0anacron
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2042]: finished 0anacron
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2033]: starting 0yum-hourly.cron
Jun 10 18:01:01 localhost run-parts(/etc/cron.hourly)[2048]: finished 0yum-hourly.cron
开头几行似乎不是我们正在寻找的工作,而是cron.hourly
工作。
在 Linux 系统上,有多种指定 cron 作业的方法。 在 RHEL 系统中,/etc/
中有几个目录以cron
开头:
# ls -laF /etc/ | grep cron
-rw-------. 1 root root 541 Jun 9 2014 anacrontab
drwxr-xr-x. 2 root root 34 Jan 23 15:43 cron.d/
drwxr-xr-x. 2 root root 62 Jul 22 2014 cron.daily/
-rw-------. 1 root root 0 Jun 9 2014 cron.deny
drwxr-xr-x. 2 root root 44 Jul 22 2014 cron.hourly/
drwxr-xr-x. 2 root root 6 Jun 9 2014 cron.monthly/
-rw-r--r--. 1 root root 451 Jun 9 2014 crontab
drwxr-xr-x. 2 root root 6 Jun 9 2014 cron.weekly/
cron.daily
、cron.hourly
、cron.monthly
和cron.weekly
目录都是可以包含脚本的目录。 这些脚本将按照目录名中指定的时间运行。
例如,让我们看看/etc/cron.hourly/0yum-hourly.cron
:
# cat /etc/cron.hourly/0yum-hourly.cron
#!/bin/bash
# Only run if this flag is set. The flag is created by the yum-cron init
# script when the service is started -- this allows one to use chkconfig and
# the standard "service stop|start" commands to enable or disable yum-cron.
if [[ ! -f /var/lock/subsys/yum-cron ]]; then
exit 0
fi
# Action!
exec /usr/sbin/yum-cron /etc/yum/yum-cron-hourly.conf
前面的文件是一个简单的bash
脚本,crond
守护进程将每小时执行一次,因为它位于cron.hourly
目录中。 通常,包含在这些目录中的脚本是由系统服务放置在那里的。 但是,这些目录也对系统管理员开放,系统管理员可以放置自己的脚本。
如果我们继续向下查看日志文件,我们可以看到与我们的自定义作业相关的条目:
Jun 10 18:10:01 localhost CROND[2087]: (vagrant) CMD (/opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml > /dev/null)
这一行显示了应用支持团队引用的processor
命令。 这一行必须是应用支持团队遇到问题的工作。 日志记录告诉我们相当多有用的信息。 首先,它为我们提供了传递给这个作业的命令行选项:
/opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml > /dev/null
它还告诉我们作业以vagrant
的形式执行。 这个日志条目告诉我们的最重要的事情是作业正在执行。
因为我们知道作业正在执行,所以我们应该验证作业是否成功。 为了做到这一点,我们将采取一种简单的方法,手动执行工作:
$ /opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml
Initializing with configuration file /opt/myapp/conf/config.yml
- - - - - - - - - - - - - - - - - - - - - - - - - -
Starting message processing job
Traceback (most recent call last):
File "app.py", line 28, in init app (app.c:1488)
IOError: [Errno 24] Too many open files: '/opt/myapp/queue/1433955823.29_0.txt'
我们应该省略 cron 任务末尾的> /dev/null
,因为这会将输出重定向到/dev/null
。 这是丢弃 cron 作业输出的一种常见方法。 对于这个手动执行,我们可以利用输出来帮助解决问题。
一旦执行,任务似乎失败了。 它不仅失败了,而且在失败的同时还产生了一条错误消息:
IOError: [Errno 24] Too many open files: '/opt/myapp/queue/1433955823.29_0.txt'
这个错误很有趣,因为它似乎表明应用打开了太多的文件。 这有什么关系?
在 Linux 系统上,每个进程都有限制。 设置这些限制是为了防止进程占用过多的系统资源。
虽然这些限制是对每个用户强制执行的,但是,可以为每个用户设置不同的限制。 要查看默认情况下对vagrant
用户设置了哪些限制,可以使用ulimit
命令:
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3825
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3825
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
当我们执行ulimit
命令时,我们是作为流浪用户执行的。 这一点很重要,因为当我们作为任何其他用户(包括 root)运行ulimit
命令时,输出将是该用户的限制。
如果我们看一下ulimit
命令的输出,我们可以看到可以设置很多限制。
让我们来看看和细分几个关键限制:
file size (blocks, -f) unlimited
第一个有趣的项目是file size
限制。 这个限制将限制用户可以创建的文件的大小。 流浪用户的当前设置是unlimited
,但是如果我们将该值设置为更小的数字,会发生什么情况?
我们可以通过执行ulimit –f
和限制文件的块数来实现。 例如,考虑以下命令行:
$ ulimit -f 10
在将值设置为10
后,我们可以再次运行ulimit –f
来验证它是否生效,但这一次没有设置值:
$ ulimit -f
10
现在我们的限制设置为 10 个块,让我们尝试使用dd
命令创建一个 500 MB 的文件:
$ dd if=/dev/zero of=/var/tmp/bigfile bs=1M count=500
File size limit exceeded
Linux 上的用户限制的一个好处是,所提供的错误通常是自明的。 我们可以从前面的输出中看到,dd
命令不仅无法创建文件,而且还收到了一个错误,说明文件的大小限制已经超出。
另一个有趣的极限是max processes
极限:
max user processes (-u) 3825
此限制可防止用户同时拥有太多的进程。 这是一个非常有用和有趣的限制,因为它可以很容易地防止流氓应用接管系统。
它也可能是你经常遇到的限制。 对于启动许多子进程或线程的应用尤其如此。 为了了解这种限制是如何工作的,我们可以将设置更改为10
:
$ ulimit -u 10
$ ulimit -u
10
与文件大小限制一样,我们可以使用ulimit
命令修改进程限制。 然而,这一次我们使用了-u
标志。 使用ulimit
命令,每个用户限制都有自己的唯一标志。 我们可以在ulimit –a
的输出中看到这些标志,当然,每个标志都在ulimit
的手册页中引用。
现在我们已经将进程限制为10
,我们可以通过运行一个命令来执行这个限制:
$ man ulimit
man: fork failed: Resource temporarily unavailable
通过简单地通过 SSH 登录到流浪用户,我们已经在利用多个进程。 很容易遇到10
进程的限制,因为我们运行的任何新命令都将使我们的登录超过限制。
从前面的示例中,我们可以看到,当man
命令被执行时,无法启动子进程,因此返回了一个错误Resource temporarily unavailable
。
我想要探索的最后一个有趣的用户限制是open files
限制:
open files (-n) 1024
open files
限制将限制一个进程打开的文件数量不能超过定义的数量。 这个限制可以用来防止一个进程一次打开太多的文件。 当防止应用占用过多的系统资源时,这是非常有用的。
像其他极限一样,让我们看看当我们把这个极限简化到一个非常不合理的数时会发生什么:
$ ulimit -n 2
$ ls
-bash: start_pipeline: pgrp pipe: Too many open files
ls: error while loading shared libraries: libselinux.so.1: cannot open shared object file: Error 24
与其他示例一样,在本例中我们收到了一个错误Too many open files
。 然而,这个错误看起来很熟悉。 如果我们回顾从预定作业接收到的错误,我们将看到原因。
IOError: [Errno 24] Too many open files: '/opt/myapp/queue/1433955823.29_0.txt'
在将最大打开文件数设置为2
后,ls
命令产生了一个错误; 该错误与应用在早些时候执行时收到的错误消息完全相同。
这是否意味着我们的应用试图打开比系统配置允许的更多的文件? 这种可能性很大。
因为我们怀疑open files
限制阻止了应用的执行,所以我们可以将其限制设置为一个更高的值。 然而,这并不像执行ulimit –n
那么简单; 下面的输出是我们在执行它时得到的结果:
$ ulimit -n
1024
$ ulimit -n 5000
-bash: ulimit: open files: cannot modify limit: Operation not permitted
$ ulimit -n 4096
$ ulimit -n
4096
默认情况下,在我们的示例系统中,游荡用户可以将open files
限制提高到的最高值是4096
。 从前面的错误中我们可以看出,任何更高的都被拒绝; 但就像 Linux 的大多数事情一样,我们可以改变这一点。
我们一直在中使用和修改的用户限制是 Linux PAM 系统的一部分。 PAM 或可插入身份验证模块是一个提供模块化身份验证系统的系统。
例如,如果我们的系统要使用 LDAP 进行身份验证,那么将使用pam_ldap.so
库来提供此功能。 但是,由于我们的系统使用本地用户进行身份验证,所以pam_localuser.so
库处理用户身份验证。
我们可以通过读取/etc/pam.d/system-auth
文件来验证:
$ cat /etc/pam.d/system-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required pam_env.so
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so
account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account required pam_permit.so
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password required pam_deny.so
session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
如果我们看前面的例子,我们可以看到pam_localuser.so
和account
作为第一列:
account sufficient pam_localuser.so
这个意味着pam_localuser.so
模块是一个允许使用帐户的sufficient
模块,这本质上意味着如果用户有正确的/etc/passwd
和/etc/shadow
条目,就可以登录。
session required pam_limits.so
如果我们看前面一行,我们可以看到在哪里执行了用户限制。 这一行实际上告诉系统,pam_limits.so
模块对于所有用户会话都是必需的。 这有效地确保了在每个用户会话上强制执行pam_limits.so
模块标识的用户限制。
这个 PAM 模块的配置位于/etc/security/limits.conf
和/etc/security/limits.d/
:
$ cat /etc/security/limits.conf
#This file sets the resource limits for the users logged in via PAM.
# - core - limits the core file size (KB)
# - data - max data size (KB)
# - fsize - maximum filesize (KB)
# - memlock - max locked-in-memory address space (KB)
# - nofile - max number of open files
# - rss - max resident set size (KB)
# - stack - max stack size (KB)
# - cpu - max CPU time (MIN)
# - nproc - max number of processes
# - as - address space limit (KB)
# - maxlogins - max number of logins for this user
# - maxsyslogins - max number of logins on the system
# - priority - the priority to run user process with
# - locks - max number of file locks the user can hold
# - sigpending - max number of pending signals
# - msgqueue - max memory used by POSIX message queues (bytes)
# - nice - max nice priority allowed to raise to values: [-20, 19]
# - rtprio - max realtime priority
#
#<domain> <type> <item> <value>
#
#* soft core 0
#* hard rss 10000
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
#@student - maxlogins 4
当我们阅读limits.conf
文件时,我们可以看到很多关于用户限制的有用信息。
在这个文件中,列出了可用的限制,并对该限制强制执行的内容进行了描述。 例如,在前面的命令行中,我们可以看到以下关于open files
限制的数量:
# - nofile - max number of open files
从这一行中我们可以看到,如果我们想改变对用户可用的打开文件的数量,我们将需要使用nofile
类型。 在列出每个限制所做的上面,limits.conf
文件还包含了为用户和组设置自定义限制的示例:
#ftp hard nproc 0
给出这个例子,我们可以看到我们需要使用什么格式来设置限制; 但是我们也应该设置什么限制呢? 如果我们回头看看作业中的错误,可以看到错误列出了/opt/myapp/queue
目录下的一个文件:
IOError: [Errno 24] Too many open files: '/opt/myapp/queue/1433955823.29_0.txt'
可以放心地说,应用正试图打开这个目录中的文件。 因此,为了确定这个进程需要打开多少个文件,让我们通过下面的命令行来找出这个目录中有多少个文件:
$ ls -la /opt/myapp/queue/ | wc -l
492304
前面的命令使用ls –la
列出queue/
目录中的所有文件和目录,并将输出重定向到wc –l
。 wc
命令将计算所提供输出的行数(-l
),这实际上意味着在queue/
目录中有 492,304 个文件和/或目录。
由于数量很大,我们应该将open files
的数量限制设置为500000
,这足以处理queue/
目录,以防万一还需要一点额外的量。 我们可以通过在limits.conf
文件中添加以下行来实现:
# vi /etc/security/limits.conf
在使用vi
或其他文本编辑器添加行后,我们可以使用tail
命令验证它是否存在:
$ tail /etc/security/limits.conf
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
#@student - maxlogins 4
vagrant soft nofile 100000
vagrant hard nofile 500000
# End of file
更改这些设置并不意味着我们的登录 shell 会立即限制500000
。 我们的登录会话仍然有4096
设置的限制。
$ ulimit -n
4096
我们也仍然不能增加它超过这个价值。
$ ulimit -n 9000
-bash: ulimit: open files: cannot modify limit: Operation not permitted
为了使我们的更改生效,我们必须再次登录到我们的用户。
正如前面所讨论的,这些限制是由 PAM 设置的,它在 shell 会话登录期间应用。 由于限制是在登录期间设置的,所以我们仍然受到上次登录时获得的前一个值的限制。
为了获得新的限制,我们必须注销并重新登录(或生成一个新的登录会话)。 对于我们的示例,我们将注销 shell 并重新登录。
$ ulimit -n
100000
$ ulimit -n 501000
-bash: ulimit: open files: cannot modify limit: Operation not permitted
$ ulimit -n 500000
$ ulimit -n
500000
如果我们看一下前面的命令行,我们可以看到一些非常有趣的东西。
当我们这次登录时,我们的文件数量限制被设置为100000
,正好与我们在limits.conf
文件中设置的soft
限制相同。 这是因为soft
限制是为每个会话默认设置的限制。
hard
限制是该用户可以设置的soft
限制之上的最高值。 我们可以在前面的示例中看到这一点,因为我们能够将nofile
限制设置为500000
而不是501000
。
我们将soft
限制设置为100000
的原因是我们正在为未来类似的场景做计划。 通过将soft
限制设置为100000
,运行此调度作业的 cron 作业将被限制为 100,000 个打开的文件。 但是,由于hard
限制被设置为500000
,因此用户可以在登录会话上手动运行设置更高限制的作业。
只要queue
目录中的文件数量不超过 500,000 个,任何人都不应该再需要编辑/etc/security/limits.conf
文件。
既然我们的限制已经增加,我们可以尝试再次运行作业。
$ /opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml
Initializing with configuration file /opt/myapp/conf/config.yml
- - - - - - - - - - - - - - - - - - - - - - - - - -
Starting message processing job
Traceback (most recent call last):
File "app.py", line 28, in init app (app.c:1488)
IOError: [Errno 23] Too many open files in system: '/opt/myapp/queue/1433955989.86_5.txt'
我们再次收到一个错误。 然而,这一次的错误只是有一点不同。
在之前的运行中,我们收到了以下错误。
IOError: [Errno 24] Too many open files: '/opt/myapp/queue/1433955823.29_0.txt'
然而,这次我们收到了这个错误。
IOError: [Errno 23] Too many open files in system: '/opt/myapp/queue/1433955989.86_5.txt'
差异非常细微,但是在第二次运行中,我们的错误显示系统中打开的文件太多,而第一次运行中没有包含in system
。 这是因为我们遇到了不同类型的限制,不是用户限制,而是系统限制。
Linux 内核本身也可以在系统上设置限制。 这些限制是根据内核参数定义的。 其中一些参数是静态的,不能在运行时更改; 而另一些人。 如果可以在运行时更改内核参数,则称为可调参数。
我们可以使用sysctl
命令查看静态和可调内核参数及其当前值:
# sysctl -a | head
abi.vsyscall32 = 1
crypto.fips_enabled = 0
debug.exception-trace = 1
debug.kprobes-optimization = 1
dev.hpet.max-user-freq = 64
dev.mac_hid.mouse_button2_keycode = 97
dev.mac_hid.mouse_button3_keycode = 100
dev.mac_hid.mouse_button_emulation = 0
dev.parport.default.spintime = 500
dev.parport.default.timeslice = 200
由于有许多可用参数,我使用head
命令将输出限制为前 10 个。 我们之前收到的错误提到了系统上的一个限制,这表明我们可能达到了内核本身施加的限制。
唯一的问题是我们怎么知道是哪一个? 最快的答案当然是搜索谷歌。 由于有如此多的内核参数(在我们正在处理的系统上有 800 多个),很难简单地读取sysctl –a
的输出并找到正确的一个。
更现实的方法是简单地搜索我们想要修改的参数类型。 我们的场景的一个搜索示例是Linux parameter max open files
。 如果我们执行这个搜索,我们很可能会找到参数和如何修改它。 然而,如果谷歌不是一个选择,还有另一种方法。
通常,内核参数有一个名称来描述该参数控制的内容。
例如,如果我们要寻找禁用 IPv6 的内核参数,我们将首先搜索net
字符串,如 network:
# sysctl -a | grep -c net
556
然而,这仍然返回大量的结果。 在这些结果中,我们可以看到字符串ipv6
。
# sysctl -a | grep -c ipv6
233
尽管如此,还是有很多结果; 但是,如果我们添加字符串disable
的搜索,则会得到以下输出:
# sysctl -a | grep ipv6 | grep disable
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.enp0s3.disable_ipv6 = 0
net.ipv6.conf.enp0s8.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0
我们终于可以缩小可能的参数范围了。 然而,我们并不完全了解这些参数的作用。 至少现在还没有。
如果我们通过/usr/share/doc
进行快速搜索,我们可能会找到一些文档来解释这些设置的作用。 我们可以通过使用grep
在该目录中执行-r
的递归搜索来快速完成这一任务。 为了保持输出简单,我们可以添加-l
(列表文件),这会导致grep
只列出它在以下文件中找到所需字符串的文件名:
# grep -rl net.ipv6 /usr/share/doc/
/usr/share/doc/grub2-tools-2.02/grub.html
在基于 Red Hat 的 Linux 系统上,/usr/share/doc
目录用于系统手册页之外的其他文档。 如果我们被限制只能使用系统本身上的文档,那么/usr/share/doc
目录就是首先要检查的地方之一。
因为我们喜欢以困难的方式执行任务,所以我们将尝试识别可能限制我们的内核参数,而不需要在谷歌上搜索它。 执行此操作的第一步是在sysctl
输出中搜索字符串file
。
我们搜索file
的原因是我们正在达到文件数量的限制。 虽然这可能不能提供我们试图识别的确切参数,但搜索至少会让我们开始:
# sysctl -a | grep file
fs.file-max = 48582
fs.file-nr = 1088 0 48582
fs.xfs.filestream_centisecs = 3000
搜索file
实际上可能是一个很好的选择。 仅仅根据参数的名称,我们可能感兴趣的两个是fs.file-max
和fs.file-nr
。 在这一点上,我们不知道哪个控制打开的文件的数量,或者它们中是否有一个控制。
要找到更多的信息,我们可以搜索doc
目录。
# grep -r fs.file- /usr/share/doc/
/usr/share/doc/postfix-2.10.1/README_FILES/TUNING_README:
fs.file-max=16384
似乎位于 Postfix 服务文档中名为TUNING_README
、的文档至少引用了我们的一个值。 让我们检查一下这个文件,看看这个文档对这个内核参数说了什么:
* Configure the kernel for more open files and sockets. The details are
extremely system dependent and change with the operating system version. Be
sure to verify the following information with your system tuning guide:
o Linux kernel parameters can be specified in /etc/sysctl.conf or changed
with sysctl commands:
fs.file-max=16384
kernel.threads-max=2048
如果我们阅读文件中列出内核参数的地方的内容,可以看到它专门调用参数来为内核配置更多的打开文件和套接字。
该文档调用了两个内核参数,以允许打开更多的文件和套接字。 第一个称为fs.file-max
,这也是我们通过sysctl
搜索所确定的。 第二个被称为kernel.threads-max
,这是一个相当新的概念。
仅仅根据名称,似乎我们想要修改的可调参数是fs.file-max
参数。 让我们看看它的当前值如下:
# sysctl fs.file-max
fs.file-max = 48582
我们可以通过执行sysctl
后跟参数名来列出该参数的当前值(如前面的命令行所示)。 这将简单地显示当前定义的值; 这个数字似乎被设置为48582
,远远低于我们目前的用户限制。
在前面的例子中,我们在后缀文档中找到了这个参数。 虽然这可能是好的,但并不准确。 如果您经常发现自己需要在本地搜索内核参数,那么安装kernel-doc
包将是一个好主意。 包包含相当多的信息,特别是关于可调参数的信息。
因为我们认为fs.file-max
参数控制系统可以打开的最大文件数,所以我们应该更改这个值以允许作业运行。
与 Linux 上的大多数系统配置项一样,可以选择在重新启动时更改这个值。 前面我们设置了limits.conf
文件,以允许游移用户能够打开 100,000 个文件作为soft
的限制,并允许 500,000 个文件作为hard
的限制。 问题是,我们希望这个用户能够正常操作打开 500,000 个文件吗? 或者这应该是一个一次性的任务来纠正我们目前面临的问题?
答案很简单:看情况!
如果我们看看我们目前正在处理的情况,所讨论的工作已经有一段时间没有运行了。 因此,队列中积压了大量的消息。 然而,这些都不是正常情况。
在前面,当我们将用户限制设置为 100,000 个文件时,我们这样做是因为这对于这个作业来说是比较合适的值。 考虑到这一点,我们还应该将内核参数设置为略高于100000
的值,但不要过大。
对于这个场景和这个环境,我们将执行两个操作。 第一个是配置系统,默认允许125,000 个打开的文件。 第二种方法是将当前参数设置为525,000 个打开文件,以允许计划的作业成功运行。
由于我们希望在默认情况下将fs.file-max
的值更改为125000
,因此我们需要编辑sysctl.conf
文件。 sysctl.conf
文件是一个系统配置文件,它允许您为可调内核参数指定自定义值。 在每次系统重新引导期间,将读取该文件并应用其中的值。
为了将我们的fs.file-max
值设置为125000
,我们可以简单地将以下行添加到这个文件:
# vi /etc/sysctl.conf
fs.file-max=125000
现在我们已经添加了自定义值,我们需要告诉系统应用它。
如前所述,sysctl.conf
文件是在重新引导时应用的,但是我们也可以在任何时候使用带有–p
标志的sysctl
命令将这些设置应用到该文件。
# sysctl -p
fs.file-max = 125000
当给出了–p
标志时,sysctl
命令将读取并将值应用到指定的文件,或者如果没有指定文件/etc/sysctl.conf
。 因为我们没有在–p
标志之后指定文件,所以sysctl
命令应用添加到/etc/sysctl.conf
的值并打印它修改的值。
让我们再次执行sysctl
来验证是否正确地应用了它。
# sysctl fs.file-max
fs.file-max = 125000
似乎在中,该值被适当地应用了,但是如果将其设置为525000
呢?
虽然简单的足以将/etc/sysctl.conf
更改为更高的值,但是应用它,然后恢复更改。 有一种更简单的方法可以临时更改可调值。
当提供了–w
选项时,sysctl
命令将允许修改可调值。 要查看实际效果,我们将使用此方法将fs.file-max
值设置为525000
。
# sysctl -w fs.file-max=525000
fs.file-max = 525000
就像应用sysctl.conf
文件的值一样,当我们执行sysctl –w
时,它会打印它所应用的值。 如果我们再次验证它们,我们将看到该值被设置为525000
files:
# sysctl fs.file-max
fs.file-max = 525000
现在,我们已经为流浪用户设置了的open files
限制为500000
,并为整个系统设置了525000
。 我们可以再次手动执行该任务,这一次应该成功:
$ /opt/myapp/bin/processor --debug --config /opt/myapp/conf/config.yml
Initializing with configuration file /opt/myapp/conf/config.yml
- - - - - - - - - - - - - - - - - - - - - - - - - -
Starting message processing job
Added 492304 to queue
Processing 492304 messages
Processed 492304 messages
这一次作业执行时没有提供任何错误! 我们可以从作业的输出中看到,/opt/myapp/queue
中的所有文件也都被处理了。
现在我们已经解决了问题,让我们花点时间看看我们是如何解决问题的。
为了解决我们的问题,我们手动执行了一个调度的 cron 作业。 如果我们回到前面的章节,这是一个主要的例子,重复一个问题,并看到它自己。
在这种情况下,工作没有执行它应该执行的任务。 为了确定原因,我们手动运行它。
在手动执行过程中,我们能够识别以下错误:
IOError: [Errno 24] Too many open files: '/opt/myapp/queue/1433955823.29_0.txt'
此错误非常常见,是由于作业运行到阻止单个用户打开过多文件的用户限制而导致的。 为了解决这个问题,我们在/etc/security/limits.conf
文件中添加了自定义设置。
这些更改将我们的用户的open files
的soft
限制默认设置为100000
。 我们还允许用户通过hard
设置将open files
限制增加到500000
:
IOError: [Errno 23] Too many open files in system: '/opt/myapp/queue/1433955989.86_5.txt'
在修改了这些限制之后,我们再次执行作业,并遇到了一个类似但不同的错误。
这一次,将open files
限制强加于系统本身,在本例中,将系统范围内的打开文件限制为 48,000 个。
为了解决这个问题,我们在/etc/sysctl.conf
文件中设置一个永久的125000
设置,并将其值临时更改为525000
。
从那时起,我们就可以手动执行任务了。 但是,在这个实例之外,由于我们更改了默认限制,我们还为这个作业提供了更多的资源来正常执行。 只要不存在超过 100,000 个文件的积压,该任务在未来应该可以毫无问题地执行。
说到正常执行,为了减少内核对打开文件的限制,我们可以使用–p
选项再次执行sysctl
命令。 这将把值重置为/etc/sysctl.conf
文件中定义的值。
# sysctl -p
fs.file-max = 125000
该方法需要注意的一点是,sysctl -p
将只重置/etc/sysctl.conf
中指定的值; 它在默认情况下只包含少数几个可调值。 如果在/etc/sysctl.conf
中没有指定的值被修改,sysctl -p
方法不会将该值重置为默认值。
在本章中,我们非常熟悉 Linux 中的内核和用户限制。 这些设置变得非常有用,因为任何使用许多资源的应用最终都将运行其中一个。
在下一章中,我们将集中讨论一个非常普遍但又非常棘手的问题。 我们将着重于故障排除和确定系统内存耗尽的原因。 当系统耗尽内存时,会产生很多后果,比如应用进程被杀死。