Skip to content

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.servicepostgresql-common
RHEL / Rocky 单实例postgresql-18.servicePGDG RPM
源码编译需自行编写用户
多实例(同一版本)postgresql@18-main.service、postgresql@18-report.servicepostgresql-common / 自定义模板

版本说明

RHEL 系从 PostgreSQL 10 起,服务名改为带版本号的 postgresql-18.service,允许同一台机器上并存多个大版本的单元文件。Debian 系则一直使用 postgresql@<版本>-<集群名>.service 的实例形式。

语法与用法 ​

systemctl 常用命令 ​

bash
# 启动 / 停止 / 重启
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

查看日志 ​

bash
# 跟踪服务日志(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 startpg_ctl start由单元文件决定启动参数
systemctl stoppg_ctl stop -m fast默认 KillSignal=SIGINT 即 fast
systemctl reloadpg_ctl reloadExecReload=/bin/kill -HUP $MAINPID
systemctl restartpg_ctl restart先 stop 再 start,非原子
systemctl statuspg_ctl status额外显示 cgroup 与最近日志

详细说明 ​

官方单元文件解析(RHEL / PGDG 版) ​

ini
# /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
指令取值作用修改风险
Typenotify / simple / forking启动就绪判定方式notify 需编译时带 --with-systemd
User / Grouppostgres运行身份不可改为 root
EnvironmentPGDATA=...环境变量改错会启动到错误目录
ExecStartpostgres -D ...启动命令不要在此拼大量 -c 参数
KillSignalSIGINT停止信号改为 SIGQUIT 会造成崩溃恢复
KillModemixed信号发送范围改为 process 可能留下孤儿后端进程
TimeoutSec300停止超时太短会导致检查点未完成即被强杀
Restarton-failure自动重启策略改为 always 会让计划内停机也重启
WantedBymulti-user.target自启目标——

不要直接编辑 /usr/lib 下的单元文件

/usr/lib/systemd/system/ 是包管理器管理的目录,包升级时会直接覆盖你的修改,且不留备份。自定义必须通过 /etc/systemd/system/ 下的覆盖文件或 drop-in 完成。

Debian / Ubuntu 的双层结构 ​

Debian 系的 postgresql.service 只是一个包装单元,真正的实例由 postgresql@.service 模板生成:

ini
# /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
ini
# /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
bash
# Debian 系的日常操作:既可用实例名,也可用包装单元
sudo systemctl restart postgresql@18-main
sudo systemctl restart postgresql          # 一次性作用于所有集群

# 查看当前有哪些实例单元被启用
systemctl list-units 'postgresql@*' --all --no-pager
text
  UNIT                          LOAD   ACTIVE SUB     DESCRIPTION
  postgresql@18-main.service    loaded active running PostgreSQL Cluster 18-main

多实例与模板单元 ​

同一台机器上运行多个集群(不同端口、不同数据目录)时,用实例单元是最标准的做法。

ini
# /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
bash
# 使用模板:%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 片段:

ini
# /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
bash
# 必须先创建目录,再创建文件
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最高推荐,可避免路径写错
bash
# 推荐:用 systemctl edit 自动创建 drop-in 并校验语法
sudo systemctl edit postgresql-18
# 等价于创建 /etc/systemd/system/postgresql-18.service.d/override.conf
# 保存后自动执行 daemon-reload

实战示例 ​

场景一:完整的日常运维流程 ​

bash
# ---------- 步骤 1:确认服务状态 ----------
systemctl is-enabled postgresql-18
systemctl is-active postgresql-18
systemctl status postgresql-18 --no-pager -l | head -20
text
enabled
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"
bash
# ---------- 步骤 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
bash
# ---------- 步骤 3:验证重启后状态 ----------
systemctl is-active postgresql-18
sudo -u postgres psql -c "SHOW shared_buffers;"
text
active
 shared_buffers
----------------
 4GB
(1 row)

场景二:新增一个副本实例 ​

bash
# ---------- 步骤 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
text
● 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
bash
# ---------- 步骤 4:确认两个实例同时运行且端口不同 ----------
sudo ss -lntp | grep postgres
text
LISTEN 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))

场景三:排障——服务反复重启 ​

bash
# 1. 先看单元状态与退出码
systemctl status postgresql-18 --no-pager -l
text
× 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)
bash
# 2. 从 journal 里捞出真正的报错
journalctl -u postgresql-18 -n 40 --no-pager | grep -E "FATAL|ERROR|PANIC"
text
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
bash
# 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
text
● 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)

参考链接 ​