现在一般新发行的版本会采用新的 init 进程,也就是 systemd ,其中启动过程可以通过 man bootup 查看。
在此,简单介绍一下 systemd 。
简介
内核加载完成之后会启动用户态的 init 进程,完成剩余的系统初始化,在 Linux 历史上,包含几种启动方案,目前为止,大部分都采用的是 systemd 进行系统启停、服务管理等。
以 Ubuntu 为例,大致分成了三阶段:A) Ubuntu 6.10 之前采用 Sysvinit;B) Ubuntu 14.10 以及之前版本使用 Upstart 同时还存留着 Sysvinit 版本;C) 从 Ubuntu 15.04 开始使用 Systemd ,不过仍然允许使用 Upstart 。
而 CentOS 从 7 开始使用 systemd ,可以通过 rpm -qif `which init`
命令查看 init 来自于那个包。
其中 Upstart 类似于中间过渡版本,而且只在部分发行版本上使用,这里就不做过多介绍,仅对 SysvInit 做简单介绍,主要关注 Systemd 的使用。
SysvInit
老的 Linux 发行版本 (例如 MCC、TAMU 等) 都是采用的 SysvInit 机制,定义了 0-6 总共七个系统层级。
SysvInit 全称为 System V style Init programs,最初由 Miquel van Smoorenburg 开发,用来控制系统的启动和停止,现在由 Savannah 维护。从 1992.02 在 Minix 系统上使用,后来在 1992.07 迁移到 Linux 系统上,并一直在维护,详细可以查看 ChangeLog 。
支撑 SysvInit 功能的大部分是 Bash 脚本,也就是所谓的 LSB(Linux Standard Base) Init Script ,一般在
/etc/init.d
目录下,通常在开始部分包含 LSB headers ,一般以### BEGIN INIT INFO
开始,以### END INIT INFO
,包含了对脚本信息的描述,会通过insserv
命令计算启动顺序。几乎每个服务都对应了脚本,各个脚本提供了 start、stop、restart、reload、status 类似的命令用来控制服务,甚至对返回码也有规定。
启动时,会在 /etc/inittab
中读取包含 initdefault
的默认启动层级,并根据配置的脚本启动,一般包括了设置内核参数、设定系统时钟、设置主机名、启动 swap 分区、挂载文件系统等等,可以通过 chkconfig
命令管理服务。
Systemd
虽然越来越多的发行版本开始使用 systemd ,但开始也是最具争议的一个项目,因为其不只替换了 init,而且还包括了一整套的系统任务,违背了 Linux 中的单一职责原则。
作为最新的系统和服务管理器,其设计思路借鉴了 Mac 的启动程序 Launchd,兼容 SysV 和 LSB 的启动脚本。事实上其作用远不仅是启动系统,它还接管了系统服务的启动、结束、状态查询和日志归档等职责,并支持定时任务和通过特定事件,如插入特定 USB 设备和特定端口数据触发的任务。
其有以下特性:支持并行化任务、同时采用 socket 和 D-BUS 总线式激活服务、按需启动相应的守护进程、利用 Linux 的 cgroup 监控进程、支持快照和系统恢复、维护挂载点和自动挂载点,各服务间基于依赖关系进行精密控制。
可以通过 pstree 查看启动的进程树,接下来查看一下 systemd 的特性。
资源隔离
systemd 是基于 cgroup 来实现的,当前服务层级可以通过 systemd-cgls
命令查看,通过 slice scope service 划分。
- service 会作为一个或一组进程单元,这样进程可以作为整体进行启停。
- scope 可以由任意进程启动,会在运行时会注册到 PID 1 进程。
- slice 仅作为 service 和 scope 的中间层,并不会真正的包含进程。
实际上,可以将 service 和 scope 作为同一级别,用来管理服务,只是使用场景不同;而 slice 则主要是为了划分层级,并不会真正的管理服务进程。
其中 service 作为主要的服务管理配置方式,可以详细查看 服务管理 中的介绍,这里主要介绍 scope 和 slice 。
slice
如上所述,systemd 是通过 slice 对 cgroup 划分层级,在启动时,默认会包含如下几个 slice (详细可以查看 /usr/lib/systemd/system
目录下对应的配置文件) :
-.slice
作为所有 slice 的根 Slice ,类似于根目录。system.slice
系统服务的默认存放位置。user.slice
用户会话的默认存放位置。machine.slice
虚拟机和容器的默认存放位置。
另外,需要注意 slice 的命名,会通过 -
分割父子 slice 名称,例如通过 parent-name.slice
可以知道其父 slice 是 parent.slice
,而当前 slice 还可以包含子 slice ,如 parent-name-child.slice
。
而且默认是不会创建 slice 的,只有在对应 slice 下有服务启动时创建相关的 slice 配置。
scope VS. service
service 和 slice 一般是通过配置文件
scope 并不是通过配置文件生成的,而是通过 systemd API 调用生成,当然,也包括了通过 systemd-run --scope
命令行方式调用。与 service 区别是,service 通常由 systemd 来 fork 进程,而 scope 可以有进程执行 fork ,常见的如 ssh、x11 等。
最常见的就是 /run/systemd/system/session-*.scope
中的文件,这是由 systemd-logind
服务在用户登录时针对用户所创建的,可以通过 loginctl list-sessions
查看当前会话。
使用示例
假设想对 docker 服务进行资源限制,可以创建一个 docker.slice
用来限制总体的资源,内容如下。
[Unit]
Description=Docker Slice
Before=slices.target
[Slice]
MemoryAccounting=true
MemoryLimit=2048M
CPUAccounting=true
CPUQuota=25%
TasksMax=4096
再修改相关的服务配置,在 [Service]
段中增加 Slice=docker.slice
配置项,并通过 systemctl daemon-reload
命令重新加载,然后通过 systemctl start docker
启动。
执行 systemd-cgls
命令查看层级时会看到如下内容。
├─docker.slice
│ └─docker.service
│ ├─1298 /usr/bin/dockerd --add-runtime oci=/usr/sbin/docker-runc
│ └─1383 docker-containerd --config /var/run/docker/containerd/containerd.toml --log-level warn
如果要手动执行命令,也可以通过 systemd-run --uid=UID --gid=GID -t --slice=docker.slice /path/your/command
方式运行进程。
Systemd
Systemd 使用 target
来处理引导和服务管理过程,这些 systemd 里的 target
文件被用于分组不同的引导单元以及启动同步进程。
如果 A 要求 B 在 A 之前运行,则在 [Unit] 段中添加 Requires=B 和 After=B,如果依赖关系是可选的,可添加 Wants=B 和 After=B;注意 Wants= 和 Requires= 并不意味着 After=,即如果 After= 选项没有设置,这两个单元将被并行启动。
执行顺序
执行的第一个目标是 /etc/systemd/system/default.target
,对于桌面版本,通常会指向 /usr/lib/systemd/system/graphical.target
,该文件为文本,可以查看其实际会依次依赖于 multi-user.target => basic.target => sysinit.target
。
另外,local-fs.target
单元并不会启动用户相关的服务,它只处理与文件系统相关的底层核心服务,会根据 /etc/fstab
和 /etc/inittab
来执行相关操作。
sysinit.target
会启动重要的系统服务例如系统挂载,内存交换空间和设备,内核补充选项等等。basic.target
会启动普通服务特别是图形管理服务,它通过 /etc/systemd/system/basic.target.wants
目录来决定哪些服务会被启动。
在这个阶段,会启动 multi-user.target
而这个 target 将自己的子单元放在目录 /etc/systemd/system/multi-user.target.wants
里。这个 target 为多用户支持设定系统环境,非 root 用户会在这个阶段的引导过程中启用。防火墙相关的服务也会在这个阶段启动。
登陆是通过 systemd-logind.service
进行,可以通过 systemctl help systemd-logind.service
查看帮助,通常是针对 XWindow,而终端登陆则通过 /usr/lib/systemd/system/getty@.service
执行。
依赖关系可以查看 System bootup process 。
启动优化
sysvinit 只能一次一个串行地启动进程,而 Systemd 则并行地启动系统服务进程,并且最初仅启动确实被依赖的那些服务,极大地减少了系统引导的时间。
任何启动项,只要是在系统启动时有被执行到,不论启动成功还是失败,systemd 都能够记录下他们的状态,可以通过 systemctl 查看当前的服务。
详细信息可以通过 systemctl status systemd-logind.service
查看;启动的结构树可以通过 systemd-cgls
查看。
# systemd-analyze ← 查看系统引导用时
# systemd-analyze time ← 同上
# systemd-analyze blame ← 查看初始化任务所消耗的时间
# systemd-analyze plot > systemd.svg ← 将启动过程输出为svg图
# systemd-cgtop ← 查看资源的消耗状态
常用 systemctl 命令
通过 systemctl 命令可以管理整个系统。
----- 查看、设置、取消开机启动
# systemctl is-enabled nginx
# systemctl enable nginx
# systemctl disable nginx
----- 启动、停止、kill、重启、查看服务状态
# systemctl start nginx
# systemctl stop nginx
# systemctl kill nginx
# systemctl restart nginx
$ systemctl status nginx
----- 修改service之后重新加载
# systemctl daemon-reload
----- 杀死一个服务的所有进程,传递信号到指定服务的所有进程
# systemctl kill crond.service
----- 指定信号类型,如下两者相同,所有fork的进程都会被杀死
# systemctl kill -s SIGKILL crond.service
# systemctl kill -s 9 crond.service
----- 发送指定信号到服务的主进程
# systemctl kill -s HUP --kill-who=main crond.service
----- 其它操作
# systemctl ← 列出正在运行的单元
# systemctl list-units ← 同上
# systemctl list-units --type service ← 同上,只是以service为单位
# systemctl --failed ← 查看失败的任务
# systemctl list-unit-files ← 所有已经安装的任务
# systemctl list-dependencies nginx ← 查看特定服务的依赖关系
所有可用的单元文件存放在 /usr/lib/systemd/system
和 /etc/systemd/system
目录中,一个单元配置文件可以为,系统服务(.service) 、挂载点(.mount)、sockets(.sockets)、系统设备、交换分区/文件、启动目标(target)、文件系统路径、由 systemd 管理的计时器,详见 man 5 systemd.unit
。
通过enable 设置为开机启动时,相当于在 /etc/systemd/system/default.target
符号链接指向的目标对应目录下添加指向 nginx 的符号链接。
设置启动级别
在 sysVinit 的 runlevels
大多是以数字分级的,而在 CentOS 7 中可以通过 systemd 修改运行的等级,常用的命令如下。
----- 列出当前系统支持的所有等级
# systemctl list-units --type=target
----- 查看当前等级,并设置当前以及默认的等级
# systemctl get-default
# systemctl isolate graphical.target
# systemctl set-default graphical.target
----- 查看所有已经启动的服务,包括启动失败的
$ systemctl list-units --type=service
实际上在通过 isolate
设置启动方式的时候是将 /etc/systemd/system/default.target
指向对应的启动等级。
日志管理
journald 是 systemd 独有的日志系统,替换了 sysVinit 中的 syslog 守护进程,通过命令 journalctl 读取日志。
Systemd 统一管理所有 Unit 的启动日志,好处是,只需要 journalctl 一个命令,就可以查看所有日志 (内核日志和应用日志) 的内容;配置文件为 /etc/systemd/journald.conf 。
----- 查看某个Unit的日志
# journalctl -u nginx.service
# journalctl -u nginx.service --since today
----- 实时滚动显示某个Unit的最新日志
# journalctl -u nginx.service -f
# 合并显示多个 Unit 的日志
$ journalctl -u nginx.service -u php-fpm.service --since today
更多内容可以查看 How To Use Journalctl to View and Manipulate Systemd Logs 。
电源管理
systemctl 命令也可以用来关机、重启、挂起、休眠。
# systemctl poweroff
# systemctl reboot
# systemctl suspend
# systemctl hibernate
时区设置
systemd 提供了一个 timedatectl 命令行,可用于配置时区信息。
----- 查看当前所支持的时区信息
$ timedatectl list-timezones
----- 选择上述中的时区,然后设置
# timedatectl set-timezone zone
----- 查看当前时区设置的状态
# timedatectl status
管理目标
服务 systemctl 脚本存放在 /usr/lib/systemd/
目录下,有系统 (system) 和用户 (user) 之分,前者开机后无需登录即可运行,后者则需要在用户登录后才能运行程序。
常见的服务如 nginx 等存放在 /usr/lib/systemd/system
目录下;下面以 nginx 为例,编写脚本时可以直接参考 nginx 的编写方法。
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
# Nginx will fail to start if /run/nginx.pid already exists but has the wrong
# SELinux context. This might happen when running `nginx -t` from the cmdline.
# https://bugzilla.redhat.com/show_bug.cgi?id=1268621
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process
KillSignal=SIGQUIT
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
一个服务以 .service 结尾,一般会分为 3 部分:[Unit]
、[Service]
和 [Install]
。
服务脚本按照上面编写完成后,以 754 的权限保存在 /usr/lib/systemd/system
目录下,这时就可以利用 systemctl
进行配置了。
Unit
设置描述、帮助文档、启动顺序以及服务的启动依赖条件等,如下是 SSHD 服务的。
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service
After
和 Before
分别设置在哪些服务之后或者之前启动,用于配置各个服务的启动顺序,注意,这里是启动顺序而非依赖关系。
通过 Wants
和 Requires
设置弱依赖和强依赖关系,前者表示依赖的服务启停不会影响当前服务,或者表示如果依赖退出,那么该服务同时退出。
Service
用于配置如何启动服务;注意,命令应该使用绝对路径。
User=nginx
Group=nginx
PIDFile=/run/nginx.pid
Type=forking # 定义启动类型
# simple : 默认值,通过ExecStart字段启动进程
# notify : 类似于simple,服务启动结束后会发出通知信号,然后Systemd再启动其他服务
EnvironmentFile=-/etc/sysconfig/nginx # 依赖环境,可以指定多个
EnvironmentFile=-/etc/default/nginx
ExecStartPre=/usr/bin/rm -f /run/nginx.pid # 启动服务之前执行的命令
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx # 启动时,多个会被最后一个覆盖
ExecStartPost= # 启动服务之后执行的命令
ExecReload=/bin/kill -s HUP $MAINPID # 重启服务时执行的命令
ExecStop= # 停止服务时执行的命令
ExecStopPost= # 停止服务之后执行的命令
TimeoutStopSec=5 # 设置停止超时时间
KillMode=process # 重启行为配置,详见如下介绍
# control-group : 默认值,当前控制组里面的所有子进程,都会被杀掉
# process : 只杀主进程,信号可以通过如下方式定义
# mixed : 主进程将收到SIGTERM信号,子进程收到SIGKILL信号
# none : 没有进程会被杀掉,只是执行服务的stop命令
KillSignal=SIGQUIT
Restart=on-failure # 意外失败后重启方式,正常停止不重启
# no : 默认值,退出后不重启
# on-success : 只有正常退出时(退出状态码为0),才会重启
# on-failure : 非正常退出时 (退出状态码非0),包括被信号终止和超时,才会重启
# on-abnormal : 只有被信号终止和超时,才会重启
# on-abort : 只有在收到没有捕捉到的信号终止时,才会重启
# on-watchdog : 超时退出,才会重启
# always : 不管是什么退出原因,总是重启
RestartSec=10 # 重启服务之前,需要等待的秒数,默认100ms
PrivateTmp=True # 给服务分配独立的临时空间
在所有的服务配置之前,都可以加上一个连词号 (-
),表示 “抑制错误”,也即即使发生错误也不影响其他命令的执行。
Install
在通过 enable 设置为开机启动时,添加到那个 target 里面,也即定义如何安装这个配置文件,即怎样做到开机启动。
资源隔离
相关内容可以查看 CGroup 。
其它
systemd 带来了一整套与操作系统交互的新途径,如可以用 hostnamectl 获得机器的 hostname 和其它有用的独特信息。
# hostnamectl // 查看机器信息
除了 restart 命令,也可以使用 try-start 选项,它只会在服务已经在运行中的时候重启服务。
自动登陆
在此查看下如何自动登陆,首先创建一个新的类似于 getty@.service
的服务。
# cp /lib/systemd/system/getty@.service /etc/systemd/system/autologin@.service
# ln -s /etc/systemd/system/autologin@.service /etc/systemd/system/getty.target.wants/getty@tty8.service
# vi /etc/systemd/system/getty.target.wants/getty@tty8.service
...
ExecStart=-/sbin/mingetty --autologin USERNAME %I
Restart=no
...
Alias=getty.target.wants/getty@tty8.service
最后重新加载守护进程,运行服务:
# systemctl daemon-reload
# systemctl start getty@tty8.service
需要注意的是,如果你退出了 tty8 的会话,你需要等到下次重新启动才能使用,除非你给 Restart 的值是 'always'
,这样可以使用 systemctl 手动开启,不过,出于安全考虑,强烈建议不要那么做。
对比
杂项
如果重启过于频繁会报 “uagent.service start request repeated too quickly, refusing to start.”
参考
systemd System and Service Manager,system daemon 官方网站。