CentOS7下Java应用作为systemd服务启动失败问题排查
从你给出的日志、脚本和systemd配置来看,核心问题出在systemd服务类型设置、PID文件权限以及脚本与systemd进程管理的冲突上,咱们一步步拆解和修复:
问题根源分析
1. Type=simple与脚本后台启动不匹配
你在systemd服务里设了Type=simple,这个类型要求ExecStart启动的进程必须前台运行——systemd会把这个进程当作服务的主进程,一旦它退出,就会认为服务终止,触发你配置的Restart=always重启服务。
但你的启动脚本里用了&把Java进程后台运行,这就导致:
- 脚本执行完后台启动命令后,自身进程直接退出
- systemd看到主进程(脚本)退出,立刻触发重启
- 重启时脚本的
is_running检测到后台还在跑的Java进程,就输出"Already started" - 紧接着systemd的重启逻辑或者脚本的停止逻辑被触发,又执行停止操作,形成恶性循环
2. PID文件的权限问题
你的PID文件放在/var/run/myservice.pid,这个目录默认是root权限。当systemd以appuser身份运行脚本时,appuser没有权限直接写入/var/run,会导致PID文件创建失败,进而让is_running函数的判断出错——比如脚本以为服务没启动,但实际Java进程已经在后台,下次启动就会误报"Already started"。
3. 自定义进程检测与systemd逻辑冲突
systemd本身有完善的进程管理能力,你自己写的get_pids、is_running这些逻辑反而会和systemd的状态判断打架,两边对"服务是否运行"的认知不一致,进一步加剧混乱。
修复方案
方案一:调整脚本与systemd配置兼容
1. 修改systemd服务文件
把Type=simple改成Type=forking(这个类型专门适配启动后后台运行的进程),同时调整PID文件路径到appuser有权限写入的位置:
- 要么创建
appuser专属的PID目录:mkdir -p /home/appuser/run && chown appuser:appgrp /home/appuser/run,然后PID文件设为/home/appuser/run/myservice.pid - 要么给
/var/run下创建专属目录:mkdir -p /var/run/myservice && chown appuser:appgrp /var/run/myservice,PID文件设为/var/run/myservice/myservice.pid
修改后的服务文件关键片段:
[Service] Type=forking WorkingDirectory=/app/myworkingdir/run PIDFile=/home/appuser/run/myservice.pid # 换成你选的有权限的路径 ExecStart=/app/myworkingdir/run/myscript.sh start ExecStop=/app/myworkingdir/run/myscript.sh stop User=appuser Group=appgrp Restart=always RestartSec=30 StartLimitInterval=60 StartLimitBurst=5 TimeoutStartSec=30 LimitNOFILE=65536
2. 简化脚本的进程检测逻辑
把is_running改成优先依赖PID文件的准确判断,避免用ps模糊匹配:
is_running() { if [ -f "$pid_file" ]; then local pid=$(cat "$pid_file") ps -p "$pid" > /dev/null 2>&1 return $? else return 1 fi }
同时修改start部分,直接把后台进程的PID写入文件(不要用ps去查):
start) if is_running; then echo "Already started" exit 0 else # 用nohup后台运行,直接把进程PID写入文件 su -s /bin/sh $user -c "cd /app/myworkingdir ; nohup java -jar myjar.jar >> /var/log/systemout.log 2>> /var/log/systemerr.log & echo \$! > $pid_file" # 等待2秒确保进程启动完成 sleep 2 if ! is_running; then echo "Failed to start service" exit 1 fi fi ;;
3. 重新加载配置并测试
systemctl daemon-reload # 先停掉残留的服务和进程 systemctl stop myservice.service pkill -u appuser java # 启动服务并查看状态 systemctl start myservice.service systemctl status myservice.service
方案二:直接用systemd管理Java进程(更推荐)
其实完全可以去掉中间的shell脚本,让systemd直接管理Java进程,这样更简洁也更符合systemd的设计:
[Unit] Description=myservice After=syslog.target network.target [Service] Type=simple WorkingDirectory=/app/myworkingdir User=appuser Group=appgrp ExecStart=/usr/bin/java -jar myjar.jar Restart=always RestartSec=30 StartLimitInterval=60 StartLimitBurst=5 TimeoutStartSec=30 LimitNOFILE=65536 # 日志交给systemd管理,不用自己维护日志文件 StandardOutput=journal+console StandardError=journal+console [Install] WantedBy=multi-user.target
这样systemd会前台运行Java进程,日志自动进入journalctl,进程管理更可靠,也不会出现脚本和systemd冲突的问题。
内容的提问来源于stack exchange,提问作者Sven




