Systemd详解与使用
龚正阳龚正阳 -背景不太适合进入
docker
,需要直接部署在宿主机上面,拥有root
权限,操作修改系统,读取系统的一些信息等保证开机启动以及异常重启有一套完善的日志系统支持Before/After
的启动机制支持基于cgroup
的限制以最低的系统开销实现该目的方便打包为deb
包进行安装,参考各种服务的安装,比如apt install nginx docker postgresql
等deb
包背后的逻辑与实现兼容基本全部的linux
发行版本虽然之前使用过的supervisor
也可以满足上述大部分需求,但是打工人的世界总是需要去追求一下极致的,于是决定好好学习一下systemd
Systemd
基本介绍内核加载到内存之后启动的第一个用户空间程序,pid
是1,是所有进程的父进程,会托管所有僵尸进程,如果这个程序挂了,系统也就直接关机了systemd
不是内核的一部分,但是却成为了Debian/Ubuntu
系和Fedora/Centos/Redhat/RockyLinux
系的系统初始化进程Linux
启动流程systemd
是系统启动流程中初始化阶段的主要角色,有必要介绍一下linux
系统启动流程
机器电源接通时,存储在主板芯片上面的固件初始化,POST(Power On Self Test)
上电自检BIOS
阶段
MBR
或者EFI
分区的第一阶段引导程序,并且移交控制权MBR
或者EFI
程序阶段,查找第二阶段的GRUB(GRand Unified Boot)
引导程序并安装到BootLoder
BootLoader
启动引导阶段(第二阶段)stage1
: 执行BootLoader
的主程序(GRUB
程序),开始启动stage1.5
stage1.5
: 引导文件系统中的stage2
stage2
: 加载GRUB
核心映像grub.conf/grub.cfg
阶段
grub.conf/grub.cfg
配置文件,加载默认内核镜像和 initrd
(初始磁盘镜像,对应于/boot/initrd.img
文件)镜像到内存中,当所有镜像准备好后,即跳转到内核镜像,移交系统控制权给内核,接下去进入到内核阶段文件后缀名称在Ubuntu22
上面看到的是.cfg
,可能其他系统是.conf
结尾该配置文件路径是/grub/grub.cfg
或者/boot/grub/grub.cfg
,路径具体位置取决于系统安装时候是否分割了一个/boot
分区,之前装系统的时候/boot
分区都是必须分配的内核引导阶段(第三阶段)/boot/kernel and Kernel parameter
/boot
下面的文件,执行加载查看/boot
分区的内容如下,可以看到内核保存了两个版本的镜像(img-5.15.0-53-generic
和img-5.15.0-56-generic
),其中新镜像是目前系统正常运行时候加载的镜像,每次apt
更新系统的时候如果有新版本内核释放,更新时候会保留一份老版本镜像,等下一次新镜像更新到时候才会删除,一份冗余
$ ll /boot
总用量 165M
-rw-r--r-- 1 root root 256K 十月 18 02:36 config-5.15.0-53-generic
-rw-r--r-- 1 root root 256K 十一月 22 23:08 config-5.15.0-56-generic
drwx------ 3 root root 4.0K 一月 1 1970 efi
drwxr-xr-x 5 root root 4.0K 十二月 3 09:44 grub
lrwxrwxrwx 1 root root 28 十二月 2 09:45 initrd.img -> initrd.img-5.15.0-56-generic
-rw-r--r-- 1 root root 65M 十二月 1 09:01 initrd.img-5.15.0-53-generic
-rw-r--r-- 1 root root 65M 十二月 4 09:04 initrd.img-5.15.0-56-generic
lrwxrwxrwx 1 root root 28 十二月 2 09:45 initrd.img.old -> initrd.img-5.15.0-53-generic
drwx------ 2 root root 16K 五月 26 2021 lost+found
-rw-r--r-- 1 root root 179K 二月 7 2022 memtest86+.bin
-rw-r--r-- 1 root root 181K 二月 7 2022 memtest86+.elf
-rw-r--r-- 1 root root 181K 二月 7 2022 memtest86+_multiboot.bin
-rw------- 1 root root 6.0M 十月 18 02:36 System.map-5.15.0-53-generic
-rw------- 1 root root 6.0M 十一月 22 23:08 System.map-5.15.0-56-generic
lrwxrwxrwx 1 root root 25 十二月 2 09:45 vmlinuz -> vmlinuz-5.15.0-56-generic
-rw------- 1 root root 12M 十月 18 02:41 vmlinuz-5.15.0-53-generic
-rw------- 1 root root 12M 十一月 23 01:07 vmlinuz-5.15.0-56-generic
lrwxrwxrwx 1 root root 25 十二月 2 09:45 vmlinuz.old -> vmlinuz-5.15.0-53-generic
/boot/initrd
initrd
解压载入在内存中加载内核使用的root
文件系统,执行initrd
文件系统中的init
程序,完成加载其他驱动模块执行/sbin/init
进程
发现/sbin
是指向/usr/sbin
的一个软链接
$ ll / |grep bin
lrwxrwxrwx 1 root root 7 五月 26 2021 bin -> usr/bin
lrwxrwxrwx 1 root root 8 五月 26 2021 sbin -> usr/sbin
查看init
发现init
是指向systemd
的一个软链接
$ ll /usr/sbin |grep init
lrwxrwxrwx 1 root root 20 九月 10 02:47 init -> /lib/systemd/systemd
-rwxr-xr-x 1 root root 13K 三月 15 2022 mkinitramfs
lrwxrwxrwx 1 root root 14 九月 10 02:47 telinit -> /bin/systemctl
-rwxr-xr-x 1 root root 6.8K 二月 9 2022 update-initramfs
Systemd
初始化阶段(第四阶段)内核启动第一个用户空间应用程序,系统控制权移交给systemd
该阶段会加载执行级别,加载服务,启动shell
和图形化界面初始化阶段有3个类型,SysV
初始化,UpStart
(Ubuntu
开发的用于替换SysV
的初始化程序)和Systemd
(由Redhat
工程师开发),很多老系统一般都是SysV
作为初始化程序,从centos7
(大概是2014年)以后的系统大多都是采用Systemd
,Ubuntu15
开始使用systemd
为什么各大主流linux
系统采用systemd
改进启动项的效率,避免频繁的进程创建,内核/用户切换,为系统的启动和管理提供一套完整的解决方案利用 Dbus
进程间通讯与 socket
激活机制,解决任务启动时的依赖问题,实现启动并行化实现任务daemons
的精确控制,使用内核的 cgroup
机制,不依赖 pid
来追踪进程,进程多次fork
之后生成的进程也不会脱离 systemd
的控制统一任务定义,用户不需要编写shell
脚本,而是使用 systemd
制定的 unit
规则systemd
有两大设计理念,启动更少的服务,更多地并行启动服务,最终实现用户可以快速进入系统systemd
使用C
编写,用于替换传统的脚本shell
启动,shell
中调用系统命令的操作会导致新进程的生成,比如awk, sed, grep
等都会生成新进程,产生了很大的开销,systemd
的运行开销比SysV
和UpStart
小很多,速度也更快systemd
做了什么解决启动项的依赖性Socket
依赖:例如服务A
依赖服务B
的socket
,但是服务B
还未启动,所以systemd
创建了一个临时socket
用于接收服务A
的请求与数据,当B
真正起来的时候再把临时socket
缓存的数据以及描述符替换为B
的socket
D-Bus(Desktop Bus)
依赖:是一个用来在进程间通信的服务,该服务支持Bus Activation
特性,即服务A
要通过 D-Bus
服务和B
通讯,但是B
没有启动,那么 D-Bus
可以把B
起来,在B
启动的过程中,D-Bus
缓存数据,systemd
使用这个特性并行启动A
和B
文件系统依赖: 系统启动过程中,systemd
参考了 autofs
的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作,监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作提供了一个系统和服务管理器利用 Linux
的 cgroups
监视进程支持快照和系统恢复维护挂载点和自动挂载点各服务间基于依赖关系进行精密控制基于journald
的服务日志管理控制基础系统配置维护登陆用户列表以及系统账户运行时目录和设置运行容器和虚拟机简单的管理网络配置网络时间同步日志转发...systemd
的管控范围已经远超作为系统启动项的角色,可以理解为已经大一统管理linux
各项功能了,职责和最初的sysv
和upstart
有所脱离,变得极其复杂庞大,这个也是最初redhat/centos
系列和debian/ubuntu
系列采用systemd
引起的巨大波澜
在此推荐阅读一下itwire
于2014发表的 Linus Torvalds
关于systemd
的访谈文章(Linus
保持中立意见,如果这个人没有开喷的话,说明是已经取得巨大的成功了)
https://www.itwire.com/business-it-news/open-source/65402-torvalds-says-he-has-no-strong-opinions-on-systemd
也可以阅读一下systemd
的作者Lennert
发表的关于PID 1
的再思考Rethinking PID 1
http://0pointer.de/blog/projects/systemd.html
systemd
的Unit
单元的文件类型系统初始化需要很多操作,比如启动后台服务,挂载文件系统,配置网络等,过程中的每一步都被 Systemd
抽象为一个配置单元,即 Unit
,具体包含的类型如下
Service unit
.service
封装一个后台服务进程,比如sshd
,dockerd
Target unit
.target
将多个单元在逻辑上组合在一起。Device unit
.device
定义内核识别的设备,在 sysfs(5)
里面作为 udev(7)
设备树展示Socket unit
.socket
用于标识进程间通信用到的 socket
文件Snapshot unit
.snapshot
管理系统快照Swap unit
.swap
用于标识 swap
文件或设备Mount unit
.mount
封装一个文件系统挂载点(也向后兼容传统的 /etc/fstab
文件)Automount unit
.automount
封装一个文件系统自动挂载点Path unit
.path
根据文件系统上特定对象的变化来启动其他服务。Time unit
.timer
封装一个基于时间触发的动作。取代传统的 crond
等任务计划服务Slice unit
*.slice
控制特定 CGroup
内所有进程的总体资源占用。Systemd Unit
文件的位置与执行优先级不同发行版本的路径是不同的,当三个目录下面存在同名文件的时候,优先级最高目录下的文件会被优先使用
/usr/lib/systemd/system
或 /lib/systemd
: 使用包管理器安装的软件的 systemd unit
的实际配置文件的存放位置,在Ubuntu22
上面/lib
路径是指向/usr/lib
的一个软链接,优先级最低/run/systemd/system
:在运行时创建的 systemd unit
文件,该目录优先于已安装服务单元文件的目录/etc/systemd/system
: 优先级最高,由 systemctl
命令创建的 systemd unit
文件以及为扩展服务而添加的 unit
文件都将启用,系统管理员安装的单元Systemd Unit
文件的组成主要由三块Unit
,Service
,Install
组成
具体版本或者全部字段解释执行如下命令查看
$ man systemd.unit
$ man systemd.service
$ man systemd.exec
....
或者访问地址https://www.freedesktop.org/software/systemd/man/
或者第三方中文版http://www.jinbuguo.com/systemd/systemd.index.html
常见字段解释如下
Unit
块下面的字段Requires
: 强依赖,依赖的其它 Unit
列表,列在其中的 Unit
会在这个Unit
启动时的同时被启动,如果其中任意一个Unit
启动失败,这个Unit
也会启动失败Wants
: 弱依赖,与 Requires
相似,但只是在当前 Unit
启动时,触发启动列出的 Unit
,而不考虑这些 Unit
启动是否成功After
:与 Requires
相似,该字段列出的所有 Unit
全部启动后,才会启动当前的 Unit
Before
: 与 After
相反,当前 Unit
应该在列出的 Unit
之前启动OnFailure
:Unit
启动失败时,自动启动列出的每个 Unit
Conflicts
: 与这个 Unit
有冲突的模块,如果列出的 Unit
已经在运行时,当前 Unit
不能启动,反之亦然Service
块下面的字段EnvironmentFile
: 指定当前服务的环境变量定义文件,该文件内部的 key=value
键值对,可以用 $key
的形式,在当前.service
文件中引用ExecStart
: 定义启动进程时执行的命令ExecStartPre
: 启动当前服务之前执行的命令
ExecStartPost:
启动当前服务之后执行的命令ExecStop
: 停止服务时执行的命令ExecStopPost
: 停止服务之后执行的命令
ExecReload
: 重启服务时执行的命令Type
: 启动类型
simple
: 默认值,执行ExecStart
指定的命令,启动主进程forking
: 以 fork
方式从父进程创建子进程,创建后父进程会立即退出oneshot
: 一次性进程,Systemd
会等当前服务退出,再继续往下执行dbus
: 当前服务通过D-Bus
启动notify
: 当前服务启动完毕,会通知Systemd
,再继续往下执行idle
: 若有其他任务执行完毕,当前服务才会运行
WorkingDirectory
: 指定服务的工作目录RootDirectory
: 指定服务进程的根目录,如果配置了这个参数,服务将无法访问指定目录以外的文件User
: 指定运行服务的用户
Group
: 指定运行服务的用户组KillMode
: 定义 Systemd
如何停止服务,可选项如下control-group
: 默认值,当前控制组里面的所有子进程,都会被杀掉process
: 只杀主进程mixed
: 主进程将收到 SIGTERM
信号,子进程收到 SIGKILL
信号none
: 没有进程会被杀掉,只是执行服务的 stop
命令install块下面的字段WantedBy
值为一个或多个 Target
,当前 Unit
激活 (enable
,即开机启动) 时符号链接会放入 /usr/lib/systemd/system
目录下以 <Target>.wants
后缀构成的子目录中,该参数常见的值是multi-user.target
,这样,当该target
中的任意一个Unit
启动的时候,本Unit
都会跟着一起启动,这就是配置开机自启动的关键Also
当前 Unit enable/disable
时,同时 enable/disable
的其他 Unit
Alias
当前 Unit
可用于启动的别名systemd Unit
的配置与使用最好的样例还是参考操作系统已经有的内容
比如现在要编写一个自启动服务,可以参考docker.service
和sshd.service
$ systemctl cat docker
$ systemctl cat sshd
如果要编写一个定时任务,可以参考apt-dayly.timer
$ systemctl cat apt-daily.timer
systemctl
使用管理系统和服务的命令行工具
设备启动,关机等操作,可以看到设备运行操作命令是指向systemctl
的软链接
$ ll /usr/sbin |grep system
lrwxrwxrwx 1 root root 14 九月 10 02:47 halt -> /bin/systemctl
lrwxrwxrwx 1 root root 20 九月 10 02:47 init -> /lib/systemd/systemd
lrwxrwxrwx 1 root root 14 九月 10 02:47 poweroff -> /bin/systemctl
lrwxrwxrwx 1 root root 14 九月 10 02:47 reboot -> /bin/systemctl
lrwxrwxrwx 1 root root 14 九月 10 02:47 runlevel -> /bin/systemctl
lrwxrwxrwx 1 root root 14 九月 10 02:47 shutdown -> /bin/systemctl
lrwxrwxrwx 1 root root 14 九月 10 02:47 telinit -> /bin/systemctl
systemd
常用操作
systemctl status
列出正在运行的单元systemctl
or systemctl list-units
列出失败的单元systemctl --failed
列出已安装的单元systemctl list-unit-files
Show process status for a PIDsystemctl status {pid}
cgroup slice, memory and parent
检查单元状态 显示单元的手册页systemctl help {unit}
如果单元支持显示单元的状态systemctl status {unit}
包括其是否在运行检查单元是否配置为自动启动systemctl is-enabled {unit}
启动、重新启动和重新加载单元 立即启动单元systemctl start {unit}
立即停止单元systemctl stop {unit}
重新启动单元systemctl restart {unit}
重新加载单元及其配置systemctl reload {unit}
重新加载 systemd 配置systemctl daemon-reload
扫描单元的变动 启用单元 开机自动启用单元systemctl enable {unit}
开机自动启用单元,并立即启动它systemctl enable --now {unit}
取消开机自动启用单元systemctl disable {unit}
重新启用 单元systemctl reenable {unit}
先取消启用,再启用 禁用单元禁用单元,使其无法启动systemctl mask {unit}
取消禁用单元systemctl unmask {unit}
systemd
其他常用命令模块journalctl
查询systemd
服务的日志信息
$ journalctl
loginctl
systemd
的登入管理器,可以查看目前登录到当前计算机的用户信息
$ sudo loginctl
SESSION UID USER SEAT TTY
2 1000 gong seat0 tty2
640 1000 gong pts/17
641 1000 gong pts/19
如果使用ssh {用户名}@localhost
再次登录到本地计算机,使用loginctl
的时候会看到多了一个session
这个时候可以使用如下命令,指定一个session id
的方式关闭某个用户的访问
$ sudo loginctl kill-session 641
systemd-analyze
分析系统启动过程中的耗时,关闭或者调整某些服务可以优化启动速度
$ sudo systemd-analyze blame
1min 55.462s fstrim.service
39.372s fwupd-refresh.service
33.827s apt-daily.service
31.949s apt-daily-upgrade.service
16.637s systemd-networkd-wait-online.service
6.193s plymouth-quit-wait.service
5.070s docker.service
1.551s snapd.service
1.540s netfilter-persistent.service
1.318s snapd.seeded.service
1.196s vmware.service
1.129s freeradius.service
1.049s gpu-manager.service
.....
systemd service
使用示例编写一个文件/usr/lib/systemd/system/test-boot.service
如下
[Unit]
Description=test boot
# 表示在网络模块启动之后才启动本Unit
After=network.target
[Service]
# 该命令是阻塞的,每隔1秒会打印当前日期
ExecStart=/bin/sh -c "while true; do date; sleep 1; done"
# 配置查询的工作路径,该路径需要存在,否则会报错
WorkingDirectory=/tmp/
[Install]
# 该参数表示此Unit是开机启动时候关联到multi-user.target
# 当multi-user.target下面的任意一个Unit启动都会触发本Unit的启动
# 即enable状态的时候会创建一个链接到/etc/systemd/system/multi-user.target.wants/目录下面
WantedBy=multi-user.target
执行命令如下
$ sudo systemctl daemon-reload
查看当前服务状态
$ sudo systemctl status test-boot
○ test-boot.service - test boot
Loaded: loaded (/lib/systemd/system/test-boot.service; disabled; vendor preset: enabled)
Active: inactive (dead)
配置开机启动,注意观察软链接的位置是配置在
$ sudo systemctl enable test-boot
Created symlink /etc/systemd/system/multi-user.target.wants/test-boot.service → /lib/systemd/system/test-boot.service.
接下去启动服务并查看状态
$ sudo systemctl start test-boot
$ sudo systemctl status test-boot
● test-boot.service - test boot
Loaded: loaded (/lib/systemd/system/test-boot.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2023-03-16 17:32:13 CST; 20s ago
Main PID: 940646 (sh)
Tasks: 2 (limit: 38197)
Memory: 364.0K
CGroup: /system.slice/test-boot.service
├─940646 /bin/sh -c "while true; do date; sleep 1; done"
└─940825 sleep 1
三月 16 17:32:24 gong sh[940765]: 2023年 03月 16日 星期四 17:32:24 CST
三月 16 17:32:25 gong sh[940769]: 2023年 03月 16日 星期四 17:32:25 CST
三月 16 17:32:26 gong sh[940778]: 2023年 03月 16日 星期四 17:32:26 CST
三月 16 17:32:27 gong sh[940782]: 2023年 03月 16日 星期四 17:32:27 CST
三月 16 17:32:28 gong sh[940786]: 2023年 03月 16日 星期四 17:32:28 CST
三月 16 17:32:29 gong sh[940795]: 2023年 03月 16日 星期四 17:32:29 CST
三月 16 17:32:30 gong sh[940799]: 2023年 03月 16日 星期四 17:32:30 CST
三月 16 17:32:31 gong sh[940803]: 2023年 03月 16日 星期四 17:32:31 CST
三月 16 17:32:32 gong sh[940814]: 2023年 03月 16日 星期四 17:32:32 CST
最后可以重启机器校验一下是否可以开机自启动
参考阅读Linux
系统启动流程
Linux
的小伙伴 systemd
详解
linux
的init
程序发展历史
Linux PID 1
和 Systemd
Docker
基础技术:Linux CGroup
archlinux
的systemd wiki
文档
itwire
于2014发表的 Linus Torvalds
关于systemd
的访谈
systemd
的作者Lennert
发表的关于PID 1
的再思考
systemd
的unit
配置
Tags 标签
systemdlinux运维扩展阅读
Linux 常用命令
2019-01-12 11:26:35 []linux命令行查看系统有哪些用户
2020-06-28 19:09:43 []【问题合集】Problem with the SSL CA cert (path? access rights?)
2020-09-20 09:57:21 []关于 MAC 配置 Apache2 + PHP
2020-09-21 12:36:34 []Laravel Vapor(1)
2020-09-27 09:50:57 []Laravel项目上线注意点
2020-10-20 21:55:08 []PHP-FPM中-D命令的实现
2020-10-23 13:54:26 []2020年10月php面试笔记
2020-10-23 01:09:55 []协程 shell_exec 如何捕获标准错误流
2020-11-03 10:11:33 []亲测三遍!8步搭建一个属于自己的网站
2020-11-15 11:37:21 []加个好友,技术交流
