0Pricing
Linux Command Line & Bash Scripting Mastery · 课时

控制 systemd 服务并编写单元文件

使用 systemctl 管理服务,并为脚本化守护进程编写自定义单元和计时器文件。

控制 systemd 服务并编写单元文件 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

systemd 和 systemctl 简介

systemd 是大多数现代 Linux 发行版使用的 init 系统和服务管理器。它负责启动、停止和管理系统服务,同时处理启动序列和系统状态。

与 systemd 交互的主要工具是 systemctl。您可以使用它:

  • 启动和停止服务
  • 启用或禁用服务在启动时运行
  • 查看服务状态和日志
  • 重新加载配置而无需重启

由 systemd 管理的所有服务都由单元文件定义。这些声明式配置文件存储在 /etc/systemd/system/(系统范围)或 ~/.config/systemd/user/(按用户划分)下。

常用的 systemctl 命令

以下是日常服务管理中最常用的 systemctl 命令。每条命令都针对一个单元名称操作,例如 nginx.service 或直接使用 nginx。

  • systemctl start <unit> — 立即启动服务
  • systemctl stop <unit> — 停止正在运行的服务
  • systemctl restart <unit> — 先停止再启动(完全重启)
  • systemctl reload <unit> — 发送 SIGHUP 以重新加载配置,但不停止服务
  • systemctl enable <unit> — 创建符号链接,使单元在启动时自动运行
  • systemctl disable <unit> — 删除这些符号链接
  • systemctl status <unit> — 显示状态、PID 和最近的日志行
#!/usr/bin/env bash
# Quick reference: inspect and control an nginx service
# (Requires nginx to be installed; run as root or with sudo)

systemctl status nginx
systemctl start nginx
systemctl enable nginx
systemctl reload nginx
systemctl restart nginx
systemctl stop nginx
systemctl disable nginx

详细检查服务状态

systemctl status 会提供单元的详细快照。理解其输出对于快速诊断问题至关重要。

  • 已加载:单元文件的路径,以及该单元是否已设置为在启动时启用
  • 活动:当前状态 — active (running)、inactive (dead)、failed
  • 主 PID:服务主进程的进程标识符
  • 控制组:属于该服务的所有子进程
  • 日志行:该单元最近的日志记录

您也可以使用 systemctl is-active 只查询活动状态,或使用 systemctl is-enabled 只查询启用状态。这两个命令非常适合在脚本中使用。

#!/usr/bin/env bash
# Script-friendly service health check
SERVICE="sshd"

if systemctl is-active --quiet "$SERVICE"; then
    echo "$SERVICE is running."
else
    echo "$SERVICE is NOT running. Attempting restart..."
    systemctl restart "$SERVICE"
fi

if systemctl is-enabled --quiet "$SERVICE"; then
    echo "$SERVICE is enabled at boot."
else
    echo "WARNING: $SERVICE is not enabled at boot."
fi

列出并筛选单元

管理大量服务时,您需要高效列出和筛选单元的方法。systemctl list-units 会显示当前已加载的所有单元,而 list-unit-files 会显示所有已安装的单元文件及其启用状态。

实用的筛选选项:

  • --type=service — 仅显示服务单元
  • --state=failed — 仅显示失败的单元
  • --state=active — 仅显示活动的单元
  • --all — 在列表中包含非活动单元

在维护脚本中将这些筛选条件与 grep 结合使用,可以构建功能强大的检查管道。

#!/usr/bin/env bash
# List all failed services and report them
echo "=== Failed Services ==="
systemctl list-units --type=service --state=failed --no-legend

echo ""
echo "=== Services Enabled at Boot ==="
systemctl list-unit-files --type=service --state=enabled --no-legend | awk '{print $1}'

systemd 服务单元文件的结构

服务单元文件是一种纯文本的 INI 风格文件,由多个部分组成。每个部分负责控制单元生命周期的不同方面。

  • [Unit] — 元数据:描述信息和排序依赖项(After=、Requires=、Wants=)
  • [Service] — 如何启动或停止进程:ExecStart、ExecStop、Restart、User、WorkingDirectory
  • [Install] — 何时启用此单元:WantedBy=multi-user.target 表示它会在正常的多用户启动过程中激活

将单元文件放置到或编辑 /etc/systemd/system/ 下的单元文件后,您必须运行 systemctl daemon-reload,以便 systemd 读取这些更改。

编写您的第一个服务单元文件

让我们为自定义的 Python HTTP 服务器脚本创建一个简单的服务单元文件。该单元文件会准确告诉 systemd 如何管理此进程,就像管理其他系统服务一样。

这里使用的关键 [Service] 指令:

  • Type=simple — systemd 将 ExecStart 启动的进程视为主进程
  • User=www-data — 使用非 root 账户运行,以提高安全性
  • WorkingDirectory — 设置进程的工作目录
  • Restart=on-failure — 如果进程以非零代码退出,则自动重启
  • RestartSec=5 — 每次重启尝试之间等待 5 秒
#!/usr/bin/env bash
# Create a custom service unit file for a simple HTTP server
# Run as root

cat > /etc/systemd/system/mywebserver.service << 'EOF'
[Unit]
Description=My Custom Python Web Server
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/mysite
ExecStart=/usr/bin/python3 -m http.server 8080
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now mywebserver.service
systemctl status mywebserver.service

服务类型与进程生命周期

[Service] 中的 Type= 指令会告诉 systemd 如何判断服务何时完成启动。选择错误的类型是排序错误的常见原因。

  • Type=simple — 默认类型;ExecStart 启动后,systemd 就认为服务已启动。不需要就绪信号。
  • Type=forking — ExecStart 进程创建子进程后退出;真正的守护进程作为子进程运行。请使用 PIDFile=,以便 systemd 跟踪它。
  • Type=notify — 进程在真正就绪时发送 sd_notify(READY=1)。对于复杂的守护进程,这是最可靠的方式。
  • Type=oneshot — 运行一次后退出;后续单元会等待其完成。非常适合设置脚本。
  • Type=idle — 类似于 simple,但会延迟执行,直到所有活动任务完成。
#!/usr/bin/env bash
# Example: oneshot service for a database migration script
# /etc/systemd/system/db-migrate.service

cat > /etc/systemd/system/db-migrate.service << 'EOF'
[Unit]
Description=Run Database Migrations
After=postgresql.service
Requires=postgresql.service

[Service]
Type=oneshot
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/scripts/migrate.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl start db-migrate.service

环境变量与安全强化

服务单元文件支持使用多个指令传递环境变量并应用安全限制 — 这两点对于生产系统都很重要。

环境指令:

  • Environment="KEY=value" — 在行内设置单个变量
  • EnvironmentFile=/etc/myapp/env — 从文件加载变量(每行一个 KEY=value)

常见的安全强化指令:

  • NoNewPrivileges=true — 防止进程通过 setuid 获取额外权限
  • ProtectSystem=strict — 以只读方式挂载 /usr、/boot 和 /etc
  • PrivateTmp=true — 为服务提供独立隔离的 /tmp
  • CapabilityBoundingSet= — 删除所有 Linux 功能权限
#!/usr/bin/env bash
# Hardened service unit with environment file

cat > /etc/systemd/system/secureapp.service << 'EOF'
[Unit]
Description=Secure Application Daemon
After=network.target

[Service]
Type=simple
User=secureapp
Group=secureapp
WorkingDirectory=/opt/secureapp
EnvironmentFile=/etc/secureapp/env
ExecStart=/opt/secureapp/bin/server
Restart=on-failure
RestartSec=10

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
CapabilityBoundingSet=
ReadWritePaths=/var/lib/secureapp /var/log/secureapp

[Install]
WantedBy=multi-user.target
EOF

# Create the environment file separately so secrets stay out of the unit file
install -m 600 -o secureapp /dev/null /etc/secureapp/env
echo 'APP_PORT=9000' >> /etc/secureapp/env
echo 'DB_URL=postgresql://localhost/mydb' >> /etc/secureapp/env

systemctl daemon-reload

systemd 定时器单元简介

systemd 定时器是一种按计划激活另一个单元(通常是 .service)的单元。它用集成度更高、可记录日志且更易控制的机制取代了传统的 cron 任务。

每个定时器单元(foo.timer)都与一个基本名称相同的服务单元(foo.service)配对,由后者执行实际工作。您应启用并启动定时器,而不是直接启动服务。

定时器类型:

  • 实时(日历)定时器 — 使用 OnCalendar= 表达式在墙上时钟的指定时间触发,例如 daily、weekly 或 Mon *-*-* 02:00:00
  • 单调定时器 — 使用 OnBootSec=、OnActiveSec= 或 OnUnitActiveSec=,相对于系统事件触发

编写定时器单元文件

让我们构建一组完整的定时器与服务单元,使其每天 02:00 运行备份脚本。请注意,[Timer] 部分位于独立的 .timer 文件中,而实际工作由配对的 .service 文件负责。

关键的 [Timer] 指令:

  • OnCalendar= — 用于定义计划的日历表达式
  • Persistent=true — 如果定时器应触发时系统处于关机状态,则在下次启动时立即运行
  • RandomizedDelaySec= — 增加最多 N 秒的随机延迟,防止许多主机使用相同计划时出现惊群效应
  • Unit= — 明确指定目标单元(名称匹配时可选)
#!/usr/bin/env bash
# Create a daily backup timer pair
# Run as root

# 1. The service that does the work
cat > /etc/systemd/system/daily-backup.service << 'EOF'
[Unit]
Description=Daily Database Backup
After=network.target postgresql.service

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-db.sh
StandardOutput=journal
StandardError=journal
EOF

# 2. The timer that schedules it
cat > /etc/systemd/system/daily-backup.timer << 'EOF'
[Unit]
Description=Run Daily Database Backup at 02:00

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

[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now daily-backup.timer

# Verify
systemctl list-timers daily-backup.timer

检查日志并排查单元问题

systemd 会将所有服务输出发送到日志系统,您可以使用 journalctl 查询这些内容。这样,所有单元的日志都会集中存储在一个可搜索的地方。

排查服务问题时常用的 journalctl 选项:

  • -u <unit> — 按单元名称筛选
  • -f — 实时跟踪日志(类似于 tail -f)
  • -n 50 — 仅显示最后 50 行
  • --since "1 hour ago" — 按时间限制结果
  • -p err — 仅显示错误级别及更高级别的记录
  • -b — 仅显示本次启动期间的日志

当单元启动失败时,首先应运行 journalctl -u <unit> -n 30 --no-pager,然后运行 systemctl status <unit>,以获取最近的错误上下文。

#!/usr/bin/env bash
# Troubleshooting helper: dump unit status and recent logs
# Usage: ./service-debug.sh <unit-name>

UNIT="${1:-nginx.service}"

echo "=============================="
echo "STATUS: $UNIT"
echo "=============================="
systemctl status "$UNIT" --no-pager -l

echo ""
echo "=============================="
echo "RECENT LOGS (last 50 lines): $UNIT"
echo "=============================="
journalctl -u "$UNIT" -n 50 --no-pager

echo ""
echo "=============================="
echo "ERRORS ONLY (current boot)"
echo "=============================="
journalctl -u "$UNIT" -b -p err --no-pager

知识检查:systemd 单元文件

测试您对 systemd 服务和定时器单元配置的理解。

课程回顾:控制 systemd 服务并编写单元文件

在本课中,您以专业水平学习了 systemd 服务管理和单元文件编写的核心概念。以下是本课内容总结:

  • systemctl 基础 — 使用 start、stop、restart、reload、enable、disable、status、is-active 和 is-enabled,以便执行适合脚本的检查
  • 列出与筛选 — 将 list-units 和 list-unit-files 与 --type、--state 选项结合,筛选出失败或已启用的服务
  • 单元文件结构 — 三个部分 [Unit]、[Service] 和 [Install] 及其关键指令
  • 服务类型 — simple、forking、notify、oneshot,以及每种类型的适用场景
  • 环境与安全 — 使用 EnvironmentFile、NoNewPrivileges、ProtectSystem 和 PrivateTmp 强化守护进程
  • 定时器单元 — 配对 .timer 和 .service 文件,使用 OnCalendar 表达式,以及 Persistent 的补执行行为
  • Journalctl — 按单元、时间、启动过程和优先级筛选日志,从而高效诊断故障

掌握这些技能后,您就能管理任何系统服务,可靠地安排重复任务,并为自己的守护进程编写适用于生产环境的单元文件。

常见问题解答

「控制 systemd 服务并编写单元文件」课时是免费的吗?

是的 — 「控制 systemd 服务并编写单元文件」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

「控制 systemd 服务并编写单元文件」这节课中我会学到什么?

使用 systemctl 管理服务,并为脚本化守护进程编写自定义单元和计时器文件。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「控制 systemd 服务并编写单元文件」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?

能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 自动化用户与用户组配置
  2. 控制 systemd 服务并编写单元文件
  3. 磁盘、文件系统与挂载自动化
  4. 构建系统健康检查与告警脚本
← 返回 Linux Command Line & Bash Scripting Mastery