在 CentOS 7 中使用 systemd 创建自定义服务
systemd 创建自定义服务
- 简述创建自定义服务步骤
- 文件覆盖优先级
- 创建服务流程
- 在 /etc/systemd/system/ 目录下创建 .service 文件(需 root 权限):
- 编写服务配置模板
- Systemd 服务文件三大区块详解
- [Unit] 区块 - 服务元数据与依赖
- [Service] 区块 - 进程运行配置
- [Install] 区块 - 开机启动配置
- 完整示例
- 设置权限和路径
- 管理服务命令
- 启动失败排查
- 注意事项
简述创建自定义服务步骤
- 创建服务文件:在 /etc/systemd/system/ 目录下创建一个新的服务文件,例如 myapp.service。使用 sudo 权限进行创建。
- 编辑服务文件:使用文本编辑器(如 vi 或 nano)编写服务单元文件。
- 重新加载 systemd 配置:每次修改服务文件后,需要运行 sudo systemctl daemon-reload 来重新加载配置。
- 启用服务:使用 sudo systemctl enable myapp.service 设置开机启动。
- 启动服务:使用 sudo systemctl start myapp.service 启动服务。
- 检查服务状态:使用 sudo systemctl status myapp.service 查看服务状态。
文件覆盖优先级
在 CentOS 7 的 systemd 系统中,/usr/lib/systemd/system/ 和 /etc/systemd/system/ 两个目录有明确的区别:
- systemd 加载配置的顺序(从高到低):
/etc/systemd/system/service.d/*.conf
/etc/systemd/system/service
/run/systemd/system/service.d/*.conf(临时配置)
/usr/lib/systemd/system/service.d/*.conf
/usr/lib/systemd/system/service
需求 | 操作位置 |
---|---|
安装新服务 | /etc/systemd/system/ |
修改现有服务参数 | /etc/systemd/system/service.d/ |
完全替换服务文件 | /etc/systemd/system/ |
查看软件原始配置 | /usr/lib/systemd/system/ |
添加开机启动依赖 | /etc/systemd/system/service.d/ |
/lib/systemd/system/(实际上是 /usr/lib/systemd/system/ 的符号链接)
特性 | /usr/lib/systemd/system/ | /etc/systemd/system/ |
---|---|---|
来源 | 软件包安装的原始服务文件 | 管理员自定义的服务文件或覆盖配置 |
优先级 | 低 | 高(覆盖 /lib/ 中的配置) |
是否会被覆盖 | 软件升级时可能被覆盖 | 不会被软件升级覆盖 |
推荐修改方式 | 不应直接修改 | 应在此目录添加自定义配置 |
目录类型 | 系统默认目录(只读) | 管理员配置目录(可写) |
- 作用:存放软件包(通过 yum/rpm 安装)提供的原始服务文件
- 特点:
- 系统级别的默认配置
- 软件升级时会覆盖此目录的文件
- 不要直接修改这里的文件(修改会被覆盖)
创建服务流程
在 /etc/systemd/system/ 目录下创建 .service 文件(需 root 权限):
sudo vi /etc/systemd/system/myapp.service
编写服务配置模板
[Unit]
Description=服务描述
After=network.target # 依赖关系[Service]
Type=forking # 服务类型
ExecStart=/path/to/command
User=username # 运行用户
Restart=on-failure # 重启策略[Install]
WantedBy=multi-user.target
Systemd 服务文件三大区块详解
[Unit] |
---|
• 服务标识 (Description) |
• 启动顺序 (After/Before) |
• 依赖关系 (Requires/Wants) |
[Service] | [Install] |
---|---|
• 进程控制 (Type/ExecStart) | • 开机启动目标 (WantedBy) |
• 运行环境 (User/Env) | • 别名管理 (Alias) |
• 资源限制 (MemoryLimit) | |
• 安全策略 (PrivateTmp) |
[Unit] 区块 - 服务元数据与依赖
定义服务的描述、依赖关系和启动顺序
指令 | 说明 | 示例值 |
---|---|---|
Description | 必填 服务描述信息(显示在 systemctl status 中) | Description=Nginx Web Server |
After | 定义启动顺序依赖(在此服务之后启动) | After=network.target |
Before | 定义反向依赖(在此服务之前启动) | Before=shutdown.target |
Requires | 强依赖 - 依赖服务失败则本服务失败 | Requires=mysql.service |
Wants | 弱依赖 - 依赖服务失败不影响本服务 | Wants=postfix.service |
Conflicts | 冲突服务 - 不能同时运行的服务 | Conflicts=httpd.service |
Documentation | 服务文档链接 | Documentation=man:nginx(8) |
Condition… | 启动条件检查(如 ConditionPathExists=/etc/nginx.conf) |
[Unit] 区块示例
[Unit]
Description=High Performance Web Server
After=network.target remote-fs.target nss-lookup.target
Wants=postgresql.service
Documentation=https://nginx.org/en/docs/
[Service] 区块 - 进程运行配置
定义服务进程的执行方式和运行时行为
类别 | 指令 | 说明 | 常用值 |
---|---|---|---|
启动类型 | Type | 必填 进程启动类型 | simple、forking、oneshot、notify |
ExecStart | 必填 启动命令(绝对路径) | /usr/sbin/nginx -g “daemon off;” | |
ExecStartPre | 主命令前执行的预备命令 | /bin/mkdir -p /run/nginx | |
ExecStartPost | 主命令后执行的后续命令 | /bin/echo “Service started” | |
ExecReload | 重载服务时执行的命令 | /bin/kill -HUP $MAINPID | |
运行环境 | User | 运行服务的用户 | nginx、nobody |
Group | 运行服务的用户组 | www-data | |
WorkingDirectory | 工作目录 | /var/www/html | |
Environment | 设置环境变量 | PORT=8080 | |
EnvironmentFile | 从文件加载环境变量 | /etc/sysconfig/nginx | |
重启策略 | Restart | 服务退出时重启策略 | no、always、onfailure、on-abort |
RestartSec | 重启前等待时间 | 5s、1min | |
资源限制 | MemoryLimit | 内存限制 | 512M |
CPUQuota | CPU配额 | 80% | |
安全控制 | PrivateTmp | 使用私有/tmp目录(增强安全) | true |
ProtectSystem | 文件系统保护级别 | full、strict |
[Service]示例
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
User=nginx
Group=nginx
Environment="NGINX_LOGLEVEL=info"
Restart=on-failure
RestartSec=5s
MemoryLimit=500M
PrivateTmp=true
常见 Type 类型:
特性 | Type=simple | Type=forking |
---|---|---|
工作方式 | 主进程直接在前台运行 | 主进程 fork 子进程后退出 |
systemd 监控对象 | ExecStart 启动的进程 | fork 出来的子进程 |
适用场景 | 现代应用 (Python/Node.js/Go 等) | 传统守护进程 (Nginx/MySQL 等) |
PID 文件 | 不需要 | 必须 通过 PIDFile= 指定 |
启动完成判定 | 立即标记为 active | 需等待主进程退出 |
日志处理 | 自动捕获 stdout/stderr | 需程序自行处理日志 |
启动速度 | 快 (直接启动) | 稍慢 (需完成 fork 过程) |
典型应用 | Flask, Redis, systemd 自身服务 | Apache, PostgreSQL, Zabbix Server |
判断应该使用哪种类型:
# 手动测试启动程序
/usr/sbin/nginx -c /etc/nginx/nginx.conf# 观察行为:
# 1. 如果命令立即返回且后台有进程 → forking
# 2. 如果命令阻塞在前台 → simple
[Install] 区块 - 开机启动配置
定义服务安装到哪个运行级别(target)
指令 | 说明 | 示例值 |
---|---|---|
WantedBy | 必填 指定服务关联的 target(实现开机启动) | multi-user.target |
RequiredBy | 指定强依赖本服务的 target | graphical.target |
Alias | 服务别名 | Alias=webserver.service |
Also | 安装时同时启用的其他服务 | Also=nginx-sockets.service |
[Install] 示例
[Install]
WantedBy=multi-user.target
Alias=web.service
完整示例
[Unit]
Description=My Custom Application # 服务描述
After=network.target # 在网络启动后运行[Service]
Type=simple # 服务类型(常用 simple 或 forking)
User=appuser # 运行服务的用户
Group=appgroup # 运行服务的组
WorkingDirectory=/opt/myapp # 工作目录
ExecStart=/usr/bin/java -jar /opt/myapp/app.jar # 启动命令(必须绝对路径)
Restart=on-failure # 失败时自动重启
RestartSec=5s # 重启间隔
Environment="PORT=8080" # 设置环境变量
PrivateTmp=true # 启用私有临时目录(安全增强)# 日志配置(可选)
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp[Install]
WantedBy=multi-user.target # 多用户模式下启用
设置权限和路径
# 确保可执行文件有权限
sudo chmod +x /opt/myapp/start.sh
# 环境变量文件(可选)
sudo vi /etc/sysconfig/myapp # 定义变量 APP_ENV=production
服务文件中引用:
EnvironmentFile=/etc/sysconfig/myapp
ExecStart=/opt/myapp/start.sh ${APP_ENV}
管理服务命令
# 重载 systemd 配置(修改服务文件后必须执行)
sudo systemctl daemon-reload# 启动/停止服务
sudo systemctl start myapp.service
sudo systemctl stop myapp.service# 设置开机自启
sudo systemctl enable myapp.service# 查看状态和日志
systemctl status myapp # 服务状态
journalctl -u myapp -f # 实时日志(-f 跟踪日志)
journalctl -u myapp --since "2020-01-01" --until "1 hour ago" # 时间筛选
启动失败排查
systemctl status myapp # 查看错误摘要
journalctl -xe # 检查详细日志
- 测试启动命令: 手动执行 ExecStart 中的命令,验证路径和权限。
- 环境变量问题: 使用 systemctl show myapp 检查最终环境变量
注意事项
- 路径必须为绝对路径(包括脚本和命令)
- 修改服务文件后必须执行 daemon-reload
- 生产环境建议用非 root 用户运行(User=)