systemd 服务管理
现代 Linux 发行版统一使用 systemd 管理系统服务。PostgreSQL 官方与各发行版都提供了标准的单元文件(Unit File),让你用 systemctl 完成启停、开机自启、查看日志与故障诊断。本节解析官方单元文件的内容,并讲解多实例模板与自定义配置的正确做法。
前置知识
阅读本节前,建议先了解:pg_ctl 启停服务
基础概念
- systemd:现代 Linux 的初始化系统与服务管理器,PID 1,负责启动、监控与重启服务
- 单元文件(Unit File):描述一个 systemd 管理对象的文本文件,服务类的后缀为
.service - 单元(Unit):单元文件在 systemd 中的运行时实例,用
systemctl按名字操作 - 目标(Target):一组单元的集合,类似传统运行级别,如
multi-user.target对应「多用户命令行模式」 - 实例单元(Instantiated Unit):文件名含
@的模板单元,如postgresql@.service,@后为实例名 - 丢弃式配置(Drop-in):放在
<unit>.service.d/目录下的片段文件,用来覆盖主单元文件的单项设置 - 通知就绪(Type=notify):服务在真正可用时通过 sd_notify 主动告知 systemd,避免「进程起来了但还不能连接」
| 管理对象 | 单元名示例 | 负责方 |
|---|---|---|
| Debian / Ubuntu 单实例 | postgresql.service(wrapper)+ postgresql@18-main.service | postgresql-common |
| RHEL / Rocky 单实例 | postgresql-18.service | PGDG RPM |
| 源码编译 | 需自行编写 | 用户 |
| 多实例(同一版本) | postgresql@18-main.service、postgresql@18-report.service | postgresql-common / 自定义模板 |
版本说明
RHEL 系从 PostgreSQL 10 起,服务名改为带版本号的 postgresql-18.service,允许同一台机器上并存多个大版本的单元文件。Debian 系则一直使用 postgresql@<版本>-<集群名>.service 的实例形式。
语法与用法
systemctl 常用命令
# 启动 / 停止 / 重启
sudo systemctl start postgresql-18
sudo systemctl stop postgresql-18
sudo systemctl restart postgresql-18
# 只重载配置(触发 SIGHUP,等价于 pg_ctl reload)
sudo systemctl reload postgresql-18
# 设置 / 取消开机自启
sudo systemctl enable postgresql-18
sudo systemctl disable postgresql-18
# enable 并立即启动的组合写法
sudo systemctl enable --now postgresql-18
# 查看状态(含最近日志)
systemctl status postgresql-18 --no-pager -l
# 判断是否处于运行状态(脚本里用,输出 active / inactive)
systemctl is-active postgresql-18
# 判断是否已设为开机自启
systemctl is-enabled postgresql-18
# 阻止任何人/依赖启动它(比 stop 更彻底,可防止被依赖拉起)
sudo systemctl mask postgresql-18
sudo systemctl unmask postgresql-18
# 重新加载所有单元文件(修改 unit 后必须执行)
sudo systemctl daemon-reload
# 查看单元文件最终生效内容(含所有 drop-in 合并结果)
systemctl cat postgresql-18查看日志
# 跟踪服务日志(systemd 会捕获 stdout/stderr 到 journal)
journalctl -u postgresql-18 -f
# 只显示本次启动以来的日志
journalctl -u postgresql-18 -b
# 最近 100 行,不进入分页器
journalctl -u postgresql-18 -n 100 --no-pager
# 按时间范围过滤
journalctl -u postgresql-18 --since "2026-09-21 09:00" --until "2026-09-21 12:00"
# 只显示错误及以上级别
journalctl -u postgresql-18 -p err -n 50 --no-pager| systemctl 命令 | 等价 pg_ctl 操作 | 说明 |
|---|---|---|
systemctl start | pg_ctl start | 由单元文件决定启动参数 |
systemctl stop | pg_ctl stop -m fast | 默认 KillSignal=SIGINT 即 fast |
systemctl reload | pg_ctl reload | ExecReload=/bin/kill -HUP $MAINPID |
systemctl restart | pg_ctl restart | 先 stop 再 start,非原子 |
systemctl status | pg_ctl status | 额外显示 cgroup 与最近日志 |
详细说明
官方单元文件解析(RHEL / PGDG 版)
# /usr/lib/systemd/system/postgresql-18.service
# 由 PGDG RPM 提供,位于 /usr/lib/systemd/system/ 下(发行版自带目录)
[Unit]
Description=PostgreSQL 18 database server
Documentation=man:postgres(1)
Documentation=https://www.postgresql.org/docs/18/
# 网络就绪后再启动,避免监听地址绑定失败
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
# 以 postgres 用户运行,绝不使用 root
User=postgres
Group=postgres
# 把 PGDATA 注入环境,postgres 二进制会读取它
Environment=PGDATA=/var/lib/pgsql/18/data/
# 关键:以当前进程为主进程启动,systemd 直接管理 postgres 主进程
ExecStart=/usr/pgsql-18/bin/postgres -D ${PGDATA}
# reload 对应 SIGHUP
ExecReload=/bin/kill -HUP $MAINPID
# 优雅停止:SIGINT 等价于 pg_ctl stop -m fast
KillMode=mixed
KillSignal=SIGINT
# 关闭超时设为 300 秒,给检查点留足时间
TimeoutSec=300
# 异常退出时自动重启
Restart=on-failure
RestartSec=5
[Install]
# 设为开机自启时加入哪个 target
WantedBy=multi-user.target| 指令 | 取值 | 作用 | 修改风险 |
|---|---|---|---|
Type | notify / simple / forking | 启动就绪判定方式 | notify 需编译时带 --with-systemd |
User / Group | postgres | 运行身份 | 不可改为 root |
Environment | PGDATA=... | 环境变量 | 改错会启动到错误目录 |
ExecStart | postgres -D ... | 启动命令 | 不要在此拼大量 -c 参数 |
KillSignal | SIGINT | 停止信号 | 改为 SIGQUIT 会造成崩溃恢复 |
KillMode | mixed | 信号发送范围 | 改为 process 可能留下孤儿后端进程 |
TimeoutSec | 300 | 停止超时 | 太短会导致检查点未完成即被强杀 |
Restart | on-failure | 自动重启策略 | 改为 always 会让计划内停机也重启 |
WantedBy | multi-user.target | 自启目标 | —— |
不要直接编辑 /usr/lib 下的单元文件
/usr/lib/systemd/system/ 是包管理器管理的目录,包升级时会直接覆盖你的修改,且不留备份。自定义必须通过 /etc/systemd/system/ 下的覆盖文件或 drop-in 完成。
Debian / Ubuntu 的双层结构
Debian 系的 postgresql.service 只是一个包装单元,真正的实例由 postgresql@.service 模板生成:
# /lib/systemd/system/postgresql.service(Debian 包装单元,节选)
[Unit]
Description=PostgreSQL RDBMS
Documentation=https://www.postgresql.org/docs/18/
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/true
# 重载时由 pg_ctlcluster 统一处理所有集群
ExecReload=/bin/true
RemainAfterExit=on
[Install]
WantedBy=multi-user.target# /lib/systemd/system/postgresql@.service(实例模板,节选)
[Unit]
Description=PostgreSQL Cluster %i
# 依赖包装单元,保证统一加载
PartOf=postgresql.service
ReloadPropagatedFrom=postgresql.service
After=network.target
[Service]
Type=notify
# %i 会被替换为实例名,如 18-main
Environment=PGDATA=/var/lib/postgresql/18/main
# 通过 pg_ctlcluster 操作,它会读取 /etc/postgresql/18/main/ 下的配置
ExecStart=/usr/bin/pg_ctlcluster --skip-systemctl-redirect %i start
ExecStop=/usr/bin/pg_ctlcluster --skip-systemctl-redirect -m fast %i stop
ExecReload=/usr/bin/pg_ctlcluster --skip-systemctl-redirect %i reload
TimeoutSec=0
RemainAfterExit=on
KillMode=mixed
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target# Debian 系的日常操作:既可用实例名,也可用包装单元
sudo systemctl restart postgresql@18-main
sudo systemctl restart postgresql # 一次性作用于所有集群
# 查看当前有哪些实例单元被启用
systemctl list-units 'postgresql@*' --all --no-pager UNIT LOAD ACTIVE SUB DESCRIPTION
postgresql@18-main.service loaded active running PostgreSQL Cluster 18-main多实例与模板单元
同一台机器上运行多个集群(不同端口、不同数据目录)时,用实例单元是最标准的做法。
# /etc/systemd/system/postgresql@.service
# 自定义模板:适用于源码编译环境,%i 为实例名(如 18-replica)
[Unit]
Description=PostgreSQL Cluster %i
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=postgres
Group=postgres
# %i 直接参与路径拼接,约定实例名格式为 <版本>-<用途>
Environment=PGDATA=/var/lib/pgsql/%i/data
Environment=PGLOG=/var/log/pgsql/%i.log
ExecStart=/usr/local/pgsql-18/bin/postgres -D ${PGDATA}
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/usr/local/pgsql-18/bin/pg_ctl -D ${PGDATA} -m fast -w stop
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=300
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target# 使用模板:%i 会被替换为 18-replica
sudo install -d -o postgres -g postgres -m 700 /var/lib/pgsql/18-replica/data
sudo systemctl daemon-reload
sudo systemctl enable --now postgresql@18-replica实例名中的 %i 能用在哪些地方
%i 可以用在 Environment=、ExecStart=、ExecStop=、WorkingDirectory= 等几乎所有值中。但它不能在 Requires=/After= 这类单元依赖里替代任意单元名,需要显式写出。
自定义配置(Drop-in)
要修改官方单元的行为,正确做法是在 /etc/systemd/system/ 下创建 drop-in 片段:
# /etc/systemd/system/postgresql-18.service.d/override.conf
# 目录名必须是 <原单元名>.d,文件名任意但需以 .conf 结尾
[Service]
# 调整停止超时:大 shared_buffers 下关闭检查点耗时更久
TimeoutSec=600
# 追加自定义环境变量(注意 Environment 是赋值,重复会覆盖同名项)
Environment=PGOPTIONS=-c log_statement=none
# 追加依赖:本机有独立的数据盘挂载单元时,需在其后启动
After=var-lib-pgsql.mount
Requires=var-lib-pgsql.mount# 必须先创建目录,再创建文件
sudo mkdir -p /etc/systemd/system/postgresql-18.service.d
sudo systemctl daemon-reload
# 用 cat 确认覆盖已合并生效
systemctl cat postgresql-18 | tail -20| 覆盖方式 | 路径 | 优先级 | 升级安全性 |
|---|---|---|---|
| drop-in 片段 | /etc/systemd/system/<unit>.d/*.conf | 最高 | 安全,不受包升级影响 |
| 同名完整单元 | /etc/systemd/system/<unit>.service | 高于 /usr/lib | 安全,但需自行跟进上游改动 |
直接改 /usr/lib | /usr/lib/systemd/system/<unit>.service | 最低 | 危险,会被覆盖 |
systemctl edit | 自动创建 drop-in | 最高 | 推荐,可避免路径写错 |
# 推荐:用 systemctl edit 自动创建 drop-in 并校验语法
sudo systemctl edit postgresql-18
# 等价于创建 /etc/systemd/system/postgresql-18.service.d/override.conf
# 保存后自动执行 daemon-reload实战示例
场景一:完整的日常运维流程
# ---------- 步骤 1:确认服务状态 ----------
systemctl is-enabled postgresql-18
systemctl is-active postgresql-18
systemctl status postgresql-18 --no-pager -l | head -20enabled
active
● postgresql-18.service - PostgreSQL 18 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-18.service; enabled; preset: disabled)
Active: active (running) since Sat 2026-09-21 09:12:33 CST; 2h 41min ago
Docs: man:postgres(1)
https://www.postgresql.org/docs/18/
Main PID: 1147 (postgres)
Status: "Ready to accept connections"
Tasks: 7 (limit: 48935)
Memory: 218.4M
CPU: 1.203s
CGroup: /system.slice/postgresql-18.service
├─1147 /usr/pgsql-18/bin/postgres -D /var/lib/pgsql/18/data/
├─1159 "postgres: checkpointer"
└─1160 "postgres: background writer"# ---------- 步骤 2:修改配置并选择正确的生效方式 ----------
sudo -u postgres psql -c "ALTER SYSTEM SET log_min_duration_statement = '500ms';"
# ALTER SYSTEM 写入 postgresql.auto.conf,属于可动态生效参数
systemctl reload postgresql-18
# 若改的是需要重启的参数,则必须 restart
sudo sed -i 's/^#*shared_buffers.*/shared_buffers = 4GB/' \
/var/lib/pgsql/18/data/postgresql.conf
systemctl restart postgresql-18# ---------- 步骤 3:验证重启后状态 ----------
systemctl is-active postgresql-18
sudo -u postgres psql -c "SHOW shared_buffers;"active
shared_buffers
----------------
4GB
(1 row)场景二:新增一个副本实例
# ---------- 步骤 1:准备数据目录与端口 ----------
sudo -u postgres /usr/pgsql-18/bin/initdb \
-D /var/pgsql/18-replica/data -E UTF8 --locale=C.UTF-8 --data-checksums
# 副本实例必须使用不同端口,否则与主实例冲突
sudo -u postgres bash -c \
"echo \"port = 5433\" >> /var/pgsql/18-replica/data/postgresql.conf"
# ---------- 步骤 2:编写模板单元(见上文 postgresql@.service) ----------
sudo tee /etc/systemd/system/postgresql@.service >/dev/null <<'EOF'
[Unit]
Description=PostgreSQL Cluster %i
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=postgres
Group=postgres
Environment=PGDATA=/var/pgsql/%i/data
ExecStart=/usr/pgsql-18/bin/postgres -D ${PGDATA}
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=300
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
# ---------- 步骤 3:启用并启动实例 ----------
sudo systemctl daemon-reload
sudo systemctl enable --now postgresql@18-replica
systemctl status postgresql@18-replica --no-pager | head -5● postgresql@18-replica.service - PostgreSQL Cluster 18-replica
Loaded: loaded (/etc/systemd/system/postgresql@.service; enabled; preset: disabled)
Active: active (running) since Sat 2026-09-21 11:58:02 CST; 8s ago# ---------- 步骤 4:确认两个实例同时运行且端口不同 ----------
sudo ss -lntp | grep postgresLISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=1147,fd=6))
LISTEN 0 244 127.0.0.1:5433 0.0.0.0:* users:(("postgres",pid=12043,fd=6))场景三:排障——服务反复重启
# 1. 先看单元状态与退出码
systemctl status postgresql-18 --no-pager -l× postgresql-18.service - PostgreSQL 18 database server
Active: failed (Result: exit-code) since Sat 2026-09-21 12:03:44 CST; 22s ago
Process: 12211 ExecStart=/usr/pgsql-18/bin/postgres -D ${PGDATA} (code=exited, status=1)
Main PID: 12211 (code=exited, status=1)# 2. 从 journal 里捞出真正的报错
journalctl -u postgresql-18 -n 40 --no-pager | grep -E "FATAL|ERROR|PANIC"Sep 21 12:03:44 db01 postgres[12211]: FATAL: could not write to log file: No space left on device
Sep 21 12:03:44 db01 postgres[12211]: LOG: background worker "logical replication launcher" exited
Sep 21 12:03:44 db01 systemd[1]: postgresql-18.service: Main process exited, code=exited, status=1# 3. 定位磁盘占用并处理
df -h /var/lib/pgsql
sudo du -sh /var/lib/pgsql/18/data/pg_wal
# 清理后重置失败计数并重启
sudo systemctl reset-failed postgresql-18
sudo systemctl start postgresql-18● postgresql-18.service - PostgreSQL 18 database server
Active: active (running) since Sat 2026-09-21 12:07:10 CST; 3s ago
Status: "Ready to accept connections"单元文件语法校验
修改单元文件后,可用 systemd-analyze verify postgresql-18.service 检查语法错误;执行 systemctl daemon-reload 时若语法有误,systemd 也会在 journalctl -u postgresql-18 中给出提示,但不会阻止 reload 成功,容易被忽略。
注意事项
- 修改
/usr/lib/systemd/system/下的单元文件会被包升级静默覆盖,必须使用/etc/systemd/system/下的 drop-in - 任何单元文件改动后都要执行
systemctl daemon-reload,否则旧定义持续生效 Restart=on-failure与计划内systemctl stop不冲突(stop 属正常退出),但改成always会让运维停机变成「停了又起」- 启用
Type=notify依赖编译时带--with-systemd;源码编译若未加该参数,应改用Type=simple或forking,否则start会一直等到超时 systemctl reload只在单元定义了ExecReload时可用;Debian 包装单元里它是个空操作,需操作具体实例KillSignal应为SIGINT(fast),改成SIGQUIT会强制崩溃恢复,改错代价很高TimeoutSec过短会在关闭检查点未完成时强杀进程,进而触发崩溃恢复;磁盘慢的环境应适当调大mask比disable更彻底,被 mask 的单元无法被依赖间接拉起;排障后记得unmask- 服务日志默认进入 journal,若
logging_collector = on,则 stderr 日志与 journal 会重复;应明确只保留一处,避免误导 - journal 默认有容量上限(通常为磁盘的 10%),历史日志可能被自动清理;合规要求长期保留时应配置持久化与转发
systemctl status显示的Status:行来自Type=notify的就绪通知,若为空说明通知机制未生效
最佳实践
- 一律通过 drop-in(
systemctl edit)自定义,绝不直接编辑/usr/lib下的原始单元文件 - 生产禁用「自动重启风暴」:
Restart=on-failure配合RestartSec=5与StartLimitBurst限制 - 用 systemd 管理服务生命周期,用
pg_ctl处理实例内部操作(如promote、logrotate),分工明确 - 把 systemd 与数据库日志统一收敛:要么全靠 journal,要么全用
logging_collector,不要两者混用 - 修改单元后先在测试环境用
systemd-analyze verify校验,再滚动到生产 - 为每个实例编写独立的 drop-in,把实例特有的环境变量与依赖显式写出,避免模板里堆条件判断
- 关键实例配置
After=数据盘挂载单元,防止数据盘未挂载就启动服务(这会以空目录启动,实为灾难) - 把
systemctl enable状态纳入配置管理(Ansible 等),避免「机器重启后服务没起来」这类初级事故 - 监控至少覆盖两个维度:
systemctl is-active与实际的pg_isready,前者正常不代表数据库可用 - 定期检查
journalctl --disk-usage,必要时配置SystemMaxUse限制 journal 占用
下一节
继续学习:多版本并存(pg_lsclusters)