Linux/Ubuntu服务自启动原理剖析及三种实现方式

面向Linux系统,并非只是Ubuntu;系统版本不同,配置上可能有所不同。

1、自启动的原理剖析

1.1、 运行等级

Linux分了7个运行等级,分别用数字0,1,2,3,4,5,6表示,每个运行等级支持的功能不一样

  • 0:关机 (init 0 关机)

  • 1:单用户模式 (应用场景:管理员在配置系统的时候,不希望有人登录)

  • 2:无网络连接的多用户命令行模式

  • 3:有网络连接的多用户命令行模式

  • 4:系统不可用

  • 5:带图形界面的多用户模式 (一般安装默认的运行级别, 可以编辑 /etc/inittab来修改默认运行级别)

  • 6:重新启动 (init 6==reboot)

1.2、 运行等级原理

  • /etc/rc.d/init.d 下有很多服务程序脚本(支持start/restart/stop操作的shell脚本)

  • /etc/rc.d有7个rcN.d目录,对应7个运行级别;N对应0~6,共7个数字

  • Ubuntu系统中没有rc.d目录,直接在etc目录下;(22新系统)

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第1张图片

  • rcN.d目录下都是一些符号链接,都链接到init.d目录的服务脚本;命名规则:

    • K+两位数字+服务名:表示要关闭的服务;K:kill的意思,杀死,关闭

    • S+两位数字+服务名:表示要开启的服务;S:start的意思,开启,开始

    • 两位数字表示启动优先级

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第2张图片

  • 查看系统的运行级别:runlevel

在这里插入图片描述

  • 系统启动后更新运行级别进入到对应的rcN.d目录,遍历对应的链接文件,并执行(开启或关闭)

  • init 0 关机,init 6 重启

1.3、自启动服务相关命令

1> chkconfig

RedHat系统下的命令

  • 显示开机可以自动启动的服务
chkconfig --list
  • 添加开机自动启动服务
chkconfig --add 服务名
  • 删除开机自动启动服务
chkconfig --del 服务名
  • 开机自启动/关闭开机自启动
# -level 35:运行在3和5模式
# on:开启
# off:关闭
chkconfig -level 35 服务名 on/off
  • 查看服务状态
chkconfig 服务名 status

2> sysv-rc-conf

Ubuntu下的命令sysv-rc-conf

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第3张图片

  • 显示开机可以自动启动的服务
sysv-rc-conf --list

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第4张图片

  • 开机自启动/关闭开机自启动
# -level 35:运行在3和5模式
# on:开启
# off:关闭
sysv-rc-conf -level 35 服务名 on/off

Ubuntu下 sysv-rc-conf命令的安装

# 基本安装步骤:
# apt 或 apt-get 都可以
apt-get update
apt-get install sysv-rc-conf

安装时可能遇到的问题

  • 没有公钥

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第5张图片

# 下载公钥
# 最后的 C0B21F32 就是上面提示中缺少的公钥后8位数字
apt-key adv --recv-keys --keyserver keyserver.Ubuntu.com C0B21F32

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第6张图片

  • 软件包安装时出错

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第7张图片

原因分析:dpkg: 处理归档A时出错:
正试图覆盖 x.x,它同时被包含于软件包XX,在处理时有错误发生:B.deb

出现上述错误的原因是因为该文件因为之前的某个deb的安装已经有内容了,安装与他相关文件时,需要覆盖该文件,但是没有成功。参考自:正试图覆盖…它同时被包含于软件包…在处理时有错误发生…

解决办法:执行如下命令dpkg -i --force-overwrite /B.deb 强制覆盖安装错误的内容,B.deb是你需要安装的deb文件

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第8张图片

2、自启动实现方式

2.1、方式一:rc.local

  • rc.local文件位置:/etc/rc.local,没有的话可以自行创建。系统在开机时会执行/etc/rc.local

  • Ubuntu中不能直接使用该方式;有解决方案;参考:ubuntu20.4 rc.local不运行解决办法

1> rc.local初始情况

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

2> 添加执行命令

假如在/home/test下有一个测试的jar包 test.jar,需要开机自启动

# 打开编辑;如果rc.local不存在的话,会创建新的
vim /etc/rc.local

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# 后台运行jar
# 命令前面加:nohup;后面加:&
nohup java -jar /home/test/test.jar &

# 退出
exit 0

关于台后运行:在shell执行命令后加 & 是为了让应用程序在后台运行,rc.local也是一个脚本,主进程在运行这个脚本时必须能够返回,如果在这个脚本里面执行了一些死循环或者其他无法返回的任务,整个系统就很可能卡死在这里,无法启动,所以在这里运行的用户程序必须是能够返回或者本身就使用一些后台运行的进程。

# 赋予可执行权限
chmod 755 /etc/rc.local

3> 添加执行脚本文件

rc.local 中不直接添加执行命令,而是添加 .sh脚本文件,把具体的执行命令放入.sh文件中,这种方式更加优雅!如果配置项比较多时优势更明显。但是必须要给.sh脚本文件赋于相应的执行权限。脚本文件.sh可以放置在其它地方。

脚本文件:test.sh

#!/bin/bash
java -jar /home/test/test.jar
# 给脚本文件赋予可读可执行权限
# 7: 表示可读可写可执行
# 5: 表示可读可执行
chmod 755 /home/test/test.sh

rc.local 中添加

# 添加脚本文件的全路径名
nohup ./home/test/test.sh &

4> 删除自启动

删除相关.sh脚本文件,并删除rc.local中添加的相关命令即可

2.2、方式二:/etc/init.d

  • 将自己的脚本文件复制添加到/etc/init.d

  • 链接到自启动程序列表当中,在下次系统启动时,将会自动执行用户脚本

1> 复制脚本

复制脚本到/etc/init.d中;必须要放在这个文件夹中

  • 脚本文件
#!/bin/bash
nohup java -jar /home/test/test.jar &
# 给脚本文件赋予可读可执行权限
chmod 755 /home/test/test.sh
# 复制
cp /home/test/test.sh /etc/init.d/

2> 链接自启动程序列表

update-rc.d 命令其实就是把init.d中的脚本文件,在rcN.d中创建一个软链接;

# 切换目录
cd /etc/init.d
# 链接到自启动程序列表中
# update-rc.d  链接命令
# defaults  自启动程序列表
# 99 优先级;0~99之间,越大时,启动越靠后
update-rc.d test.sh defaults 99
# 警告信息:原因是脚本文件不规范
insserv: warning: script 'diodon' missing LSB tags and overrides  
  • 规范的脚本文件
#!/bin/bash
### BEGIN INIT INFO
# Provides:          downey
# Required-Start:    $local_fs $network
# Required-Stop:     $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: tomcat service
# Description:       tomcat service daemon
### END INIT INFO  

nohup java -jar /home/test/test.jar &
  • Required-Start:运行这个脚本需要的环境

  • Required-Stop:停止这个脚本需要的环境

  • Default-Start:提供运行的运行级别

  • Default-Stop:不运行的运行级别

  • Description:描述

3> 删除自启动

# 切换目录
cd /etc/init.d

# 删除软链接
update-rc.d -f test.sh remove

# 删除 init.d中的脚本文
rm -rf test.sh

2.3、方式三(推荐):systemd

上面的两种方式适用于经典的system V控制系统启动和关闭的情况,但是目前在大多数发行版上都开始使用了systemd的系统软件控制方式,包括Ubuntu16,centos.systemd系统管理着linux下的进程运行,属于应用程序,不属于linux内核的范畴。

1> 查看systemd版本

# 查看版本
systemd --version
# 输出如下类似信息时,表示系统支持systemd
systemd 249 (249.11-0ubuntu3.6)
+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified

2> 添加配置文件

  • 在系统执行目标文件之前,需要为目标文件设置一个配置文件,以便系统在开机自启动时,可以根据配置文件确认其依赖、运行级别、运行环境等,只有这样系统才知道怎么正确地去运行目标文件

  • 配置文件的后缀:.service,比如:要运行/home/test/目录下的test.sh脚本,就需要添加一个配置文件 test.service

  • 配置文件 test.service

# ------ 单元模块 ------
[Unit]
# 运行软件的描述信息
Description=  
# 软件的文档
Documentation= 

# 因为软件的启动通常依赖于其他软件,这里是指定在哪个服务被启动之后再启动,设置优先级
After=network.target,mnt-data.mount

# 弱依赖于某个服务,目标服务的运行状态可以影响到本软件但不会决定本软件运行状态
Wants=
# 强依赖于某个服务,目标服务的状态可以决定本软件运行。
Requires=

# ------ 服务模块 ------
[Service]
# 执行启动命令时使用的脚本文件或文件的启动程序
ExecStart=/home/test/test.sh
# 执行关闭命令时使用的脚本文件
ExecStop=
# 执行重启命令时使用的脚本文件
ExecReload=/home/test/test.sh
# 软件运行方式,默认为simple
Type=simple

# ------ 安装模块 ------
[Install]
# 相当于设置软件,选择运行在linux的哪个运行级别,只是在systemd中不再有运行级别概念
WantedBy=multi-user.target

在上面的配置文件中,为了演示起见,将一些本来测试脚本不需要,但是又比较重要的配置项也写了出来,其实如果不需要可以删除,但是[Unit][Service][Install]这三个标签需要保留,如果某一项的依赖有多个,用逗号,作为分隔

  • 将配置文件放在 系统路径:/usr/lib/systemd/system 或者 用户路径:/etc/systemd/system 目录下

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第9张图片

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第10张图片

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第11张图片

Linux/Ubuntu服务自启动原理剖析及三种实现方式_第12张图片

service文件示例

# ------ 单元模块 ------
[Unit]
# 服务描述
Description=test-server
# 服务依赖(网络连接之后启用)
After=network-online.target
# 服务依赖(在nss启动前)
Before=nss-lookup.target
# 弱依赖
Wants=network-online.target nss-lookup.target
# 强依赖
#Requires=

# ------ 服务模块 ------
[Service]
# 运行方式,默认为simple;该服务将立即启动
Type=simple
# 启用执行命令
ExecStart=/home/tuwer/envs/test/test.sh
# 关闭执行命令
ExecStop=/home/tuwer/envs/test/test.sh
# 重启执行命令
ExecReload=/home/tuwer/envs/test/test.sh
# 何时重启:当服务进程异常退出时
Restart=on-failure
# 重启间隔秒数
RestartSec=3s
# 进程在执行时的用户
User=tuwer

# ------ 安装模块 ------
[Install]
# 该服务所在的服务组;类似于运行级别
WantedBy=multi-user.target

3> 启动服务

systemctl start test.service

4> 查看服务是否运行

ps -ef|grep test.sh

5> 设置开机自启动

systemctl enable test.service
# 自启动设置成功;创建一个软链接
Created symlink /etc/systemd/system/multi-user.target.wants/test.service → /usr/lib/systemd/system/test.service. 
  • test.service放在/usr/lib/systemd/system/中,如果启动成功,就可以看到在/etc/systemd/system/multi-user.target.wants/目录下创建了一个/usr/lib/systemd/system/test.service文件的软链接,到这里设置开机自启动就完成了。

  • 两个目录分别假设为:A和B,如果把service文件放在A中,启动成功后,会在B中生成一个指向A中文件的软链接;如果把service文件放在B中,启动成功后,会在A中生成一个指向B中文件的软链接;

  • 类似于第二种方式中的 update-rc.d 命令

6> 查看服务状态

如果在重启系统后,test服务没有自启动,可以通过以下命令进行排查

# 查看 systemd 的运行日志
systemctl status test.service

7> 删除自启动

# 停止服务
systemctl stop test.service  
# 使服务失效
systemctl disable test.service

# 先切换到配置时放置的目录
cd /usr/lib/systemd/system/
# cd /etc/systemd/system/
# 删除 test.service
rm -rf test.service

8> systemctl命令汇总

  • systemctl start 服务名 开启服务

  • systemctl stop 服务名 关闭服务

  • systemctl status 服务名 显示状态

  • systemctl restart 服务名 重启服务

  • systemctl enable 服务名 开机启动服务

  • systemctl disable 服务名 禁止开机启动

  • systemctl daemon-reload 修改服务配置文件后需要重新加载服务

  • systemctl is-enabled 服务名 查询是否是自启动服务

  • systemctl list-units 查看系统中所有正在运行的服务

  • systemctl list-unit-files 查看系统中所有服务的开机启动状态

  • systemctl list-dependencies 服务名 查看系统中服务的依赖关系

  • systemctl mask 服务名 冻结服务

  • systemctl unmask 服务名 解冻服务

  • systemctl set-default multi-user.target 开机时不启动图形界面

  • systemctl set-default graphical.target 开机时启动图形界面

  • systemctl is-active 服务名 显示某个 Unit 是否正在运行

  • systemctl is-failed 服务名 显示某个 Unit 是否处于启动失败状态

  • systemd-analyze blame 查看每个服务的启动耗时

service服务配置文件样例

[Unit]
Description=ToDesk Daemon Service (服务描述)
After=network-online.target (服务依赖,再这些服务后启动本服务)
Before=nss-lookup.target (服务依赖,再这些服务前启动本服务)
Wants=network-online.target nss-lookup.target (与当前服务配合的其他服务,如果它们没有运行,当前服务不会启动失败)

[Service]
Type=simple (默认值,systemd认为该服务将立即启动)
ExecStart=/opt/todesk/bin/ToDesk_Service (启动当前服务的命令)
ExecStop=/bin/kill -SIGINT $MAINPID (停止当前服务时执行的命令)
Restart=on-failure (定义何种情况 Systemd 会自动重启当前服务,当前是仅在服务进程异常退出时重启)
RestartSec=3s (自动重启当前服务间隔的秒数)
User=root (设置进程在执行时使用的用户,当前是root)

[Install]
WantedBy=multi-user.target (表示该服务所在的服务组,当前是服务所在的服务组是multi-user.target)

service配置文件字段详解

[Unit]区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系。它的主要字段如下。

Description:简短描述

Documentation:文档地址

Requires:当前 Unit 依赖的其他 Unit,如果它们没有运行,当前 Unit 会启动失败

Wants:与当前 Unit 配合的其他 Unit,如果它们没有运行,当前 Unit 不会启动失败

BindsTo:与Requires类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行

Before:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之后启动

After:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之前启动

Conflicts:这里指定的 Unit 不能与当前 Unit 同时运行

Condition...:当前 Unit 运行必须满足的条件,否则不会运行

Assert...:当前 Unit 运行必须满足的条件,否则会报启动失败

[Service]区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块。它的主要字段如下。

Type:定义启动时的进程行为。它有以下几种值。

Type=simple:默认值,执行ExecStart指定的命令,启动主进程

Type=forking:以 fork 方式从父进程创建子进程,创建后父进程会立即退出

Type=oneshot:一次性进程,Systemd 会等当前服务退出,再继续往下执行

Type=dbus:当前服务通过D-Bus启动

Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行

Type=idle:若有其他任务执行完毕,当前服务才会运行

ExecStart:启动当前服务的命令

ExecStartPre:启动当前服务之前执行的命令

ExecStartPost:启动当前服务之后执行的命令

ExecReload:重启当前服务时执行的命令

ExecStop:停止当前服务时执行的命令

ExecStopPost:停止当其服务之后执行的命令

RestartSec:自动重启当前服务间隔的秒数

Restart:定义何种情况 Systemd 会自动重启当前服务,可能的值包括always(总是重启)、on-success、on-failure、on-abnormal、on-abort、on-watchdog

TimeoutSec:定义 Systemd 停止当前服务之前等待的秒数

Environment:指定环境变量

[Install]通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。它的主要字段如下。

WantedBy:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中

RequiredBy:它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中

Alias:当前 Unit 可用于启动的别名

Also:当前 Unit 激活(enable)时,会被同时激活的其他 Unit

你可能感兴趣的:(Ubuntu,Linux,ubuntu,linux,服务器)