华为云 ECS 运维:用 systemd 托管常驻任务的五个细节

举报
yd_257405204 发表于 2026/10/24 12:42:48 2026/10/24
【摘要】 很多常驻脚本用 nohup 启动,遇到重启、OOM 或磁盘写满就悄悄消失。本文以华为云 ECS 上的 systemd 为例,讲清 unit 编写、SIGTERM 优雅退出、journald 日志、cgroup 资源限制、

为什么不再用 nohup

很多脚本上线时都是这样启动的:nohup python worker.py > worker.log 2>&1 &。白天没问题,夜里机器重启、OOM 或磁盘写满之后,进程就悄悄消失了。日志只有一个不断增长的 worker.log,没有轮转;想看退出码,只能靠猜。nohup 解决的是“终端断开后进程别死”,但常驻任务还需要开机自启、异常重启、资源限制、日志轮转和优雅退出,这些都应该交给 init 系统。

一个最小可用的 unit

在华为云 ECS 上,以 root 运行的服务放到 /etc/systemd/system/worker.service:

[Unit]
Description=Order callback worker
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=app
WorkingDirectory=/opt/app
Environment="PYTHONUNBUFFERED=1"
ExecStart=/usr/bin/python3 /opt/app/worker.py
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
KillSignal=SIGTERM
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

几个参数值得解释。Type=simple 表示 ExecStart 启动的就是主进程,不要再用 shell 包一层,否则 systemd 跟踪到的是 shell,脚本挂了它也感知不到。Restart=on-failure 只对非零退出、信号杀死和超时生效,正常退出不会拉起;如果希望无论怎么退出都重启,用 always,但要配合 StartLimitIntervalSec 防止崩溃循环。TimeoutStopSec 给进程 30 秒处理 SIGTERM,超时后 systemd 才发 SIGKILL。

让进程真正接住 SIGTERM

Restart 能不能做到“不丢正在处理的任务”,取决于代码有没有处理优雅退出。Python 里可以这样写:

import signal, threading
stop = threading.Event()

def handle(signum, frame):
    stop.set()

signal.signal(signal.SIGTERM, handle)
signal.signal(signal.SIGINT, handle)

while not stop.is_set():
    msg = queue.get(timeout=1)
    process(msg)
# 退出前把已取出的消息确认或放回队列
queue.nack_unfinished()

注意不要在信号处理器里做耗时操作,只置标志位,让主循环自己收尾。如果用了线程池,还要给每个任务设置超时,否则主线程会一直等在 join 上,systemd 最后只能 SIGKILL。

日志交给 journald,而不是自己写文件

服务里直接 print 到 stdout 即可,systemd 会把输出收进 journal。查最近日志用 journalctl -u worker -n 200 --no-pager,按时间过滤用 --since "10 min ago"。需要长期留存再配 /etc/systemd/journald.conf 的 SystemMaxUse 和 MaxRetentionSec,不要在应用里自己切文件。这样做的好处是日志和启动、退出、重启事件在同一条时间线上,排查“为什么 3 点重启了”时不用再翻两个地方。

资源限制不是可选项

常驻任务最常见的故障不是代码异常,而是把整台机器的内存吃光。unit 里可以用 MemoryMax=512M、CPUQuota=80% 给服务划一条硬边界;超过内存上限时,内核只杀这个 cgroup 里的进程,不会把 ECS 拖死。注意 MemoryMax 不是预留,机器空闲时服务仍然可以用更多,只是不能越过上限。若进程被 OOM kill,systemd 会在 journal 里留下 oom-kill 记录,配合 Restart=on-failure 可以自动拉起,但更重要的是看趋势,别让它每天被杀一次。

定时任务优先用 timer

如果任务不是常驻而是每天跑一次,不要写 while True + sleep,也不一定要用 crontab。systemd timer 能记录上次执行结果,支持单调时钟和随机延迟:

[Unit]
Description=Daily report

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true 表示关机期间错过的任务在开机后补跑一次,这对账单、报表类任务很重要。systemctl list-timers 可以直接看到下次触发时间和上次结果,比 crontab 的“黑盒”友好得多。

上线前检查四件事

  • systemctl daemon-reload 之后再用 enable --now,改 unit 不 reload 不会生效;
  • 用 systemd-analyze verify worker.service 检查语法,别等重启时才发现拼错;
  • 确认 User 对 WorkingDirectory 和日志目录有权限,SELinux 开启时还要看 audit.log;
  • 做一次 kill -TERM 演练,确认进程能在 TimeoutStopSec 内退出,而不是每次都被 SIGKILL。

最后给服务加一个独立的 timer 做健康检查,确认端口真的在监听,而不是进程还在、业务已经卡死。把这几步做完,常驻任务才算从“能跑”变成“可运维”。nohup 不是不能用,但它只适合一次性实验;真正要跑几个月的东西,还是交给 systemd。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。