[关闭]
@Tmacy 2016-08-23T07:00:02.000000Z 字数 19416 阅读 1374

Systemd for Administrators 2

linux systemd


以下是systemd系列博客的翻译,博文为systemd的作者Lennart Poettering 所写,本译文仅供学习使用。
附录中有其他学习资料供参考


Part 7

blame 游戏

Fedora 15 是第一个支持systemd的Fedora。我们主要目标是把集成所有的东西并且良好运行。我们在Fedora16上要进一步优化并且加快systemd的推广。为了做好准备,我们实现了几个工具(已经在Fedora15上应用),可以帮助我们准确找出在启动时最大的问题是什么。这个博文我希望阐明如果找到系统启动慢的原因,以及如何去处理。我们想帮你找到系统组件问题真正的原因并且解决它。

第一个工具很简单:systemd会在启动完成后,自动写log信息到syslog/kmsg。

  1. systemd[1]: Startup finished in 2s 65ms 924us (kernel) + 2s 828ms 195us (initrd) + 11s 900ms 471us (userspace) = 16s 794ms 590us.

从上面可以看出:内核初始化花了2s时间,之后是初始化RAM disk(initrd,例如dracut)。在initrd上花费大概不到3s。最后,initrd启动系统init守护进程(systemd)后,花费了不到12秒的时间启动了用户空间。从boot loader启动内核到systemd完成所有启动任务所化时间少于17s。这相当不错,而且简单易懂--并且也容易误解:它不包括初始化你的GNOME会话的时间,因为其不属于系统启动范畴。并且,多数情况下,这只是属于systemd完成后所做的动作。一些守护进程仍然会在启动后继续完成他们的任务。因此,这个时间统计对与通常的启动速度的指导是有价值的,但它不是通常用户感觉到的启动时间。

但是,这很粗略。它无法告诉我们系统组件会占用多少时间。为了解决此问题,我们提供了一个工具:systemd-analyze blame:

  1. systemd-analyze blame
  2. 6207ms udev-settle.service
  3. 5228ms cryptsetup@luks\x2d9899b85d\x2df790\x2d4d2a\x2da650\x2d8b7d2fb92cc3.service
  4. 735ms NetworkManager.service
  5. 642ms avahi-daemon.service
  6. 600ms abrtd.service
  7. 517ms rtkit-daemon.service
  8. 478ms fedora-storage-init.service
  9. 396ms dbus.service
  10. 390ms rpcidmapd.service
  11. 346ms systemd-tmpfiles-setup.service
  12. 322ms fedora-sysinit-unhack.service
  13. 316ms cups.service
  14. 310ms console-kit-log-system-start.service
  15. 309ms libvirtd.service
  16. 303ms rpcbind.service
  17. 298ms ksmtuned.service
  18. 288ms lvm2-monitor.service
  19. 281ms rpcgssd.service
  20. 277ms sshd.service
  21. 276ms livesys.service
  22. 267ms iscsid.service
  23. 236ms mdmonitor.service
  24. 234ms nfslock.service
  25. 223ms ksm.service
  26. 218ms mcelog.service
  27. ...

这个工具列举出那个systemd的单元在初始化过程中需要多长时间,最长的会在最上面。我们可以看到在这个启动列表中超过一秒的服务有两个:udev-settle.service和cryptsetup@luks\x2d9899b85d\x2df790\x2d4d2a\x2da650\x2d8b7d2fb92cc3.service。这个工具也很容易被误解。它不会列出为什么这个服务会花费这么多时间,这个取决于服务本身所做的事情。而且注意到列举的时间很可能是并行的,比如两个服务会在同时初始化并且初始化这两个服务时间会远远少于分别单独启动的时间总和。

让我们来仔细看一下花费时间最长的服务:udev-settle.service。为啥它会占用这么多时间来初始化,以及我们能做什么呢?这个服务实际上做的事情不多:它只是等待这个设备被udev处理完之后就退出了。设备可能会比较慢。在这个例子,设备占用超过6s的时间的原因是3G模块编译到机器里了,而它却没有插入sim卡以至于等待检测软件反馈花费了很长时间。这个软件检测ModemManager的一部分,能够让Networkmanager轻松连接到3G网络。显而易见,时间花费在ModemManager的如此之慢的软件探测上。但是事实上不是这样:硬件传感器经常会很慢,并且在这个例子中,ModeManager实际上面对一个很慢的3G硬件。对于硬件传感器,硬件探测的过程中每个独立的传感器进行探测肯定是要花费时间的。实际上的元凶是其他的方面:实际上我们确实需要等待这个探测过程,换句话说,udev-settle.service是我们启动不可缺少的一部分。

那么,为啥udev-settle.service是我们启动过程中不可少的一部分呢?好吧,它其实不必一定启动。它是被Fedora的存储设置逻辑所带出来的,准确的说,是被LVM,RAID和Multipath设置脚本。这些存储服务至今未实现硬件检测和探测方面的工作:它们期盼在初始化时就已经完成了所有硬件的探测工作,因此它们简单遍历可用磁盘的列表然后做它们的工作。然而,在现代机器上,硬件实际上不是如此工作的:硬件可以随时连接与断开,在启动过程中或者运行过程中。对一些技术而言,根本不可能知道硬件列表何时准备完成(例如:USB或者iSCSI)。这样等待所有的设备出现并且就绪一定是固定的时间,这个时间是它假定所有硬件就绪的时间。这个例子中,存储脚本强制我们延迟启动,等待所有可能的设备加载并就绪--即使这些设备我们实际上并不需要。尤其是我们实际上并没有用到LVM,RAID或者Multipath

了解了这些,我们现在知道应该关闭udev-settle.service,既然我们没有用到LVM,RAID以及Multipath,下次启动就可以加快启动改了。

  1. # ln -s /dev/null /etc/systemd/system/udev-settle.service
  2. # ln -s /dev/null /etc/systemd/system/fedora-wait-storage.service
  3. # ln -s /dev/null /etc/systemd/system/fedora-storage-init.service
  4. # systemctl daemon-reload

重启后,我们发现时间比之前快了1s,为什么只是1s?好吧,第二个费时的服务是cryptsetup:机器有个加密的/home目录。出于测试的目的我已经在磁盘的一个文件中存储了密码,以至于不会由于我很慢的输入而拖慢启动时间。cryptsetup工具仍然会占用超过5s的时间来加密磁盘。懒于修复cryptsetup,我们只是修补它:在执行bootup和启动系统服务之前,systemd通常会等待所有在/etc/fstab中未标记noauto选项的文件系统出现,被检查修复并且挂载。至于/home,我们知道它是最后被用到的(比如当用户登陆的时候)。一个简单的修补措施是,让所有挂载点在启动时都可用,不必等到cryptsetup(磁盘加密),磁盘检查以及挂载完成。你会问如何让一个挂载点在实际挂载文件系统之前可用?好吧,systemd程序有这样的魔力:在/etc/fstab中以comment=systemd.automount的形式描述挂载点。如果你这样来写,systemd会为/home创建一个自动挂载点,当第一次访问文件系统时,它仍然不是可用的文件系统,systemd会等待设备,磁盘检查以及挂载磁盘。

这里是修改/etc/fstab之后的结果:

  1. systemd[1]: Startup finished in 2s 47ms 112us (kernel) + 2s 663ms 942us (initrd) + 5s 540ms 522us (userspace) = 10s 251ms 576us.

很好,经过改进我们提高了接近7s的启动时间。并且这仅仅是修改了两个最耗时的问题,只要再细致分析,还有更大的提升空间。实际上,在不同的机器上,一个超过两年的X300笔记本电脑,我们可以做到4s的启动时间,包括完整的GNOME环境。而且还有提升的空间。

systemd-analyze blame 是个很好的简单工具来跟踪启动缓慢的服务。然而有个很大的问题:他不能很好的形象表现出服务的并行启动。我们为大家带来了systemd-analyize plot工具。想这样:

  1. systemd-analyze plot > plot.svg

它会创建图像,显示服务以及涉及的其他服务所花费的启动时间。它目前还没有准确的可视化一个服务等待那些其他服务,尽管如此也可以猜出一部分内容。

来看下我们所做的两个小小的优化所带来的影响,对比两个图片,第一个是优化前,第二个是优化后:
blame1blame2
消息灵通的读者可能知道这个与Michael Meeks' bootchart的关系。
这个plot与bootchat显示相似的图标。Bootchart是更强大的工具。它画出在启动过程中所有细节,CPU和IO的使用情况。systemd-ananlyze plot则偏重更高层次的数据,那个服务启动时花费多少时间,它需要等待什么。如果你同时使用他们,你会拥有一个快速分析启动时间的强大工具套件。

现在,在你使用这些工具开始优化你系统启动时间之前,思考两遍。这些工具给你原始数据,不要误读。我上述优化的示例希望表达的是,对于启动慢的问题,不是归咎于udev-settle.service或者ModemManager探测。归咎于子系统把这个服务放在了首位。这才是需要修正的地方。找到真正的原因所在并修复它。

本文的要点:
- systemd-analyze 是强大的工具,systemd预装了这个
- 不要误读工具产生的数据!
- 通过两个简单的修改,你可以将启动速度提高7s
- 如果没有恰当的处理动态硬件,修改你的程序
- Fedora默认安装的企业级存储管理可能需要重新思考


Part 8

新的配置文件

在systemd强大新特性中,其中一个是systemd具有一系列模块化的启动引导服务,这些服务是用简单,快速,并行化和稳定的C语言写的,取代了之前“长篇小说式”的分布在各处的shell脚本。我们的小Project Zero Shell[1]已经取得完全的成功。我们目前包含了大部分的桌面以及嵌入式发行版所需的一切,增加了一大块服务需要的部分:
- 检查和挂载所有文件系统
- 升级和启用文件系统磁盘配额(quota)
- 设置主机名称
- 配置回环网络设备(loopback network device)
- 加载SELinux策略并且在启动时从新标记/run和/dev,如果需要的话
- 在内核中注册额外的二进制形式,例如java,Mono和Wine二进制
- 设置系统本地环境变量
- 安装终端字体和键盘映射
- 创建,删除和清理临时文件和目录
- 把从/etc/fstab的挂载点应用到预挂载API VFS(虚拟文件系统)
- 应用sysctl 内核设置
- 收集和重播预读取信息
- 升级utmp启动项和关机记录
- 加载和保存随机种子
- 静态加载指定内核模块
- 安装加密硬盘和分区
- 在串行内核控制台上自动产生tty
- plymouth的维护
- 机器ID的维护
- 设置系统的UTC时区

在标准的Fedora15的安装程序中,在启动引导时期,只有少量的传统的和存储服务仍然需要shell脚本。如果你不需要这些,你可以简单的禁用它们,然后享受你无脚本的启动(正像我每天做的一样)。systemd的无脚本启动给你一个全新的Linux体验。

很多小的组建都是通过在/etc下的配置文件来进行配置的。它们其中一些是在发行版中是标准化的,因此用C来实现它们显然是非常简单。例如:/etc/fstab,/etc/crypttab,或者/etc/sysctl.conf。然而,对于哪些不标准的文件或目录,我们则被迫在源码中增加了很多#ifdef来支持其在不同发行版中保存在不同的地方。所有的这些配置文件有个共同点,它们及其简单并且没有什么理由让发行版区别它们:它们都做相同的事情,只是有点不一样。

为了改进这个情况以及从systemd强制统一中获得好处,我们决定只以回调的形式读取每个发行版的配置,并且介绍新的配置文件作为配置的主要来源,无论在那都适用。当然,这些标准配置文件不应是新的发兵,只是已使用的发行版特定配置文件的标准化。这里有一点概述,关于systemd支持的新的通用配置文件:

我们很明确要引导你在配置工具中使用新的配置文件:如果你配置前端往这些文件中写,而不是旧的文件,会自动在Linux发行版见变得兼容,并且你在推动Linux标准化。这会让用户和管理员更易使用和理解系统。当然,现在只有基于systemd的系统才会使用这些文件,不过很多主流系统都已经使用了systemd。这有点鸡和蛋的问题:一个标准变为一个标准需要被使用。为了推动所有人参与标准化这些文件,我们计划在systemd中放弃支持旧的配置文件。这意味着可能慢慢的一点点来时应新的模式。但是目标时很明确的,只使用一种配置方式。

很多配置文件不只与配置工具相关,而且(有时候主要是)与上游项目。例如我们要一些项目,例如Mono Java,或者Wine,将它们从上游系统中安装到/etc/binfmd.d目录下。没有必要每个发行版的下游要对不同的二进制支持,这样你的平台在每个发行版中都相同了。类似的事情也发生在所有需要启动和清理运行时期文件的软件,例如放在/run目录下(曾经放在/var/run)。这些项目应该放弃把配置文件放在/etc/tmpfiles.d目录中,这个习惯也是从上游系统遗留下来的。这样会帮助加速系统启动,由于每个项目都有独立SysV shell脚本来执行微不足道的东西,类似在启动时注册一个二进制格式或者创建/移除临时文件都不再需要了。另一个上游支持很给力的例子:类似X11会从/etc/vconsole.conf中读取默认键盘映射。

当然,我不会怀疑所有人都乐意放弃选择配置文件的名字和格式。最后我们不得不从这些令人信服的选择中挑选一些出来。文件的格式尽可能简单,并且容易读写,即使在shell脚本中。这就是说,/etc/bikeshed.conf当然也可能是一个给力的配置文件的名字。

那么,帮助我们标准化Linux吧!使用这些新的配置文件!在上游,下游以及所有的发行版中使用。

oh,以防你好奇:我们与各种各样的发行版中不同的人讨论过以上所有文件。即使在systemd系统之外,已经开始推动支持这些文件。


Part 9

/etc/sysconfig 与 /etc/default

本文主要内容是关于/etc/sysconfig与/etc/default在不同发行版上的看法,以及为什么我相信它们应该淡出。就像我所说的这博客只是我个人观点,并不代表Fedora项目或者我的东家。关于/etc/sysconfig的主题总会引起争论,我希望这个博文可以帮我解释systemd如何来考虑这些文件的。

介绍一点历史背景:当/etc/sysconfig被引进时我没有参与,可以这么说,很早以前,是Red Hat 与SUSE参与其中。最后,语义上类似的/etc/default被Debian引入。其他的发行版也会自己做一个类似的目录,只是名称不一样。实际上,即使Unix-OS们也支持一个类似的目录(例如SCO)。那么,即使一个想这样的目录可以被所有Linux用户和Unix用户熟知,它却从未标准化,也不在POSIX或者LSB/FHS。这些目录就是用来区分不同发行版的。

/etc/default与/etc/sysconfig的语法很类似。几乎所有文件存储在这些目录中有一个共同的特点,它们是包含环境变量声明的shell脚本。这些文件大部分源自相同名字的SysV init脚本。Debian Ploicy Manual 和Fedora Packing Guidelines中有对这些文件的使用建议。然而所有发行版并没有遵循这些。例如它们没有一个匹配SysV init的脚本,或者即使连shell脚本都没有。

为什么没有这些脚本?在SysV系统中,服务是通过在/etc/rc.d/init.d(或者其他类似目录)的init脚本启动的。/etc是存放系统配置文件的地方。本来这些init脚本应该由管理员定制,但是随着情况越来越复杂,大部分发行部不再当它们是真实的配置文件,只是一些特殊的程序。为了定制简单和升级安全,一些定制二进制程序被放到这些配置文件中。

让我们快速看一下你可以用这些文件配置什么。这有个列表,列举了通过环境变量设置可以配置的所有内容:

现在让我们来看:/etc/sysconfig哪里出错了?在systemd里,这些文件为什么要慢慢废弃?

用什么来取代?这里有几个在systemd系统下如何长期使用这些文件的建议:

当然,有一个理由足以来支持使用这些文件更长时间:升级的兼容性。但是这真的是我可以找出来的。有足够的理由来保持兼容性,但是我认为至少在新的包里逐步淘汰这些文件也是不错的。

如果兼容性很重要,systemd会允许你读取这些配置文件,即使如果你也使用本地systemd单元文件。如果你sysconfig文件只有简单的选项EnvironmentFile=-/etc/sysconfig/foobar(参见:systemd.exec(5))可能会用来导入设置到环境变量并且在命令行中使用。如果你需要编程语言使这些配置生效,那么使用像shell一样的语言。例如:在/usr/lib/<your package>/里放一个小脚本来读取这些文件,然后用守护进程的程序来执行。使用这些脚本作为但源文件中ExecStart=的参数,而不是实际的守护进程程序。

这是以上内容。谢谢关注!


Part 10

实例化的服务

在Linux/Unix中的大部分服务都是单例服务:实际上只有一个Syslog,Postfix或者Apache实例同时运行在系统中。另一方面一些主机上会运行一些服务的多个实例。例如,一个互联网服务,类似Dovecot IMAP服务,会在不同的IP端口,IP地址上运行多个实例。一个更普通的存在于各种安装器上的例子是getty,一个在每个TTY运行一次并显示一个提示符的服务。在大部分系统上,这个服务对每个虚拟终端,从tty1到tty6都实例化一次。在一些依赖管理员配置或启动参数的服务上,一个额外的getty是一个串行或虚拟终端的实例化。systemd中另一个通用实例化服务是fsck,文件系统检查器对于每个需要检查的块设备会被实例化一次。最后,在systemd socket中,每个已激活链接服务(考虑经典的inetd)也会通过实例化的服务来实现的:为每个新的链接创建一个新的实例。在本文中,我希望分析一点systemd如何实现实例化服务,以及作为系统管理员如何利用它们。

服务实例可以按需动态创建。没有深入的配置你也可以轻松在一个串口上利用systemd start 命令启动一个新的getty实例:

  1. # systemctl start serial-getty@ttyUSB0.service

如果一个类似上述命令在systemd中运行,会先寻找一个单元配置文件。如果服务文件没有找到(并且通常它不是你使用的实例服务),那么通过搜索模版名称的结果,实例ID会从名称和单元配置文件中移出。换句话说,在上面的例子中,如果serial-getty@ttyUSB0.service单元配件没有找到,serial-getty@.service会被加载。这个单元模版文件是这个服务所有实例的通用文件。对于串行getty,我们建立了一个模版单元文件(/lib/systemd/system/serial-getty@.service),类似:

  1. [Unit]
  2. Description=Serial Getty on %I
  3. BindTo=dev-%i.device
  4. After=dev-%i.device systemd-user-sessions.service
  5. [Service]
  6. ExecStart=-/sbin/agetty -s %I 115200,38400,9600
  7. Restart=always
  8. RestartSec=0

(注意,这个单元模版文件实际上更长。如果感兴趣可以查看完整文件,其中包含了与SysV兼容的额外指令,来清空屏幕和从TTY设备中移出之前的用户。为了简洁明白的说明问题,我截取了上述几行内容)

这个文件于其他单元文件看起来很像,只有一个不同之处:变量%I和%i出现在多个位置。在单元加载的时候,%I和%i被systemd替换为服务的实例标识。在我们的例子中,如果一个服务的标识为serial-getty@ttyUSB0.service,变量%I和%i会被替换为ttyUSB0.如果用sytemctl status 查看serial-getty@ttyUSB0.service,你会发现这些内容被替换:

  1. $ systemctl status serial-getty@ttyUSB0.service
  2. serial-getty@ttyUSB0.service - Getty on ttyUSB0
  3. Loaded: loaded (/lib/systemd/system/serial-getty@.service; static)
  4. Active: active (running) since Mon, 26 Sep 2011 04:20:44 +0200; 2s ago
  5. Main PID: 5443 (agetty)
  6. CGroup: name=systemd:/system/getty@.service/ttyUSB0
  7. 5443 /sbin/agetty -s ttyUSB0 115200,38400,9600

这是systemd在实例化服务的核心理念。正如你所见,systemd提供了一个非常简单的模版体系,可以用来动态的按需实例化服务。以下是几个使用方面的要点:

你可能实时实例化哪些在文件系统中.wants目录下符号链接。例如,为了确保在ttyUSB0上的串口getty自动启动,创建一个软链接:

  1. # ln -s /lib/systemd/system/serial-getty@.service /etc/systemd/system/getty.target.wants/serial-getty@ttyUSB0.service

systemd会用指定的实例名称来实例化一个单元文件的符号链接。

你不能不指定一个实例标识就实例化一个单元模版。换句话说systemctl start serial-getty@.service在没有提供实例名称时,肯定会失败。

有时候对于一个具体实例,脱离模版是很有用的。这种情况下,systemd在使用模版名称之前,首先会搜索完整的实例文件名称:确保在/etc/systemd/system下放一个有完整实例名称单元文件。它会覆盖通用的模版。

单元文件会在一些地方使用%i,或者使用%I。你可能想知道其中区别。%i会被准确的实例标识替换。而%I被第一个通过非转义算法的标识符所替代。对于一个简单的实例标识符的,例如ttyUSB0,是没有什么区别的。然而,如果设备名称包含一个或多个(“/”),是不会添加到单元名称中的。在这样的设备名称用于实例标识符前,"/"需要被替换为"-",并且其他的特殊符号(包括“-”)都会被ascii码所替代。例如:通过路径来引用一个USB串口,我们想使用的名称类似:serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0。而这个名称会被替换为:serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0。%I会引用前者,而%i引用后者。实际上这意味着%i在有必要引用其他单元的情况下更有用,比如表达额外的依赖项。另一方面%I在表达命令行的用法时,或者包含描述类型的字符串时,很有用。来看下上面的单元文件的内容:

  1. # systemctl start 'serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service'
  2. # systemctl status 'serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service'
  3. serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service - Serial Getty on serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0
  4. Loaded: loaded (/lib/systemd/system/serial-getty@.service; static)
  5. Active: active (running) since Mon, 26 Sep 2011 05:08:52 +0200; 1s ago
  6. Main PID: 5788 (agetty)
  7. CGroup: name=systemd:/system/serial-getty@.service/serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0
  8. 5788 /sbin/agetty -s serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0 115200 38400 9600

如我们所见,标识符是转义字符串,而描述字符串是非转义的。

(边注:不止有%i和%I这两个说明符,这些说明符实际上在单元文件中,不止在模版或服务实例。更多的内容参看man手册。

下文会介绍实例化服务如何用于超级守护进程。


Part 11

转换超级守护进程服务

在前面的博文中,我介绍了如何将SysV init脚本转为一个systemd 单元文件。在这里我想解释如何转换超级守护进程服务(inetd services)。

先介绍一点背景知识。inetd在经典Unix服务中有很长的历史。作为超级守护进程,它监听其他服务在互联网socket activation ,以及激活连接进来的服务。它实现了按需激活socket服务。这让Unix机器在资源有限的情况下可以提供更多的服务,而不需总是为进程分配全部资源。过了几年,在Linux发行版上出现了一些独立实现的inetd。最突出的是基于BSD的inetd和xinetd。inetd在大部分发行版上都会预装,现今它仅用于很少的几个服务并且这些服务都在无条件的启动运行,主要是为了性能。

systemd的其中一个核心功能是socket activation,一个由inetd提出的模式,然而与当时的侧重点不同。Systemd风格的socket activation关注本地socket(AF_UNIX),而不是互联网socket(AF_INET),即使两者都会支持。更重要的是,在关于按需分配方面,socket activation 在systemd中不是最主要的,但增加了更多的并行化(socket activation 允许客户端于服务器端的socket同时启动),简单化(除去了服务之间配置显示的依赖)以及健壮性(服务可以重启或崩溃时不会失去链接的socket)。systemd可以当连接进入时,按需激活服务。

任何类型的socket activation 都需要服务本身的支持。systemd提供一个服务实现socket activation 的简单接口,利用了sd_listen_fds()。它时一个非常迷你,简单的模式。然而,传统的inetd接口也很简单。它允许只传递一个单独socket到服务中:socket fd简单复制到进程的STDIN和STDOUT。为了提供兼容性,systemd为进程提供可选择的相同接口,这有利于那些已经支持inetd风格socket activation的服务,但不是systemd的原生activation。

在举例子之前,我们来看一下三个不同的使用socket activation 的模式:

  1. 并行,简洁,健壮的socket activation:
    socket绑定在引导早期并且一个为所有的客户端请求提供服务的单例服务实例在引导时直接启动。这对那些经常连续使用的服务是很有用的,并且推荐尽早启动它们以及与其他服务并行启动。例如D-ubs,Syslog。
  2. 单例服务的按需分配socket activation:
    socket绑定在引导早期并且在传入流量中执行一个单例服务实例。这对甚少使用的在真正需要时启动的服务比较有用,例如CUPS.
  3. 每连接服务实例的按需分配socket activation:*
    socket绑定在引导早期并且为每个进入的连接实例化新的服务实例,传递连接socket(不是在监听的那个)。这对甚少使用的性能需求不大的服务比较有用。比如那些为每个进入的连接创建服务进程,并消耗有限,例如SSH。

这三种模式提供不同的性能特征。在服务结束启动过程后,前两个模式的性能相当于一个独立的服务(例如一个没有超级服务和socket activation的服务),监听套接字会传递给实际的服务,一个独立的服务里所有的代码路径都是相同的,并且所有连接的过程与独立服务具有完全相同的方式。另一方面,第三种模式的性能并不好,由于对每个连接需要一个新的服务,资源消耗也很高。然而,它也有很多优点:例如客户端连接很好的隔离并且以这种方式开发服务很简单。

对于systemd,主要侧重第一种模式,其他两种模式也是支持的。inetd侧重与第三种模式,第二种模式也是支持的(第一种不支持,可能由于支持第三种模式,inetd被诟病比较慢)

说了很多背景知识,我们来简单看一下如何把一个inetd的服务转变为sytemd的socket activation。我们以SSH为例,一个非常普通而使用广泛的服务,但在大部分的机器上可能平均1小时不会启动一次。SSH很长时间都支持inetd风格的activation,并遵循上面的第三种模式。既然它每次使用才启动,并且同时启动的只有几个连接,当资源成本上升时是非常适合这个模式的:只要没有人使用,激活socket的SSH是免费的。有人通过ssh连接,它就会立刻启动,在断开连接的时候就会释放所有资源。我们来看下如何在systemd中利用其提供的inetd兼容性,建立套接字激活的SSH.

这是利用经典的inetd连接SSH的配置语句:

  1. ssh stream tcp nowait root /usr/sbin/sshd sshd -i

相同的xinetd的配置项

  1. service ssh {
  2. socket_type = stream
  3. protocol = tcp
  4. wait = no
  5. user = root
  6. server = /usr/sbin/sshd
  7. server_args = -i
  8. }

大部分都很容易理解,两段内容表达相同的含义。不明显的地方是:端口号(22)在inetd配置中是不需要配置的,但是通过在/etc/services的服务数据库中间接配置:服务名字在数据库中用作回环键并且转换为端口号。这个通过/etc/services间接设置是UNIX传统,现在不流行了。新的xinetd允许配置可以精确到端口号。这里最有意思的设置是并不直观的nowait(代表wati=no)选项。它配置了一个服务是第二个模式(wait)还是第三个模式(nowait)。最后的-i参数用于启动inetd模式。

systemd把这个配置转化为两个单元。第一个是ssh.socket,封装了关于监听的信息:

  1. [Unit]
  2. Description=SSH Socket for Per-Connection Servers
  3. [Socket]
  4. ListenStream=22
  5. Accept=yes
  6. [Install]
  7. WantedBy=sockets.target

大部分都比较容易看懂。几个说明:Accept=yes相当于nowait。希望有个更好的命名,来特指nowait的超级服务在监听时调用accept(),对于wait的服务表示服务进程执行的作业。WantedBy=sockets.target是保证在引导时正确的时间启动。

下面是匹配的服务文件 sshd@.service:

  1. [Unit]
  2. Description=SSH Per-Connection Server
  3. [Service]
  4. ExecStart=-/usr/sbin/sshd -i
  5. StandardInput=socket

文件内容也比较易懂。StandardInput=socket选项为这个服务启动了inetd兼容。StandardInput=可能用来配置这个服务的STDIN应该连接什么。把它设置为socket可以保证传递连接socket,如在简单的inetd接口中期待的那样。注意在这我们不需要明确的配置StandardOutput=,如果不配置,它会从StandardInput=继承默认配置。重要的是二进制路径前的"-"。这保证了systemd会忘记每链接sshd进程的退出状态。通常,systemd会存储所有服务实例的异常退出状态。SSH有时会异常退出,返回代码为1或其他类似的数字。我们想确保这不会导致systemd保存过量的之前连接的错误信息(直到使用systemctl reset-failed来忘记这些信息)。

sshd@.service是一个实例化的服务。对每一个进来的连接systemd会实例化一个新的sshd@.service实例,并带有连接成功后的实例标识符。

你可能想知道为什么systemd配置一个inetd服务需要两个单元文件。原因是这简化了配置,我们想保证在线单元与单元文件之间的关系是显而易见的,我们可以在依赖关系图中同时提供socket单元和服务单元,并且尽可能独立的控制单元。(思考:这允许你从实例中独立关闭socket,并且每个实例都是独立的)。

现在我们来看下这个如何工作。如果我们把文件放在/etc/systemd/system,我们可以激活服务并启动它:

  1. # systemctl enable sshd.socket
  2. ln -s '/etc/systemd/system/sshd.socket' '/etc/systemd/system/sockets.target.wants/sshd.socket'
  3. # systemctl start sshd.socket
  4. # systemctl status sshd.socket
  5. sshd.socket - SSH Socket for Per-Connection Servers
  6. Loaded: loaded (/etc/systemd/system/sshd.socket; enabled)
  7. Active: active (listening) since Mon, 26 Sep 2011 20:24:31 +0200; 14s ago
  8. Accepted: 0; Connected: 0
  9. CGroup: name=systemd:/system/sshd.socket

这显示了socket在监听状态,而且目前没有连接创建(Accepted:会显示总共有多少连接,Connected:当前有多少连接)

现在,让我们从两个不同的主机连接,看有服务的情况:

  1. $ systemctl --full | grep ssh
  2. sshd@172.31.0.52:22-172.31.0.4:47779.service loaded active running SSH Per-Connection Server
  3. sshd@172.31.0.52:22-172.31.0.54:52985.service loaded active running SSH Per-Connection Server
  4. sshd.socket

和期望的一样,这里有两个服务实例在运行,对应两个连接。它们以源地址和目的地址的IP以及端口号来命名(对于AF_UNIX socket,实例标识符会带着客户端的PID和UID)。这允许我们独立的观察和杀死指定的ssh实例,比如你想关闭一个指定的客户端连接:

  1. # systemctl kill sshd@172.31.0.52:22-172.31.0.4:47779.service

现在你可能直到关于用system关联inetd服务的大部分需要知道的事情,以及如何使用。

在SSH的例子中,为了节约资源,对于大多数发行版来说默认设置为inetd风格的socket activation 是一个好建议,但是也要提供给sshd一个可选激活的独立的单元文件。我会很快提出针对我们的Fedora SSH软件包的bug列表。

一点最后的说明,关于xinetd和systemd功能上的比较,以及xinetd是否会被systemd完全取代。简单说,systemd不会提供xinetd所有的特性,并且也不能完全取代xinetd。详细来说会有点复杂:如果你查看xinetd提供的多数选项,你会发现systemd没法比。例如,systemd不能与内置的echo,time,daytime或者废弃的服务一起启动,并且也不会包含它们。TCPMUX是不被支持的,RPC服务也是一样。然而你会发现,大部分服务与当今的互联网没有关系,或者过时了。大量inetd服务的主要功能不能直接利用这些额外的特性。实际上,在Fedora上具有的xinetd服务,没有使用这些选项。即便如此,也有一些有用的特性,systemd并不支持,例如IP ACL管理器。然而,大部分管理员同意防火墙是这类问题的最好解决方案。并且,最重要的是,systemd 通过tcpwrap 支持ACL管理器,满足了那些追求复古技术的人。另外,systemd也提供大量xinetd没有的特性,比如上面演示的可以独立控制的实例,或者实例的可执行上下文的配置能力。我相信system提供相当综合的,与传统不同但都是你需要的特性。并且,如果有systemd无法提供的,xinetd总会补充上,并且你可以用systemd来简单运行它。使用systemd的要点,应该是涵盖那些必须的东西,并且允许你裁剪建立你的系统所需要的东西。某种程度上,systemd带回了经单Unix inetd的功能,并再次把它变为Linux系统的核心。


[1] 我们的口号: "The only shell that should get started during boot is gnome-shell!" -- Yes, the slogan needs a bit of work, but you get the idea.(领会精神即可)
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注