先说下问题背景

 目前手上开发的产品是springboot微服务的,我们用jenkins来做的部署,部署脚本如下:

1.build脚本 负责从git服务器拉脚本

2.微服务脚本:

#!/bin/sh
appname=${JOB_NAME}
echo "${WORKSPACE}"
echo "Stopping $appname Application"
kill -9 $(netstat -nlp | grep :${port} | awk '{print $7}' | awk -F"/" '{ print $1 }')
rm -f /www/app/$appname-1.0.jar
cp /var/lib/jenkins/workspace/build_folder/$appname/target/$appname.jar /www/app/$appname.jar
chmod 777 /www/app/$appname.jar
\cp /var/lib/jenkins/workspace/build_folder/$appname/src/main/resources/py/* /www/app/
chmod 777 /www/app/*.py

echo "Starting $appname Application"
BUILD_ID=${JOB_NAME}-${port} nohup java -jar -Xms128m -Xmx256m /www/app/$appname.jar --server.port=${port} --spring.profiles.active=${profiles}&
echo "Running $appname Application"

微服务脚本大概就是先去kill -9 掉原有的app进程,然后删除app.jar,copy git下新的app.jar到 /www/app/ 目录下,然后chmod,最后启动jar

对于刚开始的测试项目,这个脚本好像没有什么问题,但是这个里面其实有几个隐藏的问题。

1.当服务器重启,所有服务会down掉。特别对于aws服务器,有时还会被收掉,你的服务down掉了,你都不知道。

2.如果某些服务意外down掉,服务器也不会感受到,需要额外加逻辑去处理这种异常

3.kill -9的方式并不属于进程的正常关闭。

网上搜了很多springboot的部署资料,发现都没有关于这方面的。

所以下面的方法也是自己想出来的,并不代表行业标准解决方案。

通过写 app.service 来启动所有的jar。

好处:

centos 的app.sevice可以随机启动,这点毋容置疑,而且还可以设置down掉之后重启服务,这正是我需要的,但是必须要修改jenkins部署脚本。

先说app.service如何写:

[Unit]
Description=SM EDU Simulator Service
After=sm-edu-api-registry.service

[Service]
User=jenkins
Group=jenkins
ExecStart=/usr/bin/java -jar -Xms128m -Xmx256m /www/app/simulator.jar --server.port=8083 --spring.profiles.active=dev
ExecReload=/usr/bin/ps -ef | grep simulator | grep -v grep | awk '{print $2}' | xargs kill -9 && /usr/bin/java -jar -Xms128m -Xmx256m /www/app/simulator.jar --server.port=8083 --spring.profiles.active=dev
ExecStop=/usr/bin/ps -ef | grep simulator | grep -v grep | awk '{print $2}' | xargs kill -9
Restart=always
[Install]
WantedBy=multi-user.target

 

After表示在某个进程之后启动

User和Group设置主要是以某个用户去启动

Type=

  • 设置进程的启动类型,必须是下列值之一:simple, forking, oneshot, dbus, notify, idle 之一。
  • 如果设为"simple"(设置了 ExecStart= 但未设置 BusName= 时的默认值),那么表示 ExecStart= 所设定的进程就是该服务的主进程。 如果此进程需要为其他进程提供服务,那么必须在该进程启动之前先建立好通信渠道(例如套接字),以加快后继单元的启动速度。
  • "dbus"(设置了 ExecStart= 与 BusName= 时的默认值)与"simple"类似,不同之处在于该进程需要在 D-Bus 上获得一个由 BusName= 指定的名称。 systemd 将会在启动后继单元之前,首先确保该进程已经成功的获取了指定的 D-Bus 名称。设置为此类型相当于隐含的依赖于 dbus.socket 单元。
  • "oneshot"(未设置 ExecStart= 时的默认值)与"simple"类似,不同之处在于该进程必须在 systemd 启动后继单元之前退出。 此种类型通常需要设置 RemainAfterExit= 选项。
  • 如果设为"forking",那么表示 ExecStart= 所设定的进程将会在启动过程中使用 fork() 系统调用。这是传统UNIX守护进程的经典做法。 也就是当所有的通信渠道都已建好、启动亦已成功之后,父进程将会退出,而子进程将作为该服务的主进程继续运行。 对于此种进程,建议同时设置 PIDFile= 选项,以帮助 systemd 准确定位该服务的主进程,进而加快后继单元的启动速度。
  • "notify"与"simple"类似,不同之处在于该进程将会在启动完成之后通过 sd_notify(3) 之类的接口发送一个通知消息。 systemd 将会在启动后继单元之前,首先确保该进程已经成功的发送了这个消息。 如果设置为此类型,那么 NotifyAccess= 将只能设置为"all"或者"main"(默认)。 注意,目前 Type=notify 尚不能在 PrivateNetwork=yes 的情况下正常工作。
  • "idle"与"simple"类似,不同之处在于该进程将会被延迟到所有的操作都完成之后再执行。 这样可以避免控制台上的状态信息与 shell 脚本的输出混杂在一起。

ExecStart=

  • 在启动该服务时需要执行的命令行(命令+参数)。有关命令行的更多细节可参见后文的"命令行"小节。 仅在设置了 Type=oneshot 的情况下,才可以设置任意个命令行(包括零个),否则必须且只能设置一个命令行。 多个命令行既可以在同一个 ExecStart= 中设置,也可以通过设置多个 ExecStart= 来达到相同的效果。 如果设为一个空字符串,那么先前设置的所有命令行都将被清空。 如果不设置任何 ExecStart= 指令,那么必须确保设置了 RemainAfterExit=yes 指令。 命令行必须以一个绝对路径表示的可执行文件开始,并且其后的那些参数将依次作为"argv[1] argv[2] ..."传递给被执行的进程。 如果在绝对路径前加上可选的"@"前缀,那么其后的那些参数将依次作为"argv[0] argv[1] argv[2] ..."传递给被执行的进程。 如果在绝对路径前加上可选的"-"前缀,那么即使该进程以失败状态(例如非零的返回值或者出现异常)退出,也会被视为成功退出。 可以同时使用"-"与"@"前缀,且顺序任意。 如果设置了多个命令行,那么这些命令行将以其在单元文件中出现的顺序依次执行。 如果某个无"-"前缀的命令行执行失败,那么剩余的命令行将不会被执行,同时该单元将变为失败(failed)状态。 当未设置 Type=forking 时,这里设置的命令行所启动的进程将被视为该服务的主守护进程。

ExecStartPre=, ExecStartPost=

  • 设置在执行 ExecStart= 之前/后执行的命令行。语法规则与 ExecStart= 完全相同。 如果设置了多个命令行,那么这些命令行将以其在单元文件中出现的顺序依次执行。 如果某个无"-"前缀的命令行执行失败,那么剩余的命令行将不会被执行,同时该单元将变为失败(failed)状态。 仅在所有无"-"前缀的 ExecStartPre= 命令全部执行成功的前提下,才会继续执行 ExecStart= 命令。 ExecStartPost= 命令仅在服务已经被成功启动之后才会运行,判断的标准基于 Type= 选项。 具体说来,对于 Type=simple 或 Type=idle 就是主进程已经成功启动;对于 Type=oneshot 来说就是主进程已经成功退出; 对于 Type=forking 来说就是初始进程已经成功退出;对于 Type=notify 来说就是已经发送了"READY=1"; 对于 Type=dbus 来说就是已经取得了 BusName= 中设置的总线名称。 注意,不可将 ExecStartPre= 用于需要长时间执行的进程。 因为所有由 ExecStartPre= 派生的子进程都会在启动 ExecStart= 服务进程之前被杀死。

ExecReload=

  • 这是一个可选的指令,用于设置当该服务被要求重新载入配置时所执行的命令行。语法规则与 ExecStart= 完全相同。 另外,还有一个特殊的环境变量 $MAINPID 可以用于表示主进程的PID,例如可以这样使用: /bin/kill -HUP $MAINPID 注意,像上例那样,通过向守护进程发送复位信号,强制其重新加载配置文件,并不是一个好习惯。 因为这是一个异步操作,所以不适用于需要按照特定顺序重新加载配置文件的服务。 我们强烈建议将 ExecReload= 设置为一个能够确保重新加载配置文件的操作同步完成的命令行。

ExecStop=

  • 这是一个可选的指令,用于设置当该服务被要求停止时所执行的命令行。语法规则与 ExecStart= 完全相同。 执行完此处设置的命令行之后,该服务所有剩余的进程将会根据 KillMode= 的设置被杀死(参见 systemd.kill(5) 手册)。 如果未设置此选项,那么当此服务被停止时,该服务的所有进程都将会根据 KillMode= 的设置被立即全部杀死。 与 ExecReload= 一样,也有一个特殊的环境变量 $MAINPID 可以用于表示主进程的PID 一般来说,仅仅设置一个结束服务的命令,而不等待其完成,是不够的。 因为当此处设置的命令执行完之后,剩余的进程会被 SIGKILL 信号立即杀死,这可能会导致数据丢失。 因此,这里设置的命令必须是同步操作,而不能是异步操作。

Restart=

  • 当服务进程正常退出、异常退出、被杀死、超时的时候,是否重新启动该服务。 "服务进程"是指 ExecStart=, ExecStartPre=, ExecStartPost=, ExecStop=, ExecStopPost=, ExecReload= 中设置的进程。 当进程是由于 systemd 的正常操作(例如 systemctl stop|restart)而被停止时,该服务不会被重新启动。 "超时"可以是看门狗的"keep-alive ping"超时,也可以是 systemctl start|reload|stop 操作超时。
  • 该选项可以取下列值之一:no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always "no"(默认值)表示不会被重启。"always"表示会被无条件的重启。 "on-success"表示仅在服务进程正常退出时重启,所谓"正常退出"是指: 退出码为"0",或者进程收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 信号并且退出码符合 SuccessExitStatus= 的设置。 "on-failure"表示仅在服务进程异常退出时重启,所谓"异常退出"是指: 退出码不为"0",或者进程被强制杀死(包括"core dump"以及收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 之外的其他信号), 或者进程由于看门狗或者 systemd 的操作超时而被杀死。 对于 on-failure, on-abnormal, on-abort, on-watchdog 的分别适用于哪种异常退出。

       

Unit 的状态

systemctl status命令用于查看系统状态和单个 Unit 的状态



[root@DaMoWang ~]# systemctl status    # 显示系统状态
[root@DaMoWang ~]# sysystemctl status httpd.service    # 显示单个 Unit 的状态
[root@DaMoWang ~]# systemctl -H root@192.168.94.104 status httpd.service    # 显示远程主机的某个 Unit 的状态



 

Unit 管理

对于用户来说,最常用的是下面这些命令,用于启动和停止 Unit(主要是 service)



centos7系统初始化脚本_git



[root@DaMoWang ~]# systemctl start httpd.service    # 立即启动一个服务
[root@DaMoWang ~]# systemctl stop httpd.service    # 立即停止一个服务
[root@DaMoWang ~]# systemctl restart httpd.service    # 重启一个服务
[root@DaMoWang ~]# systemctl kill httpd.service    # 杀死一个服务的所有子进程
[root@DaMoWang ~]# systemctl reload httpd.service    # 重新加载一个服务的配置文件
[root@DaMoWang ~]# systemctl daemon-reload    # 重载所有修改过的配置文件
[root@DaMoWang ~]# systemctl show httpd.service    # 显示某个 Unit 的所有底层参数
[root@DaMoWang ~]# systemctl show -p CPUShares httpd.service    # 显示某个 Unit 的指定属性的值
[root@DaMoWang ~]# systemctl set-property httpd.service CPUShares=500    # 设置某个 Unit 的指定属性

 



在最后遇到一个坑,发现jenkins没有权限执行systemctl

为了不破坏安全性。

最后解决方案为 在系统级别加入service,但是在部署时,不用service。jenkins还是保持之前的脚本。