先说下问题背景
目前手上开发的产品是 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 的状态
- [[email protected] ~]# systemctl status # 显示系统状态
- [[email protected] ~]# sysystemctl status httpd.service # 显示单个 Unit 的状态
- [[email protected] ~]# systemctl -H [email protected] status httpd.service # 显示远程主机的某个 Unit 的状态
Unit 管理
对于用户来说, 最常用的是下面这些命令, 用于启动和停止 Unit(主要是 service)
- [[email protected] ~]# systemctl start httpd.service # 立即启动一个服务
- [[email protected] ~]# systemctl stop httpd.service # 立即停止一个服务
- [[email protected] ~]# systemctl restart httpd.service # 重启一个服务
- [[email protected] ~]# systemctl kill httpd.service # 杀死一个服务的所有子进程
- [[email protected] ~]# systemctl reload httpd.service # 重新加载一个服务的配置文件
- [[email protected] ~]# systemctl daemon-reload # 重载所有修改过的配置文件
- [[email protected] ~]# systemctl show httpd.service # 显示某个 Unit 的所有底层参数
- [[email protected] ~]# systemctl show -p CPUShares httpd.service # 显示某个 Unit 的指定属性的值
- [[email protected] ~]# systemctl set-property httpd.service CPUShares=500 # 设置某个 Unit 的指定属性
在最后遇到一个坑, 发现 jenkins 没有权限执行 systemctl
为了不破坏安全性.
最后解决方案为 在系统级别加入 service, 但是在部署时, 不用 service.jenkins 还是保持之前的脚本.
来源: http://www.bubuko.com/infodetail-3184082.html