JavaLYG

systemd 显示 active (exited) 但端口未监听:用 Type、MainPID 与 cgroup 修复假运行

服务重启后,systemctl status 显示 active (exited),端口却没监听。这个状态只说明 Unit 启动流程已结束,不保证后台进程仍在。下面按 PID、cgroup 和业务探针排查。

systemd 服务状态、主进程、cgroup 与业务验收证据链

一、先分清:active (exited) 不是健康检查

active (exited) 常见于 Type=oneshot 配合 RemainAfterExit=yes。命令退出后 Unit 仍保持 active,适合一次性配置任务,却不适合持续监听端口的 Web 服务。

官方文档说明,RemainAfterExit=true 允许进程退出后继续标记 active;Type=simple 则把 ExecStart 进程作为主进程。状态没有撒谎,只是“岗位说明书”写错了。

二、五条命令建立证据链

systemctl show demo.service \
  -p ActiveState -p SubState -p MainPID -p ExecMainStatus -p ControlGroup
systemctl cat demo.service
systemctl status demo.service --no-pager -l
journalctl -u demo.service -b --no-pager -n 100
ss -lntp | grep ':8080'

MainPID=0,说明没有可跟踪的主进程;若 PID 存在,再确认归属:

ps -o pid,ppid,stat,cmd -p "$(systemctl show -p MainPID --value demo.service)"
systemd-cgls "$(systemctl show -p ControlGroup --value demo.service)"

最后用 ss 查端口,用 curl -fsS http://127.0.0.1:8080/health 查接口。active 是过程证据,接口成功才是结果。

三、最常见的四类根因

现象常见原因处理方向
active (exited),MainPID=0oneshot + RemainAfterExit持续服务改为 simple/exec
启动脚本很快退出脚本把真正进程放到后台去掉 &,让前台进程成为 MainPID
Type=forking 后 PID 不准PIDFile 缺失、写迟或残留改前台模式,或正确维护 PIDFile
手动启动正常,重启别的服务后消失进程仍属于错误的 cgroup交给独立 Unit 启动和托管

四、Type 必须匹配程序的启动模型

前台程序优先使用 Type=simple,启动脚本末尾不要加 &。老式 daemon 确实会 fork 时才考虑 Type=forking,并维护可靠的 PIDFile;一次性任务才适合 Type=oneshot

systemd active exited 故障排查决策树

软件支持 --daemon--foreground 时,通常选后者。systemd 已是监督器,没必要再让程序“隐身”。

五、别忽略 cgroup:daemonize 不等于逃离父服务

fork、setsid 或 daemonize 可以脱离终端,却不会自动脱离 systemd 的 cgroup。从另一个服务里启动的新 daemon 可能仍在父 Unit 名下;父服务 stop 或 restart 时,默认 KillMode=control-group 会处理其中的剩余进程。

PID=$(pgrep -n -f '/usr/local/bin/demo')
cat "/proc/$PID/cgroup"
systemctl status demo.service --no-pager

不要随手改成 KillMode=process。官方文档不推荐 processnone,因为子进程会逃离生命周期与资源管理。应给程序创建独立 Unit。

六、一份稳妥的持续服务 Unit

[Unit]
Description=Demo HTTP Service
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/demo
EnvironmentFile=-/etc/demo/demo.env
ExecStart=/usr/local/bin/demo --foreground --listen 127.0.0.1:8080
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
KillMode=control-group

[Install]
WantedBy=multi-user.target

EnvironmentFile 前的减号表示文件缺失时不阻止启动。修改后先静态检查,再 reload:

systemd-analyze verify /etc/systemd/system/demo.service
systemctl daemon-reload
systemctl enable --now demo.service
systemctl restart demo.service

若通过 systemctl edit demo.service 覆盖已有 ExecStart,应先写一行空的 ExecStart= 清除旧值,再写新命令,否则可能触发“多个 ExecStart”错误。

七、修完仍失败,按分支继续查

  • 状态 failed:ExecMainCodeExecMainStatus 和 journal,先解决可执行文件、权限、目录或参数错误。
  • 进程在但端口不在:检查监听地址、端口冲突、配置文件是否加载;不要先怪防火墙,因为本机都没监听时,放行端口也没用。
  • 频繁重启:systemctl show demo.service -p NRestarts -p Result 判断启动崩溃,避免把 Restart=always 当万能创可贴。
  • 启动慢:检查 TimeoutStartSec,同时确认程序是否具备明确的就绪信号;盲目拉长超时只会让错误晚一点出现。

八、验收清单

  • systemd-analyze verify 无 Unit 语法错误;
  • ActiveState=active,持续服务的 MainPID 大于 0;
  • 主进程命令、PPID 与 cgroup 归属符合预期;
  • 目标端口已监听,监听地址正确;
  • 本机健康接口请求成功;
  • 执行一次受控 systemctl restart 后再次通过上述检查;
  • 重启策略没有掩盖持续崩溃,journal 中无循环报错。

active 是 Unit 生命周期判断,不是业务 SLA。把 Type、MainPID、cgroup 和业务探针串起来,才能确认服务真的可用。

参考资料

🔕 评论已关闭