如何配置一项在崩溃或重启后自动启用的Linux服务——第二部分:参考

提供:ZStack云计算

系列教程

本教程为如何配置一项在崩溃或重启后自动启用的Linux服务系列两篇中的第二篇。

内容介绍

在本文中,我们将更为具体地解释上篇教程中提到的各init进程,帮助大家更好地理解其如何控制守护程序的start-up行为。

在第一部分中,我们探讨了如何利用MySQL在崩溃或重启情况下实现Linux服务的自动启用。

在前篇文章内,我们谈到三种不同的init模式,即System V、Upstart与systemd。参阅第一部分回顾各发行版中使用的默认init系统。

在这里,我们将更为详尽地说明流程中所使用的命令与需要编辑的配置文件。首先从System V init守护程序入手,了解其为何逐步被新型init模式所替代。

先决条件

要完成本篇教程,大家首先需要根据上一篇内容构建三套DigitalOcean Droplets。

具体包括:

  • 一套运行MySQL的Debian 6服务器
  • 一套运行MySQL的Ubuntu 14.04服务器
  • 一套运行MySQL的CentOS 7服务器

另外,我们还需要root用户或者拥有sudo权限的非root用户。要了解sudo权限相关内容,请参阅此篇教程。

请注意,大家不应将本篇教程中提及的任何命令、查询或者配置应用于生产Lnux服务器。

Runlevels(运行级别)

一条runlevel代表的是一套Linux系统的当前状态。

其概念源自System V init,具体步骤为Linux系统引导、初始化内核、进入某种(惟一一种)runlevel。

例如,runlevel可以代表Linux服务器的停机状态、单用户模式以及重启模式等等。每种模式都决定了能够运行于该状态下的具体服务。

部分服务能够运行在多种runlevel之下,但部分服务只能匹配一种runlevel。

Runlevels由单一数字表现,取值范围为0到6,具体含义如下:

  • Runlevel 0: 系统停机
  • Runlevel 1: 单用户救援模式
  • Runlevels 2, 3, 4: 多用户文本模式,网络功能启用
  • Runlevel 5: 多用户,网络功能启用,图形模式
  • Runlevel 6: 系统重启

Runlevels 2, 3与4在不同发行版中有所区别。例如,某些Linux版本不提供runlevel 4,而有些则提供。部分发行版在这三种级别之间拥有区分。总体来讲,runlevel 2,3与4代表Linux在多用户、网络功能启用的文本模式下运行。

当将服务设定为auto-start时,我们需要将其添加至runlevel当中。在System V中,系统会以特定runlevel启动,并同时启动全部与该runlevel相关联的服务。

在systemd中,runlevel则被替换为target,我们将在后文中进行说明。

Init与PID 1

init为Linux系统在完成设备引导及内核加载后的首个进程。

它的作用是决定用户进程或者系统服务如何加载、采用怎样的顺序以及是否自动启动。

Linux中的每个进程都拥有一个进程ID(PID),而init的PID为1。它是其它全部进程的父进程。

Init的历史

随着Linux的逐步演进,init守护程序也同时发生着变化。最初,Linux使用的是System V init,与Unix保持一致。在此之后,Linux开始使用Upstart init守护程序(由Ubuntu创建),如今则转换为systemd init守护程序(最初来自Fedora)。

大部分Linux发行版都已经移除了System V或者以自己的方式将其淘汰,只保留向下兼容能力。作为Unix的变种,FreeBSD采用的就是BSD init。Debian的早期版本使用的正是SysV init。

不同init版本拥有着不同的服务管理方式。这些变化背后的原因在于对强大服务管理工具的需求,用户需要其在服务之外,同时管理设备、端口及其它资源,意味着init必须并行加载资源且能够从崩溃中顺利实现恢复。

System V Init执行步骤

System V使用inittab文件,而Upstart等后续init方案继续保留向下兼容能力。

下面来看看System V的启动流程:

  1. 由二进制文件/sbin/init负责创建
  2. 该init守护程序首先读取的文件为/etc/inittab
  3. 此文件中的一个条目负责决定设备引导所使用的runlevel。如果该runlevel值被设定为3,则Linux会引导为多用户、网络功能启动的文本模式。(此runlevel为默认值。)
  4. 接下来,init会查找/etc/inittab文件并读取runlevel相关init脚本。

在读取runlevel相关init脚本时,init守护程序实际上是在搜索需要启动的各项服务。这些init脚本负责指定各服务的配置启动行为。

下面具体谈谈init脚本。

System V配置文件:Init脚本

init脚本负责控制System V中的特定服务,例如MySQL Server。

Init脚本可由应用供应方或者Linux发行版(针对原生服务)负责提供。我们也可以为定制化服务创建自己的init脚本。

当某一进程或者服务启动时,其二进制程序文件需要被加载至内存中。

根据实际服务配置,此程序可能需要持续存在于后台(并接收客户端连接)。而二进制应用的启动、停止或者重载则由该服务的init脚本实现,而之所以称其为init脚本,是因为其负责对该服务进行初始化(initialize)。

在System V中,init脚本属于shell脚本。

Init脚本亦被称为rc(即run命令)脚本。

目录结构

/etc目录为init脚本的主目录。各init shell脚本的实际位置保存在/etc/init.d。这些脚本被符号链接至rc目录处。

在/etc目录中存在着多个rc目录,其名称中皆包含一个数字。这些数字代表着不同runlevels,因此实际名称可能为/etc/rc0.d、rc1.d等等。

在每个rcn.d目录内,我们都能够找到以K或者S作为文件名开头的文件,字母之后则是两位数字。这些符号链接文件指向回具体init shell脚本。为什么用K与S?K代表Kill(即停止),而S代表Start。

两位数字则为该脚本的执行顺序。因此,如果我们的文件名为K25,那么其执行优先级高于K99脚本。

Startup

下面再来看启动序列。这些init脚本如何调用?又由谁来调用?

K与S脚本并非直接由init守护程序调用,而是接受另一脚本的调用:/etc/init.d/rcscript。

大家可能还记得,/etc/inittab文件告知init守护程序当前系统应默认使用怎样的runlevel。对于每种runlevel,/etc/inittab文件内都有一行负责调用/etc/init.d/rc脚本,并将该runlevel作为参数进行传递。以该参数为基础,脚本随后调用对应/etc/rcn.d目录下的文件。因此,如果服务器以runlevel 2引导,那么/etc/rc2.d下的脚本就会得到调用。

而在rc目录内,首先全部K脚本按次序配合“stop”参数运行,而后是全部S脚本配合“start”参数以同样的方式运行。这意味着各init shell脚本将分别以stop与start作为参数接受调用。

由于/etc/rcn.d目录下的各文件(Knn与Snn文件)仅属于符号链接,因此调用它们意味着实际调用的是对应init shell脚本。

总体来讲,当Linux服务器进入某runlevel时,特定脚本将运行以停止某些服务,其它一些脚本则运行以启动某些服务。

这种init脚本调用方式同样存在于新runlevel的系统切换过程中,具体流程同上。

整个流程能够确保任何不应运行在当前runlevel中的服务被停止,而全部应运行在该runlevel下的服务得到启动。

System V自动启动

在引导时将某项服务设定为auto-start,则意味着对其init行为加以修改。

例如,当我们将某服务在runlevel 3中进行自动启动,则相当于在/etc/rc3.d目录下创建对应的链接。

听起来有点复杂,不过别担心,下面我们通过示例进行理解。

System V示例

下面来看我们的MySQL服务示例,这一次我们着重讲解其中的理论内容。

第一步——登录Debian Droplet

出于教程需要,我们使用第一部分中创建的Debian 6 Droplet。使用SSH命令接入该服务器(Windows用户亦可使用PuTTy等工具接入)。

  • ssh sammy@your_server_ip

第二步——查看inittab

运行以下命令查看inittab文件内容:

  • cat /etc/inittab | grep initdefault

输出结果如下:

Output

id:2:initdefault:

id字段中的2代表系统配置为runlevel 2,即其默认runlevel。在本示例中,Debian的runlevel 2代表多用户文本模式。如果大家执行以下命令:

  • cat /etc/inittab | grep Runlevel

则输出结果如下:

Output

# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.

第三步——查看rc目录

运行以下命令列出全部rc目录:

  • ls -ld /etc/rc*.d

    Output

    drwxr-xr-x 2 root root 4096 Jul 31 07:09 /etc/rc0.d
    drwxr-xr-x 2 root root 4096 Jul 31 07:09 /etc/rc1.d
    drwxr-xr-x 2 root root 4096 Jul 31 07:21 /etc/rc2.d
    drwxr-xr-x 2 root root 4096 Jul 31 07:21 /etc/rc3.d
    drwxr-xr-x 2 root root 4096 Jul 31 07:21 /etc/rc4.d
    drwxr-xr-x 2 root root 4096 Jul 31 07:21 /etc/rc5.d
    drwxr-xr-x 2 root root 4096 Jul 31 07:09 /etc/rc6.d
    drwxr-xr-x 2 root root 4096 Jul 23 2012 /etc/rcS.d

由于系统引导为runlevel 2(inittab文件中的默认init),因此系统启动时直接使用/etc/rc2.d目录下的脚本。

列出此目录内容:

  • ls -l /etc/rc2.d

此目录中只包含符号链接,各自指向/etc/init.d目录下的对应脚本文件:

Output

. . .
lrwxrwxrwx 1 root root  17 Jul 23  2012 S01rsyslog -> ../init.d/rsyslog
lrwxrwxrwx 1 root root  22 Jul 23  2012 S02acpi-support -> ../init.d/acpi-support
lrwxrwxrwx 1 root root  15 Jul 23  2012 S02acpid -> ../init.d/acpid
lrwxrwxrwx 1 root root  17 Jul 23  2012 S02anacron -> ../init.d/anacron
lrwxrwxrwx 1 root root  13 Jul 23  2012 S02atd -> ../init.d/atd
lrwxrwxrwx 1 root root  14 Jul 23  2012 S02cron -> ../init.d/cron
lrwxrwxrwx 1 root root  15 Jul 31 07:09 S02mysql -> ../init.d/mysql
lrwxrwxrwx 1 root root  13 Jul 23  2012 S02ssh -> ../init.d/ssh
. . .

可以看到,其中不包含K脚本,只有S(即start)脚本。这些脚本负责启动rsyslog、cron以及ssh等服务。

请注意,S之后的两位数字代表启动顺序:例如,rsyslog启动优先级高于cron守护程序。

第四步——查看Init脚本内容

现在我们已经理解了System V下各服务的安装方式,其会在/etc/init.d目录下创建shell脚本。下面查看MySQL的shell脚本:

  • ls -l /etc/init.d/my*

输出结果

-rwxr-xr-x 1 root root 5437 Jan 14 2014 /etc/init.d/mysql

读取该文件以查看start-up脚本的具体内容:

  • cat /etc/init.d/mysql | less

在结果中,可以看到这是一套规模可观的bash脚本。

第五步——使用chkconfig或sysv-rc-conf

在CentOS这类基于RHEL的发行版中,我们可以使用chkconfig以启用或禁用System V中的服务。它还能够列出已经安装的服务及其runlevel。

检查CentOS系统下全部runlevels的服务状态,可使用以下语法:

  • chkonfig –list | grep service_name

Debian自身并不提供这样的工具(update-rc.d只能从runlevel处安装或移除服务),因此我们需要安装sysv-rc-conf工具进行服务管理。

安装sysv-rc-conf:

  • sudo apt-get install sysv-rc-conf -y

工具安装完成后,执行该命令以查看各服务的runlevel行为:

  • sudo sysv-rc-conf

输出结果如下所示。在这里,我们可以看到哪些服务适用哪些runlevels(以X标记)。

使用箭头键与空格键,我们可以禁用或启用各项服务的runlevels.

现在按下Q退出此屏幕。

第七步——测试引导中的MySQL启动行为

可以看到,MySQL目前可在runlevel 2到5中启用。

运行以下命令以禁用MySQL服务:

  • sudo update-rc.d mysql disable

    Output

    update-rc.d: using dependency based boot sequencing
    insserv: warning: current start runlevel(s) (empty) of script mysql' overwrites defaults (2 3 4 5).
    insserv: warning: current stop runlevel(s) (0 1 2 3 4 5 6) of script
    mysql’ overwrites defaults (0 1 6).

现在运行:

  • ls -l /etc/rc2.d

输出结果应显示由/etc/rc2.d到/etc/init.d/mysql下的符号链接被变更为K:

Output

. . .
lrwxrwxrwx 1 root root  15 Jul 31 07:09 K02mysql -> ../init.d/mysql
. . .

换言之,现在MySQL将不会在默认runlevel(2)下启动。

这就是我们在System V中启用及禁用服务时,背后的整个调整过程。只要目标服务在默认runlevel目录下拥有S脚本,init就会在引导时对其加以启动。

再次启动服务:

  • sudo update-rc.d mysql enable

第八步——测试崩溃时的MySQL Start-up行为

下面来看System V如何处理服务崩溃。

我们曾在第一部分教程中对/etc/inittab文件进行过变更,从而使MySQL在发生崩溃时自动恢复。现在添加以下行:

/etc/inittab

ms:2345:respawn:/bin/sh /usr/bin/mysqld_safe

这能帮助MySQL服务在崩溃后自动恢复。为检查其效果,我们重启服务器:

  • sudo reboot

当服务器恢复运行后,SSH会介入并检查MySQL进程ID:

  • ps -ef | grep mysql

请注意mysqld_safe与mysqld的进程ID。在本示例中,二者分别为895与1019:

Output

root       907     1  0 07:30 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql     1031   907  0 07:30 ?        00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --user=mysql --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306
root      1032   907  0 07:30 ?        00:00:00 logger -t mysqld -p daemon.error
root      2550  2532  0 07:31 pts/0    00:00:00 grep mysql

使用-9开关再次关闭该进程:

  • sudo kill -9 907
  • sudo kill -9 1031

等待五分钟左右,再次执行该命令:

  • sudo service mysql status

输出结果显示MySQL已经开始运行:

Output

/usr/bin/mysqladmin  Ver 8.42 Distrib 5.1.73, for debian-linux-gnu on x86_64

如果再次运行ps -ef | grep mysql命令,则可看到mysqld_safe与mysqldprocessess已经恢复运行。

多试几次,结果应该始终一致。

这种在/etc/inittab中添加额外行的方式,正是配置System V服务实现崩溃后恢复的手段。大家可以参阅第一部分内容了解具体语法构成。

另外需要注意的是,如果某项服务在2分钟之内崩溃并尝试恢复的次数超过十次,则Linux会在接下来的五分钟中禁用其恢复尝试。如果大家在控制台中收到了这样的事件,请立即检查对应应用——其已经开始发生持续崩溃。

Upstart介绍

经典SysV init长久以来一直存在于主流Linux发行版本当中,直到Upstart的出现。

随着Linux市场的不断发展,人们发现序列化加载任务及服务开始占用大量时间且变得愈发复杂。与此同时,越来越多现代服务(例如可插拔存储介质)开始出现,意味着SysV init已经很难对其进行快速处理。

在这样的情况下,Ubuntu率先迈出了尝试的脚步,Upstart作为另一种初始化守护程序正式出现。

Upstart init在以下几个方面优于System V:

  • Upstart无需处理晦涩的shell脚本即可加载并管理服务。相反,其使用更简单的配置文件,便于理解与修改。
  • Upstart不像SYstem V那样以序列方式进行服务加载,能够有效缩短系统引导时间。
  • Upstart使用灵活的事件系统定义服务对各类状态的处理方式。
  • Upstart能够更好地处理崩溃后服务的恢复。
  • 无需大量指向同一脚本的冗余符号链接。
  • Upstart向下兼容System V。/etc/init.d/rc脚本仍然可管理原生System V服务。

Upstart事件

Upstart允许多个事件与单一服务相关联。这套基于事件的架构允许Upstart更灵活地处理服务管理工作。

每个事件都能关闭负责管理该事件的shell脚本。

Upstart事件包括:

  • Starting
  • Started
  • Stopping
  • Stopped

在这些事件中,单一服务可还可处于其它状态,例如:

  • waiting
  • pre-start
  • starting
  • running
  • pre-stop
  • stopping
  • etc.

Upstart也能够执行这些事件,从而实现极为灵活的架构。

Upstart Init序列

与System V类似,Upstart同样会在启动时运行/etc/init.d/rc脚本。该脚本能够正常执行System V init脚本。

Upstart还会查看/etc/init目录并执行各服务配置文件中的shell命令。

Upstart配置文件

Upstart利用配置文件实现服务控制。

Upstart并不像System V那样使用Bash脚本,而使用服务配置文件,其标准名称为service_name.conf。

该文本由纯文本构成,其中包含多个部分,名为stanzas。每个stanza负责描述服务的一个方面及其行为方式。

不同的stanzes控制服务中的不同事件,例如pre-start、start、pre-stop或者post-stop。

这些Stanzas本身包含有shell命令。因此,我们可以为每项服务中的每个事件调用多种操作。

每个配置文件还指定以下两项内容:

  • 该服务应启动及停止于哪种runlevel
  • 该服务在崩溃后是否应当恢复

目录结构

Upstart配置文件位于/etc/init目录内。

Upstart示例

下面看看Upstart如何处理MySQL Server服务。

第一步——登录Ubuntu Droplet

返回我们在第一部分中创建的Ubuntu 14.04 Droplet,使用SSH命令接入该服务器。

  • ssh sammy@your_server_ip

第二步——查看init与rc目录

大部分Upstart配置文件位于/etc/init目录。我们需要利用它创建新服务。

登录至服务器后执行以下命令:

  • sudo ls -l /etc/init/ | less

结果显示大量服务配置文件,每次一屏。以下为Upstart下的原生运行服务:

Output

total 356
. . .
-rw-r--r-- 1 root root  297 Feb  9  2013 cron.conf
-rw-r--r-- 1 root root  489 Nov 11  2013 dbus.conf
-rw-r--r-- 1 root root  273 Nov 19  2010 dmesg.conf
. . .
-rw-r--r-- 1 root root 1770 Feb 19  2014 mysql.conf
-rw-r--r-- 1 root root 2493 Mar 20  2014 networking.conf

按Q退出。

将其与系统中的原生System V init服务进行比对:

  • sudo ls -l /etc/rc3.d/* | less

结果更为直观:

Output

-rw-r--r-- 1 root root 677 Jun 14 23:31 /etc/rc3.d/README
lrwxrwxrwx 1 root root  15 Apr 17  2014 /etc/rc3.d/S20rsync -> ../init.d/rsync
lrwxrwxrwx 1 root root  24 Apr 17  2014 /etc/rc3.d/S20screen-cleanup -> ../init.d/screen-cleanup
lrwxrwxrwx 1 root root  19 Apr 17  2014 /etc/rc3.d/S70dns-clean -> ../init.d/dns-clean
lrwxrwxrwx 1 root root  18 Apr 17  2014 /etc/rc3.d/S70pppd-dns -> ../init.d/pppd-dns
lrwxrwxrwx 1 root root  26 Apr 17  2014 /etc/rc3.d/S99digitalocean -> ../init.d//rc.digitalocean
lrwxrwxrwx 1 root root  21 Apr 17  2014 /etc/rc3.d/S99grub-common -> ../init.d/grub-common
lrwxrwxrwx 1 root root  18 Apr 17  2014 /etc/rc3.d/S99ondemand -> ../init.d/ondemand
lrwxrwxrwx 1 root root  18 Apr 17  2014 /etc/rc3.d/S99rc.local -> ../init.d/rc.local

第三步——查看Upstart文件

我们已经在第一部分教程中查看了mysql.conf文件,现在打开另一个配置文件——cron守护程序配置文件:

  • sudo nano /etc/init/cron.conf

可以看到,cron的配置文件相当简单:

/etc/init/cron.conf

# cron - regular background program processing daemon
#
# cron is a standard UNIX program that runs user-specified programs at
# periodic scheduled times

description     "regular background program processing daemon"

start on runlevel [2345]
stop on runlevel [!2345]

expect fork
respawn

exec cron

这里最重要的字段为start on、stop on与respawn。

指令中的start告知Ubuntu在系统处于runlevel 2、3、4或5时启动crond守护程序。2、3与4为多用户文本模式且启用网络功能,而5则为多用户图形模式。该服务无法运行在其它runlevel之下(例如0、1或6)。

而fork指令告知Upstart那些需要从控制台中分离并在后台运行的进程。

下面是respawn指令,它负责告知系统cron应当在崩溃后自动恢复。

不要变更文件内容,直接退出。

显然,cron配置文件非常简单,而MySQL配置文件虽然结构类似但更为复杂。除了start、stop与respawn之外,它还包含两套脚本块用于处理pre-start与post-start事件。这些代码块告知系统应在mysqld进程启动或者已经处于运行时执行怎样的操作。

关于如何构建自己的Upstart文件,请参阅本篇Upstart教程。

第四步——测试引导时的MySQL启动行为

我们都知道,MySQL实例在Ubuntu 14.04服务器上被默认为引导时自动启动。下面看看如何禁用自动启动。

在Upstart中,禁用服务需要使用/etc/init/下的service_name.override文件。该文件的内容非常简单:

manual

要了解如何利用此文件禁用MySQL,执行以下命令为MySQL创建override文件:

  • sudo nano /etc/init/mysql.override

添加以下一行:

/etc/init/mysql.override

manual

保存变更。

而后启动服务器:

  • sudo reboot

服务器重新运行后,检查该服务状态:

  • sudo initctl status mysql

输出结果应为:

Output

mysql stop/waiting

这意味着MySQL并未运行。

检查MySQL服务配置文件内的start指令是否已经变更:

  • sudo cat /etc/init/mysql.conf | grep start\ on

结果应该相同:

Output

start on runlevel [2345]

意味着在init目录下检查.conf文件并不是惟一影响服务是否自动启动的因素,因此需要确保不存在.override文件。

要重新启用MySQL,需要删除override文件并重启服务器:

  • sudo rm -f /etc/init/mysql.override

  • sudo reboot

服务器重启后,进行远程登录。

运行sudo initctl status mysql命令,可以看到该服务已经自动启动。

第五步——测试崩溃时的MySQL启动行为

在默认情况下,MySQL会在崩溃后自动重启。要停止其自动重启,打开/etc/init/mysql.conf配置文件:

  • sudo nano /etc/init/mysql.conf

将两条respawn指令修改为注释:

/etc/init/mysql.conf

# respawn
# respawn limit 2 5

运行以下命令重启服务:

  • sudo initctl stop mysql

  • sudo initctl start mysql

我们需要明确关闭及启动该服务,因为测试显示initctl restart或initctl reload命令在这里并不起效。

第二条命令用于显示MySQL的PID:

Output

mysql start/running, process 1274

注意MySQL实例的PID。如果在这里崩溃掉mysql进程,那么其无法自动恢复。关闭进程ID:

  • sudo kill -9 1274

检查其状态

  • sudo initctl status mysql

    Output

    mysql stop/waiting

多试几次,大家会发现MySQL确实无法恢复运行,这是因为该服务的配置文件中已经不存在respawn指令。

第一部分教程中包含有关于respawn指令的详尽解释。

那么我们什么时候会希望Upstart服务在重启或崩溃后不再自动恢复?

假定我们已经对Linux内核进行了更新或者安装了最新补丁,这时需要不受任何额外干扰地单纯重启服务器。通过禁用auto-start项目,我们能够显著降低升级风险。

如果大家的服务能够恢复但很快再次崩溃,也需要变更其respawn指令以检查问题原因。

systemd介绍

最后一款Linux init守护程序为systemd。事实上,它并不仅仅属于init守护程序:systemd是一套新型框架,综合了大量现代Linux系统中的组件。

其主要功能之一就是作为Linux的系统与服务管理器存在。systemd能够控制服务在崩溃或者设备重启后的操作。具体请参阅systemd的systemctl工具一文。

systemd向下支持System V命令及初始化脚本。这意味着任意System V服务都能够运行在systemd之下。这是因为大多数Upstart与System V管理命令都已经针对systemd进行了修改。

事实上,如果我们在系统上运行ps -ef | grep systemd命令,其不会返回任何结果。因为systemd在引导时将自身重命名为init。/sbin/init文件作为符号链接指向/bin/systemd。

systemd配置文件:Unit文件

systemd的核心在于unit文件。每个unit文件代表着一种系统资源。systemd与另外两种init方案的主要区别在于,systemd不仅负责初始化服务守护程序,同时也面向嵌套、设备操作系统路径、安装点等资源类型。

每个unit文件都代表一种特定系统资源,其名称为service name.unit。

因此我们会看到dbus.service、sshd.socket或者home.mount这样的文件名。

目录结构

在CentOs等基于红帽的系统中,unit文件存在于两个位置。其主位置为/lib/systemd/system/。

定制化unit文件或者现有unit文件都可由系统管理员修改,其保存于/etc/systemd/system目录下。

如果两个目录中存在同名文件,systemd会使用位于/etc下的那个。如果某服务可在引导时或其它target/runlevel条件下启动,则创建符号链接以指向/etc/systemd/system内对应目录中的服务unit文件。/etc/systemd/system下的unit文件实际上属于指向/lib/systemd/system下同名文件的符号链接。

systemd Init序列:Target Unit

这里要提一种特殊的unit文件类型,即target unit。

这类target unit的后缀名为.target。Target unit与其它unit文件不同,因为它们并不代表某种特定资源。相反,它们代表的是系统在任意时间内所处的状态。

Target unit会将多个unit文件进行分组与启动,并将其作为该状态的组成部分。systemd target因此能够在一定程度上与System V runlevel进行比较,当然二者并不尽相同。

每个target都拥有一个名称而非数字。例如,我们使用multi-user.target来代替runlevel 3。

当Linux服务器启动时,multi-user.target可能负责将服务器引导至runlevel 2、3或4,即启用网络功能的多用户文本模式。

具体实现方式正是二者的差异所在。与System V不同,systemd并不会按顺序启动服务。在此过程中,它会检查其它现有服务或资源,并决定其加载顺序。这意味着系统能够并行加载服务。

另一点不同在于,一套Linux系统只能存在于一种runlevel内,而target unit却并非惟一,意味着单一target unit活动时还可将其它target作为其组成部分进行加载。

例如,Linux系统在graphical.target激活时会引导一套图形用户界面,而其反过来又会自动确保multi-user.target加载并处于激活。

以下为runlevel与target的对比表格:

Runlevel (System V init)Target Units (Systemd)

 runlevel 0 poweroff.target

 runlevel 1 resuce.target

 runlevel 2, 3, 4 multi-user.target

 runlevel 5 graphical.target

 runlevel 6 reboot.target

systemd default.target

default.target相当于默认runlevel。

在System V中,默认runlevel由inittab文件定义。在systemd中,该文件则被替换为default.target。该默认target unit文件存在于/etc/systemd/system目录内。其属于符号链接,指向/lib/systemd/system目录下的target unit文件。

在变更该默认target时,我们实际上重新创建了该符号链接并变更了系统的runlevel。

System V中的inittab文件还指定Linux执行init脚本的来源目录。而在systemd中,默认target unit则会检测引导时将要加载的各资源units。

全部units都会被激活,但并非全部以并行或者序列方式激活。资源unit的具体加载方式取决于它所需要的其它资源unit。

systemd依赖性:想要与需要

之所以对unit文件与target文件进行探讨,是为了强调systemd如何处理不同守护程序之间的依赖性。

如之前所提到,Upstart利用配置文件确保服务的并行加载。在System V中,服务可以在特定runlevel下启动,但同时亦可等待其它服务或资源可用后再行启动。同样的,systemd服务也可加载至一个或者多个targets当中,或者等待其它服务或资源开始活动后再行启动。

在systemd中,要求另一unit方可正常运行的unit无法早于前者进行加载。另外,如果运行所需要的unit发生故障,但与之关联的unit仍处于活动,则后者亦同时停止。

systemd示例

现在让我们看systemd中的MySQL启动行为。

第一步——登录CentOS Droplet

为了理解各概念及服务自动启动机制,我们再次使用第一部分中创建的CentOS 7 Droplet。

使用SSH命令接入该服务器。

  • ssh sammy@your_server_ip

第二步——查看default.target文件与依赖性

这部分内容很长,因为我们需要尽可能多地深入查看.target设计方式。systemd的启动序列包含一套极长的依赖性链。

defaul.target

default.target文件控制服务器常规引导时,哪些服务会一同启动。

执行以下命令显示该默认target unit文件:

  • sudo ls -l /etc/systemd/system/default.target

输出结果如下:

Output

lrwxrwxrwx. 1 root root 37 Jul  8  2014 /etc/systemd/system/default.target -> /lib/systemd/system/multi-user.target

可以看到,默认的target属于指向/lib/systemd/system/下多用户target文件的符号链接。因此,该系统应当在multi-user.target之下引导,类似于runlevel 3。

multi-user.target.wants

下面,执行命令以检查multi-user.target文件想要的服务:

  • sudo ls -l /etc/systemd/system/multi-user.target.wants/*.service

输出结果如下:

Output

. . .
lrwxrwxrwx. 1 root root  37 Jul  8  2014 /etc/systemd/system/multi-user.target.wants/crond.service -> /usr/lib/systemd/system/crond.service
. . .
lrwxrwxrwx  1 root root  38 Jul 31 22:02 /etc/systemd/system/multi-user.target.wants/mysqld.service -> /usr/lib/systemd/system/mysqld.service
lrwxrwxrwx. 1 root root  46 Jul  8  2014 /etc/systemd/system/multi-user.target.wants/NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
lrwxrwxrwx. 1 root root  39 Jul  8  2014 /etc/systemd/system/multi-user.target.wants/postfix.service -> /usr/lib/systemd/system/postfix.service
lrwxrwxrwx. 1 root root  39 Jul  8  2014 /etc/systemd/system/multi-user.target.wants/rsyslog.service -> /usr/lib/systemd/system/rsyslog.service
lrwxrwxrwx. 1 root root  36 Jul  8  2014 /etc/systemd/system/multi-user.target.wants/sshd.service -> /usr/lib/systemd/system/sshd.service
. . .

其中全部为符号链接文件,指回/lib/systemd/system/下的实际unit文件。我们可以看到,mysqld.service属于multi-user.target的组成部分。

执行以下命令过滤输出结果可找到同样的信息:

  • sudo systemctl show –property “Wants” multi-user.target | fmt -10 | grep mysql

    Output

    mysqld.service

除了multi-user.target,target还拥有多种类型,例如system-update.target以及basic.target。

要查看multi-user target所依赖的各targets,执行以下命令:

  • sudo systemctl show –property “Requires” multi-user.target | fmt -10

    Output

    Requires=basic.target

这样系统在启动为multi-user.target模式前,会首先加载basic.target。

basic.target

要查看basic.target的其它依赖性,执行以下命令:

  • sudo systemctl show –property “Requires” basic.target | fmt -10

输出结果为:

Output

Requires=sysinit.target

sysinit.target

下面来看sysinit.target所需要的units。虽然结果显示没有必要的unit,但我们可以进一步查看其想要的服务:

  • sudo systemctl show –property “Wants” sysinit.target | fmt -10

其会显示sysinit想要的服务数量。

Output

Wants=local-fs.target
swap.target
cryptsetup.target
systemd-udevd.service
systemd-update-utmp.service
systemd-journal-flush.service
plymouth-read-write.service
. . .

可以看到,系统无法只依赖于单一target运行。其会根据不同targets之间的事务决定需要加载的服务。

第三步——查看Unit文件

再进一步,我们看看服务的unit文件。这里以sshd的unit文件为例:

  • sudo nano /etc/systemd/system/multi-user.target.wants/sshd.service

内容如下:

Output

[Unit]
Description=OpenSSH server daemon
After=syslog.target network.target auditd.service

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

[Install]
WantedBy=multi-user.target

与Upstart守护程序的配置文件类似,此服务unit文件明确且易于理解。

首先需要理解的是After子句。其表达的是SSHD服务需要在系统与网络targets外加审计登录服务加载完成后,才能进行加载。

该文件还显示此服务属于multi-user.target想要的条目,意味着此target会加载该服务,但其不会导致sshd发生故障。

由于multi-user.target属于默认target,sshd守护程序会在引导时启动。

退出编辑器。

第四步——测试引导时的MySQL启动行为

在第一部分教程中,我们已经开始运行MySQL服务,下面来看如何进行调整。

在上一章节里,我们运行了一条命令并确认mysqld.service属于multi-user.target_想要_的服务。而后,我们列出了/etc/systemd/system/multi-user.target.wants/目录下的内容,发现其中一条符号链接指回/usr/lib/systemd/system/下的原始服务unit。

运行以下命令禁用该服务,确保其不会在引导时自动运行:

  • sudo systemctl disable mysqld.service

现在运行以下命令,检查MySQL是否仍然为multi-user.target“想要”的服务:

  • sudo systemctl show –property “Wants” multi-user.target | fmt -10 | grep mysql

没有返回任何结果。运行以下命令检查符号链接是否仍然存在:

  • sudo ls -l /etc/systemd/system/multi-user.target.wants/mysql*

链接已经不存在:

Output

ls: cannot access /etc/systemd/system/multi-user.target.wants/mysql*: No such file or directory

现在重新启用该服务:

  • sudo systemctl enable mysqld.service

链接也会正常恢复:

  • sudo ls -l /etc/systemd/system/multi-user.target.wants/mysql*

    Output

    lrwxrwxrwx 1 root root 38 Aug 1 04:43 /etc/systemd/system/multi-user.target.wants/mysqld.service -> /usr/lib/systemd/system/mysqld.service

可以看到,我们能够在默认target的wants目录下启用或禁用systemd服务,同时创建或删除符号链接。

第五步——测试崩溃时MySQL启动行为

MySQL会在崩溃后自动恢复,下面看看如何禁用这一机制。

打开MySQL服务的unit文件:

  • sudo nano /etc/systemd/system/multi-user.target.wants/mysqld.service

在标题信息之后,文件内容如下:

/etc/systemd/system/multi-user.target.wants/mysqld.service

[Unit]
Description=MySQL Community Server
After=network.target
After=syslog.target

[Install]
WantedBy=multi-user.target
Alias=mysql.service

[Service]
User=mysql
Group=mysql

# Execute pre and post scripts as root
PermissionsStartOnly=true

# Needed to create system tables etc.
ExecStartPre=/usr/bin/mysql-systemd-start pre

# Start main service
ExecStart=/usr/bin/mysqld_safe

# Don't signal startup success before a ping works
ExecStartPost=/usr/bin/mysql-systemd-start post

# Give up if ping don't get an answer
TimeoutSec=600

Restart=always
PrivateTmp=false

如第一部分所见,Restart参数的值被设定为always(对于sshd,其被设定为on-failure only)。这意味着MySQL服务会在完全、不完全退出以及超时情况下进行重启。

Restart的各项参数请参阅systemd服务指南页面:

Restart settings/Exit causesnoalwayson-successon-failureon-abnormalon-aborton-watchdog

 Clean exit code or signal X X

 Unclean exit code X X

 Unclean signal X X X X

 Timeout X X X

 Watchdog X X X X

在systemd服务unit文件内,- Restart与- RestartSec两项参数负责控制崩溃行为。第一项参数指定服务在崩溃后重启,第二项则定义重启前的等待时长。

将Restart指令设为注释,保存文件后退出。

/etc/systemd/system/multi-user.target.wants/mysqld.service

# Restart=always

下面重载该systemd守护程序,而后重启mysqld服务:

  • sudo systemctl daemon-reload

  • sudo systemctl restart mysqld.service

接下来,找到该服务的主PID:

  • sudo systemctl status mysqld.service

    Output


    Main PID: 11217 (mysqld_safe)

使用-9命令关闭该主PID。

  • sudo kill -9 11217

运行sudo systemctl status mysqld.service,结果显示该服务发生故障:

  • sudo systemctl status mysqld.service

    Output

    mysqld.service - MySQL Community Server
    Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled)
    Active: failed (Result: signal) since Sun 2015-06-21 02:28:17 EDT; 1min 33s ago
    Process: 2566 ExecStartPost=/usr/bin/mysql-systemd-start post (code=exited, status=0/SUCCESS)
    Process: 2565 ExecStart=/usr/bin/mysqld_safe (code=killed, signal=KILL)
    Process: 2554 ExecStartPre=/usr/bin/mysql-systemd-start pre (code=exited, status=0/SUCCESS)
    Main PID: 2565 (code=killed, signal=KILL)

    Jun 21 02:20:09 test-centos7 systemd[1]: Starting MySQL Community Server…
    Jun 21 02:20:09 test-centos7 mysqld_safe[2565]: 150621 02:20:09 mysqld_safe Logging to ‘/var/log/mysqld.log’.
    Jun 21 02:20:09 test-centos7 mysqld_safe[2565]: 150621 02:20:09 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
    Jun 21 02:20:10 test-centos7 systemd[1]: Started MySQL Community Server.
    Jun 21 02:28:16 test-centos7 systemd[1]: mysqld.service: main process exited, code=killed, status=9/KILL
    Jun 21 02:28:17 test-centos7 systemd[1]: Unit mysqld.service entered failed state.

多试几次,确认服务不再处于运行状态。

这样,我们就成功模拟了崩溃后服务不再重启的情况。

现在再次编辑mysqld.service unit文件,取消Restart参数注释,保存并重载该systemctl守护程序,最终启动服务。现在一切应该恢复至改动前的状态了。

通过这部分讲解,我们已经了解了如何在服务unit文件的[Service]部分中添加Restart(或者RestartSec)控制服务在崩溃后的重启行为。

总结

这就是Linux处理服务启动的全部方式。我们在文章中探讨了System V、Upstart以及systemd几类init流程的起效方式,外加三者在重启或崩溃后实现服务重启的具体原理。

Upstart配置文件以及systemd unit文件中的声明语法较System V init脚本显然更加简单易用。

在操作自己的Linux环境时,大家应当检查具体发行版版本并查看其所支持的init守护程序。

另外,大家也应当认真考虑何时启用及禁用某些服务。在大多数情况下,我们不需要对第三方应用或者原生Linux守护程序进行修改。换方之,startup与respawn操作的对象一般仅限于我们自己定制的服务应用。

本文来源自DigitalOcean Community。英文原文:How To Configure a Linux Service to Start Automatically After a Crash or Reboot – Part 2: Reference By Sadequl Hussain

翻译:diradw

你可能感兴趣的:(如何配置一项在崩溃或重启后自动启用的Linux服务——第二部分:参考)