本文将要讲述的关于 “如何在Linux启动时自动执行命令或开启服务的”探索之路。
背景
今年双十一腾讯云又给出了另我“怀疑人生”的云服务优惠力度——腾讯云S2型2核4G内存5M带宽3年仅需998元。
看到这个价格,我又忍不住“剁手”了。
毕竟我是一个在去年双十一以1800左右价格入手的同等配置但只有1M带宽的服务器,都还觉得自己捡了很大便宜的人。
有了新服务器,我便准备把各项核心项目转移到这台新服务器。
各项流程都比较顺利,于是准备做一些之前用旧的服务器没有实现的便捷功能。
比如,本文将要讲述的关于 “如何在Linux启动时自动执行命令或开启服务的”探索之路。
项目背景
同样以我的个人网站“曾小满的盒子V3”为例,这个版本是以前后端分离来设计的:
项目 | 标识 | 语言 | 技术栈 | 项目启动方式 |
---|---|---|---|---|
曾小满的盒子前端 | ShareManNuxt | Javascript | Vue.js / Node.js / Nuxt.js | Pm2 Node项目管理器启动 |
曾小满的盒子后端 | ShareManLaravel | PHP | Laravel / Swoole | Swoole PHP协程框架启动 |
Linux开机启动方案
在尝试各种开机启动方式的时候补充了不少关于Linux系统的知识点,总的来说常用Linux开机启动方案:
- 使用
Crontab
时间调度器执行 (通过特殊时间@reboot) - 使用
/etc/rc.d/rc.local
本地特殊启动配置处理 (通过追加配置) - 使用
Systemctl
系统管理指令设定Service Unit
(通过写入Unit配置文件) - 使用
Superviors
工具进行驻守监测启动 使用(老旧命令,现在更推荐用Systemctl管理)chkconfig
运行级别控制命令管理
在实际实践过程中,我主要选择了前三种方式来作为我的解决方案。
我将过程省略,将最终操作步骤和相关知识点整理写在后面两节中。
前端:PM2开机启动
PM2 在Linux系统启动后要将项目“跑起来”,主要需要运行如下命令:
# sudo pm2 start project-name
sudo pm2 start shareman-box-v3
这么做前提是在Pm2任务管理中已经有你要运行的项目,只是状态处于stopped
。
编写脚本
找一个目录,编写我们的启动Shell脚本。
示例如下:我写了一个简单的函数来处理如果有多个前端项目或实例的情况(避免维护多个脚本)。
vim /scripts/start-pm2-project.sh
# 以下是脚本内容
#!/bin/sh
# Author: ShareManT
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "================================="
echo "Start time : " $(date "+%Y-%m-%d %H:%M:%S")
echo "================================="
echo "================================="
echo "[Pm2] Start project"
echo -e
cd /www/wwwroot/ShareManNuxt
pm2 start npm --name "shareman-box-v3" -- run start
echo "================================="
echo "================================="
echo "end time : " $(date "+%Y-%m-%d %H:%M:%S")
echo "================================="
echo -e '\n\n\n'
脚本写完后记得给脚本执行权限:
chomod 777 start-pm2-project.sh
PlanA: 使用Crontab任务调度器
除了可以在Crontab中使用如下规定格式来编写任务:
分 时 月中日 月 周中日 脚本
{minute} {hour} {day-of-month} {month} {day-of-week} {full-path-to-shell-script}
还可以使用特殊事件格式来编写任务:
- @reboot 启动时候执行一次
- @yearly 每年1月1号0时0分年执行一次 转换为 0 0 1 1 *
- @annually 与@yearly一致
- @monthly 每月1号0时0分执行一次 转换为0 0 1
- @weekly 每周日0点0时0分执行一次 转换为0 0 0
- @daily 每天0时0分执行一次 转换为0 0 *
- @midnight 与@daily一致
- @hourly 每小时0分执行一次 转换为0
我们用其中的@reboot
特殊时间格式来进行编写:
# 进入crontab编辑模式
crontab -e
# 追加以下字符串作为任务
@reboot /scripts/start-pm2-project.sh >> /scripts/start-pm2-project.log 2>&1 &
# >> 可将任务执行时输出内容传递到日志文件
# 2>&1 & 这么做目的是阻止任务调度器每次执行都会向我们发送“邮件”
保存后 crontab 会install我们的新调度任务,这样重启就可以达到自动执行脚本的效果了。
PlanB:使用 /etc/rc.d/rc.local 本地特殊启动配置
该方式和使用crontab方式要处理的步骤差不多。
这个配置文件会在用户登陆之前读取,这个文件中写入了什么命令,在每次系统启动时都会执行一次。 也就是说,如果有需要在系统启动时运行的工作,可以写入 /etc/rc.d/rc.local 配置文件即可。
# 进入编辑
vim /etc/rc.d/rc.local
# 追加以下zi'fu'chu
/scripts/start-pm2-project.sh >> /scripts/start-pm2-project.log 2>&1 &
# 2>&1 & 这么做目的也是阻止任务调度器每次执行都会向我们发送“邮件”
planC: 使用PM2 starup命令生成 Service Unit
2019-12-01凌晨补充。 之前没有注意到PM2内置的starup命令可以自动生成Service Unit。
首先,使用把要开机运行的项目通过pm2运行起来。
接着,再使用pm2 save命令将当前状态下的项目保存记录下来。
# pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /root/.pm2/dump.pm2
之后,我们使用pm2 startup命令生成启动时用的Service Unit。
# pm2 startup systemd root@sharemanserver1
[PM2] Init System found: systemd
Platform systemd
Template
[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target
[Service]
Type=forking
User=root
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/root/.pm2
PIDFile=/root/.pm2/pm2.pid
Restart=on-failure
ExecStart=/usr/local/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/local/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/local/lib/node_modules/pm2/bin/pm2 kill
[Install]
WantedBy=multi-user.target
Target path
/etc/systemd/system/pm2-root.service
Command list
[ 'systemctl enable pm2-root' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-root...
Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save
[PM2] Remove init script via:
$ pm2 unstartup systemd
这可真是非常贴心了。
Service Unit启动方法请查看后文。
后端:Swoole开启启动
Swoole是PHP的协程框架,可以作为插件安装到PHP中。
Swoole默认是不自动启动的,我尝试过用前文提到的两个方法来进行Swoole的启动,结果失败了。
原因如下:
Swoole启动的项目需要连接网络,比如进行数据库模型映射这些步骤都需要在Linux的网络服务开启后才能生效,否则连接不上数据库(没错,即使是连接本地127.0.0.1数据库也需要网络服务的支持)。
但由于Linux系统启动的顺序的原因,使用前文的两个方法都是在Linux网络服务启动之前执行的,这时就会出问题。
最后尝试了使用 Systemctl
系统管理指令设定 Service Unit
解决。
编写脚本
同样地,我们需要编写一个脚本。(一般仅仅通过一条指令不能很好地完善我们要达到的效果)
vim /scripts/start-swoole-project.sh
# 以下是脚本内容
#!/bin/sh
# Author: ShareMan
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
startProjectViaSwoole(){
echo "start project $2"
cd $1
php artisan swoole:http restart
}
echo "================================="
echo "Start time : " $(date "+%Y-%m-%d %H:%M:%S")
echo "================================="
echo "================================="
echo "[Swoole] Start project"
echo -e
startProjectViaSwoole /www/wwwroot/ShareManLaravel shareman-box-v3
echo "================================="
echo "================================="
echo "end time : " $(date "+%Y-%m-%d %H:%M:%S")
echo "================================="
echo -e '\n\n\n'
同样要记得给脚本执行权限。
编写Service Unit
Systemd 可以管理所有系统资源。不同的资源统称为 Unit(单位)。
我们可以在/usr/lib/systemd/system 路径下看到各种“单元”,以.service
结尾的就是“服务单元”。
每一个 Unit 都有一个配置文件,告诉 Systemd 怎么启动这个 Unit 。 Systemd 默认从目录/etc/systemd/system/读取配置文件。 但是,里面存放的大部分文件都是符号链接,指向目录/usr/lib/systemd/system/,真正的配置文件存放在那个目录。
服务单元的组成如下,贴上我整理的思维导图:
最重要的是我们可以在 Unit 区块中指定该服务在什么时候启动,我们指定在network
相关服务“集合/状态点”启动后再执行。
简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。 启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。 从这个意义上说,Target 这个概念类似于”状态点”,启动某个 Target 就好比启动到某种状态。
我们根据各个“区块”的字段来编写Swoole的 Service Unit:
vim /usr/lib/systemd/system/swoole.service
# 以下是内容
[Unit]
Description=Start swoole project
After=network.target
After=syslog.target
[Service]
Type=simple
LimitNOFILE=65535
ExecStart=/scripts/start-swoole-project.sh >> /scripts/starts-swoole-project.log
ExecReload=/bin/kill -USR1 $MAINPID
Restart=always
[Install]
WantedBy=multi-user.target graphical.target
开启swoole.service
通过系统管理命令来启动swoole.service
systemctl enable swoole.service
systemctl enable命令用于在上面两个目录之间,建立符号链接关系。 如果配置文件里面设置了开机启动,systemctl enable命令相当于激活开机启动。
通过以上步骤我们就可以实现前后端的服务自动启动。
记得重启试试!Good9t!