Skip to content

21 系统服务

达成 service 的程序我们就称呼他为 daemon。可以将这两者视为等价关系。

1. 早期 System V 的 init

旧版的centos 启动系统服务的管理方式被称为 SysVinit 脚本程序的处理方式,系统内核第一支呼叫的程序是 init , 然后 init 去唤起所有的系统所需要的服务。

init 的管理机制有几个特点:

  • 所有的服务启动脚本通通放置于 /etc/init.d/ 底下,基本上都是使用 bash shell script 所写成的脚本程序。
  • 服务是独立启动或被一只总管程序管理而分为两大类:
  • 独立启动模式 (stand alone):服务独立启动,该服务直接常驻于内存中,提供本机或用户的服务行为,反应速度快。
  • 总管程序 (super daemon):由特殊的 xinetd 或 inetd 这两个总管程序提供 socket 对应或 port 对应的管理。当没有用户要求某 socket 或 port 时, 所需要的服务是不会被启动的。若有用户要求时,xinetd 总管才会去唤醒相对应的服务程序。当该要求结束时,这个服务也会被结束掉~ 因为透过xinetd 所总管,因此这个家伙就被称为 super daemon。好处是可以透过 super daemon 来进行服务的时程、联机需求等的控制,缺点是唤醒服务需要一点时间的延迟。

  • 服务的依赖性问题。init 在管理员自己手动处理这些服务时,是没有办法协助依赖服务的唤醒的。

  • 执行等级的分类。init 是开机后核心主动呼叫的, 然后 init 可以根据用户自定义的执行等级(runlevel)来唤醒不同的服务,以进入不同的操作界面。基本上 Linux 提供 7 个执行等级,分别是 0, 1, 2...6 , 比较重要的是 1)单人维护模式、3)纯文本模式、5)文字加图形界面。而各个执行等级的启动脚本是透/etc/rc.d/rc[0-6]/SXXdaemon (S 为启动该服务, XX 是数字,为启动的顺序)连结到 /etc/init.d/daemon。在开机时可以依序执行所有需要的服务,同时也能解决依赖服务的问题。
  • 执行等级的切换行为。当你要从纯文本界面 (runlevel 3) 切换到图形界面 (runlevel 5), 不需要手动启动、关闭该执行等级的相关服务,只要init 5即可切换。

附:鸟哥对于init的详细介绍 TODO

2. systemd

还可以看看这篇文章:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html

CentOS 7.x 以后,从 init 启动转为 systemd 启动服务。特色:

  • 平行处理所有服务,加速开机流程。

旧的 init 启动脚本是一项一项任务依序启动的模式,因此不相依的服务也是得要一个一个的等待。

  • 一经要求就响应的 on-demand 启动方式。

systemd 由于常驻内存,因此任何要求(on-demand) 都可以立即处理后续的 daemon 启动的任务。

  • 服务依赖性的自我检查。

systemd 可以自定义服务依赖性的检查。

  • 依 daemon 功能分类。

首先 systemd 先定义所有的服务为一个服务单位 (unit),并将该 unit 归类到不同的服务类型 (type) 去。

  • 将多个 daemons 集合成为一个群组。

如同 systemV 的 init 里头有个 runlevel 的特色,systemd 亦将许多的功能集合成为一个所谓的 target 项目。

  • 向下兼容旧有的 init 服务脚本。

旧的 init 启动脚本也能够透过 systemd 来管理,只是更进阶的 systemd 功能就没有办法支持就是了。

不过 systemd 也是有些地方无法完全取代 init 的:

  • 在 runlevel 的对应上,大概仅有 runlevel 1, 3, 5 有对应到 systemd 的某些 target 类型而已;
  • systemctl 支持的语法有限制,不像/etc/init.d/daemon就是纯脚本可以自定义参数,systemctl 不可自定义参数;
  • 如果某个服务启动是管理员自己手动执行启动,而不是使用 systemctl 去启动的,那么 systemd 将无法侦测到该服务,而无法进一步管理;
  • systemd 启动过程中,无法与管理员透过 standard input 传入讯息。自行撰写 systemd 的启动设定时,务必要取消交互机制和启动时的标准输入信息。

2.1 配置文件的存放目录

  • /usr/lib/systemd/system/:每个服务最主要的启动脚本设定,有点类似以前的 /etc/init.d 底下的文件;
  • /run/systemd/system/:系统执行过程中所产生的服务脚本;
  • /etc/systemd/system/:管理员依据主机系统的需求所建立的执行脚本,其实这个目录有点像以前 /etc/rc.d/rc5.d/Sxx 之类的功能。

这三个文件夹下的文件的执行优先级依次递增

2.2 常见服务类型

/usr/lib/systemd/system/扩展名区分不同的服务类型。

[sink@dev ~]$ ll /usr/lib/systemd/system/ | egrep '(vsftpd|crond|multi)'
-rw-r--r--. 1 root root  318 Aug  9 07:07 crond.service
-rw-r--r--. 1 root root  623 Aug  9 07:08 multipathd.service
-rw-r--r--. 1 root root  492 Aug  8 19:58 multi-user.target
drwxr-xr-x. 2 root root  258 Oct 22 16:17 multi-user.target.wants
lrwxrwxrwx. 1 root root   17 Oct 22 16:01 runlevel2.target -> multi-user.target
lrwxrwxrwx. 1 root root   17 Oct 22 16:01 runlevel3.target -> multi-user.target
lrwxrwxrwx. 1 root root   17 Oct 22 16:01 runlevel4.target -> multi-user.target
-rw-r--r--. 1 root root  171 Oct 31  2018 vsftpd.service
-rw-r--r--. 1 root root  184 Oct 31  2018 vsftpd@.service
-rw-r--r--. 1 root root   89 Oct 31  2018 vsftpd.target

常见的服务类型:

扩展名 主要服务功能
.service 一般服务类型 (service unit):主要是系统服务,包括服务器本身所需要的本机服务以及网络服务。
.socket 内部程序数据交换的插槽服务 (socket unit):主要是 IPC (Inter-process communication) 的传输讯息插槽文件 (socket file) 功能。
.target 执行环境类型 (target unit):其实是一群 unit 的集合。
.mount
.automount
文件系统挂载相关的服务 (automount unit / mount unit)。
.path 侦测特定文件或目录类型 (path unit):某些服务需要侦测某些特定的目录来提供队列服务。
.timer 循环执行的服务 (timer unit):类似于 anacrontab 。不过是由 systemd 主动提供的,比 anacrontab 更加有弹性

2.3 服务状态

  • static:这个 daemon 不可以自己启动 (enable 不可),不过可能会被其他的 enabled 的服务来唤醒 (依赖 的服务)
  • mask:这个 daemon 无论如何都无法被启动!因为已经被强制注销 (非删除)。可透过 systemctl unmask 方 式改回原本状态

2.4 服务管理

systemctl mask <unit-name>          强制注销服务,增强版的 disable,无论如何都不能启动该服务
systemctl unmask <unit-name>

本质上就是利用/etc/systemd/system/优先级最高,建立一条指向/dev/null的无用链接。

[root@dev ~]# systemctl is-active cups.service 
active
[root@dev ~]# systemctl is-enabled cups.service 
enabled
[root@dev ~]# systemctl mask cups.service 
Created symlink from /etc/systemd/system/cups.service to /dev/null.
[root@dev ~]# systemctl is-enabled cups.service 
masked
# 注意这里仍然是启动状态,所以正确的做法是先关闭服务,再注销服务
[root@dev ~]# systemctl is-active cups.service 
active
[root@dev ~]# ll /etc/systemd/system/cups.service 
lrwxrwxrwx. 1 root root 9 Nov 25 14:11 /etc/systemd/system/cups.service -> /dev/null

2.5 查看socket文件

systemctl list-sockets [--all]

2.6 查看服务

systemctl                           显示 enabled 服务
systemctl list-unit-files           列出所有已经安装的服务
systemctl list-units [--type=unit-type] [--all] 列出目前启动的 unit, --all:把 inactive 的unit也列出来

2.7 管理不同的操作环境

2.7.1 查看

[root@dev ~]# systemctl list-units --type target --all
  UNIT                       LOAD      ACTIVE   SUB    DESCRIPTION
  basic.target               loaded    active   active Basic System
  cryptsetup.target          loaded    active   active Local Encrypted Volumes
  ……

与操作界面相关性比较高的 target:

  • multi-user.target:纯文本多用户模式
  • graphical.target:文本加上图形界面,包含 multi-user.target
  • rescue.target:救援模式。在无法使用 root 登入的情况下,systemd 在开机时会多加一个额外的暂时系统,与你原本的系统无关。
  • emergency.target:急救模式。还是需要使用 root 登入的情况,在无法使用 rescue.target 时可以尝试
  • shutdown.target:关机的流程
  • getty.target:可以设定你需要几个 tty 之类的,如果想要降低 tty 的项目,可以修改这个东西的配置文件

2.7.2 管理

systemctl [command] [unit.target]

commands:
    get-default
    set-default     修改系统的默认操作模式
    isolate         切换模式
[sink@dev ~]$ systemctl get-default 
graphical.target
# 注意这里修改的是全部用户的默认配置
[sink@dev ~]$ systemctl set-default multi-user.target 
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /usr/lib/systemd/system/multi-user.target.
# 切换到文本模式
[sink@dev ~]$ systemctl isolate multi-user.target

2.7.3 暂停与休眠模式

  • suspend:暂停模式会将系统的状态数据保存到内存中,然后关闭掉大部分的系统硬件,并没有实际关机。唤醒的速度较快。
  • hibernate:休眠模式则是将系统状态保存到硬盘当中,保存完毕后,将计算机关机。当用户尝试唤醒系统时,系统会开始正常运作, 然后将保存在硬盘中的系统状态恢复回来。唤醒的速度与你的硬盘速度有关。

除了使用isolate来切换各种模式,systemd 还提供了简单的指令来切换模式:

systemctl suspend
systemctl hibernate

2.8 分析服务之间的依赖关系

systemctl list-dependencies [NAME]

Options 
    --reverse       列出谁依赖该 unit
    --after         位于该 unit 之前的 unit(你没看错,我没有写反)
    --before        位于该 unit 之后的 unit
    --all           默认只会列出 target unit 的依赖,使用该选项列出全部
[sink@dev ~]$ systemctl get-default 
multi-user.target
[sink@dev ~]$ systemctl list-dependencies 
default.target
● ├─abrt-ccpp.service
● ├─abrt-oops.service
● ├─abrt-vmcore.service
……
# 在该 unit 启动之后再启动的 unit,即该 unit 需要在哪些 unit 之前就启动了
[sink@dev ~]$ systemctl list-dependencies --before
default.target
● ├─systemd-readahead-done.service
● ├─systemd-readahead-done.timer
● ├─systemd-update-utmp-runlevel.service
● └─graphical.target
●   └─systemd-update-utmp-runlevel.service
# target 中只有图形界面使用到了 multi-user.target
[sink@dev ~]$ systemctl list-dependencies --reverse
default.target
● └─graphical.target

[sink@dev ~]$ systemctl list-dependencies vsftpd
vsftpd.service
● ├─system.slice
● └─basic.target
●   ├─firewalld.service
●   ├─microcode.service
……

2.9 与 systemd 的 daemon 运行过程相关的目录

哪些目录跟系统的 daemon 运作有关:

  • /usr/lib/systemd/system/

默认的启动脚本配置文件都放在这里,这里的数据尽量不要修改(到/etc/systemd/system修改)

  • /run/systemd/system/

系统执行过程中所产生的服务脚本,这些脚本的优先序要比/usr/lib/systemd/system/高。

  • /etc/systemd/system/

管理员依据主机系统的需求所建立的执行脚本,执行优先序又比/run/systemd/system/高。

  • /etc/sysconfig/*

几乎所有的服务都会将初始化的一些选项设定写入到这个目录下。

  • /var/lib/

一些会产生数据的服务都会将他的数据写入到该目录下。

  • /run/

放置了好多 daemon 的临时文件,包括 lock file 以及 PID file 等等。

2.10 service 类型配置文件

修改配置文件最好在/etc/systemd/system/下面新增或者修改,而不要改动原先在/usr/lib/systemd/system/就有的配置文件。

vsftpd.service为例,对目录的作用进行说明:

  • /usr/lib/systemd/system/vsftpd.service:安装时预设的配置文件;
  • /etc/systemd/system/vsftpd.service.d/custom.conf:自定义配置文件,以.conf为后缀,该目录下的文件会累加到 /usr/lib/systemd/system/vsftpd.service 里面;
  • /etc/systemd/system/vsftpd.service.wants/*:此目录下都是链接文件,意思是启动了 vsftpd.service 之后,最好这些服务也启动;
  • /etc/systemd/system/vsftpd.service.requires/*:此目录下都是链接文件,意思是启动 vsftpd.service 之前,这些服务必须启动。

2.10.1 配置文件内容

/usr/lib/systemd/system/sshd.service为例:

[root@dev ~]# cat /usr/lib/systemd/system/sshd.service 
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=notify
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

查看上面的文件,其内容主要分为三个部分:

  • [Unit]:unit 本身的说明、文档、依赖等;
  • [Service]:Service 表明这是service类型的服务,其他 unit 类型也有自己对应的名称,这部分与实际执行的指令参数有关,包括如何启动、环境配置文件等;
  • [Install]:此 unit 挂载到哪个 target 下面。
[root@dev ~]# ll /etc/systemd/system/multi-user.target.wants/sshd.service
lrwxrwxrwx. 1 root root 36 Oct 18 19:48 /etc/systemd/system/multi-user.target.wants/sshd.service -> /usr/lib/systemd/system/sshd.service
2.10.1.1 配置文件规则
  • 设定参数通常是可以重复的,不过后面的会覆盖掉前面的设定,那么归零可以这样写:After=
  • 如果设定参数需要有是/否的项目 (布尔值, boolean),则可以使用下面的任意值及其相反值:1,on,true,yes
  • 开头为# 或 ;的那一行,都代表注释。
2.10.1.2 配置文件参数

每个部分的部分参数如下所示(所有参数的查阅:man systemd.unit中有[Unit] 和 [Install]部分的说明,各种类型的说明在各自的man systemd.<unit_type>中。):

systemd配置文件unit部分

systemd配置文件service部分-1

systemd配置文件service部分-2

systemd配置文件install部分

vsftpd2.service为例新建一个ftp服务:

[root@dev vsftpd]# cd /etc/vsftpd/
[root@dev vsftpd]# cp vsftpd.conf vsftpd2.conf
# 在前面的 SELinux 章节修改了端口号为 555,把它改回来 21
[root@dev vsftpd]# vi vsftpd.conf
[root@dev vsftpd]# cd /etc/systemd/system/
[root@dev system]# cp /usr/lib/systemd/system/vsftpd.service vsftpd2.service
# 把里面的启动配置文件换成 vsftpd2.conf
[root@dev system]# vi vsftpd2.service
# 因为已经在 SELinux 的规则中添加了 555 端口,所以这里不会报错啦
[root@dev system]# systemctl restart vsftpd.service vsftpd2.service
[root@dev system]# netstat -ntlp | grep ftp
tcp6       0      0 :::555                  :::*                    LISTEN      10565/vsftpd        
tcp6       0      0 :::21                   :::*                    LISTEN      10564/vsftpd

2.10.2 配置多个重复设定的服务

vsftpd可以存在多个实例,那如果每个几乎相同的实例都要在/etc/systemd/system/下面编写配置岂不是要累死人了?

[root@dev ~]# cd /etc/vsftpd
# 把端口改为 9003
[root@dev vsftpd]# vi vsftpd3.conf
# 不要 enable 该服务!!!
[root@dev vsftpd]# systemctl start vsftpd@vsftpd3.service
[root@dev vsftpd]# netstat -ntlp | grep ftp
tcp6       0      0 :::9003                 :::*                    LISTEN      11681/vsftpd        
tcp6       0      0 :::555                  :::*                    LISTEN      10565/vsftpd        
tcp6       0      0 :::21                   :::*                    LISTEN      10564/vsftpd

怎么样,是不是很神奇,居然只复制了一份ftp运行时配置就可以直接起来一个服务!主要发挥作用的就是下面这个文件啦:

[root@dev vsftpd]# cat /usr/lib/systemd/system/vsftpd@.service 
[Unit]
Description=Vsftpd ftp daemon
After=network.target
PartOf=vsftpd.target

[Service]
Type=forking
ExecStart=/usr/sbin/vsftpd /etc/vsftpd/%i.conf

[Install]
WantedBy=vsftpd.target

在启动的脚本设定当中, %i 或 %I 就是代表 @ 后面接的范例文件名的意思,有了这个文件,就能像上面那样重复启动服务啦。

Note

这次增加端口却不需要更改SELinux的配置了,这是因为默认启动小于 1024 号码以下的端口口时, 需要使用到 root 的权限,因此小于 1024 以下端口的启动较可怕。而这次范例中,我们使用 9003 端口,他对于系统的影响可能小一些 (其实一样可怕!), 所以就忽略了 SELinux 的限制了。

2.10.3 制作服务示例

只执行一次的服务。

# 具有执行权限的脚本
[root@dev ~]# ll /home/backup.sh 
-rwxr-xr-x. 1 root root 199 Nov 25 17:25 /home/backup.sh
[root@dev ~]# cat /home/backup.sh 
#!/bin/sh

[ ! -d /tmp/boot_backup ] && mkdir /tmp/boot_backup
date_str=$(date +%Y%m%d)
tar -zcf /tmp/boot_backup/boot_backup_${date_str}.tar.gz /boot &> /tmp/boot_backup/boot_backup_${date_str}.log
# 服务配置文件
[root@dev ~]# cat /etc/systemd/system/backup.service
[Unit]
Description=/boot backup service
Requires=atd.service

[Service]
Type=simple
# 这里好像不支持 shell 拓展,只能写在 bash 参数里面作为支持???
ExecStart=/bin/bash -c "/usr/bin/echo /home/backup.sh | at now"

[Install]
WantedBy=multi-user.target
[root@dev ~]# systemctl daemon-reload
# 执行一次
[root@dev ~]# systemctl start backup.service
# 因为是一次性的服务,所以这里是 inactive
[root@dev ~]# systemctl status backup.service backup.service - /boot backup service
   Loaded: loaded (/etc/systemd/system/backup.service; disabled; vendor preset: disabled)  # vendor preset:供应商预设
   Active: inactive (dead)

Nov 25 17:31:34 dev systemd[1]: Started /boot backup service.
Nov 25 17:31:34 dev bash[12851]: job 7 at Mon Nov 25 17:31:00 2019
# 查看结果
[root@dev ~]# ll /tmp/boot_backup/
total 186324
-rw-r--r--. 1 root root        44 Nov 25 17:31 boot_backup_20191125.log
-rw-r--r--. 1 root root 190790040 Nov 25 17:31 boot_backup_20191125.tar.gz

2.11 timer 类型的配置文件

systemd.timer 的优势

  • 由于所有的 systemd 的服务产生的信息都会被纪录 (log),因此比 crond 在 debug 上面要更清楚方便的多;

  • 各项 timer 的工作可以跟 systemd 的服务相结合;

  • 各项 timer 的工作可以跟 control group (cgroup,用来取代 /etc/secure/limit.conf 的功能) 结合,来限制该工作的资源利用。

使用 timer 的前提:

  • 系统的 timer.target 一定要启动
  • 要有个 sname.service 的服务存在 (sname 是你自己指定的名称)
  • 要有个 sname.timer 的时间启动服务存在

2.11.1 配置文件内容

timer 配置文件的基本内容(man systemd.timer & man systemd.time):

systemd配置文件timer部分

2.11.1.1 OnCalendar参数的设置

基本上的格式如下所示:

语法: 英文周名        YYYY-MM-DD      HH:MM:SS
范例: Thu           2015-08-13        13:40:00

也可以直接使用间隔时间来处理:

  • us 或 usec:微秒 (10-6 秒)
  • ms 或 msec:毫秒 (10-3 秒)
  • s, sec, second, seconds
  • m, min, minute, minutes
  • h, hr, hour, hours
  • d, day, days
  • w, week, weeks
  • month, months
  • y, year, years
# 通常英文的写法,小单位写前面,大单位写后面~所以先秒、再分、再小时、再天数等~   3   小时:                      3h  3hr  3hours
隔   300 分钟过 10  秒:              10s 300m
隔   5   天又  100 分钟:             100m 5day

此外,你也可以使用英文常用的口语化日期代表,例如today,tomorrow等。

2.11.2 配置文件示例

2.11.2.1 循环周期配置

采用上一小节编写的backup.service来作为服务,为其编写 timer:

[root@dev ~]# cd /etc/systemd/system/
[root@dev system]# cat backup.timer 
[Unit]
Description=back up /boot timer

[Timer]
# 系统启动 2小时 后执行
OnBootSec=2h
# 服务第一次执行完毕后每隔 2天 执行一次
OnUnitActiveSec=2d

[Install]
WantedBy=multi-user.target

[root@dev system]# systemctl daemon-reload
[root@dev system]# systemctl enable backup.timer
[root@dev system]# systemctl start backup.timer
# service 无需 enable
[root@dev system]# systemctl list-unit-files | grep backup
backup.service                                disabled
backup.timer                                  enabled

[root@dev system]# systemctl show timers.target | grep ConditionTimestamp=
ConditionTimestamp=Mon 2019-11-25 08:32:25 CST
[root@dev system]# systemctl show backup.service | grep ExecMainExitTimestamp=
ExecMainExitTimestamp=Tue 2019-11-26 09:36:24 CST
# .timer 的时间是根据 .service 与 timers.target 的时间差来计算的,这里时间有点对不上,不过误差不大
[root@dev system]# systemctl show backup.timer | egrep "NextElapseUSecMonotonic|LastTriggerUSecMonotonic="
NextElapseUSecMonotonic=3d 1h 7min 5.741522s    # 下次在什么时候执行
LastTriggerUSecMonotonic=1d 1h 7min 5.739590s   # 上次是什么时候执行的
# Monotonic:单调时间,即从某个时间点开始到现在过去的时间。
2.11.2.2 固定日期配置
[root@dev system]# cat backup2.timer 
[Unit]
Description=back up /boot timer2

[Timer]
OnCalendar=Sun *-*-* 00:00:00
Persistent=yes
Unit=backup.service

[Install]
WantedBy=multi-user.target

[root@dev system]# systemctl daemon-reload
[root@dev system]# systemctl enable backup2.timer
[root@dev system]# systemctl start backup2.timer

[root@dev system]# systemctl show backup2.timer | grep NextElapseUSecRealtime=
NextElapseUSecRealtime=49y 10month 4w 1d 1h
# Realtime:是实际的时间。是Unix的标准时间 1970-01-01 00:00:00 以来的时间