本文将要讲述的关于 “如何在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 工具进行驻守监测启动
  • 使用 chkconfig 运行级别控制命令管理 (老旧命令,现在更推荐用Systemctl管理)

在实际实践过程中,我主要选择了前三种方式来作为我的解决方案。

我将过程省略,将最终操作步骤和相关知识点整理写在后面两节中。

前端:PM2开机启动

PM2 在Linux系统启动后要将项目“跑起来”,主要需要运行如下命令:

  1. # sudo pm2 start project-name
  2. sudo pm2 start shareman-box-v3

这么做前提是在Pm2任务管理中已经有你要运行的项目,只是状态处于stopped

编写脚本

找一个目录,编写我们的启动Shell脚本。
示例如下:我写了一个简单的函数来处理如果有多个前端项目或实例的情况(避免维护多个脚本)。

  1. vim /scripts/start-pm2-project.sh
  2. # 以下是脚本内容
  3. #!/bin/sh
  4. # Author: ShareManT
  5. PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
  6. export PATH
  7. echo "================================="
  8. echo "Start time : " $(date "+%Y-%m-%d %H:%M:%S")
  9. echo "================================="
  10. echo "================================="
  11. echo "[Pm2] Start project"
  12. echo -e
  13. cd /www/wwwroot/ShareManNuxt
  14. pm2 start npm --name "shareman-box-v3" -- run start
  15. echo "================================="
  16. echo "================================="
  17. echo "end time : " $(date "+%Y-%m-%d %H:%M:%S")
  18. echo "================================="
  19. echo -e '\n\n\n'

脚本写完后记得给脚本执行权限:

  1. chomod 777 start-pm2-project.sh

PlanA: 使用Crontab任务调度器

除了可以在Crontab中使用如下规定格式来编写任务:

  1. 月中日 周中日 脚本
  2. {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特殊时间格式来进行编写:

  1. # 进入crontab编辑模式
  2. crontab -e
  3. # 追加以下字符串作为任务
  4. @reboot /scripts/start-pm2-project.sh >> /scripts/start-pm2-project.log 2>&1 &
  5. # >> 可将任务执行时输出内容传递到日志文件
  6. # 2>&1 & 这么做目的是阻止任务调度器每次执行都会向我们发送“邮件”

保存后 crontab 会install我们的新调度任务,这样重启就可以达到自动执行脚本的效果了。

PlanB:使用 /etc/rc.d/rc.local 本地特殊启动配置

该方式和使用crontab方式要处理的步骤差不多。

这个配置文件会在用户登陆之前读取,这个文件中写入了什么命令,在每次系统启动时都会执行一次。 也就是说,如果有需要在系统启动时运行的工作,可以写入 /etc/rc.d/rc.local 配置文件即可。

  1. # 进入编辑
  2. vim /etc/rc.d/rc.local
  3. # 追加以下zi'fu'chu
  4. /scripts/start-pm2-project.sh >> /scripts/start-pm2-project.log 2>&1 &
  5. # 2>&1 & 这么做目的也是阻止任务调度器每次执行都会向我们发送“邮件”

planC: 使用PM2 starup命令生成 Service Unit

2019-12-01凌晨补充。 之前没有注意到PM2内置的starup命令可以自动生成Service Unit。

首先,使用把要开机运行的项目通过pm2运行起来。

接着,再使用pm2 save命令将当前状态下的项目保存记录下来。

  1. # pm2 save
  2. [PM2] Saving current process list...
  3. [PM2] Successfully saved in /root/.pm2/dump.pm2

之后,我们使用pm2 startup命令生成启动时用的Service Unit。

  1. # pm2 startup systemd root@sharemanserver1
  2. [PM2] Init System found: systemd
  3. Platform systemd
  4. Template
  5. [Unit]
  6. Description=PM2 process manager
  7. Documentation=https://pm2.keymetrics.io/
  8. After=network.target
  9. [Service]
  10. Type=forking
  11. User=root
  12. LimitNOFILE=infinity
  13. LimitNPROC=infinity
  14. LimitCORE=infinity
  15. 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
  16. Environment=PM2_HOME=/root/.pm2
  17. PIDFile=/root/.pm2/pm2.pid
  18. Restart=on-failure
  19. ExecStart=/usr/local/lib/node_modules/pm2/bin/pm2 resurrect
  20. ExecReload=/usr/local/lib/node_modules/pm2/bin/pm2 reload all
  21. ExecStop=/usr/local/lib/node_modules/pm2/bin/pm2 kill
  22. [Install]
  23. WantedBy=multi-user.target
  24. Target path
  25. /etc/systemd/system/pm2-root.service
  26. Command list
  27. [ 'systemctl enable pm2-root' ]
  28. [PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
  29. [PM2] Making script booting at startup...
  30. [PM2] [-] Executing: systemctl enable pm2-root...
  31. Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
  32. [PM2] [v] Command successfully executed.
  33. +---------------------------------------+
  34. [PM2] Freeze a process list on reboot via:
  35. $ pm2 save
  36. [PM2] Remove init script via:
  37. $ pm2 unstartup systemd

这可真是非常贴心了。

Service Unit启动方法请查看后文。

后端:Swoole开启启动

Swoole是PHP的协程框架,可以作为插件安装到PHP中。

Swoole默认是不自动启动的,我尝试过用前文提到的两个方法来进行Swoole的启动,结果失败了。

原因如下:

Swoole启动的项目需要连接网络,比如进行数据库模型映射这些步骤都需要在Linux的网络服务开启后才能生效,否则连接不上数据库(没错,即使是连接本地127.0.0.1数据库也需要网络服务的支持)。

但由于Linux系统启动的顺序的原因,使用前文的两个方法都是在Linux网络服务启动之前执行的,这时就会出问题。

最后尝试了使用 Systemctl 系统管理指令设定 Service Unit 解决。

编写脚本

同样地,我们需要编写一个脚本。(一般仅仅通过一条指令不能很好地完善我们要达到的效果)

  1. vim /scripts/start-swoole-project.sh
  2. # 以下是脚本内容
  3. #!/bin/sh
  4. # Author: ShareMan
  5. PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
  6. export PATH
  7. startProjectViaSwoole(){
  8. echo "start project $2"
  9. cd $1
  10. php artisan swoole:http restart
  11. }
  12. echo "================================="
  13. echo "Start time : " $(date "+%Y-%m-%d %H:%M:%S")
  14. echo "================================="
  15. echo "================================="
  16. echo "[Swoole] Start project"
  17. echo -e
  18. startProjectViaSwoole /www/wwwroot/ShareManLaravel shareman-box-v3
  19. echo "================================="
  20. echo "================================="
  21. echo "end time : " $(date "+%Y-%m-%d %H:%M:%S")
  22. echo "================================="
  23. 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/,真正的配置文件存放在那个目录。

Linux开机自启动PM2与PHP Swoole探索(Crontab 、Systemctl Unit) - 图1

服务单元的组成如下,贴上我整理的思维导图:

Linux开机自启动PM2与PHP Swoole探索(Crontab 、Systemctl Unit) - 图2

最重要的是我们可以在 Unit 区块中指定该服务在什么时候启动,我们指定在network相关服务“集合/状态点”启动后再执行。

简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。 启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。 从这个意义上说,Target 这个概念类似于”状态点”,启动某个 Target 就好比启动到某种状态。

我们根据各个“区块”的字段来编写Swoole的 Service Unit:

  1. vim /usr/lib/systemd/system/swoole.service
  2. # 以下是内容
  3. [Unit]
  4. Description=Start swoole project
  5. After=network.target
  6. After=syslog.target
  7. [Service]
  8. Type=simple
  9. LimitNOFILE=65535
  10. ExecStart=/scripts/start-swoole-project.sh >> /scripts/starts-swoole-project.log
  11. ExecReload=/bin/kill -USR1 $MAINPID
  12. Restart=always
  13. [Install]
  14. WantedBy=multi-user.target graphical.target

开启swoole.service

通过系统管理命令来启动swoole.service

  1. systemctl enable swoole.service

systemctl enable命令用于在上面两个目录之间,建立符号链接关系。 如果配置文件里面设置了开机启动,systemctl enable命令相当于激活开机启动。

通过以上步骤我们就可以实现前后端的服务自动启动。
记得重启试试!Good9t!