我们都看到过疯狂的程序完全消耗了一个主机。监禁一个程序并不能解决这个问题。您可以对 login.conf 进行各种调整,以帮助限制特定的程序,但这并不能限制整个jail。FreeBSD允许您限制程序、用户、登录类或jail可以与 rctl(8)
和 cpuset(8)
一起使用的资源。我们将重点介绍它们在jail中的应用,但你可以出于任何原因在任何主机上使用它们。
资源限制不是奇迹。系统管理员的主要职责之一是重新安排瓶颈,以从有限的资源中获得最佳性能。资源限制与此相反:施加瓶颈以强制分配有限的资源。有多少次,你删除了一个瓶颈,却发现它后面还有另一个瓶颈?我曾经修复了数据库服务器的慢速磁盘读取,发现它们是唯一能防止数据库耗尽主机内存的东西。对jail的内存施加限制,它可能会分页得如此激烈,以至于你的磁盘无法使用。资源限制是无限沮丧或无限讽刺娱乐的来源,这取决于你的性格。
不过,一些资源短缺可以很容易地解决。
第十一章:资源限制和删除periodic(8)
和 Jails资源限制RCTL规则RCTL资源RCTL操作管理规则过滤器RCTL和 iocage处理器限制主机 CPU 拓扑结构CPU集cpuset 和标准 Jailscpuset 和 iocage不完整的 Jails修剪FreeBSD网络继承Jails 作为控制组
periodic(8)
和 JailsFreeBSD每天、每周和每月定时运行 periodic(8)
,就像 /etc/crontab 中计划的一样。这就是FreeBSD执行日常维护并生成每日、每周和每月状态报告的方式。jail也需要这种维护,但这些任务会占用存储I/O。一旦主机有多个jail,当计划的作业开始时,它就会陷入爬行状态。
最简单的方法是重新安排jail中的定期任务。我给host一个小时的时间来完成定期任务,然后把jail错开五分钟。您可以安排连接到不同存储控制器的jail同时运行。
您可能会发现,从主机而不是jail安排jail定期任务更容易。禁用每个jail的定期(periodic)条目。使用主机的root crontab
并使用 jexec
定期启动。
您还可以审计jail执行的任务。我不想在日常维护中看到jail的网络接口输出:jail没有足够的访问权限来给我提供准确的结果。我唯一关心的接口报告来自主机。我可以在我的日常状态邮件中禁用该输出。主机处理电子邮件吗?如果没有,请停止检查sendmail队列。在jail的 /etc/periodic.conf 中调整所有这些任务。
当您遇到反复出现的性能问题时,请务必检查您的jail是否同时运行计划任务。如果你拥有人类已知的最大、最糟糕的存储阵列和控制器,这并不重要;足够多的流程会让它步履蹒跚。我的四十个数据库jail可以每隔十分钟运行一次转储到文件的备份,没有人会注意到任何事情,但同时进行的数据库转储会中断服务。与定期任务一样,我经常发现从主机的crontab中安排软件维护最简单。
有些问题不太容易解决。
软件不可信。即使是一个有着良好意图和细致编码实践的高技能开发人员,编写的程序在出错时也会着火。他们无能为力;他们用来编写软件的软件也不可信。jail的风险之一是,一所jail可能会要求不公平的宿主资源份额,导致其他jail挨饿。FreeBSD使用RCTL和 rctl(8)
来限制资源消耗。虽然旧版本的RCTL影响了系统性能,但它现在对系统容量和非限制性进程的影响可以忽略不计。
RCTL是一个专门用于限制资源使用的内核特性。您必须在引导时通过在 /boot/loader.conf 中将 kern.racct.enable
设置为 1
来启用它。如果启用了RCTL,RCTL应该会无错误地运行。
xxxxxxxxxx
# rctl
在你有规则之前,它不会产生任何输出。
RCTL通过规则管理资源。使用 rctl(8)
添加、删除和查看规则。规则都有四个组成部分,用冒号分隔:
subject type : subject : resource : action
process
、 user
、 loginclass
和 jail
。主题类型将用户phk与名为phk的jail区分开来。总体而言,这允许您声明诸如“将用户phk限制在一个CPU的50%”之类的规则:
xxxxxxxxxx
user:phk:pcpu:deny=50
在这里,我将jail loghost 限制为每秒10次磁盘读取操作:
xxxxxxxxxx
jail:loghost:readiops:throttle=10
限制大型应用程序可以使用的内存量是有意义的。在这个规则中,我将jail dba1 限制为2GB的内存:
xxxxxxxxxx
jail:dba1:vmemoryuse:deny=2g
规则允许你施加近乎任意的限制。在这个规则中,如果进程1运行超过30秒,我会发送SIGKILL。(您只能将流程规则分配给存在的流程,当流程退出时,规则会自动销毁。)
xxxxxxxxxx
process:1:wallclock:sigkill=30
从现在开始,我们将主要关注jail规则。编写好的规则是关于理解资源和行动。
RCTL支持二十多种资源,但有些资源比其他资源更友好。也许你知道如何计算jail在被随意终止之前应该使用的适当CPU时间,但我不知道。对jail的刑期设定上限同样是一个棘手的问题。或者,您可能是不幸需要限制jail可以使用的 sysvipc
内存量或限制专用于 sysvipc
数据类型之一的内存量的人之一。RCTL可以做到这一点,但这肯定是不常见的。我们将重点介绍我认为最有用的资源:处理器、内存和磁盘吞吐量。一旦你理解了这些规则,你就可以限制其他资源。
pcpu
资源允许您限制jail可以访问的CPU功率,以处理器的百分比衡量。100的限制意味着“不超过一个处理器”,而50的限制意味著“不超过半个处理器”。我们将在本章后面的“CPU集”中探讨另一种CPU限制方法。
vmemoryuse
资源限制了物理内存(RAM)和交换jail可以使用的总字节数。可以通过 memoryuse
和 swapuse
资源分别限制物理内存量并交换可能访问的jail,但也可以通过这种方式对磁盘进行冲击。你最好设置jail可以访问的最大内存量,让内核解决分配问题。
RCTL为磁盘I/O提供了四种资源:readbps
、writebps
、 readiops
和 writeiops
。每个都是关于限制读写,并以每秒字节数或每秒I/O操作数为单位进行测量。使用最不让你烦恼的测量方法。
RCTL支持的五个操作是 deny
(拒绝)、throttle
(限制)、 signal
(信号)、 log
(日志)和 devctl
。并非所有行动对所有资源都有意义。
deny
操作告诉内核拒绝资源请求。对于一条规则,你只能有一个拒绝操作:你不能既拒绝2GB的jail内存,又拒绝3GB的jail内存。内核不能拒绝某些资源类型,如各种时间度量。即使服务器锁定,时钟也会继续移动。内核不能拒绝磁盘I/O请求——好吧,理论上可以,但软件不是为了优雅地处理拒绝而编写的,所以RCTL不支持它。
虽然你不能拒绝磁盘I/O请求,但你可以降低它们的速度。throttle
操作可以让你减慢内核分配资源的速度,给程序提供关于其限制的软反馈。throttle
操作对处理器时间或内存等资源没有意义——无论是否存在——但 throttle
非常适合磁盘活动。
signal
操作向进程发送信号,使您可以SIGTERM或SIGKILL错误的进程。此操作对流程规则非常有用。您可以为一个流程设置多个规则,只要您使用不同的信号。
log
操作向syslog发送警告。如果您阅读了系统日志,那么在jail接近上限时创建一个记录规则是获得即将到来的资源短缺预警的好方法。随心所欲地制定日志规则。
最后,devctl
操作会触发一个 devd
规则,允许您在超过限制时采取任意操作。有关开发事件的详细信息,请参阅 rctl(8)
。
通过设置等号来定义动作中的限制。
现在您已经了解了各种操作,让我们组装一些规则。
我的jail logdb 一直在消耗它可以窃取的所有内存,我需要将其限制在2GB或更少。我的规则中的前两个条目将是 jail:logdb:
。控制jail可以访问的虚拟内存的资源是 vmemoryuse
,所以这是我的第三个条目。action
是最棘手的部分。我不想记录违规行为或发送 devd
事件:我想直截了当地拒绝为这个jail分配更多内存,让里面运行的软件来处理后果。这是一种否认行为。
使用 rctl -a
添加RCTL规则:
xxxxxxxxxx
# rctl -a jail:logdb:vmemoryuse:deny=2g
通过运行不带任何参数的rctl查看所有规则:
xxxxxxxxxx
# rctl
jail:logdb:vmemoryuse:deny=2147483648
请注意,限制已更改。虽然 rctl
在命令行上接受 2g
,但它会自动将其转换为字节。如果您想要人类可读的输出,请使用 -h
标志:
xxxxxxxxxx
# rctl -h
jail:logdb:vmemoryuse:deny=2048M
使用 -r
标志和规则来删除指定的规则。您不需要在规则中指定操作,但如果您这样做,它必须与规则的操作完全匹配:
xxxxxxxxxx
# rctl -r jail:logdb:vmemoryuse:deny=3g
rctl: failed to remove rule 'jail:logdb:vmemoryuse:deny=3g': No such process
我们没有规则对此jail的虚拟内存大小定义3GB的内存限制。这是2GB的限制。请从命令行中删除该操作,然后重试:
xxxxxxxxxx
# rctl -r jail:logdb:vmemoryuse
将RCTL规则存储在 /etc/rctl.rules 中。启用 rctl
服务在启动时读取它们:
xxxxxxxxxx
# service rctl enable
您还可以通过 exec.created
参数对每个jail进行设置。
在复杂的主机上,RCTL规则可能会变得很长。虽然规则必须包含所有四个组件,但您可以让 rctl
根据部分规则显示和删除规则。这些过滤器告诉 rctl
只显示或删除提供的规则组件。像 jail:
、::vmemoryuse:
或 :logdb::
这样的字符串是一个过滤器。在这里,我告诉 rctl
只给我看jail的规则:
xxxxxxxxxx
# rctl jail:
您必须使用足够的冒号,以便 rctl
能够找出您指定的规则的哪一部分。下面例子中,我想查看应用于资源 vmemoryuse
的所有规则。我给出前两个冒号,因为 rctl
需要知道我不是在谈论jail vmemoryuse
,但我不需要最后一个冒号:
xxxxxxxxxx
# rctl ::vmemoryuse
我一次可以打多场比赛。假设我希望所有的规则都适用于jail:
xxxxxxxxxx
# rctl jail::readiops
您可以删除基于筛选器的规则。我受够了资源的限制,决定让jail为统治地位而战。
xxxxxxxxxx
# rctl -r jail:
所有jail现在都没有资源限制,但将 PID 1
限制在30秒寿命的示例规则仍然存在。
iocage为每个RCTL资源提供一个参数。它在启动jail时增加了RCTL规则,并在关闭jail时删除了这些规则。你不必担心RCTL主题类型——它总是“jail”。同样,RCTL主题总是你设置参数的jail。你只需要关注RCTL的资源和行动。
在这里,我想将jail www1 的 vmemoryuse
资源设置为2GB:
xxxxxxxxxx
# iocage set vmemoryuse="deny=2g" www1
虽然即使jail不存在,jail的通用RCTL规则仍然存在,但iocage在开设jail时增加了RCTL规则,并在jail关闭时将其删除。
现代服务器可以有几十个处理器和太多的处理器内核。FreeBSD的调度程序在将进程分配给内核方面做得很好,你应该让它来完成它的工作。虽然RCTL允许您限制一个jail可以垄断多少处理器时间,但可能需要为特定的jail锁定一组处理器内核,将jail限制在可用处理器的子集上运行,或者保留一些内核供主机专用。FreeBSD允许您通过 cpuset(1)
干预这些细节。
cpuset
的效果和影响与非统一内存访问(Non-Uniform Memory Access——NUMA)密切相关。NUMA基本上是考虑流程数据的哪些部分存储在系统中的哪个位置。如果一个进程上次在CPU 19上运行,则该进程的数据可能仍在CPU 19的缓存中。将进程移动到CPU 204意味着将系统中的所有数据拖动到新处理器。FreeBSD的NUMA功能正在积极开发中,虽然您会看到对几个NUMA策略的引用,但您应该忽略它们。满足于将一个jail绑定到一组处理器上,让FreeBSD处理调度进程的细节。
这些CPU限制只有在jail主机在真实硬件上运行时才有意义。管理员故意向客户机隐瞒底层硬件的细节。配置有192个核心的虚拟服务器可能在只有一个核心的服务器上运行。
在将jail连接到处理器之前,您应该了解您拥有的处理器。
在引导过程中,FreeBSD探索可用的CPU并发现它们是如何相互连接的。调度器使用此信息高效地将进程分配给处理器。在真实的非虚拟硬件上运行时,您可以获得相同的信息,并使用它来合理地将jail分配给处理器。sysctl kern.sched.topology_spec
显示了FreeBSD认为您的主机在处理器和内核方面的情况。以下是您在小型主机上可能会发现的内容:
xxxxxxxxxx
# sysctl kern.sched.topology_spec
kern.sched.topology_spec:
0
处理器被分成若干组。这显示了一个组,组1。在该组中,处理器1是核心编号0。此主机有一个处理器,一个内核。
一个可能更现实的jail服务器的主机看起来更像这样:
xxxxxxxxxx
kern.sched.topology_spec:
0, 1, 2, 3, 4, 5, 6, 7
此主机有八个处理器。它们的编号是从0到7。往下看第一组:
xxxxxxxxxx
0, 1
THREAD groupSMT group
该组包括两个处理器内核,编号为0和1。它们被标记为SMT(Simultaneous Multi-Threading——同步多线程)。它们是同一CPU上的两个逻辑核心。沿着整个处理器列表往下看,你会看到所有八个处理器都是成对存在的。该主机有四个处理器芯片,每个芯片有两个处理器内核。当你指定一个jail只运行一个核心时,这些信息并不是非常重要。但是,如果您授予jail多个内核,通常将它们放在单个处理器上以利用任何共享缓存空间是有意义的。
CPU集或 cpuset
是一个或一组进程可以分配给的处理器列表。每个进程都属于一个 cpuset
。进程通常属于 cpuset 1
,即根cpuset,它授予所有处理器访问权限。使用 cpuset -g
查看根数据集:
xxxxxxxxxx
# cpuset -g
pid -1 mask: 0, 1, 2, 3, 4, 5, 6, 7
pid -1 domain policy: first-touch mask: 0
第一行列出了此cpu中可使用的处理器。第二行给出了分配进程的NUMA策略。
每个jail在创建时都会自动获得自己的 cpuset
,但它是根cpuset的副本。使用 -j
标志查看分配给jail的cpuset:
xxxxxxxxxx
# cpuset -g -j logdb
jail 20 mask: 0, 1, 2, 3, 4, 5, 6, 7
jail 20 domain policy: first-touch mask: 0
这个jail可以访问所有处理器。让我们改变这一点。使用 -c
标志更改现有的 cpuset
。 -l
标志允许您指定cpuset可以访问哪些处理器。在这里,我让 jail logdb 的cpuset只允许访问处理器4和5:
xxxxxxxxxx
# cpuset -j logdb -cl 4-5
然后使用 cpuset -g
查看结果:
xxxxxxxxxx
# cpuset -gj logdb
jail 25 mask: 4, 5
jail 25 domain policy: first-touch mask: 0
这个jail仅限于4号和5号处理器。
如果你有多核处理器,你应该试着将某个jail限制在同一处理器上的核上,但现实会让这变得困难。如果必须分配不在整齐范围内的处理器,请用逗号分隔它们。在这里,我将jail logdb 限制为处理器1、3、6和7:
xxxxxxxxxx
# cpuset -j logdb -cl 1,3,6-7
您可以减少jail可以运行的处理器数量,但不能添加以前禁止的处理器。在这个例子中,我可以再次运行 cpuset
将允许的处理器减少到 6
和 7
,但我无法将处理器 0
添加到允许的列表中。增加允许的处理器数量需要重新启动jail。
请注意,内核不是进程,也不属于cpu。如果主机受到网络攻击或计算昂贵的存储加密的攻击,内核会将痛苦分散到主机的处理器之间。
在命令行中配置cpuset看起来很简单,但我们如何将其连接到jail中?
虽然 jail.conf 没有定义 cpuse
t的参数,但很容易通过 exec.created
触发。您不想使用exec.poststart
,因为进程已经启动,可能会使其他处理器过载。在这里,我希望jail logdb 只使用处理器 6
和 7
:
xxxxxxxxxx
logdb {
exec.created="cpuset -j logdb -cl 6-7";
…
通过将 cpuset
命令设置为默认值,您可以将jail全局限制为处理器的一个子集:
xxxxxxxxxx
exec.created="cpuset -j $name -cl 2-5";
这将处理器 0
和 1
专门保留给主机。无法保证主机将只使用处理器 0
和 1
——就像内核一样,主机会做它必须做的事情来继续运行。这就是每个虚拟化系统的工作原理。但是内核极有可能在这些空闲的处理器上调度主机进程。
只读参数 cpuset.id
给出当前cpuset的编号。不过,如果你需要访问cpuset,使用 -j
和jail名称要容易得多。
iocage中的 cpuset
属性允许您直接设置jail可以访问的处理器:
xxxxxxxxxx
# iocage set cpuset=4-7 www1
最常见的用例是将所有jail限制在一组特定的处理器上,将其他处理器留给主机。调整默认设置以实现此目的。如果你有一个可以访问其他处理器的jail,请单独设置:
xxxxxxxxxx
# iocage set cpuset=2-5 default
# iocage set cpuset=6-7 dba2
据推测,处理器 0
和 1
留给主机。
jail是存在于一组更改的名称空间中的一组文件和程序。有时,您可能不希望更改所有这些名称空间,或者您可能希望更改文件。jail可能不需要完整的FreeBSD用户区。您可能希望有意共享网络堆栈,甚至根文件系统。这听起来可能很奇怪,但在系统管理中,今天的奇怪是明天的正常。
这是一个长期存在的讨论:如果你不在jail里运行SSH服务器,为什么要安装一个?jail应该有一个编译器吗?如果没有,请删除它。所有这些共享库——丢弃程序不需要的一切!这让人回想起手工制作的 chrooted
守护进程的黄金时代,在那个时代,攻击者会闯入一个服务,却发现自己被锁在一个只有 /dev/null 陪伴的贫瘠洞穴里。
这并不是说这很难。一旦你玩了几次 ldconfig
并了解了 /bin/sh -x
,你就可以成功地将用户空间减少到最低限度。问题是,flensing
非常乏味,每次FreeBSD项目发布新的安全更新时都必须重新测试。
您还可以尝试使用各种 WITHOUT
选项构建自定义用户区。这样的构建往往很脆弱,每次FreeBSD进行安全更新时都需要重建,但它们肯定是一种选择。
FreeBSD项目有一个即将到来的解决方案:pkgbase
,一个打包的基础系统。它将基础系统分解为大约800个包。这些包都有依赖关系信息,因此您可以安装尽可能简单的系统,并确保只获得所需的内容。最初计划与FreeBSD 12集成,但目前已被推迟到FreeBSD 13。它具有很高的可用性,在生产中被很多人使用,但FreeBSD开发人员对质量非常执着。(作为参考,vnet
出现在FreeBSD 8中。尽管使用量很大,但直到FreeBSD 12才将其设置为默认值。)
如果你想安装一个最小的系统,在互联网上搜索“FreeBSD pkgbase”并一起玩。我没有举例子,因为 pkgbase
还没有最终确定;你得等我的包装书。如果你碰巧发现了一个bug,修复它将比花几个小时调试你个人提交的用户空间要容易得多。
通过将ip4和ip6参数设置为 inherit
(继承),可以允许jail访问主机的网络命名空间。这允许jail对主机上的每个IP地址进行访问。这违背了jail的初衷之一,但在网络资源有限的主机上是有道理的。
大多数虚拟服务器公司都很乐意为您提供额外的IPv4地址,只需支付高利贷的月费。我不想为每个jail都支付这笔费用。我可以将jail绑定到环回接口上的私有地址,并使用数据包过滤器来处理往返于它的NAT流量。我可以为每个jail分配自己的VNET,用克隆的环回接口将它们桥接在一起,然后使用NAT。您的安全模型可能需要这样做。
不过,如果你的安全模型不要求在jail中进行这种隔离,可以考虑让jail继承主机的网络堆栈。考虑一下这个标准的jail(尽管你可以用iocage做同样的事情)。我已经将所有重要参数(如路径和主机名)设置为默认值,剩下的就是。
xxxxxxxxxx
www1 {
ip4=inherit;
}
这使jail可以访问jail上的两个IP地址,即公共地址和 127.0.0.1
。jail无法ping,因为它无法访问 raw sockets
。它无法嗅探数据包,因为没有 /dev/bpf 。它的所有文件都被监禁了。如果有人闯入jail,他们将无法联系到host。
当然,这也有缺点。配置错误的jail可能会尝试绑定到主机预期使用的TCP端口。不过,你的主机应该在启动jail之前启动所有服务。您必须跟踪哪些守护进程使用哪些端口,就像使用防火墙一样。如果你有一个复杂的网络环境,你可能不希望jail继承整个网络堆栈,但你可能还有额外的地址要分配给jail,这使得整个问题变得毫无意义。
我并不是说你应该使用继承的网络堆栈,而不是NAT绑定到环回地址的jail。我的意思是,你应该看看你的具体应用程序,并考虑在这种特定情况下,设置环回绑定jail是否比完全继承的网络堆栈有所改进。当我在jail里使用一个被剥离的用户区时,我发现继承堆栈是最明智的。
如果你只有一个IP,继承的堆栈不够,你真的应该一路给每个jail自己的vnet。创建环回地址并连接到网桥。给网桥一个IP地址,然后将所有vnet桥接到它。设置防火墙并继续。
有时,你真正关心的jail的唯一部分是共享进程名称空间。您没有试图保护主机上的文件,也没有限制网络地址。您只想将一组流程视为单个实体。也许他们需要一起运行,或者共享资源限制。jail允许您创建一个与Solaris控制组或 cgroup
非常相似的实体,并对其进行离散管理。
考虑一下这个jail定义,它适用于运行一个非常复杂的Java Application Server的主机,该服务器包括一大堆不同的守护进程和无数进程。同样,我从全局默认值中获取了一堆配置,包括 ip_hostname
提供的网络。
xxxxxxxxxx
jas1 {
path=/;
mount.nodevfs;
exec.start="/bin/sh /etc/rc.java-apps.sh start";
exec.stop="/bin/sh /etc/rc.java-apps.sh stop";
}
jail可以访问整个文件系统。jail可以立即获得任何操作系统或软件更新。jail中的进程只能访问其命名空间中的其他进程,但它们可以访问本地套接字以与其他软件通信。
如果这个jail挂载了一个设备文件系统,它将覆盖主机的 /dev 。主机将失去其设备节点。这是jail不需要 devfs
的罕见案例之一。
无论服务的纠结多么复杂,它们都是一个单一的实体。如果我需要重启应用服务器,我会关闭jail。内核明确地终止了jail中的每个进程,无论是否优雅。当我重新启动jail时,每个程序都以确定的顺序启动。我可以通过CPU集和RCTL来限制jail。
我在这里举了几个不同的例子,但真正的问题是,你可以通过多种方式利用jail。命名空间转换不仅仅是创建轻量级虚拟机;它创建了各种高度可调的轻量级虚拟机。
jail是一个多方面的多功能工具。小心使用,它们会让你的生活更轻松。如果使用不当,你会砍掉一根手指,在任何人给你绷带之前,你必须解释这一团糟。