ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PostgreSQL 13源码编译安装实战:从configure到systemd全流程

PostgreSQL 13源码编译安装实战:从configure到systemd全流程

PostgreSQL 13发布那阵子,我正准备给某个内部数据平台换数据库底座。当时手里同时跑着好几个业务库,既有老的9.6,也有别人装好的10,版本杂、管理乱,加上刚好需要一台独立实例承接新的报表任务,我就决定用源码方式重新部署一套。选择2021年3月这个时间点,是因为当时13.2已经发布,修复了不少前几个补丁版本的问题,稳定性更适合直接进生产。

这篇不是官方文档的复述,而是把我从解压源码包到最终跑通业务的完整过程记录下来:每一步为什么这么干、哪些坑我踩过、哪些参数值得你多看一眼。适合正在给团队搭测试环境、或者第一次从二进制包转向源码编译的运维和DBA阅读。

1. 为什么在生产环境我仍坚持源码安装:选型背后的考量

1.1 二进制包真的不够用吗

很多人会问:明明yum install或者apt install一行搞定,为什么非要从源码编译?我的理由并不复杂,主要集中在以下三点。

第一,可控性。发行版仓库里的PostgreSQL版本总是落后几个小版本,并且某些Linux发行版的软件源还会打上自己的补丁,行为表现会和官方发现场编译的存在细微差异。比如我之前遇到过系统自带版本默认禁用了某一类索引特性,查了半天才发现是发行版编译参数跟我们业务预期不一致。源码安装则能让我明确知道当前实例的configure选项、编译器和补丁状态。

第二,多版本共存。测试环境经常需要同时在多个大版本之间比对行为差异,比如验证从9.6迁移到13后执行计划的变化。发行版的二进制包通常会固定安装到一个系统路径,很难做到同一台机器上多个大版本互不干扰。源码编译把每个版本装进独立的前缀目录,切换使用环境只需调整PATH参数,干净利落。

第三,排查问题的便利性。源码编译保留了完整的中间文件和符号信息,一旦碰到疑似数据库本身的问题,可以带调试符号重新编译,甚至直接附加调试器查看内部状态。生产环境我一般不需要这么做,但排查问题时这种能力确实可以成为关键时刻的救命稻草。

当然,源码安装也有代价:编译时间、依赖准备、升级时需要重复操作,这些都必须提前想清楚。我的经验是做增量部署时优先考虑源码,但不排斥在生产环境使用官方提供的二进制包,具体场景要具体分析。

1.2 版本节奏与补丁时机的把控

选择13这个版本而不是当时的14或者更旧的版本,我是有明确理由的。2021年3月这个时间点,PostgreSQL 14还在开发期,13系列已经经历13.0和13.1两个版本,13.2算是功能稳定、修复充分的成熟版本。我不建议在刚发布的大版本上直接跑核心业务,通常等三四个月,让社区把早期暴露的稳定性问题处理得差不多,再考虑升级。

源码安装还有一个额外好处:日后补丁升级只需在旧源码目录基础上增量编译,不需要重做initdb。比如从13.1升到13.2,我的流程是先下载新源码包,编译安装到同一前缀目录,然后重启实例即可。数据目录不用动,配置文件不用改,整个升级窗口可以压缩到分钟级。这个特性在应急修复安全漏洞时非常有用。

2. 编译前的基础准备:依赖、用户与目录布局

2.1 依赖安装阶段的常见遗漏

源码编译依赖GCC、make、readline和zlib相关的开发包。不同发行版包名不完全相同,我当时的环境是CentOS 7.8,安装命令是:

yum install -y gcc gcc-c++ make readline-devel zlib-devel flex bison

这里最容易漏掉的是flex和bison。前者在configure检查阶段不一定报错,但编译到语法分析部分时会突然中断;后者版本如果太旧,可能无法正确处理新的语法文件。我的建议是顺手都装上,别省这些功夫。

另外,如果你的环境需要SSL支持,务必提前安装openssl-devel,并在configure时加上--with-openssl参数。需要Python或Perl过程语言支持的,还要提前装好对应的开发头文件。依赖装不全最麻烦的问题是排查过程曲折,报错信息又不直观,折腾一圈回头发现只是少了一个基础包。

2.2 用户创建与目录规划

生产环境操作数据库绝不建议用root直接跑。我习惯为每个独立的PG实例创建一个专用系统用户,既便于权限隔离,也方便通过systemd统一管理。当时我执行的命令是:

groupadd postgres useradd -g postgres -s /bin/bash postgres

密码不需要设置,默认也无法登录系统,但后续所有数据库操作都在这个用户下执行。这里有个经验:把用户shell指定为/bin/bash而不是/sbin/nologin,可以在排查问题时方便地切换身份跑命令;如果不放心安全边界,生产环境可以改回nologin。

目录规划方面,我强烈建议把软件目录和数据目录彻底分开。软件目录通常放在/usr/local/pgsql13这种带版本号的位置,数据目录则放在独立的数据盘挂载点上,比如/data/pgsql13。这样升级软件时数据目录完全不受影响,磁盘空间不足时也可以单独扩容数据目录而不用动软件。

我实际的布局是这样:

mkdir -p /data/pgsql13/data mkdir -p /data/pgsql13/logs chown -R postgres:postgres /data/pgsql13

日志单独放一个目录也很关键。因为PostgreSQL运行日志如果不加管理,会无限增长导致磁盘写满。独立的日志目录方便接入日志轮转工具,也方便日常巡检。

2.3 configure前的编译选项取舍

进入源码包目录后的第一步是configure,这里的选择直接影响数据库编译出来的能力和性能。我当时用的完整命令是:

./configure --prefix=/usr/local/pgsql13 --with-openssl --with-perl --with-python --with-pam --with-libxml --with-libxslt

--prefix指定安装路径,这是多版本共存的关键。--with-openssl启用SSL加密连接,当前业务要求所有链路加密,必须加。--with-perl和--with-python是为了后续可能用到PL/Perl和PL/Python写存储过程,暂时用不到也可以先编译进去,省得日后重新编译。

有个参数需要特别提醒:--with-pam。如果你需要通过操作系统账号来认证数据库用户,这个选项就非常重要,但启用后需要保证PAM配置文件正确。我当时因为没有这个需求所以没加,如果你有LDAP或操作系统集成认证需求,需要在configure阶段就计划好,否则后续追加比较麻烦。

另一个容易忽略的点是优化级别。默认configure会使用-O2优化,对于生产环境是合理的选择。如果你需要更强的调试能力可以加--enable-debug,但同时会牺牲部分性能。不建议在普通生产环境启用debug。

3. configure / make / make install 逐步走完

3.1 完整编译过程与耗时观察

configure脚本跑完后,输出信息里会有一段总结,告诉你启用了哪些特性、禁用了哪些特性。我建议认真看一下这段总结,确认需要的功能全部出现,而不是直接滚屏跳过。

接下来执行编译:

make -j 8

-j参数指定并行编译作业数,通常按CPU物理核心数或逻辑核心数的一半设置比较稳妥。我当时的机器是8核,用-j8编译大约花了十二分钟。如果你用的是2核小机器,就不要贪心,直接make就好,并行参数设置过高会导致系统卡顿甚至内存不足。

编译是一个比较漫长的过程,期间终端会滚动大量输出。我的建议是不要干等,可以把编译日志重定向到文件,比如make -j8 > /tmp/make.log 2>&1,这样既能保留信息又避免终端内容刷屏。编译中断时查看日志末尾几百行往往就能定位到问题。

3.2 安装阶段与目录结构验证

编译完成后执行make install,这一步相对快,主要把可执行文件、库文件、头文件和支持文件复制到prefix目录。安装结束后建议验证一下目录结构:

ls -l /usr/local/pgsql13/bin

bin目录下应该有postgres、psql、pg_ctl、initdb、pg_dump等一组可执行文件。如果缺少某工具,多半是configure阶段关了相关选项。比如pg_dump和pg_restore是与安装包一起编译的,正常情况不会缺失。

还有一步容易被忽略:安装附加模块。PostgreSQL源码包contrib目录下有大量实用扩展,比如pg_stat_statements,这是性能监控的神器。需要这些扩展时不能只make install,还要额外编译contrib部分:

cd contrib make -j 8 make install

我建议即使是初始安装也把contrib编译安装上,因为pg_stat_statements这类功能往往等业务上线后发现需要时才开始配置,届时还得重新编译或改配置重启,不如一次性备好。

4. initdb初始化与配置文件的三个核心改动

4.1 initdb参数逐一拆解

环境变量和PATH配置好之后,用postgres用户执行initdb:

su - postgres export PATH=/usr/local/pgsql13/bin:$PATH initdb -D /data/pgsql13/data -U postgres -E UTF8 --locale=en_US.UTF-8 --auth=scram-sha-256

-D指定数据目录,-U指定超级用户名称,我沿用默认的postgres。-E UTF8指定数据库集群默认字符集,只要不是特殊业务要求,UTF8就是最稳妥的选择。--locale指定区域设置,这个参数其实需要仔细掂量。

如果你的操作系统环境locale是en_US.UTF-8,那么数据库也用它最省心。只有在需要中文排序规则等特殊场景下才考虑用zh_CN类的locale。locale选择在initdb之后就不能随意更改,所以这一步要想清楚再执行。

--auth=scram-sha-256设置了初始认证方式。默认情况下PostgreSQL使用trust认证,也就是本地连接不需要密码,这在自己开发机没什么问题,但放到生产环境无论如何都说不过去。scram-sha-256是目前最安全的密码认证方式,建议从一开始就习惯它。

4.2 postgresql.conf改动点

初始化完成后需要登录服务器修改postgresql.conf。我需要改的主要是以下几项:

listen_addresses = 'localhost' port = 5432 max_connections = 200 shared_buffers = 1GB

listen_addresses默认只监听localhost,意味着只能本机连接。如果业务需要远程访问,这里要改成应用服务器的IP或网段。安全起见,不要直接设置为*,尽量缩小监听范围。

shared_buffers是PostgreSQL最重要的内存参数之一,决定共享缓冲区大小。经验上说,设置为系统物理内存的25%左右是起步推荐,需要结合total memory和实际负载综合评估。我那个测试机是8GB内存,设置为2GB;如果内存紧张,1GB也够基础业务使用。

max_connections同样需要预估业务规模。默认100有时不够用,但也不要盲目调大,因为每个连接都会占用一定内存,设置过大可能导致内存超分配。Pool层有连接池的话,数据库端连接数可以适当压缩。

wal_level建议设置为replica,这是开启流复制的基础配置,即使当前是单机,也应该提前预留这个可能性。fsync默认开启,不必动它。

4.3 环境变量与PATH配置

为了让日常维护命令更顺手,我习惯在postgres用户的bash配置文件里写入以下内容:

export PATH=/usr/local/pgsql13/bin:$PATH export LD_LIBRARY_PATH=/usr/local/pgsql13/lib:$LD_LIBRARY_PATH export PGDATA=/data/pgsql13/data

PATH解决了命令找不到的问题。LD_LIBRARY_PATH很多人会漏掉,如果不设置,psql等客户端工具运行时可能因找不到libpq.so.5而报错。PGDATA可以减少手动输入-D参数的频率。这三行配置写好后,后续维护会轻松得多。

5. 让PostgreSQL像系统服务一样被管理:systemd注册

5.1 为什么不用直接pg_ctl来启动

早期我图省事,曾直接借助pg_ctl启动数据库,进程确实能正常运行,一旦服务器重启就无法自动拉起,而且进程崩溃后也没有自动恢复机制。生产环境必须依赖系统服务管理器来守护进程,在主流Linux发行版上,这套工具就是systemd。

手动方式适合临时验证,服务注册则决定运维自动化能力。使用systemd统一管理数据库实例后,可以取得几个现实的便利:开机自动启动、coredump交给系统统一处理、日志通过journald归集、其他服务可以声明依赖关系等。这些能力对于生产部署都很有价值。

5.2 服务单元文件的关键配置

我在/etc/systemd/system/下创建了postgresql-13.service文件,内容如下:

[Unit] Description=PostgreSQL 13 database server After=network.target [Service] Type=notify User=postgres Group=postgres ExecStart=/usr/local/pgsql13/bin/postgres -D /data/pgsql13/data ExecReload=/bin/kill -HUP $MAINPID KillMode=mixed KillSignal=SIGINT TimeoutSec=300 [Install] WantedBy=multi-user.target

Type=notify是关键项。PostgreSQL在启动完成后会向systemd发送通知,systemd据此判断服务是否真正可用,而不是简单认为进程fork出来就算成功。这也是postgres推荐的运行方式,配置这个选项后systemctl status里能看到更准确的状态。

KillMode=mixed配合SIGINT信号,能让数据库在停止时优雅关闭,先让主进程处理完事务,再终止剩余进程。TimeoutSec=300表示等待数据库关闭的时限,大事务回滚时这个时间可能比较长,别设置得太短导致强杀数据库。

还需要注意PID文件。默认PostgreSQL会把PID写到数据目录的postmaster.pid里。systemd不需要手动指定PIDFile也能工作,但如果你需要精确的进程状态显示,可以在[Service]里加一行PIDFile=/data/pgsql13/data/postmaster.pid,同时确认postgresql.conf里没有把unix_socket_directories改到奇怪的位置。

5.3 服务启停与开机自启验证

配置文件写好后执行:

systemctl daemon-reload systemctl start postgresql-13 systemctl enable postgresql-13

enable之后,整机重启时数据库会自动启动。我建议养成一个习惯:每次修改服务文件后先执行daemon-reload,否则systemd可能还在使用旧的配置缓存。

启动完成后可以用systemctl status postgresql-13查看状态,输出一般会包含进程号、内存占用和最近日志。如果启动失败,用journalctl -u postgresql-13 -n 50查看最近五十行日志信息,绝大多数问题能在这里找到线索。

6. 实际部署中的三个经典报错与排查链路

6.1 configure阶段报缺readline库

我这次编译还算顺利,但在另一台干净的最小化系统上,configure阶段碰到了经典的readline缺失问题。报错信息类似:

configure: error: readline.h not found

这个报错的含义很简单:readline开发头文件没有安装。普通的readline运行时库可能已经存在,但编译还需要头文件,所以必须安装readline-devel。这个问题通常只需:

yum install -y readline-devel

然后重新运行configure即可。值得注意的是,如果之前配置过程已经执行到一半,重新运行前最好清理一下缓存的检查结果,直接删除源码目录并重新解压,避免旧的环境缓存干扰判断。

6.2 psql运行时找不到libpq.so.5

编译安装完成后,我曾在另一台机器的SSH会话里直接执行psql(未加载环境变量),结果提示:

psql: error while loading shared libraries: libpq.so.5: cannot open shared object file

这个问题的根源是动态链接器没有在默认路径下找到PostgreSQL自带的libpq库。源码安装到/usr/local/pgsql13后,该目录不在ld默认搜索路径中。解决方案有三个:

  1. 设置LD_LIBRARY_PATH环境变量(最简单,但只对当前shell生效)
  2. 在/etc/ld.so.conf.d/下新建一个postgresql13.conf文件,写入/usr/local/pgsql13/lib,然后执行ldconfig(系统级生效)
  3. 编译时使用--with-pgport等参数时顺带配置rpath

我推荐第二种,因为用户在交互式shell里可以靠LD_LIBRARY_PATH解决,但定时任务、脚本等非交互环境经常不加载该变量,只有ldconfig才能真正一劳永逸。

6.3 启动时数据目录权限被拒绝

还有一次我在普通用户下执行pg_ctl start,结果日志显示:

FATAL: data directory "/data/pgsql13/data" has invalid permissions

这是因为data目录的属主或权限位不对。PostgreSQL的data目录权限要求是700,并且只能由数据库服务用户访问。用root解压或创建目录后再切换给postgres用户,经常会出现权限位变成755或属主错误的情况。

解决方式很简单:

chown -R postgres:postgres /data/pgsql13 chmod 700 /data/pgsql13/data

顺便提醒一点:PostgreSQL对data目录的安全非常敏感,除了属主本人,任何其他用户(包括root组)都不应具备访问权限。如果发现目录权限异常,通常优先怀疑安装脚本或者备份恢复时使用了root操作。

6.4 端口占用与防火墙拦截

初始化完成后,第一次尝试远程连接时发现连接超时,本地psql却能正常登录。检查systemctl状态一切正常并没有报错,排除了服务问题后,怀疑方向主要是防火墙。

CentOS 7默认使用firewalld,需要放行端口才允许外部访问:

firewall-cmd --permanent --add-port=5432/tcp firewall-cmd --reload

如果是在云平台环境,还需要到安全组里出方向放行对应规则。这些网络层的坑在本地虚拟机里通常不会遇到,一旦部署到云上就容易踩,最好是提前规划好端口清单,在部署前就把防火墙规则一次性配置完毕。

7. 初始化后的验证与日常管理清单

7.1 确认安装信息

数据库启动完成后,第一件事是确认版本信息:

psql -U postgres -c "SELECT version();"

正常输出的version函数会返回类似"PostgreSQL 13.2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5"的完整信息。这里建议同时检查configure参数是否包含预期特性:

pg_config --configure

pg_config会输出编译时的完整配置参数,方便日后升级时参考。我记得当时使用pg_config确认了--with-openssl和--with-perl均成功启用。

7.2 常用维护操作速查

初始化完成后的日常管理命令,我整理一个速查表供参考:

操作命令
启动数据库systemctl start postgresql-13
停止数据库systemctl stop postgresql-13
重启数据库systemctl restart postgresql-13
查看运行状态systemctl status postgresql-13
加载配置(不重启)systemctl reload postgresql-13
查看最近日志journalctl -u postgresql-13 -n 100

如果修改了postgresql.conf里不影响实例运行的参数,用reload即可,不需要重启。修改端口、listen_addresses这类网络参数则需要完整重启。

7.3 本轮源码安装的最终体会

从2021年3月做这次13.2源码安装到现在,中间经历过几次小版本升级,都是同样的套路:下载新源码包、configure、make、make install、重启实例,数据目录始终没有动过。源码安装虽然首次部署比二进制包多花半小时,但它带来的版本控制力和排障灵活性,在后来的运维中体现出了实实在在的价值。如果你也想走这条路,记得把这套流程整理成脚本或文档留在团队内部,半年后再次部署时,你会感谢当初认真记录的自己。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进