momo zone

调核人的blog

systemd的一些总结

systemd的设计目标:

  • 改进效能。使用二进制代码替换松散的SYSV启动脚本,减少频繁的进程创建,库加载,内核/用户切换。
  • 利用Dbus进程间通讯与socket激活机制,解决任务启动时的依赖问题,实现启动并行化。
  • 实现任务(daemons)的精确控制。使用内核的cgroup机制,不依赖pid来追踪进程,即使是两次fork之后生成的守护进程也不会脱离systemd的控制。
  • 统一任务定义。用户不需要自行编写shell脚本,而仅依据systemd制定的unit规则。

systemd的结构:

Systemd_components.svg

 

尽管底层要求内核cgroup,autofs但却不需要以往的用户空间库文件。说明systemd自己可以按需直接访问这些组件。kdbus目前用的还不多,很多发行版内核暂没有并入kdbus的代码。值得注意的是如果启动了kdbus,那个他的pid将是1!

systemd libraries是systemd依赖的库。systemd core是systemd自己的库。systemd daemons 和 systemd targets是自带的一些基本unit,target,作用与定位类似sysV自带的脚本。

 

Linux_kernel_unified_hierarchy_cgroups_and_systemd.svg

 

注意上图systemd pid1里面的内容,systemd已经可以废掉以往一大堆传统的系统服务,比如consolekit(replace by logind),syslog(replace by journal),getty,crond(replace by timer unit)等等。看到这里很多人害怕了,一夜回到解放前,以前的技能都白练了。

systemd的主命令是systemctl,可以man systemctl查看详细。

systemctl可以添加参数-H <username>@<password>进行远程管理,该过程依赖ssh。

opensuse13.2提供systemd-ui的rpm包,其中包含systemadm图形界面。

服务/任务单元(unit):

unit类型:

一个unit文件可以描述如下内容之一:系统服务(.service)、挂载点(.mount)、sockets(.sockets) 、系统设备(.device)、交换分区(.swap)、文件路径(.path)、启动目标(.target)、由 systemd 管理的计时器(.timer)。详情参阅 man 5 systemd.unit

unit文件:

使用 systemctl 控制单元时,通常需要使用单元文件的全名,包括扩展名(例如 sshd.service)。但是有些单元可以在systemctl中使用简写方式。

  • 如果无扩展名,systemctl 默认把扩展名当作 .service。例如 netcfgnetcfg.service 是等价的。
  • 挂载点会自动转化为相应的 .mount 单元。例如 /home 等价于 home.mount
  • 设备会自动转化为相应的 .device 单元,所以 /dev/sda2 等价于 dev-sda2.device

有一些单元的名称包含一个 @ 标记, (e.g. name@string.service): 这意味着它是模板单元 name@.service的一个 实例。 string 被称作实例标识符, 在 systemctl 调用模板单元时,会将其当作一个参数传给模板单元,模板单元会使用这个传入的参数代替模板中的 %I 指示符。

在实例化之前,systemd 会先检查 name@string.suffix 文件是否存在(如果存在,应该就是直接使用这个文件,而不是模板实例化了)。大多数情况下,包换 @ 标记都意味着这个文件是模板。如果一个模板单元没有实例化就调用,该调用会返回失败,因为模板单元中的 %I 指示符没有被替换。

  • 书写unit的时候开头的#属于合法的,末尾加入#是非法的。
  • 使用systemd时,可通过正确编写单元配置文件来解决其依赖关系。典型的情况是,单元A要求单元BA启动之前运行。在此情况下,向单元A配置文件中的 [Unit] 段添加 Requires=BAfter=B 即可。若此依赖关系是可选的,可添加 Wants=BAfter=B。请注意 Wants=Requires= 并不意味着 After=,即如果 After= 选项没有制定,这两个单元将被并行启动。依赖关系通常被用在服务(service)而不是目标(target)上。例如, network.target 一般会被某个配置网络接口的服务引入,所以,将自定义的单元排在该服务之后即可,因为 network.target 已经启动。
  • unit文件位于/usr/lib/systemd/system,/usr/lib/systemd/user(安装应用的时候都放这里)以及/etc/systemd/system,/etc/systemd/user后面两个的优先级高于前面两个的。

编写自定义的 service 文件时,可以选择几种不同的服务启动方式。启动方式可通过配置文件 [Service] 段中的 Type= 参数进行设置。具体的参数说明请参阅 man systemd.service

  • Type=simple(默认值):systemd认为该服务将立即启动。服务进程不会fork。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。
  • Type=forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。
  • Type=oneshot:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。
  • Type=notify:与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。
  • Type=dbus:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
  • Type=idle: idle类型和 Type=simple 类似,但是,它会在所有任务(jobs)都处理后,才会执行。这种方式可以避免不同shell服务输出到控制台的状态信息出现交错的情况。

unit控制:

  • 输出激活的单元:systemctl 或 systemctl list-units
  • 输出运行失败的单元:systemctl –failed
  • 输出已安装的unit:systemctl list-unit-files
  • 启动unit:systemctl start <unit>
  • 停止unit:systemctl stop <unit>
  • 重启unit:systemctl restart <unit>
  • 查看unit状态:systemctl status <unit>
  • 开机自动激活unit:systemctl enable <unit>
  • 取消开机自动激活unit:systemctl disable <unit>
  • 重新载入systemd,扫描有变动的unit:systemctl daemon-reload

目标(target):

创建一个新的目标 /etc/systemd/system/<新目标>(可以参考 /usr/lib/systemd/system/graphical.target),创建 /etc/systemd/system/<新目标>.wants 目录,向其中加入额外服务的链接(指向 /usr/lib/systemd/system/ 中的单元文件)。

 

SysV 启动级别 Systemd 目标 注释
0 runlevel0.target, poweroff.target 中断系统(halt)
1, s, single runlevel1.target, rescue.target 单用户模式
2, 4 runlevel2.target, runlevel4.target, multi-user.target 用户自定义启动级别,通常识别为级别3。
3 runlevel3.target, multi-user.target 多用户,无图形界面。用户可以通过终端或网络登录。
5 runlevel5.target, graphical.target 多用户,图形界面。继承级别3的服务,并启动图形界面服务。
6 runlevel6.target, reboot.target 重启
emergency emergency.target 急救模式(Emergency shell)

切换目标:systemctl isolate graphical.target

修改默认目标:

  1. 内核参数:systemd.unit=rescue.target
  2. systemctl set-default multi-user.target

 

临时文件

/usr/lib/tmpfiles.d//etc/tmpfiles.d/ 中的文件描述了 systemd-tmpfiles 如何创建、清理、删除临时文件和目录,这些文件和目录通常存放在 /run/tmp 中。配置文件名称为 /etc/tmpfiles.d/<program>.conf。此处的配置能覆盖 /usr/lib/tmpfiles.d/ 目录中的同名配置。

临时文件通常和服务文件同时提供,以生成守护进程需要的文件和目录。例如 samba服务需要目录 /run/samba 存在并设置正确的权限位,就象这样:

/usr/lib/tmpfiles.d/samba.conf
D /run/samba 0755 root root

此外,临时文件还可以用来在开机时向特定文件写入某些内容。比如,要禁止系统从USB设备唤醒,利用旧的 /etc/rc.local 可以用 echo USBE > /proc/acpi/wakeup,而现在可以这么做:

/etc/tmpfiles.d/disable-usb-wake.conf
w /proc/acpi/wakeup - - - - USBE
注意: 该方法不能向 /sys 中的配置文件添加参数,因为 systemd-tmpfiles-setup 有可能在相关模块加载前运行。这种情况下,需要首先通过 modinfo <模块名> 确认需要的参数,并在/etc/modprobe.d的下文件中设置改参数。另外,还可以使用udev,在设备就绪时设置相应属性。

日志:

systemd 提供了自己日志系统(logging system),称为 journal. 使用 systemd 日志,无需额外安装日志服务(syslog)。读取日志的命令:

# journalctl

默认情况下(当 Storage= 在文件 /etc/systemd/journald.conf 中被设置为 auto),日志记录将被写入 /var/log/journal/。该目录是 system软件包的一部分。若被删除,systemd 不会自动创建它,直到下次升级软件包时重建该目录。如果该目录缺失,systemd 会将日志记录写入 /run/systemd/journal。这意味着,系统重启后日志将丢失。

显示本次启动后的所有日志:journalctl -b

显示上次启动到本次启动间的日志:journalctl -b -1

显示从指定时间开始:journalctl –since=”2012-10-30 18:17:16″

显示指定最后一段时间的日志:journalctl –since “20 min ago”

显示最新日志:journalctl -f

显示指定进程的日志:journalctl /usr/lib/systemd/systemd

显示指定pid的日志:journalctl _PID=1

显示指定unit的日志:journalctl -u netcfg

将日志重定向到tty:编辑/etc/systemd/journald.conf:

ForwardToConsole=yes
TTYPath=/dev/tty10
MaxLevelConsole=info

systemctl restart system-journald

电源管理 :

安装了polkit后才能用一般用户权限进行电源管理。

重启:systemctl reboot

关机:systemctl poweroff

待机:systemctl suspend

休眠:systemctl hibernate

混合待机休眠:systemctl hybrid-sleep

 

 

 

调试systemd:

启动调试终端:systemctl start debug-shell.service

其实就是在tty9开一个虚拟终端,而且不需要任何身份验证。

 

关于并行化的一些实现:

并行化Socket服务

同步过程导致了启动过程的串行化。能否摆脱同步与串行化的缺点呢?答案是能够! 首先我们要理解 daemon 对彼此的真正依赖是什么,为什么它们在启动时被延迟了。 对于传统的 Unix daemon,答案是:它们在所需要的服务所提供的 socket 准备好连接之前一直处于等待状态。 常见的是 AFUNIX,但也可能是 AFINET。比如,需要DBus服务的客户程序将在 /var/run/dbus/systembussocket 可以被连接之前等待,syslog 服务的客户程序将在 /dev/log 上等待…而这是它们唯一需求的内容!(意指只要这一条件满足,客户程序即无需等待,不论实际的服务是否已经启动。下文详细介绍)

以上就是等待的真正含义。如果能尽早建立客户程序所必须的 socket 而令客户程序处于等待状态而不是在服务程序完全启动后再启动客户程序,我们就能加快启动进程,进一步并行化进程启动。如何做到这一点?在 Unix 族系统上非常简单:在真正需要启动服务之前先监听其 socket,然后将 socket 传递给 exec() (此处直译,含义不懂),如此,在 init 系统中第一步就可建立所有 daemon 所需的所有 socket,在第二步中一次运行所有的 daemon。如果一个服务需要另一个,但被需者未完全启动号,也完全没有问题在被需者查询 socket,处理请求之前,客户进程将因这个请求而被阻塞,但仅有被这个请求阻塞的客户进程被阻塞。并且服务之间的依赖关系将不再需要(手动)配置:如果一次建立所有服务所必须的所有 socket(及启动所有相关服务),则所有服务的所有需求一定在最后会被满足。

下列所说将是重点:如果同时启动了 syslog 和多个其客户进程(意指syslog尚未启动准备好处理请求),而客户进程开始发送请求,则请求会被写入 /dev/log 的Socket 缓存中。除非缓存填满,否则客户进程无需任何等待即可继续完成其启动过程。当 syslog 启动完全后,处理器所有的消息。另一个例子:DBus与其客户进程一起启动,当同步请求发出但没有接受到预想的回应,客户进程将阻塞。当 DBus 启动完成后处理请求,客户进程继续。

内核的socket 缓存将辅助达成最大的并行化。因为内核完成了同步,不再需要用户空间的任何的额外管理!如果在 daemon 启动前所有必须的 socket 都已经可用,依赖关系就变得多余(至少是次要了)。daemon A 需要另一个 daemon B,简单的连接到B。若B已经启动,A的请求成功。若B未完成启动,假如A发出的不是一个同步请求,甚至无需理会(B没有完全启动)。甚至即使B完全没有执行(比如crash了)时再重启B也为时不晚对于A而言二者没有任何分别。借此可达成最佳的并行化和随意的需求时(on-demand)加载。而且在此基础上能够更加鲁棒。因为 socket 即使在相应服务是暂时不可用(如crash)时也可用,所以客户进程不会丢失任何的请求(request入buffer,待重启服务后处理)

并行化Bus服务

Linux上现代的daemon都通过DBus而非socket来交互,现在的问题是,对于这些服务,能否施加与启动传统的、基于socket的服务逻辑相同的并行化?答案是可以,DBus已经提供了所有必要的hook:使用DBus将会在第一次访问时加载服务,并且给予最小的、每请求一个的、消费者与生产者同时启动的同步机制。例如Avahi与CUPS(CUPS需要Avahi进行 mDNS/DNS-SD上打印机扫描)同时启动,仅仅是简单的同时启动二者,若CUPS比Avahi启动快,则DBus将把请求缓存入队列,直到 Avahi服务进行了处理。

所以,总结如下:基于Socket和基于DBus的服务可一次并行启动所有进程,无需任何额外的同步。基于激活的策略还能令我们进行延迟加载服务。如果服务很少被用到,我们可以在第一次被访问时启动,而不是在启动过程中启动。

并行化文件系统任务

对于当前发行版的启动序列图可以看出,文件系统任务所发生的同步点大于 daemon 启动时的同步点:mount fsck等。现在,启动时很多时间都花费在空闲等待所有fstab 中列举的文件系统被加载、 fsck的过程中,仅在完全完成后启动进程才会继续。如何改进?答案是autofs系统。就如同connect() 调用表示一个服务对另一个服务感兴趣,open() 调用(或相似的调用)表示服务对特定的文件或文件系统有需求。所以,我们应该仅令那些访问暂时还不可用(暂未完成 mount、fsck)文件系统的服务阻塞,这样就改进了并行性。首先加载autofs,然后继续正常的mount过程,当被访问的文件系统未准备好,内核将会缓存请求调用,访问进程将被阻塞,一个未成功的访问仅可能阻塞一个进程。当真正的文件系统加载完成后,启动进程如同正常般完成,没有任何缺失的文件,如此我们能够在所有文件系统都准备好之前就加载服务。

假如有人提出将autofs内置于 init 是不妥当的我并不感觉奇怪,也许这会导致更多 crash。然而经过实验,我可以说这并不正确。使用autofs意味着无需马上提供后台 的真实文件系统,这只会导致访问被延迟。如果进程尝试访问autofs但真实的文件系统很长时间都没能加载,进程将被一个sleep中断上被挂起,意味着你可以安全的cancel 它。如果最后这个文件系统都没能加载,那么令 autofs 返回一个error Code(例如 ENOENT)。所以我认为内置 autofs 到init中是正确的。实验代码显示这一想法在实践中工作的很好。

 

关于systemd的争议:

作为 systemd 的拥护者,我前半部分尽量客观的陈述:
systemd 本来是一个先进的init程序,除了管理 daemon 之外,还实现了 socket-activation 来支持按需加载服务。
就架构上来说完胜现有的任何系统上的任何服务管理体系(我可以很负责很客观的说),但是反对的意见主要是:

  1. 不遵循 UNIX 原则。
  2. systemd 在设计之初就不考虑 Linux 以外的平台,不遵循 POSIX 标准,而且很多功能根本就是 Linux 特有的,无法移植到 Linux 之外的平台,这尤其让 BSD 爱好者们很受伤,在 Debian 7 以前,一直维护着Linux和FreeBSD两个内核,只不过后者没什么人用,Debian 8 为了支持systemd 不得不放弃支持 Debian kFreeBSD。
  3. 接管了太多设施,如 syslog 被 systemd-journal 取代,crond 也被 systemd 的 timer 单元取代,udev 也准备集成到 systemd 中来,未来甚至还可能取代 /etc/fstab。尽管这些新的服务大部分都是独立于主进程的,但是还是有整个系统被红帽控制住的感觉(systemd的作者Lennart Poettering 就职于红帽,systemd 也主要是红帽的 Fedora 首先在推,OpenSUSE 后面跟随)。这在开源社区看来是件政治不正确的事情。
  4. 有人怀疑 systemd 的可靠程度。然后就是很多管理员以前积攒的脚本全报废了(这也是管理员反对的主因吧)。

================================
后面我要说下自己的意见:

  1. 原则如果阻碍了进步,那还算个屁,不客气地说,UNIX 原则已经过时了。
  2. 移植性问题:我除了 Mac 外不用任何 BSD 系统,当然 Mac 上一般只做开发不做运维(但就算如此,Mac 上还是有 launchd,systemd 借[chao]鉴[xi]的就是 launchd)。
  3. 对于systemd接管其他设施,一般认为这样也有利于 Linux 系统标准化,在 systemd 之前,init 程序的实现就有 SysV Init,Ubuntu 的 upstart,Gentoo 的 OpenRC 等等,syslog 的实现由 syslog-ng,rsyslogd,简直就是一团乱麻,开发和部署的系统不一样的时候简直神烦(当然这种烦恼仅限于我这样主要做开发,边学边运维的)。关于udev什么的我不是很了解,但是我对 systemd 设计哲学本身就比较认可,相信这么做也是事出有因。另外有些功能在 systemd 之前根本就无法实现,比如
    1. logrotate 从来就不能保证归档日志的时候不丢失刚刚写入的 Log,systemd-journal 接管了 syslog 和 logrotate 之后日志被结构化的存储之后才解决这个问题。当然这是小问题。
    2. 从前的 init 程序根本就不管 daemon 能否正常的退出。有的时候 daemon 被挂了,但是daemon 开的子进程却没有正确退出,还占着关键资源,导致服务根本不能重启,除非重启操作系统。systemd 是能追踪全局进程树,能精确杀死一个进程下所有子进程,具备这样的能力才能称作 daemon manager。
  4. 对 systemd 的怀疑,我觉得那是很多人没用过 systemd,事实上 systemd 在设计上要完备得多(虽然其他 init 服务有各种各样些缺陷,但不是大家痛点),这种设计上就进行了充分的考量的系统,稳定下来后(比如进入 RHEL 7)必然更加可靠。
Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: