[翻译]Systemd Socket Activation
[翻译]Systemd Socket Activation
JoshuaSystemd Socket Activation
此篇blog是systemd原作者Lennart Poettering关于Socket Activation
技术的介绍以及如何使用systemd实现这项技术的小教程。
原文讲解的通俗易懂,读完让我对这项技术有了个叫基础的了解,是了解这项技术的一篇好文章,于是决定翻译留档。
第一次翻译技术类文章,如有任何翻译错误或意见,欢迎提出。
原文链接:systemd for Developers I (0pointer.de)
Socket Activation简介
在原来的一篇讲述关于systemd的博客中,我尝试解释了为什么socket activation
(套接字激活)对于启动服务是一项十分完美的技术。现在让我们再次回顾一下。
socket activation的基础逻辑已经不是什么新概念了。从一开始,inetd
就是作为Linux和Unix系统标准组件的一部分:并不是从系统已启动九八所有的本地联网应用启动好,而是inetd
会代替服务去监听,并且无论何时新的链接出现了,都会将一个代表这个服务的实列启动。这样可以让那些性能较弱的机器能以较少的资源同时提供多样的服务。然而很快它就被吐槽有点慢,因为每次新的链接建立都会让守护进程启动一个新的实列并且要初始化服务——每次新的连接都得这样,而不是一次初始化就能长久提供服务。
每一个新的连接都参数一个新的实例是inetd
刚开始使用的方式。虽然inetd
其实可以执行另外一个模式:第一个连接一来,inetd
就能通过poll()
(或是select()
),来初始化一个能为未来所有的连接提供服务的实列。以这样的方式,只是第一个连接因为要初始化服务会有点慢,但是接下来的请求都会很快的完成依靠已经初始化好正在独立运行的服务了。在这样的模式下inetd
工作在一个真正的随需应变的模式:当程序被需要时就会懒启动。
inetd
的重点显然是AF_INET
(即互联网,译者注:即TCP、UDP这种协议)套接字。随着时间的推移,Linux/Unix离开了它所熟悉的服务器领域,越来越多地与台式机、移动平台和嵌入式环境平台相关。inetd
不知怎么地迷失在了麻烦中。它运行慢,以及Linux的重点从Internet服务器转移的事实,使得运行inetd
(或其较新的实现之一,如xinetd
)的Linux机器成为少有的事情。
当苹果工程师致力于优化MacOS启动时间时,他们发现了一种利用socket activation
激活服务的新方法:他们将关注重点从AF_INET套接字转移到AF_UNIX套接字上。他们注意到使用socket activation
激活那些随机应变的需求服务只是故事的一部分:更强大的是将socket activation
用于所有的服务上(译者注:下面解释了为啥所有的本地服务都用socket activation
技术会很棒),包括那些在引导时无论如何都需要启动的服务。他们在launchd
中实现了这些想法,这是现代MacOS X
系统的核心构件,可能也是MacOS
启动速度如此之快的主要原因。
在我们继续下面的内容之前,让我们仔细看看socket activation
对于那些非按需、非网络的服务的好处是什么。考虑Syslog
、D-Bus
、Avahi
和Bluetooth(蓝牙)守护进程
这四个服务。D-Bus
将日志记录到Syslog
,因此在传统的Linux系统上,它会在Syslog
之后启动。同样,Avahi
需要Syslog
和D-Bus
,因此会在两者之后启动。最后,Bluetooth
类似于Avahi
,也需要Syslog
和D-Bus
,但不需要Avahi
。由于在传统的基于sysv
的系统中一次只能启动一个服务,所以启动顺序如下:Syslog
→D-Bus
→Avahi
→Bluetooth
(当然,Avahi
和Bluetooth
启动顺序也能换换,但我们必须在这里选择一个,所以我们简单地按服务名称的开头字母顺序启动)。为了说明这一点,这里有一个图表,显示了从启动开始到系统启动的顺序。
某些系统发行版试图改进这种严格的序列化启动:因为Avahi
和Bluetooth
服务是彼此独立的,所以它们可以同时启动。因此增加这类服务的并行化启动,总体启动时间可以略微缩短。(这在上图的中间部分可以看到)
socket activation
使得可以完全同时启动所有四个服务,而不需要任何排序。由于创建监听套接字的过程被移到了这些进程本身之外,因此我们可以同时启动它们,并且它们能够立即连接到彼此的套接字。例如,在一个步骤中创建了/dev/log
和/run/dbus/system_bus_socket的
套接字,然后在下一步中同时启动所有的四个服务。当D-Bus
想将日志记录到syslog
时,它只将消息写入到/dev/log
。只要套接字缓冲区没有满,它就可以立即进行它想要进行的其他初始化操作。一旦syslog
服务赶上,它就会处理排队的消息。如果套接字缓冲区已满,则客户端日志记录将暂时阻塞,直到套接字再次可写,并在可以再次继续写入日志消息时继续。这意味着我们服务的调度完全是由内核完成的:从用户空间的角度来看,所有的服务都是同时运行的。当一个服务超负荷运转无法再接接收其他服务的请求了,它会暂时阻塞它们的请求,一旦处理完超负荷的任务,它就会继续运行。所有这些都是完全自动的,对用户空间是不可见的。因此,socket activation
使我们能够极大地并行化启动,使以前被认为需要严格序列化的服务能够同时启动。大多数Linux服务使用套接字作为通信通道。socket activation
允许同时启动这些通道的客户端和服务器。
当然优点不仅仅是能够并行化初始化。它还提供了以下几个好处
- 我们不再需要显式地配置依赖关系。因为套接字在所有服务之前被初始化,所以它们是简单可用的,并且再也不需要在用户空间对启动程序的顺序进行排序了。因此,套接字激活极大地简化了服务的配置和开发。
- 如果一个服务死了,它的监听套接字会继续存在,不会丢失任何消息。在崩溃的服务重启后,它可以从停止的地方继续运行。
- 如果服务进程进行升级,我们可以重启服务,同时保留其套接字,从而确保服务持续响应。升级过程中不会丢失任何连接。
- 我们甚至可以在运行时以一种对客户端不可见的方式替换服务。例如,所有运行
systemd
的系统在启动时都会启动一个很小的syslog
守护进程,它会将所有写入/dev/log
(译者注:此文件默认为系统日志的入口,也是一个.socket文件)的日志消息传递到内核消息缓冲区。这样,我们从启动的第一瞬间就开始提供可靠的用户空间日志记录。然后,当实际的rsyslog
守护进程准备启动时,我们终止迷你守护进程,并用真正的守护进程替换它。同时保留原始日志套接字,在两个守护进程之间共享它,并且不丢失任何消息。由于rsyslog
会在启动后会将内核缓冲区的日志刷新到磁盘,所以来自内核、早期启动和运行时的所有日志消息都会保存在磁盘上。关于这个想法的其他解释可以在这里查看 关于systemd最初的日志系统故事.
Systemd
的socket activation
相当全面。不仅支持经典的套接字,还支持相关的技术:
AF_UNIX
套接字:支持SOCK_DGRAM
,SOCK_STREAM
和SOCK_SEQPACKET
在文件系统和抽象namespace中AF_INET
套接字,即TCP/IP
和UDP/IP
;支持IPv4和IPv6- 在文件系统下的Unix 命名 pipes/FIFOs
AF_NETLINK
套接字,用来订阅某些内核特性。udev
目前使用的是这种方法,但也可以用于其他与网络链接相关的服务,比如audit
。- 某些特殊文件(如
/proc/kmsg
)或设备节点(如/dev/input/*
)。 POSIX
消息队列
让应用程序支持Socket Activation
能够使用socket activation
的服务必须能够从systemd接收它预先初始化的套接字,而不是自己在内部创建它们。对于大多数服务来说,这个需求需要(小小的)补丁来实现。然而,由于systemd
实际上提供了inetd
兼容性,所以与inetd
一起工作的服务也将与systemd一起工作——这对于像sshd
这样的服务来说非常有用。
关于套接字激活的背景知识讲了这么多,现在让我们看看如何修补一个服务以使其可激活套接字。让我们先从foobard
这个理论上的服务开始。(在后面的博客文章中,我们将重点讨论现实中的例子)
我们这个小型的(理论上的)服务包括如下用于创建套接字的代码(大多数服务以这样或那样的方式包括这样的代码):
/* Source Code Example #1: ORIGINAL, NOT SOCKET-ACTIVATABLE SERVICE */ |
一个能使用socket activation
的服务大概会如下这样写
/* Source Code Example #2: UPDATED, SOCKET-ACTIVATABLE SERVICE */ |
systemd
可能会向您传递多个套接字(基于配置,见下文)。在这个例子中,我们只使用一个套接字。sd_listen_fds()
函数返回systemd
传递了多少个文件描述符。我们只是简单地将它与‘1’进行比较,如果超过或少于这个数,程序就运行失败了。systemd
传递给我们的文件描述符从fd #3开始一个接一个地被继承。(译者注:如果读者有Linux基础,应该知道每一个进程运行,系统都会为其创建一个独立的文件描述符表,描述表中规定序号0表示标准输入,序号1表示标准输出,序号2表示错误输出。此后该进程每打开一个程序都会从序号3开始计数,被描述在文件描述符表当中。被systemd启动绑定了socket的服务,在启动时systemd会将绑定的socket依次从描述符序号3开始往后排。因此要使用systemd传递过来的socket,打开序号为3的文件描述文件即可)(SD_LISTEN_FDS_START
是定义为3的宏常量(c语言知识))。因此,我们的代码只打开fd #3。
如您所见,这段代码实际上比原始代码要短得多。这当然是有代价的,即我们的小型服务在非socket activation
环境下将不再工作。通过最小的改动,我们可以使我们的例子在无论是否是socket activation
的情况下都能很好地工作。
/* Source Code Example #3: UPDATED, SOCKET-ACTIVATABLE SERVICE WITH COMPATIBILITY */ |
通过这个简单的更改,我们的服务现在可以利用socket activation
,但在常规环境中仍然可以不加修改地工作。现在,让我们看看如何在systemd中启用这项服务。为此,我们必须编写两个systemd单元文件:一个描述套接字,另一个描述服务。
首先是foobar.socket
配置文件(译者注:不熟悉的可查看有关systemd socket资料,systemd.socket 中文手册。不过要提醒的是service文件和socket文件文件名必须一致,否则会出现socket和service之间没办法绑定传递socket。不过当然可以通过在socket中定义service字段来手动指定特定service,就可以不同名了):
[Socket] |
然后是与之相匹配的service配置文件foobar.service
:
[Service] |
如果我们将这两个文件放在/etc/systemd/system
中,我们可以用如下的方式启用并启动它们(译者注:实际上只启动了socket组件,service组件并没有启动):
systemctl enable foobar.socket |
现在我们的创建的套接字正在监听,但是我们的服务还没有运行。如果我们现在连接到/run/foobar.sk
,服务将自动生成,以此来实现按需启动服务。通过对foobar.service
的修改,我们可以在启动时就启动我们的服务,这样就只将socket activation
用于并行化目的,而不再用于按需自动启动服务:
[Service] |
现在,让我们也来开启这个配置(译者注:同样放在/etc/systemd/system
文件中)
systemctl enable foobar.service |
现在,我们的小型守护进程无论是在按需启动服务模式下还是在启动时都可以启动了。它可以与客户端完全并行启动,当它死亡时,下次使用时会自动重启。
一个.socket文件可以包含多个ListenXXX节,这对于侦听多个套接字的服务非常有用。在这种情况下,所有配置的套接字将按照套接字单元文件中配置的顺序传递给服务. 当然,你可能有时需要配置各种各样的socket设置在.socket文件当中.
在现实运用中,在这些文件中包含描述字符串是一个好主意,为了简单起见,我们将在我们的示例中省去它。说到现运用:我们的下一部分将介绍一个现实运用中的实际例子。我们将向CUPS打印服务器添加socket activation
功能。
sd_listen_fds()
函数定义在sd-daemon.h
和sd-daemon.c
文件中。这两个文件目前是可插入的.c源代码,项目只需将它们复制到它们的源树中。最终我们计划把它变成一个合适的共享库,但是使用插入文件允许你以一种与socket activation
兼容的方式编译你的项目,即使没有使用任何systemd的编译插件。sd-daemon.c
是自由许可的,应该可以在各种unix环境上编译,并且他的算法也足够简单只要一点点代码就能重新编译,如果许可对您的项目来说是一个问题的话。除了sd_listen_fds()
之外,sd-daemon.c
还包含一些其他API函数,这些函数在项目中实现socket activation
时非常有用。例如,sd_is_socket()
可用于在传递多个服务时区分和识别特定的套接字。
让我指出,这里使用的接口没有直接绑定到systemd。它们足够通用,也可以在其他系统中实现。我们有意将它们设计得尽可能简单和最小化,以使其他人有可能采用类似的方案。
敬请关注下一期(译者注:就不翻译了,这篇外加查点资料,够用了)。如前所述,它将涵盖一个将现有守护进程转变为套接字可激活的实例:CUPS打印服务。然而,如果您计划将现有的服务转换成socket activatable
的服务,我希望这篇博客故事已经足够让您开始了。我们邀请所有人将上游项目转换为该方案。如果您有任何问题,请登录freenode上的#systemd加入我们。