2015-07-27

为什么要用 systemd 替换 Sysvinit

目录

  • Sysvinit 示例
  • systemd 介绍
  • systemd 特点
  • systemd 必知必会

在 CentOS 6 之前的版本, 如果想要开机启动某些系统服务, 比如 Apache 、MySql 之类的服务, 那就需要编写 init 脚本才能实现. 而在 CentOS 7 中, 不再使用 init , 而是使用 systmed 的方式管理系统服务. 那么为什么要换成 systemd 呢? 换成 systemd 需要注意什么呢? systemd 都有哪几部分组成呢? 接下来我们先来看一个 init 启动脚本, 然后在介绍一下 systemd , 对比一下 systemd 是如何管理系统服务的.

Sysvinit 介绍

运行级别的配置

Sysvinit 管理的系统有一个启动级别的概念, 默认的启动级别有 0-6 七个级别.

# 0 - 停机
# 1 - 单用户模式 
# 2 - 多用户,但是没有NFS 
# 3 - 完全多用户模式 
# 4 - 没有用到 
# 5 - X11 
# 6 - 重新启动(千万不要把initdefault设置为6 )

一个运行级别其实就相当于定义一组启动脚本, 在每一个运行级别下分别启动什么服务. * 在目录 /etc/rc.d/init.d 下有许多服务器脚本程序,一般称为服务 (service) * 在 /etc/rc.d 下有 7 个名为 rcN.d 的目录,对应系统的 7 个运行级别, 每一个运行级别都启动什么服务, 就在此配置 * rcN.d 目录下都是一些符号链接文件,这些链接文件都指向 init.d 目录下的 service 脚本文件,命名规则为 K+nn+ 服务名或 S+nn+ 服务名,其中 nn 为两位数字 * 系统会根据指定的运行级别进入对应的 rcN.d 目录,并按照文件名顺序检索目录下的链接文件 * 对于以 K 开头的文件,系统将终止对应的服务 * 对于以 S 开头的文件,系统将启动对应的服务 * 查看运行级别用:runlevel * 进入其它运行级别用:init N * 另外 init0 为关机,init 6 为重启系统

一般 Linux 服务器运行在 3 级别, 桌面系统运行在 5 级别. 可以编辑 /etc/inittab 文件,

id:5:initdefault:  # 标示默认运行级别是 5

启动脚本

每一个运行级别启动的服务都是通过启动脚本配置的, 下面以 CentOS 6 为例说明如何编写 init script 在 CentOS 6 中有一个示例脚本, 这个脚本已经标明了一个 init Script 必备的一些结构.

示例脚本

在 CentOS 的 /usr/share/doc/initscripts-*/sysvinitfiles 这个目录你可以看到如下所示的示例

#!/bin/bash
#
#       /etc/rc.d/init.d/<servicename>
#
#       <description of the *service*>
#       <any general comments about this init script>
#
# <tags -- see below for tag definitions.  *Every line* from the top
#  of the file to the end of the tags section must begin with a #
#  character.  After the tags section, there should be a blank line.
#  This keeps normal comments in the rest of the file from being
#  mistaken for tags, should they happen to fit the pattern.>

# Source function library.
. /etc/init.d/functions

<define any local shell functions used by the code that follows>

start() {
        echo -n "Starting <servicename>: "
        <start daemons, perhaps with the daemon function>
        touch /var/lock/subsys/<servicename>
        return <return code of starting daemon>
}

stop() {
        echo -n "Shutting down <servicename>: "
        <stop daemons, perhaps with the killproc function>
        rm -f /var/lock/subsys/<servicename>
        return <return code of stopping daemon>
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        <report the status of the daemons in free-form format,
        perhaps with the status function>
        ;;
    restart)
        stop
        start
        ;;
    reload)
        <cause the service configuration to be reread, either with
        kill -HUP or by restarting the daemons, in a manner similar
        to restart above>
        ;;
    condrestart)
        <Restarts the servce if it is already running. For example:>
        [ -f /var/lock/subsys/<service> ] && restart || :
    probe)
        <optional.  If it exists, then it should determine whether
        or not the service needs to be restarted or reloaded (or
        whatever) in order to activate any changes in the configuration
        scripts.  It should print out a list of commands to give to
        $0; see the description under the probe tag below.>
        ;;
    *)
        echo "Usage: <servicename> {start|stop|status|reload|restart[|probe]"
        exit 1
        ;;
esac
exit $?

根据示例脚本创建一个 init Script

  • 修改服务名称为 glassfish4, 并保存 init Script 到 /etc/init.d/ 目录中.
  • 使用 chkconfig 管理服务 [root@server ~]# chkconfig --add glassfish4 [root@server ~]# chkconfig --list glassfish4 glassfish4 0:off 1:off 2:off 3:on 4:on 5:on 6:off
  • 启动服务 [root@server ~]# service glassfish4 start Starting glassfish4: Waiting for domain1 to start ......................... Successfully started the domain : domain1 domain Location: /home/gfish/glassfish4/glassfish/domains/domain1 Log File: /home/gfish/glassfish4/glassfish/domains/domain1/logs/server.log Admin Port: 4848 Command start-domain executed successfully. [ OK ]
  • 关闭服务
[root@server ~]# service glassfish4 stop
Shutting down glassfish4: Waiting for the domain to stop ......
Command stop-domain executed successfully.
                                                           [  OK  ]
  • 重启服务
[root@server ~]# service glassfish4 restart
Shutting down glassfish4: Waiting for the domain to stop ..
Command stop-domain executed successfully.
                                                           [  OK  ]
Starting glassfish4: Waiting for domain1 to start ......................
Successfully started the domain : domain1
domain  Location: /home/gfish/glassfish4/glassfish/domains/domain1
Log File: /home/gfish/glassfish4/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.
                                                           [  OK  ]
  • 查看服务状态
[root@server ~]# service glassfish4 status
Checking glassfish4 status: domain1 running

Sysvinit 特点

根据上述描述, 我们可以看到 Sysvinit 有如下特点

  • 有启动级别的概念
  • 每一个启动级别可以启动不同的服务
  • 所有的服务的启动脚本都放在 /etc/init.d 中
  • /etc/rc.d/rcN.d 中都有相关启动服务的启动脚本的符号链接
  • 所有的开机启动服务都是通过脚本来维护的

另外, 本文只是粗略的介绍了一下启动脚本的编写, Sysvinit 除了启动脚本还有很多特性. 比如:

  • 当系统启动有多个服务的时候, 每一个服务都是串行启动的, 前一个服务完全启动之前, 后一个服务无法启动
  • 因为所有的系统服务都是串行启动的, 决定了系统的启动速度比较缓慢
  • 所有配置的服务, Sysvinit 都会启动, 有很多服务在大多数情况下是用不到的, 但是 Sysvinit 还是会启动
  • Sysvinit 通过进程 pid 的方式跟踪系统服务的状态的, pid 的方式需要服务的开发者使用精灵进程的方式启动服务

systemd 介绍

Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 Sysvinit 固有的缺点,提高系统的启动速度. 如下图所示, 首先要意识到 Systemd 不是一个单独的程序, 而是一整套的解决方案. systemd 架构图

systemd 特点

同 SysVinit 和 LSB init scripts 兼容

Systemd 提供了和 Sysvinit 以及 LSB initscripts 兼容的特性。系统中已经存在的服务和进程无需修改。这降低了系统向 systemd 迁移的成本,使得 systemd 替换现有初始化系统成为可能。

更快的启动速度

  • Systemd 采用了 socket / D-Bus activation 等技术启动服务。一个显而易见的结果就是:更快的启动速度。为了减少系统启动时间,systemd 的目标是:

    • 尽可能启动更少的进程
    • 尽可能将更多进程并行启动

systemd 具备按需启动的能力

当 sysvinit 系统初始化的时候,它会将所有可能用到的后台服务进程全部启动运行。并且系统必须等待所有的服务都启动就绪之后,才允许用户登录。这种做法有两个缺点:首先是启动时间过长;其次是系统资源浪费。 某些服务很可能在很长一段时间内,甚至整个服务器运行期间都没有被使用过。比如 CUPS,打印服务在多数服务器上很少被真正使用到。您可能没有想到,在很多服务器上 SSHD 也是很少被真正访问到的。花费在启动这些服务上的时间是不必要的;同样,花费在这些服务上的系统资源也是一种浪费。

Systemd 可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,systemd 可以关闭它,等待下次需要时再次启动它

Systemd 采用 Linux 的 Cgroup 特性跟踪和管理进程的生命周期

CGroup 已经出现了很久,它主要用来实现系统资源配额管理。CGroup 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 CGroup。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 CGroup,systemd 只需要简单地遍历指定的 CGroup 即可正确地找到所有的相关进程,将它们一一停止即可。

启动挂载点和自动挂载的管理

Systemd 内建了自动挂载服务,无需另外安装 autofs 服务,可以直接使用 systemd 提供的自动挂载管理能力来实现 autofs 的功能。

实现事务性依赖关系管理

能够对系统进行快照和恢复

日志服务

systemd 必知必会

如果上述 Systemd 的特点您不太明白, 也还好, 但是本节介绍的内容您一定要牢记, 因为在不久的将来可能所有的 Linux 发行版都会通过这种方式管理系统服务. Systemd 管理系统服务由如下几个基本概念组成

单元的概念

系统初始化需要做的事情非常多。需要启动后台服务,比如启动 SSHD 服务;需要做配置工作,比如挂载文件系统。这个过程中的每一步都被 systemd 抽象为一个配置单元,即 unit。可以认为一个服务是一个配置单元;一个挂载点是一个配置单元;一个交换分区的配置是一个配置单元;等等。systemd 将配置单元归纳为以下一些不同的类型。然而,systemd 正在快速发展,新功能不断增加。所以配置单元类型可能在不久的将来继续增加。

  • service :代表一个后台服务进程,比如 mysqld。这是最常用的一类。
  • socket :此类配置单元封装系统和互联网中的一个 套接字 。当下,systemd 支持流式、数据报和连续包的 AF_INET、AF_INET6、AF_UNIX socket 。每一个套接字配置单元都有一个相应的服务配置单元 。相应的服务在第一个”连接”进入套接字时就会启动(例如:nscd.socket 在有新连接后便启动 nscd.service)。
  • device :此类配置单元封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备配置单元出现。
  • mount :此类配置单元封装文件系统结构层次中的一个挂载点。Systemd 将对这个挂载点进行监控和管理。比如可以在启动时自动将其挂载;可以在某些条件下自动卸载。Systemd 会将/etc/fstab 中的条目都转换为挂载点,并在开机时处理。
    • automount :此类配置单元封装系统结构层次中的一个自挂载点。每一个自挂载配置单元对应一个挂载配置单元 ,当该自动挂载点被访问时,systemd 执行挂载点中定义的挂载行为。
    • swap: 和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。
    • target :此类配置单元为其他配置单元进行逻辑分组。它们本身实际上并不做什么,只是引用其他配置单元而已。这样便可以对配置单元做一个统一的控制。这样就可以实现大家都已经非常熟悉的运行级别概念。比如想让系统进入图形化模式,需要运行许多服务和配置命令,这些操作都由一个个的配置单元表示,将所有这些配置单元组合为一个目标(target),就表示需要将这些配置单元全部执行一遍以便进入目标所代表的系统运行状态。 (例如:multi-user.target 相当于在传统使用 SysV 的系统中运行级别 5)
    • timer:定时器配置单元用来定时触发用户定义的操作,这类配置单元取代了 atd、crond 等传统的定时服务。
    • snapshot :与 target 配置单元相似,快照是一组配置单元。它保存了系统当前的运行状态。

每个配置单元都有一个对应的配置文件,系统管理员的任务就是编写和维护这些不同的配置文件,比如一个 MySQL 服务对应一个 mysql.service 文件。这种配置文件的语法非常简单,用户不需要再编写和维护复杂的系统 5 脚本了。

依赖关系

虽然 systemd 将大量的启动工作解除了依赖,使得它们可以并发启动。但还是存在有些任务,它们之间存在天生的依赖,不能用”套接字激活”(socket activation)、D-Bus activation 和 autofs 三大方法来解除依赖(三大方法详情见后续描述)。比如:挂载必须等待挂载点在文件系统中被创建;挂载也必须等待相应的物理设备就绪。为了解决这类依赖问题,systemd 的配置单元之间可以彼此定义依赖关系。

Systemd 用配置单元定义文件中的关键字来描述配置单元之间的依赖关系。比如:unit A 依赖 unit B,可以在 unit B 的定义中用”require A”来表示。这样 systemd 就会保证先启动 A 再启动 B。

Systemd 事务

Systemd 能保证事务完整性。Systemd 的事务概念和数据库中的有所不同,主要是为了保证多个依赖的配置单元之间没有环形引用。比如 unit A、B、C,假如它们的依赖关系为:

存在循环依赖,那么 systemd 将无法启动任意一个服务。此时 systemd 将会尝试解决这个问题,因为配置单元之间的依赖关系有两种:required 是强依赖;want 则是弱依赖,systemd 将去掉 wants 关键字指定的依赖看看是否能打破循环。如果无法修复,systemd 会报错。

Systemd 能够自动检测和修复这类配置错误,极大地减轻了管理员的排错负担。

Target 和运行级别

systemd 用目标(target)替代了运行级别的概念,提供了更大的灵活性,如您可以继承一个已有的目标,并添加其它服务,来创建自己的目标。下表列举了 systemd 下的目标和常见 runlevel 的对应关系:

Systemd 和 Sysvinit 启动配置比较

  • Sysvinit 是通过脚本配置启动服务的, 不便于维护
  • Systemd 通过标准的 YAML 格式文件维护系统服务, 便于维护
  • Systemd 通过 Cgroup 的方式维护系统服务, 而不是通过 pid , 不会找错
  • Systemd 的 unit 概念对应 Sysvinit 的 service 概念
  • Systemd 的 target 的概念对应 Sysvinit 的启动级别的概念

对于开发人员需要做什么

开发人员需要了解 systemd 的更多细节。比如您打算开发一个新的系统服务,就必须了解如何让这个服务能够被 systemd 管理。这需要您注意以下这些要点:

  • 后台服务进程代码不需要执行两次派生来实现后台精灵进程,只需要实现服务本身的主循环即可。
  • 不要调用 setsid(),交给 systemd 处理
  • 不再需要维护 pid 文件。
  • Systemd 提供了日志功能,服务进程只需要输出到 stderr 即可,无需使用 syslog。
  • 处理信号 SIGTERM,这个信号的唯一正确作用就是停止当前服务,不要做其他的事情。
  • SIGHUP 信号的作用是重启服务。
  • 需要套接字的服务,不要自己创建套接字,让 systemd 传入套接字。
  • 使用 sd_notify()函数通知 systemd 服务自己的状态改变。一般地,当服务初始化结束,进入服务就绪状态时,可以调用它。

Unit 文件的编写

对于开发者来说,工作量最大的部分应该是编写配置单元文件,定义所需要的单元。

举例来说,开发人员开发了一个新的服务程序,比如 httpd,就需要为其编写一个配置单元文件以便该服务可以被 systemd 管理,类似 UpStart 的工作配置文件。在该文件中定义服务启动的命令行语法,以及和其他服务的依赖关系等。

此外我们之前已经了解到,systemd 的功能繁多,不仅用来管理服务,还可以管理挂载点,定义定时任务等。这些工作都是由编辑相应的配置单元文件完成的。我在这里给出几个配置单元文件的例子。

下面是 SSH 服务的配置单元文件,服务配置单元文件以.service 为文件名后缀

#cat /etc/system/system/sshd.service
  [Unit]
  Description=OpenSSH server daemon
  [Service]
  EnvironmentFile=/etc/sysconfig/sshd
  ExecStartPre=/usr/sbin/sshd-keygen
  ExecStart=/usrsbin/sshd –D $OPTIONS
  ExecReload=/bin/kill –HUP $MAINPID
  KillMode=process
  Restart=on-failure
  RestartSec=42s
  [Install]
  WantedBy=multi-user.target

文件分为三个小节。第一个是[Unit]部分,这里仅仅有一个描述信息。第二部分是 Service 定义,其中,ExecStartPre 定义启动服务之前应该运行的命令;ExecStart 定义启动服务的具体命令行语法。第三部分是[Install],WangtedBy 表明这个服务是在多用户模式下所需要的。

那我们就来看下 multi-user.target 吧:

cat multi-user.target


  [Unit]
  Description=Multi-User System
  Documentation=man.systemd.special(7)
  Requires=basic.target
  Conflicts=rescue.service rescure.target
  After=basic.target rescue.service rescue.target
  AllowIsolate=yes
  [Install]
  Alias=default.target
  

第一部分中的 Requires 定义表明 multi-user.target 启动的时候 basic.target 也必须被启动;另外 basic.target 停止的时候,multi-user.target 也必须停止。如果您接着查看 basic.target 文件,会发现它又指定了 sysinit.target 等其他的单元必须随之启动。同样 sysinit.target 也会包含其他的单元。采用这样的层层链接的结构,最终所有需要支持多用户模式的组件服务都会被初始化启动好。

在[Install]小节中有 Alias 定义,即定义本单元的别名,这样在运行 systemctl 的时候就可以使用这个别名来引用本单元。这里的别名是 default.target,比 multi-user.target 要简单一些。。。

此外在/etc/systemd/system 目录下还可以看到诸如*.wants 的目录,放在该目录下的配置单元文件等同于在[Unit]小节中的 wants 关键字,即本单元启动时,还需要启动这些单元。比如您可以简单地把您自己写的 foo.service 文件放入 multi-user.target.wants 目录下,这样每次都会被默认启动了

最后,让我们来看看 sys-kernel-debug.mout 文件,这个文件定义了一个文件挂载点:

cat sys-kernel-debug.mount

[Unit]
Description=Debug File Syste
DefaultDependencies=no
ConditionPathExists=/sys/kernel/debug
Before=sysinit.target
[Mount]
What=debugfs
Where=/sys/kernel/debug
Type=debugfs

这个配置单元文件定义了一个挂载点。挂载配置单元文件有一个[Mount]配置小节,里面配置了 What,Where 和 Type 三个数据项。这都是挂载命令所必须的,例子中的配置等同于下面这个挂载命令:

mount –t debugfs /sys/kernel/debug debugfs

系统管理员

systemd 的主要命令行工具是 systemctl。

多数管理员应该都已经非常熟悉系统服务和 init 系统的管理,比如 service、chkconfig 以及 telinit 命令的使用。systemd 也完成同样的管理任务,只是命令工具 systemctl 的语法有所不同而已,因此用表格来对比 systemctl 和传统的系统管理命令会非常清晰

Sysvinit 命令 Systemd 命令 备注
service foo start systemctl start foo.service 用来启动一个服务 (并不会重启现有的)
service foo stopsystemctl stop foo.service 用来停止一个服务 (并不会重启现有的)
service foo restart systemctl restart foo.service 用来停止并启动一个服务。
service foo reload systemctl reload foo.service 当支持时,重新装载配置文件而不中断等待操作
service foo condrestart systemctl condrestart foo.service 如果服务正在运行那么重启它
service foo status systemctl status foo.service 汇报服务是否正在运行
ls /etc/rc.d/init.d/ systemctl list-unit-files –type=service 用来列出可以启动或停止的服务列表
chkconfig foo on systemctl enable foo.service 在下次启动时或满足其他触发条件时设置服务为启用
chkconfig foo off systemctl disable foo.service 在下次启动时或满足其他触发条件时设置服务为禁用
chkconfig foo systemctl is-enabled foo.service 用来检查一个服务在当前环境下被配置为启用还是禁用。
chkconfig –list systemctl list-unit-files –type=service 输出在各个运行级别下服务的启用和禁用情况
chkconfig foo –list ls /etc/systemd/system/*.wants/foo.service 用来列出该服务在哪些运行级别下启用和禁用。
chkconfig foo –add systemctl daemon-reload 当您创建新服务文件或者变更设置时使用。
telinit 3 systemctl isolate multi-user.target (OR systemctl isolate runlevel3.target OR telinit 3) 改变至多用户运行级别

参考文献