systemd:转换 sysvinit 脚本

欢迎回到 systemd 系列的另一部分。 在本系列中,我们讨论了使用 systemd 来理解和管理您的系统的方法。 本文重点介绍如何转换您可能在系统上自定义的旧脚本。

SysV 初始化脚本

以前在 Linux 中使用的 init 系统称为 SysVinit。 在我们开始之前,了解 systemd 对 SysVinit 有很多兼容特性会很有帮助。 在某些情况下,您可能不必编辑文件来维护您的自定义解决方案。 但在其他情况下,了解如何适应和使用新的 systemd 技术会很有帮助。

用于在 SysVinit 中管理服务的脚本称为 SysV init 脚本。 添加一些内容后,它被称为 LSB 或 Linux 标准基础的 init 脚本。 不要害怕,遗留脚本的向后兼容性在 systemd 中保持 99.9% 不变。 这意味着您不需要急于转换所有脚本来简单地采用基于 systemd 的操作系统,例如 Fedora. 但是,这样做有很多好处。

初始化脚本实际上是由解释器执行的命令式 shell 脚本。 在这里,“命令式”意味着它们包含系统运行以管理服务的命令。 但是,systemd 单元文件是意图的声明性陈述。 它们向 systemd 提供管理服务的提示,但 systemd 负责执行。 因此,单元文件通常比它们替换的 SysV 初始化脚本短得多。 那是因为他们不必担心实施,只需担心服务的意图。

迁移 SysV 初始化脚本的第一步是检查您希望转换的脚本。 让我们使用 sshd 初始化脚本 从 Fedora 16 作为 example. 上一个链接将向您展示在转换为 systemd 单元文件之前的脚本。 这是一个有趣的事实:由 184 行 shell 脚本处理的所有内容现在都由 27 行 systemd 配置处理,分布在两个单元文件中。

运行级别与目标

审查 初始化脚本 上面链接,让我们更仔细地看看几个重要的部分。 首先是这一行:

# chkconfig: 2345 55 25

第一组数字 2345 表示该脚本应在 SysV 世界中的哪些运行级别运行。 SysV 运行级别是对某些进程和服务应该运行的系统状态的定义。 定义了一组特定的运行级别:

  • 0:停止
  • 1:单用户
  • 2:多用户
  • 3:多用户联网
  • 4:未定义(用户定义)
  • 5:带显示管理器的多用户(图形登录)
  • 6:重启

在 systemd 中,没有运行级别的概念。 这些被目标所取代。 在 systemd 中,可以有无限的目标集,每个目标都由一个带有 .target 后缀的单元文件表示。 systemd 中与 SysV 运行级别 3 类似的目标称为 multi-user.target。 类似地,对应于运行级别 5 的单元称为 graphics.target。

目标可以而且确实相互依赖。 因此,只有在系统达到 multi-user.target 后,它才会启动 graphics.target 中指定的服务。 顾名思义,basic.target 还存在隐式依赖关系,它建立了基本的系统服务和功能。 其中一些是通过它们自己的目标依赖关系发生的,例如 network.target。

服务订购

SysV 初始化脚本中接下来的两个数字 55 和 25 确定启动和停止服务的顺序。 在 SysVinit 下,脚本严格按顺序运行。 顺序是通过命名文件夹中的一组链接来确定的。 如果新脚本以错误的顺序启动或停止,则服务可能会失败,系统可能会出现挂起,或可能导致其他错误。

另一方面,像 Requires= 和 After= 这样的 systemd 单元文件中的指令确保单元以正确的顺序运行。 他们不需要有关系统上其他单元或服务的先验知识。 例如,如果您的单元运行需要网络的服务,您可以将 Requires=network.target 和 After=network.target 添加到单元文件中。 它只会在网络激活后运行。

旧的 SysV 初始化脚本中还包含依赖项。 但是,如果您只是在 init 系统的控制之外运行脚本,则不会强制执行它们。 systemd 的主要功能之一是运行操作的确定性结果。

这是很多代码

下面的头信息,例如硬依赖和软依赖(Required-Start、Required-Stop、Should-Start 和 Should-Stop)是服务的描述。 在这种情况下,就是启动 OpenSSH 服务器守护进程。 以下是实现脚本功能的各种功能。 标准是指脚本必须实现的某些动词,例如启动、停止和重新启动。

不过,如前所述,systemd 的做事方式是声明性的。 您不必定义和编码一组函数,而是指定要执行的过程。 没必要,因为 example用于常见任务的大量代码,例如检索服务启动的进程 ID (PID)。

让我们看一下这个脚本的 start() 函数,看看它做了什么,让我们逐行检查它。 这是一个 shell 脚本,由 init 系统启动的 shell 运行的指令程序。 如果您不懂语言,请不要担心; 说明如下。

start()
{
    [ -x $SSHD ] || exit 5
    [ -f /etc/ssh/sshd_config ] || exit 6
    # Create keys if necessary
    /usr/sbin/sshd-keygen

    echo -n $"Starting $prog: "
    $SSHD $OPTIONS && success || failure
    RETVAL=$?
    [ $RETVAL -eq 0 ] && touch $lockfile
    [ $RETVAL -eq 0 ] && cp -f $XPID_FILE $PID_FILE
    echo
    return $RETVAL
}

按顺序,这是 init 系统所做的:

  • 检查 sshd 二进制文件是否存在且可执行,如果不存在,则以状态码 5 退出。
  • 检查配置文件 /etc/ssh/sshd_config 是否存在,如果不存在,以状态码 6 退出
  • 如果密钥不存在,则运行 sshd-keygen 二进制文件以生成密钥
  • 向屏幕/日志发送即将启动服务的消息
  • 运行服务,并输出启动 sshd 进程的成功或失败消息
  • 捕获 sshd 进程本身的返回值; 0 表示 sshd 已成功成为服务守护进程
  • 根据返回值,标记几个标记文件以指示服务正在运行,以防系统管理员再次尝试运行它
  • 为屏幕/日志发送“下一行”消息
  • 将返回值传递出去,以防在其他地方需要它

请注意,这只是 SysV sshd 初始化脚本中的一项功能. 许多其他功能也需要编码,包括停止、重新启动等。 这些是一百多行代码! 但是相比之下,systemd 单元文件是什么样的呢?

systemd:减少代码

相比之下,这里是 sshd.service 单元文件,来自 Fedora 23 安装。 请注意,这个文件要小得多,也更容易阅读:

[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

前几行再次描述了 systemd 单元,以及您可以在哪里阅读有关它的更多信息。 以下是其余行的含义,以及指向 systemd 文档的链接:

  • 声明排序,并描述 network.target 和 sshd-keygen.service 应该首先运行。
  • 想要 描述应该运行 sshd-keygen 以启动此服务。 请注意与订购的细微差别。 systemd 认为顺序和依赖是正交的,这意味着仅仅因为 sshd-keygen 先出现并不意味着它是 sshd 的依赖——尽管在这种情况下它是。 Wants 意味着 systemd 应该运行 sshd-keygen.service,但如果没有成功完成(例如,如果 SSH 服务器密钥已经存在),sshd 仍然会运行。 如果 sshd-keygen.service 需要成功完成,您将使用 Requires 代替。
  • 环境文件,类似于 SysVinit,是一个带有 sshd 选项的配置文件。 它包含一组将传递给 sshd 的 key=value 对。
  • 执行开始 是运行以启动 sshd 的命令。 这将替换旧 initscript 中的整个 start 函数。 这里的 $OPTIONS 变量是 EnvironmentFile 中为变量 OPTIONS 指定的。
  • 执行重新加载 是系统管理员重新加载 sshd 守护程序时运行的命令。 这将替换旧 initscript 中的整个 reload 函数。
  • 杀戮模式 设置 systemd 如何停止服务。 在这种情况下,systemd 只会停止主 sshd 进程。 任何其他 sshd 子进程将继续管理其打开的 SSH 会话。 这不是经常使用的选项,但在这里很重要。 例如,当您运行 systemctl 时,它会阻止您终止自己的 SSH 会话!
  • 重新开始 确定 systemd 在意外停止时如何管理服务。 在这种情况下,on-failure 意味着几乎任何意外故障都会导致 systemd 重新启动 sshd。 但是,如果 sshd 正常停止,例如使用 systemctl stop 命令,它将不会重新启动。
  • 重启安全 在 sshd 由于意外停止而重新启动之前允许 42 秒的睡眠时间。
  • 通缉者 表示当命令 systemctl enable sshd.service 运行时,默认会在 multi-user.target.wants 目录中放置一个链接。 这是 systemd 用于确定要为特定目标运行的单元的机制之一。 所以 multi-user.target 和任何依赖它的目标,都将包括运行 sshd.service。

当然还有许多其他指令可用,systemd 文档涵盖了所有这些指令。 但是,如果您要为一个简单的服务编写自己的单元文件,您甚至不需要使用此处显示的所有选项。 您可能只需要几个指令,例如 After、ExecStart 和 WantedBy。 如果您自己查看 /usr/lib/systemd/system/ Fedora 系统,您可以找到许多其他可以作为模板使用的 .service 单元文件。

附加提示

不过,systemd 的另一个显着特性是它不会放弃对 SysVinit 的兼容性。 一些管理员创建了自己的自定义启动脚本,例如 /etc/rc.d/rc.local。 如果您不准备转换它,请不要担心——systemd 会使用已经包含的 rc-local.service 文件来支持它。

请记住,如果您正在为您的系统构建自定义系统服务 Fedora,最好将它们放入 /etc/systemd/system. 通常 /etc/ 是保存所有系统配置或定制的地方。 这使您可以避免将自定义放在 /usr/lib/systemd/system 中,由 Fedora的包系统。

如果你想读另一个 example 和转换的解释,签出 此博客条目 来自“systemd for administrators”系列。