Systemd详解与使用

发布于:2023-03-16 ⋅ 阅读:(1771) ⋅ 点赞:(1)

背景

  • 不太适合进入docker,需要直接部署在宿主机上面,拥有root权限,操作修改系统,读取系统的一些信息等
  • 保证开机启动以及异常重启
  • 有一套完善的日志系统
  • 支持Before/After的启动机制
  • 支持基于cgroup的限制
  • 以最低的系统开销实现该目的
  • 方便打包为deb包进行安装,参考各种服务的安装,比如apt install nginx docker postgresqldeb包背后的逻辑与实现
  • 兼容基本全部的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-genericimg-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年)以后的系统大多都是采用SystemdUbuntu15开始使用systemd

为什么各大主流linux系统采用systemd

  • 改进启动项的效率,避免频繁的进程创建,内核/用户切换,为系统的启动和管理提供一套完整的解决方案
  • 利用 Dbus 进程间通讯与 socket 激活机制,解决任务启动时的依赖问题,实现启动并行化
  • 实现任务daemons的精确控制,使用内核的 cgroup 机制,不依赖 pid 来追踪进程,进程多次fork 之后生成的进程也不会脱离 systemd 的控制
  • 统一任务定义,用户不需要编写shell脚本,而是使用 systemd 制定的 unit 规则
  • systemd有两大设计理念,启动更少的服务,更多地并行启动服务,最终实现用户可以快速进入系统
  • systemd使用C编写,用于替换传统的脚本shell启动,shell中调用系统命令的操作会导致新进程的生成,比如awk, sed, grep等都会生成新进程,产生了很大的开销,systemd的运行开销比SysVUpStart小很多,速度也更快

systemd做了什么

解决启动项的依赖性

  • Socket依赖:例如服务A依赖服务Bsocket,但是服务B还未启动,所以systemd创建了一个临时socket用于接收服务A的请求与数据,当B真正起来的时候再把临时socket缓存的数据以及描述符替换为Bsocket
  • D-Bus(Desktop Bus)依赖:是一个用来在进程间通信的服务,该服务支持Bus Activation特性,即服务A要通过 D-Bus 服务和B通讯,但是B没有启动,那么 D-Bus 可以把B起来,在B启动的过程中,D-Bus 缓存数据,systemd使用这个特性并行启动AB
  • 文件系统依赖: 系统启动过程中,systemd 参考了 autofs 的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作,监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作

提供了一个系统和服务管理器

  • 利用 Linuxcgroups监视进程
  • 支持快照和系统恢复
  • 维护挂载点和自动挂载点
  • 各服务间基于依赖关系进行精密控制
  • 基于journald的服务日志管理
  • 控制基础系统配置
  • 维护登陆用户列表以及系统账户
  • 运行时目录和设置
  • 运行容器和虚拟机
  • 简单的管理网络配置
  • 网络时间同步
  • 日志转发
  • ...

systemd的管控范围已经远超作为系统启动项的角色,可以理解为已经大一统管理linux各项功能了,职责和最初的sysvupstart有所脱离,变得极其复杂庞大,这个也是最初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

systemdUnit单元的文件类型

系统初始化需要很多操作,比如启动后台服务,挂载文件系统,配置网络等,过程中的每一步都被 Systemd 抽象为一个配置单元,即 Unit,具体包含的类型如下

type name 作用
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文件的组成

主要由三块UnitServiceInstall组成

具体版本或者全部字段解释执行如下命令查看

$ 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 之前启动
  • OnFailureUnit 启动失败时,自动启动列出的每个 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.servicesshd.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 PID systemctl 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 详解

linuxinit程序发展历史

Linux PID 1Systemd

Docker基础技术:Linux CGroup

archlinuxsystemd wiki文档

itwire于2014发表的 Linus Torvalds关于systemd的访谈

systemd的作者Lennert发表的关于PID 1的再思考

systemdunit配置


网站公告

今日签到

点亮在社区的每一天
去签到