背景:
最近对所有项目完成了一个切换,服务管理方式由: init-> systemd。对相关知识进行总结一下。
1.启动流程
服务器的整体启动流程如下图所示:
POST:
计算机通电后进行POST( Power-On Self-Test )加电自检,检查 CPU、内存、硬盘、显卡、声卡、网卡等硬件是否正常工作; 检查完成后触发BIOS程序(来自主板)。
BIOS:
BIOS程序( Basic Input Output System )来自主板,被执行后会拥有整个裸机的执行权。BIOS会做以下几件事:
(1) 对硬件设置的检查和初始化 ;
(2) 加载MBR,并执行。BIOS加载启动盘的第一个扇区(0盘/0道/0扇区),将扇区的所有内容复制到内存中,并执行(将CPU的指令寄存器指向该内存)。
说明: (1) CPU被设计只能从内存中取数据和指令,启动过程中涉及的程序(保存在硬件中),都需要在执行前预先被加载到内存。(2) 在机器启动时,按F2(dell服务器)可以进入BIOS。在BIOS的启动选项菜单中可以选择引导设备,使用CD或者U盘安装系统时,将其作为首选引导设备;安装完成后,会自动弹出CD,重启服务器即可。如果把CD推入,重启会再走安装流程(安装完最好把它取出来)。
MBR:
MBR(Master Boot Record)表示硬盘的主引导记录,它位于第一个扇区(0盘/0道/0扇区)。MBR大小为512字节,包含446字节的引导加载程序(主boot-loader)、64字节的分区表信息、2字节的结束标志(0xAA55)。
主boot loader的唯一任务是加载次boot-loader(内核加载程序)。加载过程分为了主/次boot-loader两个加载器,因为512字节不足以完成将操作系统加载内核到内存的工作。
加载操作系统内核:
内核加载程序(grub/grub2)主要负责启动操作系统和加载内核。centos6(及以前)使用grub, 而centos7默认使用grub2。
说明:计算机上安装多个系统时,在grub/grub2阶段可以与用户交互,选择要加载的操作系统。
至此,计算机完全由操作系统接管。
init/systemd:
内核加载完成后,会启动第一个进程init/systemd. 在centos6及以前使用init进程管理服务,centos7及以后使用systemd守护进行管理服务。init/systemd会加载和运行其他的系统进程。
至此,开机启动完成。
2.init进程
2.1 Linux系统的运行级别
0-关机状态: 执行init 0会关闭计算机;
1-单用户模式:只支持root用户,不支持远程登录,一般用于系统维护;
2-多用户模式:支持多用户,不支持远程登录。
3-多用户-NFS:支持多用户,支持远程登录。
4-保留;
5-图形化(多用户、远程登录、支持图形化界面);
6-重启;
可以通过 init + 运行级别 实现系统运行级别的切换。
2.2 init进程的启动过程
(1) init进程从配置文件/etc/inittab中读取运行级别,根据运行级别来确定启动的程序。在CentOS 6中,默认的运行级别是运行级别3。
可以通过runlevel查看当前系统的运行级别:
>runlevel
N 5
(2) 启动子进程
对每个运行级别,在/etc/rc.d文件夹都有一个文件夹:rc0.d ~ rc6.d。比如:当运行级别是5时,rc3.d目录下的配置文件生效。包含如下文件:
文件名格式为:K/S + 数字 + 服务名。K表示停止进程(Kill), S表示启动进程(Start); 数字表示运行级别,数字越小,执行越靠前。
此时,init进程会拉起network进程。
为防止在各个级别下(各个rcN.d目录下)都存放重复的文件,使用链接的形式引用。目录下的所有链接文件会指向/etc/init.d/目录下的脚本。当运行级别进行切换时,会给这些脚本进行传参,start或stop. 因此,需要安装模板规定开发服务脚本, 如下所示:
#! /bin/sh
# 准备操作SERVER_NAME='demo'start(){echo -e "\nStart ${SERVER_NAME}..."#todo: 启动逻辑echo -e "\nStart ${SERVER_NAME} success!"
}stop(){echo -e "\nShutdown ${SERVER_NAME}..."#todo: 停止逻辑echo -e "\nShutdown ${SERVER_NAME} finish!"
}case "$1" in"restart")stopstart;;"stop")stop;;"start")start;;*)echo "parameter error!! ";echo "three parameters are valid----restart, start, stop";;
esac
(3) init进程根据上述流程创建一系列子进程,当系统启动完成后,init进程将变成为守护进程,监视系统其他进程的运行状态,并在需要时重新启动它们。
2.3 常用API
添加服务
chkconfig --add ${servicename}删除服务
chkconfig --del ${servicename}查看所有的系统服务
chkconfig --list#查看指定服务信息
chkconfig --list ${servicename}设置服务的运行级别
chkconfig --level 运行级别 ${servicename} on/offeg:
设施mysql在运行级别为3和5下开机自启动
chkconfig --level 35 mysqld on
2.4 案例介绍
1.在/etc/init.d/目录下新建服务名,如ewen
脚本如下所示:
#! /bin/shecho "$@"SERVER_NAME='ewen'start(){echo -e "\nStart ${SERVER_NAME}..."echo -e "\nStart ${SERVER_NAME} success!"
}stop(){echo -e "\nShutdown ${SERVER_NAME}..."echo -e "\nShutdown ${SERVER_NAME} finish!"
}case "$1" in"restart")stopstart;;"stop")stop;;"start")start;;*)echo "parameter error!! ";echo "three parameters are valid----restart, start, stop";;
esac
此时,在环境上执行service命令时,可以将参数带入脚本:
>service ewen start a1 bc2 333 d4
#输出
start a1 bc2 333 d4
Start ewen...
Start ewen success
2.通过chkconfig将ewen服务注册到init:
chkconfig --add ewen
3.设置运行级别
chkconfig --level 3 ewen ewen on/off
3.systemd进程
init进程因串行化地启动程序,存在效率问题,且需要自定义脚本; systemd通过并行启动以及通过引入service配置文件,规避了上面两个问题。
ini使用service命令,systemd使用systemctl工具来管理,并且在操作上做了兼容处理(将service指令重定向到systemctl),如下所示:
>service ewen stop
Redirecting to /bin/systemctl ewen ota.service
在centos7之后(含centos7)使用systemd来管理程序, 通过ls -al /sbin/init
查看链接指向了systemd程序:
通过查看/etc/inittab
也可以得到提示如下:
通过ps 命令查看systemd的进程号(进程号为1):
3.1 启动流程
(1) 获取运行模式
当systemd进程启动后,会读取配置文件,确定运行模式。通过/etc/systemd/system/default.target
文件链接到/usr/lib/systemd/system/multi-user.target
还是/usr/lib/systemd/system/graphical.target
确定运行模式。
(2) 获取需要开机启动的服务
以multi-user模式为例,系统进入/etc/systemd/system/multi-user.target.wants
获取所有配置的服务,并启动这些服务。
3.2 service配置文件介绍
3.2.1 文件路径
service配置文件用于自定义服务的启动顺序、运行方式、属组、启动方式等,可以将service文件放在
/usr/lib/systemd/[ system | user ]/
或 /etc/systemd/[ system | user ]/
目录下。user表示用户服务,开机时不启动服务,用户登录后,才触发启动服务;system表示系统服务,开机时启动服务,而不需要用户登录。
/usr/lib/systemd/[ system | user ]/
通常用于存放yum等软件安装的服务包。优先级/etc/systemd/system
要高于/usr/lib/systemd/system
。
一般而言,自定义的服务建议放在/etc/systemd/system
目录下。
3.2.2 service文件组成
如下所示是redis服务的service文件:
[Unit]
Description=Redis persistent key-value database
After=network.target
After=network-online.target
Wants=network-online.target[Service]
ExecStart=/usr/bin/redis-server /etc/redis.conf --supervised systemd
ExecStop=/usr/libexec/redis-shutdown
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755[Install]
WantedBy=multi-user.target
由三个部分组成: [Unit]、[Service]、[Install],以下通过分章节分别进行介绍。
3.2.2.1 service文件的unit部分
unit部分可以定义服务描述、启动顺序和依赖关系。
描述
(1) Description:systemd使用Description描述服务信息(给读者看的,一般用一个简单的名称短语即可)。
(2) Documentation: 指定服务的详细说明文档(地址),多个使用空格进行分隔.
依赖关系
(1) Requires/Requisite:所依赖的单元必须已经启动,多个用都改分隔。
(2) RequiresAny: 指定列表中任意一个已经启动即可。
(3) PartOf:关联停止和重启,多个用逗号分隔。当被关联的服务停止或启动时,该服务也将随着停止和启动。
(4) BindsTo: 设置绑定服务列表,多个用逗号分隔。绑定列表中的所有服务被启动后,该服务才能被启动;绑定列表中的任一服务停止后,该服务被迫停止。绑定配置只有单向关系,即该服务停止后,不会影响列表中服务的状态。
(5) Conflicts: 指定相互冲突的服务,多个使用空格分开。该单元启动时,Conflicts指定的列表中所有单元都将被停止;列表中的某个单元启动时,该单元被停止。
启动顺序
systemd不够友好,依赖关系并不能保证启动顺序,需要用户通过Before和After手动指定。
redis.service文件中通过After指定了在network.target(网络服务)和yslog.target(系统日志服务)之后启动。即系统启动时,redis服务要等待network.target和yslog.target启动后才可启动。
可以通过空格进行分隔,也可以通过多个After项进行配置(如redis.service):
[Unit]
After=network.target
After=syslog.target
等价于:
[Unit]
After=network.target syslog.target
Before与After完全相反。
案例介绍
基于上述说明,对于不同场景,可以有一下组合:
case1:服务B在服务A之后重启,且依赖于服务A
B.service[Unit]
After=A.service
case2:服务A重启后,服务B必须重启
B.service[Unit]
After=A.service
PartOf=A.service
case3:服务C依赖于服务A或B
C.service[Unit]
RequiresAny=A.service B.service
After=A.service B.service
case4:服务D依赖于服务A和B,但是与C处于冲突状态
D.service[Unit]
Requires=A.service B.service
After=A.service B.service
Conflicts=C.service
3.2.2.2 service文件的service部分
用于定义服务的类型和属组,启停命令和重启机制、环境变量等。
服务类型:
鉴于章节篇幅,关于simple和forking的详细介绍在章节3.3中进行
Type定义服务类型:
simple:默认类型,使用当前线程作为主进程;
forking:服务会使用fork创建新进程,新进程作为主进程;
oneshot:一般用于执行一次性任务,与simple相似,区别在于systemd执行完oneshot类型才会认为该服务执行成功,而simple开始执行即认为执行成功。
除此之外,还有dbus, notify, idle等类型,因在开发过程中很少见,这里不进行说明。
服务的属组:
User和Group定义服务运行时归属的用户和群组。
启停命令:
(1) ExecStart和ExecStop和ExecReload指定启动和停止时执行的命令(含参数);
(2) ExecStartPre和ExecStartPost设置ExecStart启动前和启动后执行的命令;
(3) ExecStopPost设置停止后执行的命令;
(4) ExecReload表示重启服务时执行的命令。
环境变量:
Environment用于添加环境变量.
重启策略:
Restart和RestartSec用于配置服务重启策略。
(1) RestartSec定义重新启动服务的间隔时间, 单位:秒;
(2) Restart:服务重启策略:
no(默认值):不重启;
always:总是重新启动;
on-success/on-failure分别表示当服务正常退出(exit 0)/异常(exit 1)退出时重启.
(3) KillMode设置systemd停止服务的策略
control-group(默认值):kill主进程和所有子进程
none:不直接kill主进程或子进程,仅执行服务的stop命令
process:仅kill主进程
mixed:向主进程发送SIGTERM(kill)信号,向所有子进程发送SIGKILL(kill -9)信号。
说明: systemd提供了丰富的配置能力,可参考 https://www.jinbuguo.com/systemd/systemd.special.html
案例介绍:
[Service]
Type=forkingUser=ewen
Group=ewenExecStart=/usr/local/buaa/bin/ewen start
ExecStop=/usr/local/buaa/bin/ewen stop
ExecReload=/bin/kill -s HUP $MAINPIDEnvironment="EWEN_HOME=/usr/local/buaa"
#设置该服务可以打开的最大文件数为65535。
LimitNOFILE=65535PrivateTmp=true
Restart=on-failure
RestartSec=10KillMode=control-group
3.2.2.3 service文件的Install部分
通过WantedBy指定开机自启动模式,可指定为multi-user.target(多用户模式) 或 graphical.target(界面模式)等。上述的redis.service文件中: WantedBy=multi-user.target
. 当执行systemctl enable redis[.service]
时,创建一个链接文件:
> systemctl enable redis
Created symlink from /etc/systemd/system/multi-user.target.wants/redis.service to /etc/systemd/system/redis.service.
当执行systemctl disable redis[.service]
时,删除该链接文件.
systemd启动时,如果是multi-user模式,则从/etc/systemd/system/multi-user.target.wants
目录中读取service文件并启动,从而实现开机自启动;graphical模式,则从/etc/systemd/system/graphical.target.wants
目录下读取。
也可为服务设置多种模式,通过空格分开,如:
[Install]
WantedBy=multi-user.target graphical.target
3.3 simple和forking服务类型
systemd启动simple和forking类型的服务流程如下图所示:
systemd进程启动服务时会fork子进程,然后委托这些systemd子进程去启动服务,即执行ExecStart配置的指令。
simple和forking对于systemd进程的核心区别在于“谁是我该监管的进程”:当启动simple类型的服务时,systemd子进程自身作为主进程,接受systemd的监管;而启动forking类型的服务时,systemd子进程将作为中间父进程退出,新创建的进程(systemd通过推断确定)作为主进程接受systemd的监管。
案例说明:
case1: simple服务类型
上述案例中redis的service部分如下所示:
[Service]
ExecStart=/usr/bin/redis-server /etc/redis.conf --supervised systemd
ExecStop=/usr/libexec/redis-shutdown
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
没有定义Type,即使用默认的Type=simple类型。
启动redis后,查询进程状态:
此时,systemd跟总的主进程就是调用ExecStart指令的进程。该进程启动后,systemd就任务该服务启动成功。
case2: forking服务类型
nginx的service配置文件如下所示:
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.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=/usr/sbin/nginx -s reload
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true[Install]
WantedBy=multi-user.target
通过Type=forking指定了服务类型。
启动nginx后,查询进程状态:
可以看到systemd实际监控的进程为18412,而执行ExecStart指令的进程为18409(中间进程,由systemd创建,已正常退出),该进程通过forking创建出了18412. 当中间进程退出且新进程生成后,systemd任务该服务启动成功。
另外,执行ExecStarPre的进程也是systemd创建的子进程。
上述案例的核心在于systemd监控的主进程,如果可以梳理清楚以下两种场景,simple和forking区别才算真正理解。
场景分析:
场景1: 应当设置为simple的服务被设置为了forking,会发生什么现象?
场景2: 应当设置为forking的服务被设置为了simple,会发生什么现象?
这里仅对第一种场景通过案例进行说明, 读者可通过相同方法对第二种进行梳理
# test.service文件
[Unit]
Description=test[Service]
Type=forking
ExecStart=/bin/bash -c "sleep 60"
给ExecStart设置的启动指令是sleep 60,即进程休眠1分钟。
执行systemctl start test
后,systemd认为test服务是一个forking类型,因此等待这个进程退出(而该进程处于休眠状态,不会退出),所以通过systemctl status test
查询test服务时,一直处于启动中状态:
1分钟后,进程退出且没有新的进程被fork出来,systemd会认为该服务启动失败:
修改为simple类型后:
# test.service文件
[Unit]
Description=test[Service]
Type=simple
ExecStart=/bin/bash -c "sleep 60"
执行systemctl start test
后,systemd认为simple服务是一个forking类型,执行完/bin/bash -c "sleep 60"
指令后,systemd认为该服务启动成功:
1分钟后,进程退出,systemd会认为该服务退出运行:
3.4 systemctl
(1) 运行模式:
#设置graphical运行模式
systemctl set-default graphical.target#设置multi-user运行模式
systemctl set-default multi-user.target#获取运行模式
systemctl get-default
设置运行模式的本质,就是将/etc/systemd/system/default.target
指向/usr/lib/systemd/system/multi-user.target
或/usr/lib/systemd/system/graphical.target
.
(2) 注册服务:
将服务的service文件放到systemd管理的路径(比如/etc/systemd/system/目录)后,指向以下命令进行重新加载:
systemctl daemon-reload
(3) 启停服务(常用):
#启动服务
systemctl start 服务名[.service]#停止服务
systemctl stop 服务名[.service]#重启服务
systemctl restart 服务名[.service]#查看服务状态
systemctl status 服务名[.service]
(4) 开机自启动:
#开启开机自启动
systemctl enable 服务名[.service]#关闭开机自启动
systemctl disable 服务名[.service]
设置开机自启动本质是在/etc/systemd/system/multi-user.target.wants/
目录下创建了一个链接文件。