Linux启动流程及systemd服务详解

一、Linux系统启动流程

Rhel6启动过程:

Rhel7启动过程:

GRUB2相较于GRUB一代的提升:更健壮、可移植、更强大。支持BIOS、EFI和OpenFirmware,支持GPT和MBR分区表。支持非Linux系统,如苹果HFS文件系统和Windows的NTFS文件系统。

Linux操作系统启动引导的过程,系统的控制权移交大致分为如下4步:

1. BIOS POST,及BIOS加电自检。
2. GRUB装载识别并装载内核。
3. 内核识别根文件系统并启动systemd。
4. systemd准备系统资源,最终完成系统启动,系统就绪。

在第一步中,从按下电源键主板BIOS开始加电自检(POST,即Power-On-Self-Test)并完成硬件初始化,如果在加电自检阶段失败,则无法启动系统,启动流程不会往后进行;当加电自检和硬件初始化操作完成,BIOS就会读取可引导设备的第一个扇区中的主引导记录(MBR),然后主引导记录中的引导装载器(GRUB/GRUB2)接管随后的启动流程。

在GRUB通过在其中记录的保存有/boot分区文件系统驱动(比如支持EXT系列文件系统、FAT文件系统、NTFS文件系统),至此,就可以识别/boot分区了。接下来通过定位/boot分区中的vmlinuz内核文件(自解压文件),将其装载到内存中,同时将随后的启动控制权移交给内核。在GRUB2以前的版本中,GRUB控制阶段可以被细分为3个过程(stage1, stage1.5, stage2)。GRUB2的官方说明中虽然并没有细分的3个阶段的说明,但是实际仍然是遵循的这个过程。

stage 1 阶段

这个阶段GRUB会根据MBR(第一个扇区的前446个字节)中记录的内容定位到stage 1.5阶段的分区位置。而stage 1.5阶段位于MBR之后,到第一个分区之前的这块区域中。MBR中的内容记录的就是 “/usr/lib/grub/i386-pc/boot.img” 的内容,但实际查看两者内容并不完全一致(在446字节之后是64字节的分区表,共记录4个分区记录,每个分区记录占16个字节,最后2个字节为MBR的固定标识,为55AA)。

关于上述文件内容与MBR的内容对比,如下所示:

[root@c7u6-ha1 ~]# ls -lh /usr/lib/grub/i386-pc/boot.img
-rw-r--r--. 1 root root 512 Nov  9  2018 /usr/lib/grub/i386-pc/boot.img
[root@c7u6-ha1 ~]# hexdump -Cv -n 512 /usr/lib/grub/i386-pc/boot.img
00000000  eb 63 90 00 00 00 00 00  00 00 00 00 00 00 00 00  |.c..............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 80 01 00 00 00  |................|
00000060  00 00 00 00 ff fa eb 05  f6 c2 80 74 05 f6 c2 70  |...........t...p|
00000070  74 02 b2 80 ea 79 7c 00  00 31 c0 8e d8 8e d0 bc  |t....y|..1......|
00000080  00 20 fb a0 64 7c 3c ff  74 02 88 c2 52 be 05 7c  |. ..d|<.t...R..||
00000090  b4 41 bb aa 55 cd 13 5a  52 72 3d 81 fb 55 aa 75  |.A..U..ZRr=..U.u|
000000a0  37 83 e1 01 74 32 31 c0  89 44 04 40 88 44 ff 89  |[email protected]..|
000000b0  44 02 c7 04 10 00 66 8b  1e 5c 7c 66 89 5c 08 66  |D.....f..\|f.\.f|
000000c0  8b 1e 60 7c 66 89 5c 0c  c7 44 06 00 70 b4 42 cd  |..`|f.\..D..p.B.|
000000d0  13 72 05 bb 00 70 eb 76  b4 08 cd 13 73 0d 5a 84  |.r...p.v....s.Z.|
000000e0  d2 0f 83 de 00 be 85 7d  e9 82 00 66 0f b6 c6 88  |.......}...f....|
000000f0  64 ff 40 66 89 44 04 0f  b6 d1 c1 e2 02 88 e8 88  |[email protected]..........|
00000100  f4 40 89 44 08 0f b6 c2  c0 e8 02 66 89 04 66 a1  |[email protected].|
00000110  60 7c 66 09 c0 75 4e 66  a1 5c 7c 66 31 d2 66 f7  |`|f..uNf.\|f1.f.|
00000120  34 88 d1 31 d2 66 f7 74  04 3b 44 08 7d 37 fe c1  |4..1.f.t.;D.}7..|
00000130  88 c5 30 c0 c1 e8 02 08  c1 88 d0 5a 88 c6 bb 00  |..0........Z....|
00000140  70 8e c3 31 db b8 01 02  cd 13 72 1e 8c c3 60 1e  |p..1......r...`.|
00000150  b9 00 01 8e db 31 f6 bf  00 80 8e c6 fc f3 a5 1f  |.....1..........|
00000160  61 ff 26 5a 7c be 80 7d  eb 03 be 8f 7d e8 34 00  |a.&Z|..}....}.4.|
00000170  be 94 7d e8 2e 00 cd 18  eb fe 47 52 55 42 20 00  |..}.......GRUB .|
00000180  47 65 6f 6d 00 48 61 72  64 20 44 69 73 6b 00 52  |Geom.Hard Disk.R|
00000190  65 61 64 00 20 45 72 72  6f 72 0d 0a 00 bb 01 00  |ead. Error......|
000001a0  b4 0e cd 10 ac 3c 00 75  f4 c3 00 00 00 00 00 00  |.....<.u........|
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 24 12  |..............$.|
000001c0  0f 09 00 52 be bd 7d 31  c0 cd 13 46 8a 0c 84 c9  |...R..}1...F....|
000001d0  75 0f be da 7d e8 cc ff  eb 96 46 6c 6f 70 70 79  |u...}.....Floppy|
000001e0  00 bb 00 70 8e c3 31 db  b8 01 02 b5 00 b6 00 cd  |...p..1.........|
000001f0  13 72 d4 b6 01 b5 4f e9  f1 fe 00 00 00 00 55 aa  |.r....O.......U.|
00000200
[root@c7u6-ha1 ~]#
接下来查看磁盘的第一个扇区的内容,即主引导记录MBR的内容。具体如下所示:
[root@c7u6-ha1 ~]# hexdump -Cv -n 512 /dev/vda
00000000  eb 63 90 10 8e d0 bc 00  b0 b8 00 00 8e d8 8e c0  |.c..............|
00000010  fb be 00 7c bf 00 06 b9  00 02 f3 a4 ea 21 06 00  |...|.........!..|
00000020  00 be be 07 38 04 75 0b  83 c6 10 81 fe fe 07 75  |....8.u........u|
00000030  f3 eb 16 b4 02 b0 01 bb  00 7c b2 80 8a 74 01 8b  |.........|...t..|
00000040  4c 02 cd 13 ea 00 7c 00  00 eb fe 00 00 00 00 00  |L.....|.........|
00000050  00 00 00 00 00 00 00 00  00 00 00 80 01 00 00 00  |................|
00000060  00 00 00 00 ff fa 90 90  f6 c2 80 74 05 f6 c2 70  |...........t...p|
00000070  74 02 b2 80 ea 79 7c 00  00 31 c0 8e d8 8e d0 bc  |t....y|..1......|
00000080  00 20 fb a0 64 7c 3c ff  74 02 88 c2 52 be 05 7c  |. ..d|<.t...R..||
00000090  b4 41 bb aa 55 cd 13 5a  52 72 3d 81 fb 55 aa 75  |.A..U..ZRr=..U.u|
000000a0  37 83 e1 01 74 32 31 c0  89 44 04 40 88 44 ff 89  |[email protected]..|
000000b0  44 02 c7 04 10 00 66 8b  1e 5c 7c 66 89 5c 08 66  |D.....f..\|f.\.f|
000000c0  8b 1e 60 7c 66 89 5c 0c  c7 44 06 00 70 b4 42 cd  |..`|f.\..D..p.B.|
000000d0  13 72 05 bb 00 70 eb 76  b4 08 cd 13 73 0d 5a 84  |.r...p.v....s.Z.|
000000e0  d2 0f 83 de 00 be 85 7d  e9 82 00 66 0f b6 c6 88  |.......}...f....|
000000f0  64 ff 40 66 89 44 04 0f  b6 d1 c1 e2 02 88 e8 88  |[email protected]..........|
00000100  f4 40 89 44 08 0f b6 c2  c0 e8 02 66 89 04 66 a1  |[email protected].|
00000110  60 7c 66 09 c0 75 4e 66  a1 5c 7c 66 31 d2 66 f7  |`|f..uNf.\|f1.f.|
00000120  34 88 d1 31 d2 66 f7 74  04 3b 44 08 7d 37 fe c1  |4..1.f.t.;D.}7..|
00000130  88 c5 30 c0 c1 e8 02 08  c1 88 d0 5a 88 c6 bb 00  |..0........Z....|
00000140  70 8e c3 31 db b8 01 02  cd 13 72 1e 8c c3 60 1e  |p..1......r...`.|
00000150  b9 00 01 8e db 31 f6 bf  00 80 8e c6 fc f3 a5 1f  |.....1..........|
00000160  61 ff 26 5a 7c be 80 7d  eb 03 be 8f 7d e8 34 00  |a.&Z|..}....}.4.|
00000170  be 94 7d e8 2e 00 cd 18  eb fe 47 52 55 42 20 00  |..}.......GRUB .|
00000180  47 65 6f 6d 00 48 61 72  64 20 44 69 73 6b 00 52  |Geom.Hard Disk.R|
00000190  65 61 64 00 20 45 72 72  6f 72 0d 0a 00 bb 01 00  |ead. Error......|
000001a0  b4 0e cd 10 ac 3c 00 75  f4 c3 00 00 00 00 00 00  |.....<.u........|
000001b0  00 00 00 00 00 00 00 00  e9 5f 09 00 00 00 80 20  |........._..... |
000001c0  21 00 83 65 24 41 00 08  00 00 00 00 10 00 00 65  |!..e$A.........e|
000001d0  25 41 82 ef 2c c3 00 08  10 00 00 00 20 00 00 ef  |%A..,....... ...|
000001e0  2d c3 8e fe ff ff 00 08  30 00 00 f8 4f 02 00 00  |-.......0...O...|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200

stage 1.5 阶段

这个阶段中存放的是core.img这个文件,其中包含了通用文件系统的驱动程序,比如EXT系列文件系统、FAT文件系统、NTFS文件系统等。通过将这个阶段的core.img装载到内存中,就可以识别到/boot分区的文件系统了。由于core.img中通常不包含lvm的软件以及相应的驱动,所以/boot分区不能安装在lvm逻辑卷上。另外,也不能将/boot分区格式化为XFS文件系统。这个阶段执行完成之后,就可以识别到/boot分区了。

stage 2 阶段

这个阶段中,GRUB就可以定位并能够识别/boot分区了,此时就可以将/boot分区中的内核文件vmlinuz装在到内存中,同时GRUB会将启动流程控制权移交给内核。由于此时,如果将根文件系统装在lvm逻辑卷上,格式化为EXT之外的其他文件系统,那么内核在这个阶段还不能识别根分区的文件系统。此时就需要借助/boot分区中的initramfs内存虚拟根文件系统中保存的对应驱动以及程序结合系统存储设备映射文件System.map识别根分区。

至此,内核就可以装载systemd程序,并将随后的启动控制权移交给systemd程序。

上述过程概括为如下图:

CentOS6 Grub第二阶段的存放位置:/boot/grub。

grub.conf:grub的关键配置文件,里面定义了内核存放的相关路径等内容:

# 配置文件格式
default:表示默认启动那个内核 #例如default=0表示默认第一个内核作为引导
timeout:在启动界面停留的时间
hidenmenu:隐藏的启动菜单,默认看不到的
	tiele:启动界面显示的菜单项
	root(hd#.#):表示boot分区所在的位置,#hd0.0--hd表示硬盘 0.0表示第一块硬盘的第一个分区
	kerner: /PATH/TO/KERNEL_FILE--内核文件所在的位置(/vmlinux表示boot目录下的vmlinux这个文件) root=UUID=xxxx--表示利用内核,要加载的文件系统的根系统,系统硬盘真正的根(比如/dev/sda1这块硬盘挂载到根上面 那么加载的就是这个硬盘)
	initrd:系统的一个辅助文件,是一个小型的linux文件系统,内核进入硬盘根的时候会借助它

bootloader是硬件和操作系统之间的一个媒介。硬件通过bootloader才能找到操作系统。

CentOS6启动流程总结:

1.POST,设备加电自检(cpu、内存、硬盘、io设备等检查)
2.MBR引导找到GRUB
3.GRUB执行完成后加载内核,内核加载硬盘的根
4.加载系统第一个进程init,并按顺序执行以下配置文件和脚本。
	/etc/inittab
	/etc/rc.d/rc.sysinit
	/etc/rcX.d/xxx
5.所有服务启动完成后执行rc.local这个脚本里面的内容。
6.终端启动,登录相关的配置和验证。

CentOS7启动流程总结:

1.POST:系统加电自检并找到MBR
2.MBR引导并加载GRUB
3.GRUB引导内核并加载硬盘文件系统上的根系统
4.启动系统的第一个进程systemd进程
5.执行systemd对应的unit(target这种类型的)

系统无法启动使用光盘的方式进入救援模式:

采用光盘启动(引导),进入rescue模式
#重启系统出现进度条的时候快速按ESC键,然后选择CD-ROM Dirver,然后选择Troubleshooting,选择Rescue a CentOS liux System,然后选择1继续,然后切换到/mnt/sysimage这个目录下操作。因为救援模式默认进入的根不是硬盘系统的根。而是ramdisk里的文件系统,并没有切换到本机硬盘上的“真正”文件系统
#正常启动的系统他不会挂载到/mnt/sysimage这个目录下的
链接:http://t.zoukankan.com/duzhaoqi-p-7327525.html
#chroot /mnt/sysimage #切换到真正的根目录下面
#grub-install /dev/sda #执行秀姑grub的命令
#sync #将缓存中的信息同步到硬盘
#exit  #退出重启
#exit

Centos6的Grub修复方法:

 首先需要使用光盘引导进入救援模式。

#grub信息被破坏的修复方法
使用grub-install来修复:grub-install --root-directory=DIR /dev/DISK
grub-install 磁盘名称  #安装grub到硬盘上,会自动找到整个硬盘的第一个扇区MBR的前446个字节。
#无法修复grub的阶段二所对应的配置文件,需要手动编写 /boot/grub/grub.conf这个配置文件

Centos7的Grub2修复方法:

主要配置文件:

/boot/grub2/grub.cfg

修复GRUB配置文件的方法:

法一:grub2-mkconfig > /boot/grub2/grub.cfg
法二:grub2-mkconfig  -o  /boot/grub2/grub.cfg  -o:表示他的标准输出

修复grub:

grub2-install /dev/sda #BIOS环境
grub2-install #UEFI环境

二、init和systemd服务管理

grub加载linux内核后,通过内核加载硬盘的根,启动系统中的第一个进程init。

内核想要进入硬盘的根,需要文件系统驱动。因为根分区有自己的文件系统。 驱动的功能由内核提供,内核的驱动存放位置在/lib目录下,例如ext4的文件系统驱动:ext4.ko.所以要加载文件系统的驱动就需要进入根的文件系统,显然不行。 所以内核此时不是从这里面加载文件系统驱动,而是从GRUB配置文件中的initrd后面指定的文件里面加载的文件系统。(是一个小型的linux文件系统)

传统的init启动模式里面,有 RunLevel ,但是Systemd 是使用 Target 来管理。

Target 与 SysV-init 进程的主要区别:

1)默认的 RunLevel (在/etc/inittab文件设置),现在被默认的 ​​Target​​​ 取代。Target 是/etc/systemd/system/default.target,通常符号链接到graphical.target(图形界面)或者multi-user.target(多用户命令行)。

2)启动脚本的位置,以前是 ​​/etc/init.d​​​ 目录,符号链接到不同的 RunLevel 目录 (比如 ​​/etc/rc3.d、/etc/rc5.d​​​ 等),现在则存放在 ​​/lib/systemd/system 和 /etc/systemd/system​​ 目录。

3)配置文件的位置,以前 init 进程的配置文件是 ​​/etc/inittab​​​,各种服务的配置文件存放在 ​​/etc/sysconfig​​​ 目录。现在的配置文件主要存放在 ​​/lib/systemd​​​ 目录,在 ​​/etc/systemd​​ 目录里面的修改可以覆盖原始设置。

init进程启动以后就会通过以下顺序执行相关的配置文件来设置系统。

/etc/inittab配置文件:

/etc/inittab文件已经不再使用了,查看里面其实都是注释与说明。

确定系统使用那个运行模式(runlevel)。

CentOS的运行模式有7个,对应数字0--6:

0:关机
6:重启
1:安全模式
3:字符模式
5:图形界面模式

运行/etc/rc.d/rc.sysinit这个脚本。初始话系统的一些信息:

设置主机名
设置欢迎信息
加载对应的服务配置
设备的挂载
交换空间的准备
系统时钟等信息

根据对应的运行模式执行对应的文件夹下面脚本:/etc/rcX.d/xxx:

比如运行模式是3:就执行/etc/rc3.d/ 下的脚本。
/etc/rc3.d/下面的文件都是软链接,指向/sbin/init下面的脚本。
以K开头的文件:开机启动 K##:##运行次序;数字越小,越先运行;
以S开头的文件:开机不启动。S: S##:##运行次序;数字越小,越先运行;

rc.local文件:

  • 是一个开机启动文件,不属于任何运行模式。开机的时候所有的服务脚本都运行完成后才会执行他。

  • CentOS7以及后面的版本想用这个文件的话,需要手动添加可执行权限才会执行。

  • ubuntu默认没有这个文件,需要手动创建并更改权限才可以使用。

/etc/rc.local
/etc/rc.d/rc.local
#/etc/rc.local -> rc.d/rc.local rc.local是 rc.d/rc.local的一个软连接
正常级别下,最后启动一个服务S99local没有链接至/etc/rc.d/init.d一个服务脚本,而是指向
了/etc/rc.d/rc.local脚本
想开机时自动运行的命令,可直接放置于/etc/rc.d/rc.local文件中
/etc/rc.d/rc.local在指定运行级别脚本后运行

用户空间中的init程序很是关键,各个系统版本采用的init都不一样。CentOS 7系列就是仿照了MAC OS X的启动方式以及管理用户进程的程序,写出了一套系统守护的一个应用程序Systemd。

CentOS 5: SysV init
CentOS 6: Upstart
CentOS 7: Systemd

systemd 被设计用来改进 sysvinit 的缺点,它和ubuntu的upstart是竞争对手,预计会取代它们。

systemd的目标是:尽可能启动更少进程;尽可能将更多进程并行启动。systemd尽可能减少对shell脚本的依赖。传统sysvinit使用inittab来决定运行哪些shell脚本,大量使用shell脚本被认为是效率低下无法并行的原因。systemd使用了Linux专属技术,不再顾及POSIX兼容。

systemd的特性:

  • 服务并行启动

  • 可以按照需要启动对应守护进行

  • 自动管理服务的依赖关系

  • Unit(单元)的概念

  • 使用systemctl管理工具

systemd 架构图:

init和Systemd的区别

init:

  • 一是启动时间长,init是串行启动,只有前一个进程启动完,才会启动下一个进程
  • 二是启动脚本复杂,Init进程只是执行启动脚本,不管其他事情,脚本需要自己处理各种情况,这往往使得脚本变得很长
  • 由Linux内核加载运行,位于 /sbin/init ,是系统中第一个进程,PID永远为1

对于支持 service 的程序,安装的时候,会自动的在 /etc/init.d 目录添加一个配置文件。当我们使用 service 控制程序时,比如执行开启httpd的服务:service httpd start 。那么我们的 service 就会开启 /etc/init.d/httpd 配置文件里面指向的 /usr/sbin/httpd 可执行文件。

systemd:

  • 按需启动服务,减少系统资源消耗。
  • 尽可能并行启动进程,减少系统启动等待时间
  • 由Linx内核加载运行,位于 /usr/lib/systemd/systemd ,是系统中第一个进程,PID永远为1

systemd是现在主流发行版的通用资源管理方式,systemd是所有其他进程的母进程,除了进程管理之外,还支持很多其他资源管理,比如挂载文件系统、启动和管理系统服务等等。

systemd接管启动流程之后,首先会通过定义/etc/fstab挂在文件系统,此时就可以访问根目录中的/etc/下的配置文件了,包括其自身的配置文件 /etc/systemd/system/default.target,这个文件决定了系统的默认运行级别,该文件只是一个链接文件,其链接到系统启动之后的目标target上。

[root@c7u6-ha1 ~]# ll /etc/systemd/system/default.target
lrwxrwxrwx. 1 root root 37 Feb 18  2020 /etc/systemd/system/default.target -> /lib/systemd/system/multi-user.target
[root@c7u6-ha1 ~]# cat /etc/systemd/system/default.target
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it 
#  under the terms of the GNU Lesser General Public License as published by 
#  the Free Software Foundation; either version 2.1 of the License, or 
#  (at your option) any later version.

[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes
[root@c7u6-ha1 ~]# 

上述即为default.target的内容。multi-user.target对应SystemV init中的运行级别为runleve 3,而graphical.target则对应于runleve 5。

关于各个运行级别,概括总结如下:

运行级别说明:

0  ==> runlevel0.target-> poweroff.target
1  ==> runlevel1.target-> rescue.target
2  ==> runlevel2.target-> multi-user.target
3  ==> runlevel3.target-> multi-user.target
4  ==> runlevel4.target-> multi-user.target
5  ==> runlevel5.target-> graphical.target#graphical.target是基于multi-user.target来的
6  ==> runlevel6.target-> reboot.target #

查看运行级别:

  • runlevel : 显示切换前的运行级别 和当前运行级别 (6/7)
  • systemctl get-default : 显示当前运行级别 (7)

修改用户级别为多用户命令行:sudo systemctlset-default multi-user.target。即为删除原有链接,新建multi-user.target新链接。可用通过命令systemctl list-dependencies multi-user.target查看 multi-user.target启动时的默认 Target。

systemd接管启动流程之后的操作如下图所示:

上图中的sysinit.target和basic.target可以看作是启动过程中的检查点,虽然systemd支持资源的并行启动,但是总会有一些服务和资源对象需要在其他资源对象之前启动。当sysinit.target执行完成之后,就会执行basic.target这个资源,当这些先决资源启动完成之后,最后就是启动运行级别资源对象开启对应的用户接口了。

永久设置开机模式:

  • systemctl set-default multi-user.target 开机默认为文本模式
  • systemctl set-default graphical.target 开机默认为图形模式
  • 修改 /etc/inittab 默认运行级别配置文件

当systemd将系统资源准备完成,操作系统就完成了启动的整个流程。此时用户就可以登陆系统进行操作了。

systemd的几个运行级别,用于指定不同的用户操作环境。而systemd作为SystemV init脚本和Upstart的替代者,其存在就是用于管理系统资源和服务。其提供了丰富的特性,比如系统启动阶段的服务并行启动、按需激活必要的守护进程以及基于依赖关系的服务控制逻辑等。

Init 进程的配置文件:

Systemd进程的配置文件: 

systemd的unit介绍

systemd中引入了单元(unit)的概念,这些单元涉及到系统服务、监听套接字以及其他init系统相关的对象。

systemd把服务都笼统称为Unit(单元),通过配置文件进行标识和配置。

Unit存放的位置:

/lib/systemd/system

要查看系统上systemd支持的单元都有哪些,可以执行命令systemctl -t help获得。在CentOS 7.6上有13个单元;而在CentOS 8.2上则减少到了11个。具体如下所示:

[root@LiuXianQiE ~]# cat /etc/redhat-release 
CentOS Linux release 8.3.2011
[root@LiuXianQiE ~]# systemctl -t help
Available unit types:
service
socket
target
device
mount
automount
swap
timer
path
slice
scope
[root@LiuXianQiE ~]# systemctl -t help | egrep -v unit | wc -l
11
[root@LiuXianQiE ~]# 
[root@c7u6-ha1 ~]# cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core) 
[root@c7u6-ha1 ~]# systemctl -t help | egrep -v unit
service
socket
busname
target
snapshot
device
mount
automount
swap
timer
path
slice
scope
[root@c7u6-ha1 ~]# systemctl -t help | egrep -v unit | wc -l
13
[root@c7u6-ha1 ~]# 

从上述输出中可以看出,CentOS 8.x中比CentOS 7.x中少了snapshot以及busname这两个单元。
关于单元,Unit类型总结如下表所示:

(1)service:类似于Centos6里面的服务脚本。文件扩招名为.service。

(2)target:类似于Centos6里面的运行级别。文件扩展名为.target。

(3)socket:义进程间通信用的socket文件。文件扩展名为.socket。

socket可以理解为:ip地址+端口+协议类型,socket就是位于两个层面(传输层和应用层)中间的一个参与者,服务于两方。

我们知道 systemd 里管理了很多会用到本机 socket 的服务,所以系统中肯定会产生很多的 socket 文件。那么,这些 socke 文件都存放在哪里呢?我们可以使用 systemctl 进行查看:

systemctl list-sockets

上述是一些类型的单元以及其扩展名。这些文件通常存放在一下三个地方,具体如下表所示: 

上述三个路径中,通常将用户定义的单元(unit)放置在第一个路径中,即/usr/lib/systemd/system这个目录中。

说明:

/usr/lib/systemd/system #每个服务最主要的启动脚本设置,类似于之前的/etc/init.d/ ;
/lib/systemd/system #大多数unit的配置文件都放在这个目录下;
/run/systemd/system #系统执行过程中所产生的服务脚本,比如用户相关的脚本和会话相关的脚本;
/etc/systemd/system:系统管理员和用户使用,类似于/etc/rcN.d/Sxx的功能,比上面目录优先运行;
注意,这个目录中主要的文件都是指向 /lib/systemd/system/ 目录中的链接文件。在我们自己创建 unit 配置文件时,既可以把配置文件放在 /lib/systemd/system/ 目录下,也可以放在 /etc/systemd/system/ 目录下。
/usr/lib/systemd/system:发行版打包者使用;
/etc/init.d/:service服务配置文件存放目录;
/etc/default/: 这个目录中放置很多服务默认的配置文件;
/var/lib/: 一些会产生数据的服务都会将他的数据写入到 /var/lib/ 目录中,比如 docker 相关的数据文件就放在这个目录下;
/run/: 这个目录放置了好多服务运行时的临时数据,比如 lock file 以及 PID file 等等。

Systemd 默认从目录/etc/systemd/system/读取配置文件。但是,里面存放的大部分文件都是符号链接,指向目录/usr/lib/systemd/system/,真正的配置文件存放在那个目录。systemctl enable命令用于在上面两个目录之间,建立符号链接关系。

设置开机启动会在/etc/systemd/system/multi-user.target.wants/目录下新建一个/usr/lib/systemd/system/docker.service 文件的链接。

而systemd自身的配置文件是/etc/systemd/system.conf,可以通过修改这个配置文件中的内容,来调整systemd的行为。比如默认的超时上限是90秒,通过修改这个配置文件中的字段 DefaultTimeoutStartSec 可以改变systemd的超时时间。比如改为 DefaultTimeoutStartSec=120 。就将超时值修改为了120秒。

systemd的主要特性

systemd提供的主要特性概括如下:

Socket-based activation:在系统启动的时候,systemd会为所有支持这个类型的服务创建监听套接字,并且在这些服务启动的时候将套接字传递给这些服务。这就可以使systemd可以实现并行启动服务,同时也使其能够在重启服务期间不会丢失掉发送给这个服务的信息:因为此时这个服务相关的套接字仍然是可以访问的,并且所有发送给这个服务的消息都会被放入其消息队列中。

systemd中为基于套接字激活使用了.socket类型的unit。

Bus-based activation:使用D-Bus作为进程间通信的系统服务可以在客户端应用首次尝试与其建立连接通讯的时候按需启动,即有客户端的连接请求的时候才会激活服务。

systemd为基于总线的激活方式使用.bus类型的unit。

Device-based activation:支持基于设备激活的系统服务可以在特定类型的硬件插入并且处于可用状态的时候被激活。

systemd为基于设备的激活使用了.device类型的unit。

Path-based activation:支持基于路径激活的系统服务可以在系统的特定文件或者目录的状态信息发生更改的时候被激活。

systemd为这个类型的服务使用了.path类型的unit。

Mount and automount point management:systemd会对系统的挂载点和自动挂载点进行监视。
systemd为这类型使用了.mount和.automount这两个类型的unit。

Aggressive parallelization:因为使用了基于套接字的激活方式,所以systemd可以在相应的套接字出现的时候并行启动系统服务。通过与支持按需启动的系统服务相结合,并行激活可以显著的减少系统启动时间。

Transactional unit activation logic:在激活或者取消激活一个单元之前,systemd会对其依赖关系进行计算,并且创建临时事务,同时对这些事务的一致性进行验证。如果事务状态不一致,systemd会在报告错误信息之前尝试自动纠正这种情况,并自动移除非必要的任务。

Backwards compatibility with SysV init:Systemd可以向下兼容SysV init脚本。

Cgroups 与 Systemd

Systemd 是一个强大的 init 系统,它甚至为我们使用 cgorups 提供了便利!Systemd 提供的内在机制、默认设置和相关的操控命令降低了配置和使用 cgroups 的难度,即便是 Linux 新手,也能轻松的使用 cgroups 了。

在 Systemd 之间的主流应用管理服务,都是使用进程树来跟踪应用的继承关系的,而进程的父子关系很容易通过两次 fork 的方法脱离。

而 Systemd 则提供通过 ​​CGroup​​ 跟踪进程关系,引补了这个缺漏。通过 CGroup 不仅能够实现服务之间访问隔离,限制特定应用程序对系统资源的访问配额,还能更精确地管理服务的生命周期

控制群组(cgroup)

控制群组(control group)(简写为cgroup)是 Linux kernel 提供的功能:在一个系统中运行的层级制进程组,您可对其进行资源分配(如 CPU 时间、系统内存、网络带宽或者这些资源的组合)。通过使用 cgroup,系统管理员在分配、排序、拒绝、管理和监控系统资源等方面,可以进行精细化控制。硬件资源可以在应用程序和用户间智能分配,从而增加整体效率。

Task(任务) 在 linux 系统中,内核本身的调度和管理并不对进程和线程进行区分,只是根据 clone 时传入的参数的不同来从概念上区分进程和线程。这里使用 task 来表示系统的一个进程或线程。

Cgroup(控制组) cgroups 中的资源控制以 cgroup 为单位实现。Cgroup 表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个 cgroup,也可以从某个 cgroup 迁移到另一个 cgroup。

Subsystem(子系统) cgroups 中的子系统就是一个资源调度控制器(又叫 controllers)。比如 CPU 子系统可以控制 CPU 的时间分配,内存子系统可以限制内存的使用量。以笔者使用的 Ubuntu 16.04.3 为例,其内核版本为 4.10.0,支持的 subsystem 如下( cat /proc/cgroups):
 blkio         对块设备的 IO 进行限制。
 cpu           限制 CPU 时间片的分配,与 cpuacct 挂载在同一目录。
 cpuacct     生成 cgroup 中的任务占用 CPU 资源的报告,与 cpu 挂载在同一目录。
 cpuset       给 cgroup 中的任务分配独立的 CPU(多处理器系统) 和内存节点。
 devices     允许或禁止 cgroup 中的任务访问设备。
 freezer      暂停/恢复 cgroup 中的任务。
 hugetlb     限制使用的内存页数量。              
 memory    对 cgroup 中的任务的可用内存进行限制,并自动生成资源占用报告。
 net_cls      使用等级识别符(classid)标记网络数据包,这让 Linux 流量控制器(tc 指令)可以识别来自特定 cgroup 任务的数据包,并进行网络限制。
 net_prio    允许基于 cgroup 设置网络流量(netowork traffic)的优先级。
 perf_event  允许使用 perf 工具来监控 cgroup。
 pids          限制任务的数量。

Hierarchy(层级) 层级有一系列 cgroup 以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的 cgroup 节点可以包含零个或多个子节点,子节点继承父节点挂载的子系统。一个操作系统中可以有多个层级。

cgroups 的主要作用

实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了四大功能:

  • 资源限制:cgroups 可以对任务是要的资源总额进行限制。比如设定任务运行时使用的内存上限,一旦超出就发 OOM。
  • 优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。
  • 资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。
  • 任务控制:cgroups 可以对任务执行挂起、恢复等操作。

cgroups 的文件系统接口

cgroups 以文件的方式提供应用接口,我们可以通过 mount 命令来查看 cgroups 默认的挂载点:

$ mount | grep cgroup

第一行的 tmpfs 说明 /sys/fs/cgroup 目录下的文件都是存在于内存中的临时文件。
第二行的挂载点 /sys/fs/cgroup/systemd 用于 systemd 系统对 cgroups 的支持,相关内容笔者今后会做专门的介绍。

其余的挂载点则是内核支持的各个子系统的根级层级结构。

需要注意的是,在使用 systemd 系统的操作系统中,/sys/fs/cgroup 目录都是由 systemd 在系统启动的过程中挂载的,并且挂载为只读的类型。换句话说,系统是不建议我们在 /sys/fs/cgroup 目录下创建新的目录并挂载其它子系统的。这一点与之前的操作系统不太一样。

下面让我们来探索一下 /sys/fs/cgroup 目录及其子目录下都是些什么:

/sys/fs/cgroup 目录下是各个子系统的根目录。我们以 memory 子系统为例,看看 memory 目录下都有什么?

这些文件就是 cgroups 的 memory 子系统中的根级设置。比如 memory.limit_in_bytes 中的数字用来限制进程的最大可用内存,memory.swappiness 中保存着使用 swap 的权重等等。

既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。

查看进程所属的 cgroups

可以通过 /proc/[pid]/cgroup 来查看指定进程属于哪些 cgroup:

每一行包含用冒号隔开的三列,他们的含义分别是:

  • cgroup 树的 ID, 和 /proc/cgroups 文件中的 ID 一一对应。
  • 和 cgroup 树绑定的所有 subsystem,多个 subsystem 之间用逗号隔开。这里 name=systemd 表示没有和任何 subsystem 绑定,只是给他起了个名字叫 systemd。
  • 进程在 cgroup 树中的路径,即进程所属的 cgroup,这个路径是相对于挂载点的相对路径。

既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。

cgroups 工具

在介绍通过 systemd 应用 cgroups 之前,我们先使用 cgroup-bin 工具包中的 cgexec 来演示 demo。Ubuntu 默认没有安装 cgroup-bin 工具包,请通过下面的命令安装:

$ sudo apt install cgroup-bin

demo:限制进程可用的 CPU

在我们使用 cgroups 时,最好不要直接在各个子系统的根目录下直接修改其配置文件。推荐的方式是为不同的需求在子系统树中定义不同的节点。比如我们可以在 /sys/fs/cgroup/cpu 目录下新建一个名称为 nick_cpu 的目录:

$ cd  /sys/fs/cgroup/cpu
$ sudo mkdir nick_cpu

然后查看新建的目录下的内容:

是不是有点吃惊,cgroups 的文件系统会在创建文件目录的时候自动创建这些配置文件!

让我们通过下面的设置把 CPU 周期限制为总量的十分之一:

$ sudo su
$ echo 100000 > nick_cpu/cpu.cfs_period_us
$ echo 10000 > nick_cpu/cpu.cfs_quota_us

然后创建一个 CPU 密集型的程序:

void main()
{
    unsigned int i, end;

    end = 1024 * 1024 * 1024;
    for(i = 0; i < end; )
    {
        i ++;
    }
}

保存为文件 cputime.c 编译并通过不同的方式执行:

$ gcc cputime.c -o cputime
$ sudo su
$ time ./cputime
$ time cgexec -g cpu:nick_cpu ./cputime

time 命令可以为我们报告程序执行消耗的时间,其中的 real 就是我们真实感受到的时间。使用 cgexec 能够把我们添加的 cgroup 配置 nick_cpu 应用到运行 cputime 程序的进程上。 上图显示,默认的执行只需要 2s 左右。通过 cgroups 限制 CPU 资源后需要运行 23s。 

demo:限制进程可用的内存

这次我们来限制进程可用的最大内存,在 /sys/fs/cgroup/memory 下创建目录nick_memory:

$ cd  /sys/fs/cgroup/memory
$ sudo mkdir nick_memory

下面的设置把进程的可用内存限制在最大 300M,并且不使用 swap:

# 物理内存 + SWAP <= 300 MB;1024*1024*300 = 314572800
$ sudo su
$ echo 314572800 > nick_memory/memory.limit_in_bytes
$ echo 0 > nick_memory/memory.swappiness

然后创建一个不断分配内存的程序,它分五次分配内存,每次申请 100M:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define CHUNK_SIZE 1024 * 1024 * 100

void main()
{
    char *p;
    int i;

    for(i = 0; i < 5; i ++)
    {
        p = malloc(sizeof(char) * CHUNK_SIZE);
        if(p == NULL)
        {
            printf("fail to malloc!");
            return ;
        }
        // memset() 函数用来将指定内存的前 n 个字节设置为特定的值
        memset(p, 0, CHUNK_SIZE);
        printf("malloc memory %d MB\n", (i + 1) * 100);
    }
}

把上面的代码保存为 mem.c 文件,然后编译:

$ gcc mem.c -o mem

执行生成的 mem 程序:

$ ./mem

此时一切顺利,然后加上刚才的约束试试:

$ cgexec -g memory:nick_memory ./mem

由于内存不足且禁止使用 swap,所以被限制资源的进程在申请内存时被强制杀死了。

下面再使用 stress 程序测试一个类似的场景(通过 stress 程序申请 500M 的内存):

$ sudo cgexec -g memory:nick_memory stress --vm 1 --vm-bytes 500000000 --vm-keep --verbose

 

stress 程序能够提供比较详细的信息,进程被杀掉的方式是收到了 SIGKILL(signal 9) 信号。

实际应用中往往要同时限制多种的资源,比如既限制 CPU 资源又限制内存资源。使用 cgexec 实现这样的用例其实很简单,直接指定多个 -g 选项就可以了:

$ cgexec -g cpu:nick_cpu -g memory:nick_memory ./cpumem

Systemd 依赖 cgroups

要理解 systemd 与 cgroups 的关系,我们需要先区分 cgroups 的两个方面:层级结构(A)和资源控制(B)。首先 cgroups 是以层级结构组织并标识进程的一种方式,同时它也是在该层级结构上执行资源限制的一种方式。我们简单的把 cgroups 的层级结构称为 A,把 cgrpups 的资源控制能力称为 B。
对于 systemd 来说,A 是必须的,如果没有 A,systemd 将不能很好的工作。而 B 则是可选的,如果你不需要对资源进行控制,那么在编译 Linux 内核时完全可以去掉 B 相关的编译选项。

Systemd 默认挂载的 cgroups 系统

在系统的开机阶段,systemd 会把支持的 controllers (subsystem 子系统)挂载到默认的 /sys/fs/cgroup/ 目录下面:

 除了 systemd 目录外,其它目录都是对应的 subsystem。

/sys/fs/cgroup/systemd 目录是 systemd 维护的自己使用的非 subsystem 的 cgroups 层级结构。这玩意儿是 systemd 自己使用的,换句话说就是,并不允许其它的程序动这个目录下的内容。其实 /sys/fs/cgroup/systemd 目录对应的 cgroups 层级结构就是 systemd 用来使用 cgoups 中 feature A 的。

Cgroup 的默认层级

通过将 cgroup 层级系统与 systemd unit 树绑定,systemd 可以把资源管理的设置从进程级别移至应用程序级别。因此,我们可以使用 systemctl 指令,或者通过修改 systemd unit 的配置文件来管理 unit 相关的资源。

默认情况下,systemd 会自动创建 slice、scope 和 service unit 的层级(slice、scope 和 service 都是 systemd 的 unit 类型,来为 cgroup 树提供统一的层级结构。

系统中运行的所有进程,都是 systemd init 进程的子进程。在资源管控方面,systemd 提供了三种 unit 类型:

  • service: 一个或一组进程,由 systemd 依据 unit 配置文件启动。service 对指定进程进行封装,这样进程可以作为一个整体被启动或终止。
  • scope:一组外部创建的进程。由进程通过 fork() 函数启动和终止、之后被 systemd 在运行时注册的进程,scope 会将其封装。例如:用户会话、 容器和虚拟机被认为是 scope。
  • slice: 一组按层级排列的 unit。slice 并不包含进程,但会组建一个层级,并将 scope 和 service 都放置其中。真正的进程包含在 scope 或 service 中。在这一被划分层级的树中,每一个 slice 单位的名字对应通向层级中一个位置的路径。

我们可以通过 systemd-cgls 命令来查看 cgroups 的层级结构:

service、scope 和 slice unit 被直接映射到 cgroup 树中的对象。当这些 unit 被激活时,它们会直接一一映射到由 unit 名建立的 cgroup 路径中。例如,cron.service 属于 system.slice,会直接映射到 cgroup system.slice/cron.service/ 中。
注意,所有的用户会话、虚拟机和容器进程会被自动放置在一个单独的 scope 单元中。

默认情况下,系统会创建四种 slice:

  • -.slice:根 slice
  • system.slice:所有系统 service 的默认位置
  • user.slice:所有用户会话的默认位置
  • machine.slice:所有虚拟机和 Linux 容器的默认位置

 

创建临时的 cgroup

对资源管理的设置可以是 transient(临时的),也可以是 persistent (永久的)。我们先来介绍如何创建临时的 cgroup。
需要使用 systemd-run 命令创建临时的 cgroup,它可以创建并启动临时的 service 或 scope unit,并在此 unit 中运行程序。systemd-run 命令默认创建 service 类型的 unit,比如我们创建名称为 toptest 的 service 运行 top 命令:

$ sudo systemd-run --unit=toptest --slice=test top -b

然后查看一下 test.slice 的状态:

创建了一个 test.slice/toptest.service cgroup 层级关系。再看看 toptest.service 的状态: 

top 命令被包装成一个 service 运行在后台了!

接下来我们就可以通过 systemctl 命令来限制 toptest.service 的资源了。在限制前让我们先来看一看 top 进程的 cgroup 信息:

$ vim /proc/2850/cgroup           # 2850 为 top 进程的 PID

 

比如我们限制 toptest.service 的 CPUShares 为 600,可用内存的上限为 550M:

$ sudo systemctl set-property toptest.service CPUShares=600 MemoryLimit=500M

再次检查 top 进程的 cgroup 信息:

在 CPU 和 memory 子系统中都出现了 toptest.service 的名字。同时去查看 /sys/fs/cgroup/memory/test.slice 和/sys/fs/cgroup/cpu/test.slice 目录,这两个目录下都多出了一个 toptest.service 目录。我们设置的 CPUShares=600 MemoryLimit=500M 被分别写入了这些目录下的对应文件中。

临时 cgroup 的特征是,所包含的进程一旦结束,临时 cgroup 就会被自动释放。比如我们 kill 掉 top 进程,然后再查看 /sys/fs/cgroup/memory/test.slice 和 /sys/fs/cgroup/cpu/test.slice 目录,刚才的 toptest.service 目录已经不见了。

通过配置文件修改 cgroup

所有被 systemd 监管的 persistent cgroup(持久的 cgroup)都在 /usr/lib/systemd/system/ 目录中有一个 unit 配置文件。比如我们常见的 service 类型 unit 的配置文件。我们可以通过设置 unit 配置文件来控制应用程序的资源,persistent cgroup 的特点是即便系统重启,相关配置也会被保留。需要注意的是,scope unit 不能以此方式创建。下面让我们为 cron.service 添加 CPU 和内存相关的一些限制,编辑 /lib/systemd/system/cron.service 文件:

$ sudo vim  /lib/systemd/system/cron.service

 

添加红框中的行,然后重新加载配置文件并重启 cron.service:

$ sudo systemctl daemon-reload
$ sudo systemctl restart cron.service

现在去查看 /sys/fs/cgroup/memory/system.slice/cron.service/memory.limit_in_bytes 和 /sys/fs/cgroup/cpu/system.slice/cron.service/cpu.shares 文件,是不是已经包含我们配置的内容了!

通过 systemctl 命令修改 cgroup

除了编辑 unit 的配置文件,还可以通过 systemctl set-property 命令来修改 cgroup,这种方式修该的配置也会在重启系统时保存下来。现在我们把 cron.service 的 CPUShares 改为 700:

$ sudo systemctl set-property cron.service CPUShares=700

查看 /sys/fs/cgroup/cpu/system.slice/cron.service/cpu.shares 文件的内容应该是 700,重启系统后该文件的内容还是 700。

Systemd-cgtop 命令

类似于 top 命令,systemd-cgtop 命令显示 cgoups 的实时资源消耗情况:

通过它我们就可以分析应用使用资源的情况。 

详情参考:资源管理指南 Red Hat Enterprise Linux 7 | Red Hat Customer Portal

三、service和systemctl管理命令

Rhel6 用 service 和 chkconfig 来管理服务,它是 SystemV 架构下的一个工具。
Rhel7 是用 systemctl 来管理服务,它融合了之前的 service 和 chkconfig 的功能于一体。可以使用它永久性或只在当前会话中启用/禁用服务。systemctl 是 systemd 架构下的一个工具。

service命令其实是去/etc/init.d目录下,去执行相关程序:

# service命令启动redis脚本
service redis start
# 直接启动redis脚本
/etc/init.d/redis start
# 开机自启动
update-rc.d redis defaults

systemctl管理命令

由于systemd是向下兼容SysV init的服务管理脚本的,所以在CentOS 7.x中仍然可以使用service命令以及chkconfig这两个命令对服务进行管理。而systemd提供的服务管理命令则是systemctl。这个命令可以对.service类型的unit执行start, stop, restart, enable, disable等操作。

关于service与systemctl命令的对比,详见下表:

上述是service命令与systemctl命令的对比。

systemctl命令兼容了service,即systemctl也会去/etc/init.d目录下。

systemd的一些常用命令:

列出所有可用单元 : systemctl list-unit-files
列出所有运行的单元: systemctl list-unit-files | grep enabled
列出所有可用服务: systemctl list-unit-files --type=service
列出所有运行的服务: systemctl list-unit-files --type=service | grep enabled
列出所有单元 :   systemctl list-units
开机启动httpd服务:systemctl enable httpd
取消开机启动httpd服务:systemctl disable httpd
是否开机启动httpd服务:systemctl is-enable httpd
unit是否激活:systemctl is-active httpd
屏蔽/禁用httpd服务:systemctl mask httpd
取消屏蔽/禁用httpd服务:systemctl umask httpd
查看启动失败的服务列表: systemctl --failed

检查 unit 之间的依赖性:

root@cn-office-saas-test:~# systemctl get-default
graphical.target
root@cn-office-saas-test:~# systemctl list-dependencies default.target 
default.target
● ├─accounts-daemon.service
● ├─apport.service
● ├─display-manager.service
● ├─grub-common.service
● ├─systemd-update-utmp-runlevel.service
● ├─ureadahead.service
● └─multi-user.target
●   ├─apport.service
●   ├─atd.service
●   ├─console-setup.service
●   ├─cron.service
●   ├─dbus.service

--reverse 选项查看 multi-user.target unit 被谁使用:

systemctl list-dependencies multi-user.target --reverse

上面命令的输出结果之中,有些依赖是 Target 类型,默认不会展开显示。如果要展开 Target,就需要使用–all参数:

systemctl list-dependencies --all nginx.service

通过 systemctl list-units --type=target 命令可以获取当前正在使用的运行目标:

$ systemctl list-units --type=target
UNIT                   LOAD   ACTIVE SUB    DESCRIPTION
basic.target           loaded active active Basic System
cryptsetup.target      loaded active active Encrypted Volumes
getty.target           loaded active active Login Prompts
graphical.target       loaded active active Graphical Interface
local-fs-pre.target    loaded active active Local File Systems (Pre)
local-fs.target        loaded active active Local File Systems
multi-user.target      loaded active active Multi-User System
network-online.target  loaded active active Network is Online
network.target         loaded active active Network
nss-user-lookup.target loaded active active User and Group Name Lookups
paths.target           loaded active active Paths
remote-fs-pre.target   loaded active active Remote File Systems (Pre)
remote-fs.target       loaded active active Remote File Systems
slices.target          loaded active active Slices
sockets.target         loaded active active Sockets
sound.target           loaded active active Sound Card
swap.target            loaded active active Swap
sysinit.target         loaded active active System Initialization
time-sync.target       loaded active active System Time Synchronized
timers.target          loaded active active Timers
LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.
20 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.

服务的状态:

loaded Unit配置文件已处理
active(running) 一次或多次持续处理的运行
active(exited) 成功完成一次性的配置
active(waiting) 运行中,等待一个事件
inactive 不运行
enabled 开机启动
disabled 开机不启动
static 开机不启动,但可被另一个启用的服务激活
indirect 重定向到别处

chkconfig命令与systemctl命令的对比结果如下表所示: 

在使用systemctl命令管理服务的时候,一般会写上.service这个后缀,但是由于默认的服务都是以.service结尾,所以这个后缀是可以省略的。所以执行systemctl start nfs-server.service与systemctl start nfs-server的效果是相同的。

对于支持 systemd 的程序,安装的时候,会自动的在 /usr/lib/systemd/system 目录添加一个配置文件。当我们使用 systemctl 控制该程序时,比如执行开启httpd服务:systemctl start httpd.service 。那么我们的 systemctl 就会开启 httpd.service 配置里面指向的 /usr/sbin/httpd 可执行文件。

如果我们想让该程序开机启动,我们可以执行命令 systemctl enable httpd,这个命令相当于在 /etc/systemd/system 目录添加一个软链接,指向 /usr/lib/systemd/system 目录下的 httpd.service 文件。这是因为开机时,Systemd只执行 /etc/systemd/system目录里面的配置文件。

另外,可以查看某个服务的详细信息,此时需要执行命令systemctl show name.service即可。如果要查看某一项信息,可以使用-p name进行指定。比如systemctl show nfs-server.service -p Names就可以查看nfs-server.service这个服务的Names这个信息,即这个服务有几个名字,是否有别名的存在,都可以被列出来。

要列出当前系统下所有已经装载的服务,可以执行命令systemctl list-units --type service。对于每一个.service类型的unit,这个命令会显示其完整的服务名称(UNIT)、该服务是否被装载(LOAD)、以及其高级(ACTIVE)和低级(SUB)单元文件的激活状态,以及该服务的短描述信息(DESCRIPTION)。

默认情况下,systemctl list-units这个命令只显示活动的单元,如果想要查看所有的单元,而不管其是否处于活动状态,需要加上-a/--all这个选项。此时的命令为systemctl list-units --type service --all。

如果要查看系统上已经被使能的服务单元,可以执行命令systemctl list-unit-files --type service进行查看。对于每一个服务单元,该命令会显示服务单元的完整名称(UNIT FILE)、该服务单元是否处于使能状态(STATE)。

要查看服务的状态,可以执行命令systemctl status name.service进行查看。该命令的输出字段包括如下:

如果要验证服务单元是否处于活动状态,或者运行状态,可以执行命令systemctl is-active name.service进行查看。

如果要查看某个服务单元是否已经被使能,可以执行命令systemctl is-enabled name.service进行查看。

如果相关的服务单元处于运行状态或者是被使能状态,那么这两条命令的退出状态码都是0。

要查看某个服务单元的依赖关系,比如查看该服务是在哪些服务之后启动,可以执行命令systemctl list-dependencies --after name.service进行查看,换句话说,就是该命令会递归列出After=这个依赖关系中列出的单元(即该服务所依赖的服务);如果要查看该服务是在哪些服务之前启动,可以执行命令systemctl list-dependencies --before name.service进行查看,换句话说,这个命令会递归列出Before=中列出的单元(也就是依赖于该服务的单元有哪些)。

要启动一个服务单元,可以执行命令systemctl start name.service进行查看。

要停止一个服务单元,可以执行命令systemctl stop name.service进行查看。

要重启一个服务单元,可以执行命令systemctl restart name.service进行查看,该命令会在当前会话中停止指定的服务单元,然后立即启动该服务单元。重要的一点是,如果指定的服务单元此时并没有处于运行状态,那么这个重启的操作也会启动它。如果只想对已经运行的服务单元执行重启操作,而未运行的服务单元则不执行重启操作,那么可以执行命令systemctl try-restart name.service这个命令来完成。另外,某系服务允许在不中断服务执行的前提下重新装在配置文件,对于此类服务,可以执行命令systemctl reload name.service进行配置文件的重新装载。入果指定的服务单元并不支持这个操作,那么这个命令执行的时候会被忽略掉。如果不确定服务是否支持reload操作,那么可以执行命令systemctl reload-or-restart name.service进行重新装载配置文件或者重启的操作;如果只希望对已经运行的服务执行重新装载或者重启的操作,那么需要执行systemctl reload-or-try-restart name.service命令进行重新装载配置文件或者对已经运行的服务单元进行重启的操作。

要使能(即设置开机自动运行)服务单元,需要执行命令systemctl enable name.service来完成,这个命令会在/etc/systemd/system目录中创建对应服务单元的符号链接文件。如果想要相关服务的符号链接文件被重新创建,此时可以执行命令systemctl reenable name.service来完成。这个命令会首先禁用指定的服务(即删除相关服务的符号链接,然后再重新创建该链接)。如果对于未运行的服务单元,在使能其运行的同时,让其立即运行,此时可以执行命令systemctl enable --now name.service,表示让该服务开机自动运行,并且现在立即运行。

要禁用指定的服务(即将其设置为开机不自动运行),此时可以执行命令systemctl disable name.service。如果想要禁止某个服务被手动启动或者被其他服务启动,此时可以将其掩盖起来,执行命令systemctl mask name.service即可,这个命令的实际操作就是/etc/systemd/system/name.service创建为/dev/null的软链接。所以要允许指定的服务可以被手动启动或者被其他服务启动,那么就需要将掩盖拿掉,即执行命令systemctl unmask name.service即可。

systemctl与target

在RHEL/CentOS 7.x之前的发行版中,使用SysV init以及Upstart实现预定义的运行级别来表达特定的操作模式,其RHEL/CentOS5.x使用的是SysV init,在RHEL/CentOS 6.x中使用的则是Upstart。在RHEL/CentOS 7.x以及8.x中则是使用的systemd的target来替代此前的两者,实现类似运行级别来表达特定的操作模式的目的。

systemd的target表现为target单元,即以.target为结尾的文件,其唯一的目的就是将其他systemd的单元(unit)按照依赖关系链进行分组。比如 graphical.target 这个单元用于启动一个图形化的交互环境,其会启动比如GNOME Display Manager(gdm.service)这个系统服务、Account Service(accounts-daemon.service)这个服务,同时也会激活 multi-user.target 这个单元。类似的,multi-user.target 这个单元会启动一些其他必要的系统服务,比如NetworkManager(NetworkManager.service)或者D-Bus(dbus.service),同时激活 basic.target 这个单元。

关于各种运行级别,参见本篇博客的第一部分的内容。

要查看和切换运行级别,新老命令的对比如下表所示:

要查看当前系统的默认target,执行命令systemctl get-default即可。要查看当前系统上已经装载的所有的target单元,执行命令systemctl list-units --type target即可。对于每一个target单元,这个命令都会显示完整的名字(UNIT)、该单元是否被装载(LOAD)、高级(ACTIVE)单元和低级(SUB)单元的激活状态以及该单元的简短描述(DESCRIPTION)。

默认情况下,systemctl list-units --target target只会列出活动的单元,如果想要列出所有的target单元,而不管其是否处于活动状态,那么此时就需要在命令行中加上选项--all/-a。此时的命令为systemctl list-units --type target --all即可。

如果要给当前系统设置默认的target,即设置默认运行级别,此时就需要执行命令systemctl set-default name.target。这个命令会将链接文件/etc/systemd/system/default.target指向/usr/lib/systemd/system/name.target,那么下次系统启动的时候,默认的运行级别就变成了name.target所指定的运行级别。

如果要在当前会话中临时修改target,那么就可以执行命令systemctl isolate name.target即可。比如将当前的target修改为营救模式,及rescue.target,执行命令systemctl isolate rescue.target即可,更简单的写法是systemctl rescue,这两种方式的效果相同。在切换到营救模式之后,系统会尝试挂在所有的本地文件系统,并且会启动一些重要的系统服务,但是并不会激活网卡接口,也不允许多用户登录,此时只允许root用户登录。在切换为营救模式之后,需要正确输入root用户的密码之后才能进入到营救模式。 另外,在切换到营救模式的时候,系统默认会给所有当前正处于登录状态的用户发送广播消息,如果要禁用这个广播消息,此时可以加上选项--no-wall即可,此时的命令为systemctl --no-wall rescue。

如果要切换为紧急模式,即emergency.target单元,此时需要执行命令systemctl isolate emergency.target,或者更简短的形式systemctl emergency,这两种命令形式效果是一样的,都是切换到紧急模式。在紧急模式中,操作系统会将根分区挂载为只读形式,并且不会尝试挂载其他文件本地文件系统,同样不会激活网卡接口,并且只会启动一些必要的服务,同样不允许多用户登录。在切换为紧急模式之后,需要正确输入root用户密码之后才能进入到紧急模式。 另外,默认情况下,在切换为紧急模式的时候系统会给当前正处于登陆状态的用户发送广播消息。如果要禁用广播消息,可以加上选项--no-wall,此时的命令为systemctl isolate emergency --no-wall或者systemctl --no-wall emergency。

修改运行级别:

#通过systemctl命令切换运行模式:
systemctl isolate name.target #效果等同于:init X

#开机的时候指定内核模式:只影响当次的启动
启动时,到启动菜单,按e键,找到在linux 开头的行后添加systemd.unit=desired.target 
#centos7是linux16开头

例如:systemd.unit=emergency.target 
systemd.unit=rescue.target

systemctl命令实现系统关闭、挂起和冬眠

RHEL/CentOS 7.x开始,由systemd提供系统的电源管理命令。相关的电源管理命令如下表所示:

如果要关闭运行的系统,可以使用传统的shutdown命令,比如shutdown -h now即可立即关闭系统。而使用systemctl命令进行系统关闭的操作,就需要执行命令systemctl poweroff即可,这个命令相当于直接长按主机的电源键进行关机。如果要通过关闭并停止系统,而不是通过类似按电源键关机的的效果,此时可以执行命令systemctl halt即可。默认情况下,这两个命令都会给登录用户发送广播消息,如果不想用户收到广播消息,可以在命令行中增加选项--no-wall即可。此时的命令为systemctl --no-wall poweroff或者systemctl --no-wall halt,这两者都可以。

如果不是立即关闭,实现延时关闭的效果,此时就需要将上述的命令改为选项模式,比如指定在几点几分关闭系统,此时可以执行命令systemctl --poweroff HH:MM即可,注意,此时poweroff这个命令已经变成长选项格式了,不要省略掉两个英文半角连字符。如果要指定在几分钟或者几个小时之后关闭系统,此时可以执行命令systemctl --halt +m,这条命令中的halt同样变为选项模式,不能省略两个英文半角连字符,同时+m表示m分钟后关闭系统。如果将+m替换为now,则等效于+0,表示立即关闭系统。对于上述尚未执行的挂起状态的关机命令,可以执行shutdown -c进行取消。

如果要重启系统,可以执行命令systemctl reboot即可,默认该命令会给所有已经登陆的用户发送广播消息,如果要禁用这个广播消息,此时可以加上命令行选项--no-wall即可,此时的命令为systemctl --no-wall reboot。

如果要挂起系统,可以执行命令systemctl suspend即可。这个命令会将系统的状态信息保存到内存中,当后面切换回系统的时候,系统会直接从内存中回复此前保存的状态,而无需重新启动系统。由于系统的状态信息是保存在系统的内存中,而不是保存在磁盘中,所以恢复速度很快,但与此同时,也会导致当系统遭遇突然的断电时造成保存的状态丢失的情况。

如果要将系统冬眠,此时就需要执行命令systemctl hibernate。这个命令将系统状态保存到磁盘中,然后关闭系统。当你重新切回到系统的时候,操作系统会从硬盘中读取保存的状态完成恢复,而无需重新启动系统。由于系统状态信息是保存在硬盘中,而不是保存在内存中,所以恢复的时候速度会相对较慢 。

如果要冬眠并且挂起系统,可以执行命令systemctl hybrid-sleep命令即可。

控制远程主机上的systemd

除了可以控制本地系统的systemd以及服务管理之外,systemctl命令还可以通过ssh协议与远程主机的systemd进行交互。需要确保远程主机上的sshd服务处于开启状态,此时就可以通过systemctl -H/--host形式的命令对远程主机进行操作了。具体命令为systemctl --host remote_user@remote_host command即可。

具体如下面的示例所示:

[root@LiuXianQiE ~]# systemctl -H root@c7u6-ha1 is-active sshd.service
active
[root@LiuXianQiE ~]# systemctl -H root@c7u6-ha1 status sshd.service
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2021-06-07 11:12:56 CST; 13h ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 2985
   CGroup: /system.slice/sshd.service
           └─1715308 /usr/sbin/sshd -D [email protected],[email protected],aes256-ctr,aes256-cbc,[email protected]>
           ‣ 2985 /usr/libexec/gsd-power
lines 1-9/9 (END)

上述在远程主机上分别执行了两条命令,第一条命令查看远程主机上的sshd.service服务是否处于激活状态;第二条命令是查看远程主机上的sshd.service服务的状态信息。为了与上述的输出结果做对比,下面在本机上再查看一下sshd.service的状态信息,具体如下所示:

[root@LiuXianQiE ~]# systemctl status sshd.service
. sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2021-06-07 11:14:26 CST; 13h ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 1715308 (sshd)
    Tasks: 1 (limit: 409082)
   Memory: 1.2M
   CGroup: /system.slice/sshd.service
           └─1715308 /usr/sbin/sshd -D [email protected],[email protected],aes256-ctr,aes256-cbc,[email protected]>

Jun 07 11:14:26 LiuXianQiE systemd[1]: Starting OpenSSH server daemon...
Jun 07 11:14:26 LiuXianQiE sshd[1715308]: Server listening on 0.0.0.0 port 22.
Jun 07 11:14:26 LiuXianQiE sshd[1715308]: Server listening on :: port 22.
Jun 07 11:14:26 LiuXianQiE systemd[1]: Started OpenSSH server daemon.
lines 1-15/15 (END)

systemd 工具集

systemctl:用于检查和控制各种系统服务和资源的状态
bootctl:用于查看和管理系统启动分区
hostnamectl:用于查看和修改系统的主机名和主机信息
journalctl:用于查看系统日志和各类应用服务日志
localectl:用于查看和管理系统的地区信息
loginctl:用于管理系统已登录用户和 Session 的信息
machinectl:用于操作 Systemd 容器
timedatectl:用于查看和管理系统的时间和时区信息
systemd-analyze 显示此次系统启动时运行每个服务所消耗的时间,可以用于分析系统启动过程中的性能瓶颈
systemd-ask-password:辅助性工具,用星号屏蔽用户的任意输入,然后返回实际输入的内容
systemd-cat:用于将其他命令的输出重定向到系统日志
systemd-cgls:递归地显示指定 CGroup 的继承链
systemd-cgtop:显示系统当前最耗资源的 CGroup 单元
systemd-escape:辅助性工具,用于去除指定字符串中不能作为 Unit 文件名的字符
systemd-hwdb:Systemd 的内部工具,用于更新硬件数据库
systemd-delta:对比当前系统配置与默认系统配置的差异
systemd-detect-virt:显示主机的虚拟化类型
systemd-inhibit:用于强制延迟或禁止系统的关闭、睡眠和待机事件
systemd-machine-id-setup:Systemd 的内部工具,用于给 Systemd 容器生成 ID
systemd-notify:Systemd 的内部工具,用于通知服务的状态变化
systemd-nspawn:用于创建 Systemd 容器
systemd-path:Systemd 的内部工具,用于显示系统上下文中的各种路径配置
systemd-run:用于将任意指定的命令包装成一个临时的后台服务运行
systemd-stdio- bridge:Systemd 的内部 工具,用于将程序的标准输入输出重定向到系统总线
systemd-tmpfiles:Systemd 的内部工具,用于创建和管理临时文件目录
systemd-tty-ask-password-agent:用于响应后台服务进程发出的输入密码请求

systemd-analyze

systemd-analyze命令用于查看启动耗时。

# 查看启动耗时
$ systemd-analyze                                                                                       
# 查看每个服务的启动耗时
$ systemd-analyze blame
# 显示瀑布状的启动过程流
$ systemd-analyze critical-chain
# 显示指定服务的启动流
$ systemd-analyze critical-chain atd.service

hostnamectl

hostnamectl命令用于查看当前主机的信息。

# 显示当前主机的信息
$ hostnamectl
# 设置主机名
$ sudo hostnamectl set-hostname rhel7

localectl

localectl命令用于查看本地化设置。

# 查看本地化设置
$ localectl
# 设置本地化参数。
$ sudo localectl set-locale LANG=en_GB.utf8
$ sudo localectl set-keymap en_GB

timedatectl

timedatectl命令用于查看当前时区设置。

# 查看当前时区设置
$ timedatectl

# 显示所有可用的时区
$ timedatectl list-timezones                                                                                   

# 设置当前时区
$ sudo timedatectl set-timezone America/New_York
$ sudo timedatectl set-time YYYY-MM-DD
$ sudo timedatectl set-time HH:MM:SS

loginctl

loginctl命令用于查看当前登录的用户。

# 列出当前session
$ loginctl list-sessions

# 列出当前登录用户
$ loginctl list-users

# 列出显示指定用户的信息
$ loginctl show-user ruanyf

systemd-ask-password

$ PASSWORD=$(systemd-ask-password "Input Your Passowrd:")
systemd-run

四、自定义unit服务文件

由于systemd的行为是由对应的单元(unit)文件定义的,而单元文件中包含了单元描述以及行为定义的指令,为了更出色的适应systemd的管理方式,管理员一般需要手动编辑以及创建单元文件。

Unit 一共分成12种:

Service unit:系统服务
Target unit:多个 Unit 构成的一个组
Device Unit:硬件设备
Mount Unit:文件系统的挂载点
Automount Unit:自动挂载点
Path Unit:文件或路径
Scope Unit:不是由 Systemd 启动的外部进程
Slice Unit:进程组
Snapshot Unit:Systemd 快照,可以切回某个快照
Socket Unit:进程间通信的 socket
Swap Unit:swap 文件
Timer Unit:定时器

其中/etc/systemd/system这个目录用于保存系统管理员创建以及自定义的单元文件,但是实际上,更多的是直接在/usr/lib/systemd/system这个目录中进行单元文件的创建以及编辑自定义操作。

主要有四种类型文件.mount、.service、.target、.wants

.mount文件:

.mount文件定义了一个挂载点,[Mount]节点里配置了What,Where,Type三个数据项。等同于以下命令:

mount -t hugetlbfs /dev/hugepages hugetlbfs

.service文件:

.service文件定义了一个服务,分为[Unit],[Service],[Install]三个小节。

.target文件: 

.target定义了一些基础的组件,供.service文件调用。

.wants文件:

.wants文件定义了要执行的文件集合,每次执行,.wants文件夹里面的文件都会执行。 

对于单元文件,可以通过目录增补额外的配置文件,比如要给 sshd.service 增补自定义配置选项,就需要创建 sshd.service.d/custom.conf 这个配置文件并在其中插入额外的配置指令。除此之外,还可以创建 sshd.service.wants/ 目录以及 sshd.service.requires/ 目录,这两个目录中包含了sshd服务所依赖服务的符号链接文件。这些符号链接文件是依据sshd服务的单元文件sshd.service中的[Install]选项自动创建出来的,或者是在运行时基于[Unit]选项自动创建出来的。当然,也可以手动创建这些目录和符号链接文件。

理解单元文件(unit file)的结构

单元文件典型的结构由三部分组成:

[Unit] :是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系。指定不依赖于这个单元类型的通用选项,这些选项提供了单元的描述信息、指定单元的行为以及设置与其他单元的依赖关系。

[unit type] :如果单元有特定类型的指令,那么这些指令会被分组到一个以这个单元类型命名的小节(section)下面。比如服务单元文件中包含 [Service] 这个小节,只有 Service 类型的 Unit 才有这个区块

[Install] :包含了systemctl enable命令以及systemctl disable命令所用的单元安装的信息。通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。

Table1: Important [Unit] Section Options

[a]: 关于[Unit]部分更详尽的配置选项,参见 **systemd.unit (5)**帮助手册,即man 5 systemd.unit。

[b]: 多数情况下,只需要在[Unit]部分通过 After 以及 Before 就足以指定顺序依赖关系。如果与此同时也设置了 Wants 或者 Requires 指定需求的单元,那么仍然需要通过 After 以及 Before 指定顺序依赖关系。这是因为顺序以来关系和需求的单元之间彼此是相互独立的。

Unit段的常用选项:

Description:简短描述
Documentation:文档地址
Requires:依赖的其它 Unit 列表,列在其中的 Unit 模板会在这个服务启动时的同时被启动。并且,如果其中任意一个服务启动失败,这个服务也会被终止
Wants:依赖到的其它units,弱依赖,只是在被配置的这个 Unit 启动时,触发启动列出的每个 Unit 模块,而不去考虑这些模板启动是否成功
BindsTo:与Requires类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行
Before:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之后启动
After:当前unit在那些unit后面启动,其功能与Before相反
Conflicts:定义units间的冲突关系,这里指定的 Unit 不能与当前 Unit 同时运行
Condition…:当前 Unit 运行必须满足的条件,否则不会运行
Assert…:当前 Unit 运行必须满足的条件,否则会报启动失败
Binds To:与 Requires相似,失败时失败,成功时成功,但是在这些模板中有任意一个出现意外结束或重启时,这个服务也会跟着终止或重启
Part Of:一个 Bind To 作用的子集,仅在列出的任务模块失败或重启时,终止或重启当前服务,而不会随列出模板的启动而启动
OnFailure:当这个模板启动失败时,就会自动启动列出的每个模块

Table2: Important [Service] Section Options

[a]: 要查看关于[Service]部分的完整选项列表,参见systemd.service (5) 的帮助手册,即执行命令man 5 systemd.service即可。

Service段常用选项

1)服务生命周期控制相关:

- Type:定义启动时的进程行为。它有以下几种值。
- Type=simple:默认值,执行ExecStart指定的命令,启动主进程
- Type=forking:以 fork 方式从父进程创建子进程,创建后父进程会立即退出
- Type=oneshot:一次性进程,Systemd 会等当前服务退出,再继续往下执行
- Type=dbus:当前服务通过D-Bus启动
- Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行
- Type=idle:若有其他任务执行完毕,当前服务才会运行
- ExecStart:启动unit要运行命令或脚本的绝对路径
- ExecStartPre:ExecStart前运行
- ExecStartPost:ExecStart后运行
- ExecReload:重启当前服务时执行的命令
- ExecStop:停止unit要运行的命令或脚本
- ExecStopPost:停止当其服务之后执行的命令
- RestartSec:重启等待时间,默认100ms
- Restart:定义何种情况 Systemd 会自动重启当前服务,可能的值包括always(总是重启)、on-success、on-failure、on-abnormal、on-abort、on-watchdog,当设定Restart=1 时,服务意外终止会再次自动启动
- TimeoutSec:定义 Systemd 停止当前服务之前等待的秒数
- TimeoutStartSec:启动服务时等待的秒数,这一配置对于使用 Docker 容器而言显得尤为重要,因其第一次运行时可能需要下载镜像,严重延时会容易被 Systemd 误判为启动失败杀死。通常,对于这种服务,将此值指定为 0,从而关闭超时检测
- TimeoutStopSec:停止服务时的等待秒数,如果超过这个时间仍然没有停止,Systemd 会使用 SIGKILL 信号强行杀死服务的进程
- simple:默认值,后台启动常驻于内存
- PrivateTmp:设定为yes时,会在生成/tmp/systemd-private-UUID-NAME.service-XXXXX/tmp/目录
- RemainAfterExit:值为 true 或 false(默认)。当配置为 true 时,Systemd 只会负责启动服务进程,之后即便服务进程退出了,Systemd 也仍然会认为这个服务还在运行中。这个配置主要是提供给一些并非常驻内存,而是启动注册后立即退出,然后等待消息按需启动的特殊类型服务使用的。

2)服务上下文配置相关:

Environment:为服务指定环境变量
EnvironmentFile:指定加载一个包含服务所需的环境变量的列表的文件,文件中的每一行都是一个环境变量的定义
Nice:服务的进程优先级,值越小优先级越高,默认为 0。其中 -20 为最高优先级,19 为最低优先级
WorkingDirectory:指定服务的工作目录
RootDirectory:指定服务进程的根目录(/ 目录)。如果配置了这个参数,服务将无法访问指定目录以外的任何文件
User:指定运行服务的用户
Group:指定运行服务的用户组
MountFlags:服务的 Mount Namespace 配置,会影响进程上下文中挂载点的信息,即服务是否会继承主机上已有挂载点,以及如果服务运行执行了挂载或卸载设备的操作,是否会真实地在主机上产生效果。可选值为 shared、slaved 或 private
shared:服务与主机共用一个 Mount Namespace,继承主机挂载点,且服务挂载或卸载设备会真实地反映到主机上
slave:服务使用独立的 Mount Namespace,它会继承主机挂载点,但服务对挂载点的操作只有在自己的 Namespace 内生效,不会反映到主机上
private:服务使用独立的 Mount Namespace,它在启动时没有任何任何挂载点,服务对挂载点的操作也不会反映到主机上

Table3: Important [Install] Section options

[a]: 关于[Install]更详细的选项列表,参见systemd.unit (5) 的帮助手册,即执行命令man 5 systemd.unit即可。

Install段常用选项:

WantedBy:被哪些units所依赖,弱依赖。它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中
RequiredBy:被哪些units所依赖,强依赖。它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中
Alias:当前 Unit 可用于启动的别名,可使用systemctl command Alias.service
Also:安装本服务的时候还要安装别的相关服务

关于这三部分的内容组织,以sshd.service单元文件的内容为例,具体如下图所示:

上图的[Unit]部分指定了sshd服务的基本描述信息,以及这个服务的帮助文档获取方式;同时指定了该服务的以来关系,即sshd服务依赖network.target和sshd-keygen.target这两个单元,需要在这两个单元就绪之后才能启动sshd服务;也通过 Wants 显式声明了该服务需要ssh-keygen.target这个单元。

在上图的[Service]部分,指定了服务的进程启动类型受 ExecStart 选项的影响方式,notify的含义是该服务的进程被 ExecStart 启动之后以主进程的形式存在,随后的单元只是在通过 sd_notify() 函数发送了通知消息之后才会启动。ExecStart 指定了服务的启动命令,ExecReload 指定了重新装载配置文件时需要执行的操作(参见man 5 systemd.service的输出);KillMode 指定了进程应该以怎样的方式被关闭,process表示只有主进程会被杀掉(关于KillMode的帮助信息,参见man 5 systemd.kill的输出)。

Restart表示在什么情况下需要重启该服务,on-failure的含义是当服务的进程返回非零退出状态码的时候,才会重启该服务的主进程(比如当服务的重新装载配置文件操作超时或者触发了看门狗超时的时候,该选项的更详细的信息,参见man systemd.service的输出);RestartSec表示重启服务之前的睡眠时间,以秒为单位,默认100秒。另外,还通过选项 EnvironmentFile 定义了两个环境变量文件,该选项指定的值应该是文件的绝对路径,或者通配符表达式,加上前缀英文半角连字符"-"的时候,表示如果指定的文件不存在,就不再尝试读取该文件,同时不会记录错误或者警告信息(关于该选项的更详细的信息,参见man 5 systemd.exec的输出)。

上图中[Install]部分指定了选项 WantedBy,表示在执行systemctl enable命令将该服务设置为开机自动运行的时候,该服务所依赖的其他单元,此处指定的值为 multi-user.target,表示在开机自动运行该服务之前,系统需要先运行 multi-user.target 这个单元之后,才会启动该服务。

Unit 文件占位符

在 Unit 文件中,有时会需要使用到一些与运行环境有关的信息,例如节点 ID、运行服务的用户等。这些信息可以使用占位符来表示,然后在实际运行被动态地替换实际的值。

%n:完整的 Unit 文件名字,包括 .service 后缀名
%p:Unit 模板文件名中 @ 符号之前的部分,不包括 @ 符号
%i:Unit 模板文件名中 @ 符号之后的部分,不包括 @ 符号和 .service 后缀名
%t:存放系统运行文件的目录,通常是 “run”
%u:运行服务的用户,如果 Unit 文件中没有指定,则默认为 root
%U:运行服务的用户 ID
%h:运行服务的用户 Home 目录,即 %{HOME} 环境变量的值
%s:运行服务的用户默认 Shell 类型,即 %{SHELL} 环境变量的值
%m:实际运行节点的 Machine ID,对于运行位置每个的服务比较有用
%b:Boot ID,这是一个随机数,每个节点各不相同,并且每次节点重启时都会改变
%H:实际运行节点的主机名
%v:内核版本,即 “uname -r” 命令输出的内容
%%:在 Unit 模板文件中表示一个普通的百分号

Unit 模板

在现实中,往往有一些应用需要被复制多份运行。例如,用于同一个负载均衡器分流的多个服务实例,或者为每个 SSH 连接建立一个独立的 sshd 服务进程。

Unit 模板文件的写法与普通的服务 Unit 文件基本相同,不过 Unit 模板的文件名是以 @ 符号结尾的。通过模板启动服务实例时,需要在其文件名的 @ 字符后面附加一个参数字符串。

[email protected] 示例:

[Unit]
Description=My Advanced Service Template
After=etcd.service docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull coreos/apache
ExecStart=/usr/bin/docker run --name apache%i -p %i:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/%H:%i running
ExecStop=/usr/bin/docker stop apache1
ExecStopPost=/usr/bin/docker rm apache1
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/%H:%i
[Install]
WantedBy=multi-user.target

启动 Unit 模板的服务实例:

在服务启动时需要在 @ 后面放置一个用于区分服务实例的附加字符参数,通常这个参数用于监控的端口号或控制台 TTY 编译号。

$ systemctl start [email protected]

创建自定义的单元文件(unit files)

要创建自定义的服务单元文件,一般的步骤如下所示:

准备自定义服务的可执行文件。可以是自己写的脚本,也可以是软件提供者提供的可执行程序。如果有必要,可以准备PID文件,用来存放自定义服务的主进程的PID信息,也可以通过环境变量文件给自定义服务提供shell变量。请确保脚本或者程序具有执行权限,如果没有执行权限,需要执行chmod a+x给其增加执行权限。

在 /etc/systemd/system/ 目录或者 /usr/lib/systemd/system/ 目录中创建自定义服务的单元文件(unit files),同时需要注意文件的权限。具体如下所示:

创建自定义服务的单元文件:

# touch /etc/systemd/system/name.service
# chmod 644 /etc/systemd/system/name.service
或者
# touch /usr/lib/systemd/system/name.service
# chmod 644 /usr/lib/systemd/system/name.service

上述的两种形式都是可以的。

用文本编辑器打开前面创建的单元文件,并添加对应的服务配置选项。下面的简单示例如下:

[Unit]
Description=service_description
After=network.target
[Service]
ExecStart=path_to_executable
Type=forking
PIDFile=path_to_pidfile
[Install]
WantedBy=default.target

在上述的内容中,service_description 部分会显示在systemctl status命令输出的日志文件中。After 确保这个自定义服务在network.target启动之后才会运行,如果依赖多个其他单元,那么可以在 After 选项的后面通过空格将多个单元分隔。path_to_executable 代表了能够启动服务的脚本或者程序。Type=forking 代表 ExecStart 选项中启动的服务会调用fork()程序启动子进程,当子进程启动完成,并且所有的通信通道都设置完成之后,此时就应该退出父进程。此时子进程作为服务的主进程持续运行,如果使用了forking的形式,那么也需要提供 PIDFile 来保存服务的PID信息,以便systemd能够识别到这个服务的进程。WantedBy 这个自定义服务所依赖的其他单元。

通知systemd守护进程,有新的自定义服务被添加进来。

以root身份执行如下命令:

# systemctl daemon-reload
# systemctl start name.service

警告: 在每次增加了新的单元文件或者修改了既有的单元文件之后,都应该执行systemctl daemon-reload命令,加载到系统的内存中才会生效。否则执行systemctl start或者systemctl enable命令的时候就无法识别新增或修改的内容。

下面通过示例演示自定义服务的单元文件的步骤和过程。仍然以sshd这个服务为例,创建sshd服务的第二个单元文件,并使用不同的服务端口号。具体过程如下所示:

复制既有的sshd服务的配置文件:

[root@c7u6-ha1 ~]# cp /etc/ssh/sshd{,-second}_config
[root@c7u6-ha1 ~]# ll /etc/ssh/sshd*_config
-rw------- 1 root root 3908 Jun  8 14:14 /etc/ssh/sshd_config
-rw------- 1 root root 3908 Jun  8 14:17 /etc/ssh/sshd-second_config
[root@c7u6-ha1 ~]#

编辑上面的sshd-second_config这个配置文件,修改其中的服务端口号和PID文件:

[root@c7u6-ha1 ~]# vim /etc/ssh/sshd-second_config
[root@c7u6-ha1 ~]# cat /etc/ssh/sshd-second_config | egrep '2212|PidFile'
Port 2212
PidFile /var/run/sshd-second.pid
[root@c7u6-ha1 ~]#

关于sshd_config的配置选项,详细信息参见 sshd_config (5) 的帮助信息,执行命令man 5 sshd_config即可。

上面在第二个配置文件中指定的PID文件无需实现创建,在重启sshd服务的时候会自动创建这个PID文件。

拷贝既有的sshd.service单元文件:

[root@c7u6-ha1 ~]# cp /usr/lib/systemd/system/sshd{,-second}.service
[root@c7u6-ha1 ~]# ll /usr/lib/systemd/system/sshd*.service
-rw-r--r--. 1 root root 313 Apr 11  2018 /usr/lib/systemd/system/sshd-keygen.service
-rw-r--r--  1 root root 373 Jun  8 14:25 /usr/lib/systemd/system/sshd-second.service
-rw-r--r--. 1 root root 373 Apr 11  2018 /usr/lib/systemd/system/sshd.service
-rw-r--r--. 1 root root 260 Apr 11  2018 /usr/lib/systemd/system/[email protected]
[root@c7u6-ha1 ~]#

上述是将sshd.service直接在/usr/lib/systemd/system/这个目录中复制了一份,也可以将其复制到/etc/systemd/system/这个目录中。RHEL官方文档建议系统管理员应该将自定义的服务单元文件放置在/etc/systemd/system/这个目录中。

修改此前复制的sshd-second.service这个单元文件:

a. 修改Description选项Description=OpenSSH server second instance daemon
b. 将sshd.service添加为该服务的依赖服务,即sshd-second.service需要在sshd.service之后启动
After=syslog.target network.target auditd.service sshd.service
c. 由于第一个sshd.service中已经包含了密钥生成,所以在第二个服务中需要将密钥生成部分移除
ExecStartPre=/usr/sbin/sshd-keygen
移除上面这一行
d. 添加-f /etc/ssh/ssh-second_config这个参数
ExecStart=/usr/sbin/sshd -D -f /etc/ssh/ssh-second_config $OPTIONS
执行了上述修改之后,此时sshd-second.service文件的内容如下所示:

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

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

[Install]
WantedBy=multi-user.target
[root@c7u6-ha1 ~]#

​​​​​至此,sshd-second.service这个单元文件就准备好了。使能sshd-second.service,使其能够在下次开机的时候自动启动。

重新装载配置文件,并设置开机自动启动,具体如下所示:

[root@c7u6-ha1 ~]# systemctl daemon-reload
[root@c7u6-ha1 ~]# systemctl enable --now sshd-second.service
Created symlink from /etc/systemd/system/multi-user.target.wants/sshd-second.service to /usr/lib/systemd/system/sshd-second.service.
[root@c7u6-ha1 ~]#

上述使用systemctl daemon-reload通知服务进程重新装载配置文件,也可以执行init q命令,具有同样的效果。做完上述设置之后,查看系统是否有2212端口处于监听状态。

[root@c7u6-ha1 ~]# ss -ntl | egrep 2212
LISTEN     0      128          *:2212                     *:*
LISTEN     0      128         :::2212                    :::*
[root@c7u6-ha1 ~]#

在宿主机上开启另一个终端,并尝试使用上面的2212号端口登录虚拟机,具体如下所示:

[root@LiuXianQiE ~]# ssh -p 2212 root@c7u6-ha1
Last login: Tue Jun  8 14:19:19 2021 from 192.168.122.1
[root@c7u6-ha1 ~]#

从输出中可以看出,可以通过2212号端口正常登录虚拟机。

上述即为创建自定义单元文件的过程。

修改既存单元文件

已经安装到系统上的服务,会将自带的默认单元文件存放在/usr/llib/systemd/system/这个目录中。一般不建议系统管理员直接修改这个目录中的单元文件,应该尽量将自定义修改放置在/etc/systemd/system/这个目录中。具体操作方式,取决于所要求更改的程度,不同的修改程度,对应于下面的两种方式:

在/etc/systemd/system/中创建一个单元文件对应的目录,命名为name.unit.d的形式,然后将.conf结尾的配置文件放置在这个目录中,比如 /etc/systemd/system/httpd.service.d/httpd_supplementary.conf 这种形式。对于绝大多数情况,这种方式都是适用的,可以实现对既有单元文件的拓展,即增量的形式进行配置,这个配置文件中无需包含完整的配置信息,只需要包含增量或者需要修改的内容即可。

使用这种方式的过程如下:

# mkdir /etc/systemd/system/name.service.d/
# touch /etc/systemd/system/name.service.d/config_name.conf

然后将相应的补充的或者修改的配置选项写入到上述的config_name.conf这个文件中,其形式跟单元文件的内容一样,也包括[Unit], [Service]和[Install]三部分。

# cat /etc/systemd/system/name.service.d/config_name.conf
[Unit]
Requires=new_dependency
After=new_dependency
[Service]
Restart=always
RestartSec=30

上述就创建了补充的配置文件,在执行systemctl restart name.service之前,一定要先执行systemctl daemon-reload命令重新装载配置文件。
下面以sshd.service这个单元文件为例,通过上述的方式自定义sshd服务。

a. 创建服务对应的目录并在其中增加对应的配置文件:

[root@c7u6-ha1 ~]# mkdir /etc/systemd/system/sshd.service.d
[root@c7u6-ha1 ~]# vim /etc/systemd/system/sshd.service.d/custom_supplementary.conf                                                             
[root@c7u6-ha1 ~]# cat /etc/systemd/system/sshd.service.d/custom_supplementary.conf     
[Unit]
Description=Customize in /etc/systemd/system/sshd.service.d/custom-supplementary.conf                                                        
[Service]
ExecStartPost=/root/custom-sshd.sh

b. 创建所需要的脚本,custom-sshd.sh

[root@c7u6-ha1 ~]# vim custom-sshd.sh
[root@c7u6-ha1 ~]# cat custom-sshd.sh 
#!/bin/bash

echo -e '\E\033[1;33mThis is test for customize exist sshd service\E\033[0m'
[root@c7u6-ha1 ~]# 

c. 重启服务并查看服务状态

[root@c7u6-ha1 ~]# systemctl daemon-reload
[root@c7u6-ha1 ~]# systemctl restart sshd.service
Job for sshd.service failed because the control process exited with error code. See "systemctl status sshd.service" and "journalctl -xe" for details.
[root@c7u6-ha1 ~]# chmod +x custom-sshd.sh 
[root@c7u6-ha1 ~]# systemctl restart sshd.service
[root@c7u6-ha1 ~]# systemctl status sshd.service
. sshd.service - Customize in /etc/systemd/system/sshd.service.d/custom-supplementary.conf
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/sshd.service.d
           └─custom_supplementary.conf
   Active: active (running) since Tue 2021-06-08 16:15:43 CST; 3s ago
     Docs: man:sshd(8)
           man:sshd_config(5)
  Process: 6004 ExecStartPost=/root/custom-sshd.sh (code=exited, status=0/SUCCESS)
 Main PID: 6003 (sshd)
   CGroup: /system.slice/sshd.service
           └─6003 /usr/sbin/sshd -D

Jun 08 16:15:43 c7u6-ha1 systemd[1]: Stopped Customize in /etc/systemd/system/sshd.service.d/custom-supplementary.conf.
Jun 08 16:15:43 c7u6-ha1 systemd[1]: Starting Customize in /etc/systemd/system/sshd.service.d/custom-supplementary.conf...
Jun 08 16:15:43 c7u6-ha1 sshd[6003]: Server listening on 0.0.0.0 port 22.
Jun 08 16:15:43 c7u6-ha1 sshd[6003]: Server listening on :: port 22.
Jun 08 16:15:43 c7u6-ha1 custom-sshd.sh[6004]: [58B blob data]
Jun 08 16:15:43 c7u6-ha1 systemd[1]: Started Customize in /etc/systemd/system/sshd.service.d/custom-supplementary.conf.
[root@c7u6-ha1 ~]# 

从上述输出中可以看出,脚本需要加上执行权限,否则无法重启服务。

另外,***/etc/systemd/system/目录中的对应服务的配置文件比/usr/lib/systemd/system/目录中的对应服务的优先级更高,从上述输出中可以看出,在配置文件中定义的Description的信息覆盖了以前的默认描述信息。

从/usr/lib/systemd/system/这个目录中复制一份单元文件到/etc/systemd/system/这个目录中,并更改这个副本,这个拷贝的单元文件会忽略掉原始的单元文件。这种方式适用于忽略掉服务本身的设置,而将一些不受服务更新与否的配置写在这个单元文件中。注意:这种方式是存量扩展,即拷贝过来的单元文件需要包含服务正常运行所需要的完整内容,在此基础上进行增加或者修改。

如果只保留增加或者修改的部分,删除掉其他部分,则此时会导致服务无法正常启动。因为这个单元文件的优先级比/usr/lib/systemd/system/httpd.service这个文件的优先级更高,所以会忽略掉默认的单元文件。

如果想要使更改信息在更新软件包之后,仍然能保持生效,就需要将服务安装时的默认单元文件拷贝到/etc/systemd/system/这个目录中,具体如下所示:

# cp /usr/lib/systemd/system/name.service /etc/systemd/system/name.service

然后修改上述的name.service文件,修改完成之后,执行下面的操作重启服务即可。

# systemctl daemon-reload
# systemctl restart name.service

下面以httpd.service为例进行示例。

a. 将httpd.service文件从/usr/lib/systemd/system/目录中拷贝到/etc/systemd/system/这个目录中,并修改其内容:

[root@c7u6-ha1 ~]# cp /usr/lib/systemd/system/httpd.service /etc/systemd/system                                                                 
[root@c7u6-ha1 ~]# vim /etc/systemd/system/httpd.service
[root@c7u6-ha1 ~]# cat /etc/systemd/system/httpd.service
[Unit]
Description=The Apache Service Configuration in /etc/systemd/system/httpd.service                                                               

[Service]
TimeoutStartSec=5

b. 重启服务

[root@c7u6-ha1 ~]# systemctl daemon-reload
[root@c7u6-ha1 ~]# systemctl restart httpd.service
Failed to restart httpd.service: Unit is not loaded properly: Invalid argument.                                                                 
See system logs and 'systemctl status httpd.service' for details.
[root@c7u6-ha1 ~]# systemctl status httpd.service
● httpd.service - The Apache Service Configuration in /etc/systemd/system/httpd.service                                                        
   Loaded: error (Reason: Invalid argument)
   Active: inactive (dead)

Jun 08 16:33:19 c7u6-ha1 systemd[1]: httpd.service lacks both ExecStart= and ExecStop= setting. Refusing.                                       
Jun 08 16:34:13 c7u6-ha1 systemd[1]: httpd.service lacks both ExecStart=    > and ExecStop= setting. Refusing.

从上述输出中可以看出,提示缺少ExecStart以及ExecStop的相关选项,因为上面将拷贝过来的文件中的这部分内容都删除了,只留下了修改的或者增加的内容。这种方式是不可以的,必须要保证完整的内容,然后在其上进行修改才可以。

重新拷贝文件,并对拷贝过来的文件增量修改:

[root@c7u6-ha1 ~]# cp /usr/lib/systemd/system/httpd.service /etc/systemd/system/                                                                
cp: overwrite ‘/etc/systemd/system/httpd.service’? y
[root@c7u6-ha1 ~]# vim /etc/systemd/system/httpd.service
[root@c7u6-ha1 ~]# cat /etc/systemd/system/httpd.service
[Unit]
Description=The Apache Configuration in /etc/systemd/system/httpd.service
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
TimeoutStartSec=5
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true

[Install]
WantedBy=multi-user.target
[root@c7u6-ha1 ~]# systemctl daemon-reload
[root@c7u6-ha1 ~]# systemctl restart httpd.service
[root@c7u6-ha1 ~]# systemctl status httpd.service
. httpd.service - The Apache Configuration in /etc/systemd/system/httpd.service
   Loaded: loaded (/etc/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2021-06-08 16:35:48 CST; 3s ago
     Docs: man:httpd(8)
           man:apachectl(8)
 Main PID: 6199 (httpd)
   Status: "Processing requests..."
   CGroup: /system.slice/httpd.service
           ├─6199 /usr/sbin/httpd -DFOREGROUND
           ├─6200 /usr/sbin/httpd -DFOREGROUND
           ├─6201 /usr/sbin/httpd -DFOREGROUND
           ├─6202 /usr/sbin/httpd -DFOREGROUND
           ├─6203 /usr/sbin/httpd -DFOREGROUND
           └─6204 /usr/sbin/httpd -DFOREGROUND

Jun 08 16:35:48 c7u6-ha1 systemd[1]: Starting The Apache Configuration in /etc/systemd/system/httpd.service...
Jun 08 16:35:48 c7u6-ha1 httpd[6199]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192... message
Jun 08 16:35:48 c7u6-ha1 systemd[1]: Started The Apache Configuration in /etc/systemd/system/httpd.service.
Hint: Some lines were ellipsized, use -l to show in full.
[root@c7u6-ha1 ~]# 

至此,服务就可以正常工作了,并且Description的内容确实发生改变了。

猜你喜欢

转载自blog.csdn.net/qq_35029061/article/details/126205378