linux小技巧:注册为服务,设置自启动
「这是我参与11月更文挑战的第30天,活动详情查看:2021最后一次更文挑战」
方法1. System V Init服务
System V Init服务都在目录/etc/init.d/下面。而实际是一个软连接
软连接连接到/etc/rc.d/init.d/
运行级别(run level)
Init进程是系统启动之后的第一个用户进程,所以它的pid(进程编号)始终为1。init进程上来首先做的事是去读取/etc/目录下inittab文件中initdefault id值,这个值称为运行级别(run-level)。它决定了系统启动之后运行于什么级别。运行级别决定了系统启动的绝大部分行为和目的。这个级别从0到6 ,具有不同的功能。不同的运行级定义如下:
- 0 - 停机(千万别把initdefault设置为0,否则系统永远无法启动)
- 1 - 单用户模式
- 2 - 多用户,没有 NFS
- 3 - 完全多用户模式(标准的运行级)
- 4 - 系统保留的
- 5 - X11 (x window)
- 6 - 重新启动 (千万不要把initdefault 设置为6,否则将一直在重启 )
/etc/rc.d/与/etc/rc.d/init.d的关系
/etc/rc.d/init.d这个目录下的脚本就类似与windows中的注册表,在系统启动的时候执行。
在决定了系统启动的run level之后,/etc/rc.d/rc这个脚本先执行。在RH9和FC7的源码中它都是一上来就check_runlevel(),知道了运行级别之后,对于每一个运行级别,在rc.d下都有一个子目录分别是rc0.d,rc1.d ….. rc6.d。
每个目录下都是到init.d目录的一部分脚本一些链接。每个级别要执行哪些服务就在相对应的目录下,比如级别5要启动的服务就都放在rc5.d下,但是放在这个rc5.d下的都是一些链接文件,链接到init.d中相对应的文件,真正干活到init.d里的脚本。
- 这些链接文件前面为什么会带一个Kxx或者Sxx呢?
- K的表示停止(Kill)服务,会自动给脚本带上stop参数
- S表示开启(Start)服务,会自动给脚本带上start参数
- K和S后面带的数字呢?
- 用来排序,就是决定这些脚本执行的顺序,数值小的先执行,数值大的后执行
- 很多时候这些执行顺序是很重要的,比如要启动Apache服务,就必须先配置网络接口
- 无意中发现同一个服务带S的和带K的链接到init.d之后是同一个脚本
- S给和K还分别给init.d下面的脚本传递了start和stop的参数。
- 传S时相当于执行了/etc/rc.d/init.d/xxx start
- 传K就相当于/etc/rc.d/init.d/xxx stop
实践
1. 创建脚本
vi /app/self-start/myApp.sh
脚本定义了接收系统启动时传过来的参数该如何运行程序。
#!/bin/sh
# chkconfig: 2345 85 15
# description:ljw test auto_run
##第一行,告诉系统使用的shell
##第二行,2345代表在设置在那个level中是on的,如果一个都不想on,那就写一个横线"-",85和15, 后面两个数字代表S和K的默认排序号 ,
##告诉chkconfig程序,需要在rc2.d~rc5.d目录下,创建名字为S80myApp的文件连接, 第一个字符是S,系统在启动的时候,运行脚本myApp
##注意上面的三行中,第二,第三行是必须的,否则在运行chkconfig –add auto_run时,会报错。
### 85 数字越小 启动优先级别越高
### 15 数字越小 关闭优先级别越高
### description描述可以在开机日志中查找:cat /var/log/messages | grep ljw
#程序名
RUN_NAME="myApp"
#jar 位置
JAVA_OPTS=/app/self-start/$RUN_NAME.jar
LOG_OPTS=/app/self-start/$RUN_NAME-log.log
echo $JAVA_OPTS
echo $LOG_OPTS
#开始方法
start() {
## 判断是否已经启动
isRun=`ps -ef | grep $JAVA_OPTS | grep -v grep | awk '{print $2}'`
if [ $isRun ]
then
echo "$RUN_NAME is running!! Do not operate"
else
nohup java -jar $JAVA_OPTS > $LOG_OPTS &
echo "$RUN_NAME started success!"
fi
}
#结束方法
stop() {
echo "stopping $RUN_NAME ..."
####赋值=号左边不能有空格,深坑啊。。。
aa=`ps -ef | grep $JAVA_OPTS | grep -v grep | grep -v stop | awk '{print $2}'`
echo kill: $aa
kill -9 $aa
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Userage: $0 {start|stop|restart}"
exit 1
esac
复制代码
2. 赋予脚本和程序权限
chmod +x /app/self-start/myApp.sh
chmod +x /app/self-start/myApp.jar
3. 在\etc\init.d 下创建脚本软连接到脚本
sudo ln -s /app/self-start/myApp.sh /etc/init.d/myApp
4. 添加为系统服务
chkconfig --add myApp
查看是否添加
chkconfig --list
5. 设置开机自启动
chkconfig myApp on
6. 服务命令
开启服务
service myApp start
停止服务
service myApp stop
重启服务
service myApp restart
7. 查看服务器重启日志
服务器重启可以重启配置的服务,配置的服务的description描述可以在开机日志中查找。
cat /var/log/messages | grep ljw
报错:服务不支持 chkconfig
请注意检查脚本的前面,是否有完整的两行:
# chkconfig: 2345 85 15
# description:ljw test auto_run
复制代码
在脚本前面这两行是不能少的,否则不能chkconfig命令会报错误。
这是根据脚本配置的chkconfig自动生成的软连接:2345级别都是S85:开启状态排序85
如果运行chkconfig老是报错,如果脚本没有问题,我建议,直接在rc0.d~rc6.d下面创建到脚本的文件连接来解决,原理都是一样的。
方法2. Systemd服务(推荐)
Systemd作为后起之秀,功能更加强大,支持的命令和参数也更多,Unit 是 systemd 进行任务管理的基本单位
Systemd中Unit文件的存放目录
- /etc/systemd/system:系统或用户自定义的配置文件
- /run/systemd/system:软件运行时生成的配置文件
- /usr/lib/systemd/system:系统或第三方软件安装时添加的配置文件。(也是lib/systemd/system;centos8中,因为/lib目录已经变成了/usr/lib的一个符号链接)
- Unit 文件按照 Systemd 约定,被放置指定的三个系统目录之一即可
- 三个目录是有优先级的越上面的优先级越高
- 在三个目录中有同名文件的时候,只有优先级最高的目录里的文件会被使用
- Systemd 默认从目录 /etc/systemd/system/ 读取配置文件,里面存放的大部分文件都是符号链接,指向目录 /usr/lib/systemd/system/
/etc/systemd/system目录下通常不放置unit文件本身,而是放置/usr/lib/systemd/system中unit文件的符号链接
# 查看所有单元
$ systemctl list-unit-files
# 查看所有 Service 单元
$ systemctl list-unit-files --type service
# 查看所有 Timer 单元
$ systemctl list-unit-files --type timer
复制代码
unit的类型
- Service unit:系统服务
- Target unit:多个 Unit 构成的一个组
- Device Unit:硬件设备
- Mount Unit:文件系统的挂载点
- Automount Unit:自动挂载点
- Path Unit:文件或路径
- Scope Unit:不是由 Systemd 启动的外部进程
- Slice Unit:进程组
- Socket Unit:进程间通信的 socket
- Swap Unit:swap 文件
- Timer Unit:定时器
- snapshot Unit:表示由 systemctl snapshot 命令创建的 Systemd Units 运行状态快照
Service unit的文件格式说明
service 类型的 unit 代表一个后台服务进程
。接下来我们就详细的介绍如何配置 service 类型的 unit。
下面我们先来看一个简单的服务配置:
cat /usr/lib/systemd/system/firewalld.service
[Unit]
Description=firewalld - dynamic firewall daemon
Before=network-pre.target
Wants=network-pre.target
After=dbus.service
After=polkit.service
Conflicts=iptables.service ip6tables.service ebtables.service ipset.service
Documentation=man:firewalld(1)
[Service]
EnvironmentFile=-/etc/sysconfig/firewalld
ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS
ExecReload=/bin/kill -HUP $MAINPID
# supress to log debug and error output also to /var/log/messages
StandardOutput=null
StandardError=null
Type=dbus
BusName=org.fedoraproject.FirewallD1
KillMode=mixed
[Install]
WantedBy=multi-user.target
Alias=dbus-org.fedoraproject.FirewallD1.service
复制代码
服务类型的配置文件名称必须以 .service 结尾。
Unit
[Unit]
区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系,包括在什么服务之后才启动此 unit 之类的设置。
-
Description:关于该unit的简短描述
-
Documentation:文档地址
-
Requires:当前 Unit 依赖的其他 Unit,如果它们没有运行,当前 Unit 会启动失败.(强依赖)
-
Wants:与当前 Unit 配合的其他 Unit,如果它们没有运行,当前 Unit 不会启动失败.(弱依赖)
-
BindsTo:与Requires类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行
-
Before:表示本服务需要在xxx服务启动之前执行
-
After:表示本服务需要在xxx服务启动之后执行
-
Conflicts:这里指定的 Unit 不能与当前 Unit 同时运行
-
Condition...:当前 Unit 运行必须满足的条件,否则不会运行
-
Assert...:当前 Unit 运行必须满足的条件,否则会报启动失败
所有的启动设置之前,都可以加上一个连词号(-
),表示抑制错误
,即发生错误的时候,不影响其他命令的执行。
比如,EnvironmentFile=-
/etc/sysconfig/sshd(注意等号后面的那个连词号),就表示使/etc/sysconfig/sshd文件不存在,也不会抛出错误。
Service
[Service]
区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块。
不同的 unit 类型就得要使用相对应的设置项目,比如 timer 类型的 unit 应该是 [Timer];socket 类型的 unit 应该是 [Socket];服务类型的 unit 就是 [Service],这个项目内主要在规范服务启动的脚本、环境配置文件文件名、重新启动的方式等等。
- Type:定义启动时的进程行为它有以下几种值
- Type=simple:默认值,执行ExecStart指定的命令,启动主进程,ExecStart字段启动的进程为主进程
- Type=forking:以 fork 方式从父进程创建子进程,创建后父进程会立即退出,子进程将作为该服务的主进程继续运行,这是传统UNIX守护进程的经典做法。(
jar服务用这个
) - Type=oneshot:一次性进程,Systemd 会等它执行完,才继续启动其他服务
- Type=dbus:类似于simple,但会等待 D-Bus 信号后启动
- Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行
- Type=idle:类似于simple,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合
- User :使用哪个用户环境变量来正确运行该服务
- ExecStart:启动当前服务的命令:不能接受 <, >, >>, |, & 等特殊字符,很多的 bash 语法也不支持。所以,要使用这些特殊的字符时,最好直接写入到脚本里面去!
- ExecStartPre:启动当前服务之前执行的命令
- ExecStartPost:启动当前服务之后执行的命令
- ExecReload:重启当前服务时执行的命令
- ExecStop:停止当前服务时执行的命令
- ExecStopPost:停止当其服务之后执行的命令
- RestartSec:自动重启当前服务间隔的秒数
- Restart:定义何种情况 Systemd 会自动重启当前服务
- no(默认值):退出后不会重启
- on-success: 只有正常退出时(退出状态码为0),才会重启
- 正常退出:退出码为"0",或者进程收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 信号并且退出码符合 SuccessExitStatus= 的设置。
- on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
- on-abnormal:只有被信号终止和超时,才会重启
- on-abort:只有在收到没有捕捉到的信号终止时,才会重启
- on-watchdog: 超时退出,才会重启
- always: 不管是什么退出原因,总是重启(
当进程是由于 systemd 的正常操作(例如 systemctl stop|restart)而被停止时, 该服务不会被重新启动。即使用kill -9还是会重启的
)
- TimeoutSec:定义 Systemd 停止当前服务之前等待的秒数
- SuccessExitStatus :定义附加的进程"正常退出"状态,如:SuccessExitStatus=1 2 8 SIGKILL ;表示当进程的退出码是 1, 2, 8 或被 SIGKILL 信号终止时,都可以视为"正常退出"
- PrivateTmp: 否使用私有的tmp目录
- KillMode:定义 Systemd 如何停止服务,可以设置的值如下:
- control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
- process:只杀主进程
- mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
- none:没有进程会被杀掉,只是执行服务的 stop 命令
- Environment:指定环境变量,可以使用多次
[Service]
# Client Env Vars
Environment=XXX_A=A
Environment=XXX_B=B
复制代码
- EnvironmentFile:环境参数文件
- 1.脚本 可以把下面的内容保存到文件 testenv 中:
- AAA_IPV4_ANCHOR_0=X.X.X.X
- 2.声明环境文件 EnvironmentFile=/testenv
- 3.使用:ExecStart=/xxx xx${AAA_IPV4_ANCHOR_0}
- 1.脚本 可以把下面的内容保存到文件 testenv 中:
Install
[Install]
通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动,这个部分主要设置把该 unit 安装到哪个target。
- WantedBy:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中(弱依赖),即是等待xxx的全部内容就绪就安装启动
- RequiredBy:它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中(强依赖)
- Alias:当前 Unit 可用于启动的别名
- Also:当前 Unit 激活(enable)时,会被同时激活的其他 Unit
Service Unit实践
1. 在/etc/systemd/system/创建Unit脚本
查看root用户所在的组,以及组内成员 groups root
创建服务脚本:vi /etc/systemd/system/myService.service
注:这里不能用软连接。直接在此路径下创建service脚本。因为取消自启动命令会把软连接一并删除的。到时又要手动创建软连接才能用,麻烦。
jar服务输入下面内容:
[Unit]
Description=My Spring Boot Service
After=syslog.target
[Service]
##类型为fork子线程执行,不然服务会重复重启到最后报错
Type=forking
#true:tmp共享可使用jps命令查看java进程;false:私有,只能用ps -ef|grep查看
#PrivateTmp=true
User=root
Group=root
##这里只配置开启命令就行。关闭和重启systemd会帮我控制,配置也是可以的。
ExecStart=/app/self-start/start.sh
### 可以使用方法1的上面myApp脚本,配置开启,重启,关闭执行的命令
#ExecStart=/app/self-start/myApp.sh start
#ExecReload=/app/self-start/myApp.sh restart
#ExecStop=/app/self-start/myApp.sh stop
Restart=always
#RestartPreventExitStatus=1
[Install]
WantedBy=multi-user.target
复制代码
注意:
- PrivateTmp=true
- 服务启动时会在/tmp/目录下生成类似:/tmp/systemd-private-bceaa1ad35764ee2aea3cdd52fab89cd-myService.service-rVFD0q/tmp/hsperfdata_root的文件夹,用于存放myService的临时文件。存放了线程的pid-私有的,只能对自己账号有操作权限
jps命令使查询/tmp/目录下得共享文件,所有配置了PrivateTmp=true的进程用该命令使看不到的。请使用:ps -ef|grep java
- PrivateTmp=false
- 服务启动时会在/tmp/目录下生成共享文件,存放了线程的pid-所有用户共有读写权限。
- multi-user.target:等待多用户系统的全部内容就绪才安装启动
2. 创建启动脚本
vi /app/self-start/start.sh
启动服务并把进程的pid保存一份到自定义的文件/app/self-start/myApp.pid
#!/bin/sh
nohup /usr/bin/java -jar /app/self-start/myApp.jar > /app/self-start/myApp.log & echo $! >/app/self-start/myApp.pid
复制代码
3. 赋予脚本和程序权限
chmod +x /app/self-start/start.sh
chmod +x /app/self-start/myApp.jar
4. 重新加载所有修改过的unit文件
systemctl daemon-reload
5.查看配置
能查看到配置证明服务文件没有语法错误,逻辑和环境等错误查不到,要看启动日志
systemctl show -p ExecStart myService
6.启动服务并配置开机启动
启动:sudo systemctl start myService
状态:sudo systemctl status myService
停止:sudo systemctl stop myService
重启:sudo systemctl restart myService
设置开机启动:sudo systemctl enable myService
设置开机启动:sudo systemctl disable myService
查看服务启动日志(问题排查):journalctl -u myService.service
Timer Unit实践
Timer unit的详细配置
Timer 类型的 unit 主要用来执行定时任务,并有可能取代 cron 服务。
与服务类型的 unit 不同,timer unit 配置文件中的主要部分是 [Timer],下面是其主要的配置项:
[Timer]
OnActiveSec
:定时器生效后,多少时间开始执行任务OnBootSec
:系统启动后,多少时间开始执行任务OnStartupSec
:Systemd 进程启动后,多少时间开始执行任务OnUnitActiveSec
:该单元上次执行后,等多少时间再次执行OnUnitInactiveSec
: 定时器上次关闭后多少时间,再次执行OnCalendar
:基于绝对时间,而不是相对时间执行AccuracySec
:如果因为各种原因,任务必须推迟执行,推迟的最大秒数,默认是60秒Unit
:真正要执行的任务,默认是同名的带有.service
后缀的单元Persistent
:如果设置了该字段,即使定时器到时没有启动,也会自动执行相应的单元WakeSystem
:如果系统休眠,是否自动唤醒系统
1. 创建备份脚本
vi /app/self-start/backup.sh
#!/bin/bash
mydate()
{
date "+%Y%m%d%H%M%S"
}
backupdate=$(mydate)
## 打包需要备份的文件夹
tar -zcPf /app/self-start/bk.${backupdate}.tar.gz /app/self-start/bk
复制代码
2. 赋予执行权限
sudo chmod +x /app/self-start/backup.sh
3. service unit 配置文件
vi /etc/systemd/system/backup.service
[Unit]
Description=my backup learn dir service
[Service]
User=root
Group=root
Type=simple
## 执行该脚本
ExecStart=/app/self-start/backup.sh
[Install]
WantedBy=multi-user.target
复制代码
4. 配置定时服务
vi /etc/systemd/system/backup.timer
[Unit]
Description=my backup learn dir timer
[Timer]
##每隔15分钟执行
#OnCalendar=*:0/15
#每月26号的凌晨0点半执行一次
#OnCalendar=*-*-26 00:30:00
#自定时器启动1分钟后间隔1秒执行一次
OnBootSec=1min
OnUnitActiveSec=30s
Persistent=true
## 配置要定时的服务
Unit=backup.service
[Install]
WantedBy=multi-user.target
复制代码
5. 相关命令
刷新配置:sudo systemctl daemon-reload
开启备份服务: sudo systemctl start backup.service
开启定时服务:sudo systemctl start backup.timer
开机启动备份服务 : sudo systemctl enable backup.service
开机启动定时服务 : sudo systemctl enable backup.timer
查看状态备份服务: sudo systemctl status backup.service
查看状态定时服务: sudo systemctl status backup.timer
查看整个日志 :sudo journalctl
查看 backup.timer 的日志 :sudo journalctl -u backup.timer
查看 backup.timer 和 backup.service 的日志: sudo journalctl -u backup
从结尾开始查看最新日志:sudo journalctl -f
从结尾开始查看 backup.timer 的日志: journalctl -f -u backup.timer
方法3:直接开机启动配置
除了上面的注册服务并设置为开机自启动,还有一个方法就是直接配置开机自启动。
实践
1. 创建脚本
vi /app/self-start/directStart.sh
#!/bin/bash
##可以等待20秒再运行。等待其他服务或环境
##sleep 10
##这时一条test命令
echo "启动了" > /app/self-start/a.txt
##使用绝对路径的java命令。因为服务器重启相关环境可能还没准备好
nohup /usr/bin/java -jar /app/self-start/myApp.jar > /app/self-start/nohup.out 2>&1 &
复制代码
2. 赋予脚本和程序权限
chmod +x /app/self-start/myApp.jar
chmod +x /app/self-start/directStart.sh
/etc/rc.local默认是没有执行权限的,要加上才能开机运行。
chmod +x /etc/rc.local
3. 修改开机启动文件/etc/rc.local
最好把命令绝对路径/bin/bash写全
/bin/bash /app/self-start/directStart.sh
复制代码
4. 检查是否配置正常
手动运行测试是否可以。
bash /etc/rc.local
5. 重启测试
reboot
问题
-
在linux下,如需添加随系统启动而自动运行的服务,只需在/etc/rc.local 脚本文件中添加即可。
-
但是遇到一个问题是脚本手动测试能正常运行,放在/etc/rc.local中java命令没有正常运行或者执行失败。touch命令能成功。脚本中的命令不带路径的如下:
nohup java -jar /app/self-start/myApp.jar > /app/self-start/nohup.out 2>&1 &
-
在系统重启执行这些命令时将报错,无法正常执行!究其原因:
由于在执行rc.local脚本时,PATH环境变量未全部初始化,需在执行/etc/profile 后才被添加到环境变量PATH中。
-
为了在开机启动时执行,部分命令需要使用
绝对路径
[root@k8s2 self-start]# which java
/usr/bin/java
复制代码
nohup /usr/bin/java -jar /app/self-start/myApp.jar > /app/self-start/nohup.out 2>&1 &
- 这和注册成服务相比是一个比较大的缺点,不能控制脚本启动时机的细腻度和需要什么样的环境。