这活儿听起来似乎不复杂,无非就是装个 PostgreSQL 而已。但真到了公司内网这种不能访问外网、yum 源形同虚设的机器上,你会发现一个再简单不过的yum install postgresql-server会变成一场依赖包大作战。我在很多内网项目里装过 pgsql,踩过各种各样的坑,从缺库文件到权限问题,再到版本不匹配,前前后后折腾了不少时间。这篇博文就把我的完整安装过程、依赖处理思路和排错经验一次性写出来,希望能帮你少走弯路。内容以 PostgreSQL 16 在 CentOS/RHEL 7/8/9 这类系统上的离线安装为例,核心方法同样适用于其他发行版和版本。
1 环境摸底与安装方案选型
1.1 为什么内网环境下“不能使用 yum”是个大麻烦
很多人在内网机器上第一反应还是敲yum install postgresql-server,结果大概率是报错:Could not resolve host、Cannot find a valid baseurl或者直接卡在加载 mirrorlist 上。原因很简单,内网机器的 yum 源默认指向公网仓库,而公司网络策略不允许访问外网,于是所有网络请求都超时或失败。有人会想:能不能把 yum 源改成内部仓库?这当然是个方向,但前提是你内部得有可用的镜像源,而且版本、架构都要对得上。很多公司并没有维护完整的 PostgreSQL 源,尤其是为新版本单独做镜像的更是少见。因此,纯离线环境的常规做法就是:不用 yum 联网安装,而是提前把 rpm 包准备好,拿到内网后用 rpm 或本地的yum localinstall搞定。
需要注意一件事:这里说的“不能使用 yum”并不是指系统里没有 yum 这个命令,而是指没有可用的远程 yum 源。如果你的内网机器原本就可以正常用 yum 但只是缺 PostgreSQL 的源,那你完全可以走本地源或离线包路线,而不是真的放弃 yum。如果机器上连 yum 都没有,或者为了减少依赖还需要禁用某些插件,那后面讲到的纯 rpm 方式更稳妥。
1.2 三种离线安装姿势,这里选择了哪种
离线装 PostgreSQL,我实际用过三种方法:
- 方式一:rpm 包离线安装。找一台同系统版本、同架构、可以访问公网的机器,把 PostgreSQL 及其依赖的 rpm 全部下载下来,再拷贝到内网安装。优点是安装路径标准、systemd 服务能直接生成、管理起来最符合系统习惯;缺点是依赖解析必须提前做好,否则到现场容易陷入“缺啥补啥”的连环坑。
- 方式二:源码编译安装。把 PostgreSQL 源码包传到内网,
./configure && make && make install。优点是不依赖系统 rpm 包,版本可以自选;缺点是编译耗时较长,而且 PostgreSQL 依赖的 readline、zlib 等库版本也要满足,如果系统本身缺开发包,问题一样绕不开。除非你有特殊定制需求,否则我不推荐在内网环境为了省事去编译。 - 方式三:用容器镜像导入。如果内网允许部署 Docker/Podman,在能访问公网的机器上
docker pull postgres:16,导出成 tar 包再导入,这是最省心的方案。但很多生产内网对容器有严格管控,或者机器资源有限,而且容器存储、网络模式也涉及额外的学习和运维成本。
我最终选择的是方式一。因为 PostgreSQL 官方的 PGDG 仓库提供了针对 RHEL/CentOS 全系版本的 rpm 包,依赖关系清晰,安装后自带 systemd 服务脚本,对后续维护最友好。对于内网环境,这通常是性价比最高的方案。
1.3 核对系统版本与架构:决定你该下载哪套安装包
离线安装最忌讳“拿错包”。在去下载机准备 rpm 之前,务必先在内网机器上把系统信息确认清楚。我一般执行这三条命令:
cat /etc/os-release cat /etc/redhat-release uname -m/etc/os-release能看到系统名称和版本号,比如VERSION_ID="7.9"或"8.8";/etc/redhat-release更直接明确了是 CentOS Linux release 7.9.2009 之类;uname -m返回x86_64,说明是 64 位。这里有个容易忽略的点:同样是 RHEL 8,依赖包和 PostgreSQL rpm 包的命名与依赖关系可能和 RHEL 7 完全不同,例如 RHEL 8 自带的是 PostgreSQL 10 模块,而 PGDG 的仓库布局也有对应分支。所以一定以实际系统版本来选择仓库源。
确认完系统版本和架构后,还要确认 PostgreSQL 版本。内网项目里经常会遇到“老项目要求 pgsql 版本必须一致”的情况,比如已有系统是 PostgreSQL 12,那新装环境最好也装 12,避免升级跨度带来的行为差异。如果没特殊要求,选当前稳定版本即可,比如 16 或 15。这里我以 PostgreSQL 16 为例,后面所有命令和包名都基于 16。
2 离线 rpm 包的准备与依赖分析
2.1 在有公网访问的机器上把依赖包一次拉全
离线安装最核心的一步,就是把所有需要的 rpm 包下载完整。这一步如果做不好,到了内网现场就只能一声叹息。我的做法是:找一台与目标机系统版本、架构一致的“下载机”,这台机器在项目允许的范围内连接公网,配置好 PGDG 仓库后,用yumdownloader或repotrack把需要的包全部拉下来。
先安装下载工具:
yum install -y yum-utils然后添加 PostgreSQL 官方 yum 仓库。不同系统版本对应的仓库 RPM 不同,例如 RHEL/CentOS 7 用pgdg-redhat-repo-latest.noarch.rpm,RHEL 8/9 也有对应的 latest 包。安装仓库后,为了保险起见,通常还要禁用系统自带的 PostgreSQL 模块,避免模块流干扰:
sudo dnf -qy module disable postgresql接下来用repotrack下载指定软件包以及所有依赖:
mkdir -p /opt/pgsql_rpms cd /opt/pgsql_rpms repotrack --repofrompath=pgdg16,https://download.postgresql.org/pub/repos/yum/16/redhat/rhel-8-x86_64 -a x86_64 postgresql16-server postgresql16-contrib如果你不想用 repotrack,也可以用yumdownloader --resolve,但它只下载直接依赖,某些深层依赖可能遗漏。repotrack会把所有依赖层级都拉下来,更可靠。下载目录里通常会有十几到几十个 rpm,包括postgresql16、postgresql16-server、postgresql16-libs、postgresql16-contrib,以及libicu、lz4、systemd-libs等系统依赖。注意,这里拉下来的包有些可能系统中已经存在,比如glibc、libstdc++,为了省事,拷贝到内网时你可以保留全部,到时安装会提示“already installed”,不影响。
如果内网机器不允许你用外部公网仓库,但公司内部有 Postgres 相关的源,你可以repoquery --requires或rpm -qpR来人工核对依赖。比如:
rpm -qpR postgresql16-server-16.1-1PGDG.rhel8.x86_64.rpm这条命令会列出该 rpm 的依赖项,例如postgresql16 = 16.1-1PGDG.rhel8、libpq.so.5()(64bit)、/bin/sh等。这样你就可以对照系统现有 rpm 列表rpm -qa判断哪些缺。这个方法虽然繁琐,但在没有公网下载机的场景下也很有用。
2.2 内网机器上的安装顺序与依赖处理
把 rpm 包拷贝到内网后,不要上来就rpm -ivh *.rpm。这个命令对当前目录下所有 rpm 按字母顺序安装,很可能先装 server 包时依赖还没装上,导致失败。推荐的做法是:先用rpm -ivh安装基础依赖包,再安装 PostgreSQL 主包。如果包比较多,可以使用一条命令逐个安装,让 rpm 自动跳过已安装的包。
最常见也最稳妥的命令是:
yum localinstall -y ./postgresql16-*.rpm ./libicu*.rpm ./lz4*.rpm注意,这里用的是yum localinstall,它不会访问远端仓库,只会在本地目录里进行依赖解析,所以就算内网没有外网,也能正常工作。如果内网机器因为安全要求连 yum 插件都禁用,或者你不想用 yum,那么用纯 rpm 时建议按这个顺序:
rpm -ivh libicu-*.rpm lz4-*.rpm rpm -ivh postgresql16-libs-*.rpm rpm -ivh postgresql16-*.rpm rpm -ivh postgresql16-contrib-*.rpm也可以用一条命令同时指定多个包,rpm 会尝试统一解析依赖关系:
rpm -ivh libicu-*.rpm lz4-*.rpm postgresql16-*.rpm如果提示某个依赖包缺失,比如libpq.so.5 is needed by ...,说明你下载的依赖包不全。别慌,可以直接在系统 ISO 镜像里找同版本基础包,或者从同版本另一台能上网的机器上重新用 repotrack 拉全。实在找不到时,还可以去 PostgreSQL 官方仓库的对应目录下补下对应 rpm,这一步需要提前准备好。
安装完成后,用以下命令确认所有关键包都已经装好:
rpm -qa | grep postgresql正常会看到postgresql16-16.x-*.x86_64、postgresql16-server、postgresql16-libs等。如果还有postgresql16-contrib,后续一些扩展工具也用到。到这里,安装包的战役就结束了。
3 PostgreSQL 初始化配置与启动实操
3.1 初始化数据目录与首启检查
rpm 方式安装 PostgreSQL 后,系统会自动创建postgres用户,但数据目录默认不会自动创建。需要你手动执行初始化脚本。PostgreSQL 16 在 RHEL/CentOS 上的初始化命令通常是:
/usr/pgsql-16/bin/postgresql-16-setup initdb这条脚本会读取默认的PGDATA环境变量,通常是/var/lib/pgsql/16/data,并在该目录下生成PG_VERSION、postgresql.conf、pg_hba.conf等初始文件。如果执行时提示initdb: error: cannot be run as root,那是因为你用的是 root 用户,脚本内部应该会自动以 postgres 用户执行;但如果没切换,你需要手动转为 postgres 用户执行:
su - postgres -c "/usr/pgsql-16/bin/initdb -D /var/lib/pgsql/16/data"用系统脚本的好处是它会自动维护数据目录的属主和权限,不容易踩权限坑。初始化完成后,先别急着改配置,先启动一次服务试试:
systemctl start postgresql-16 systemctl status postgresql-16 --no-pager如果看到Active: active (running),说明基础安装成功。这时候可以先以 postgres 用户连接一次本地数据库,验证初始化没问题:
su - postgres psql -c "SELECT version();"如果返回 PostgreSQL 16.x,说明数据目录和 socket 连接都正常。如果没有找到psql命令,大概率是 PATH 没包含/usr/pgsql-16/bin,具体问题在下一节会提到。
3.2 配置 postgresql.conf:监听地址、端口与内存参数
初始化后的 PostgreSQL 默认只监听localhost,listen_addresses是空值。内网里如果想要让其他机器连接,必须改配置。配置文件路径在/var/lib/pgsql/16/data/postgresql.conf。我习惯用 sed 快速修改,也可以编辑文件:
sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /var/lib/pgsql/16/data/postgresql.conf sed -i "s/#port = 5432/port = 5432/" /var/lib/pgsql/16/data/postgresql.conflisten_addresses = '*'表示监听所有网卡地址。如果只想监听内网某个固定 IP,建议写成具体 IP,减小暴露面。端口方面,内网如果已经有其他 PostgreSQL 实例,注意别冲突,选一个不常用的端口更安全。
内存参数方面,shared_buffers默认值通常很小,比如 128MB,生产环境根据机器内存调整。一般建议设置为物理内存的 1/4 到 1/3 左右,但不能超过系统共享内存上限。你可以先看下机器内存:
free -h假设机器是 16GB 内存,可以设置为 4GB:
sed -i "s/#shared_buffers = 128MB/shared_buffers = 4GB/" /var/lib/pgsql/16/data/postgresql.conf同样,max_connections默认 100,如果内网业务并发不高可以保持默认;如果应用连接池开得大,可以适当调大。改配置后必须重启服务:
systemctl restart postgresql-163.3 配置 pg_hba.conf:内网访问规则与认证方式
光改监听地址还不够,PostgreSQL 默认的pg_hba.conf只允许本地通过ident或peer认证,远端连接会直接被拒绝。需要新增或修改一条规则,允许内网网段访问,并指定密码认证方式。打开/var/lib/pgsql/16/data/pg_hba.conf,找到类似下面这行的位置:
host all all 127.0.0.1/32 ident在其下方添加:
host all all 192.168.0.0/16 md5如果你的 PostgreSQL 16 默认认证方式推荐使用scram-sha-256,这比md5更安全,且 PostgreSQL 14+ 默认密码加密方式已经是scram-sha-256。建议写成:
host all all 192.168.0.0/16 scram-sha-256这里192.168.0.0/16只是示例网段,实际按公司内网网段替换。改完pg_hba.conf后,不一定需要重启服务,只需要让服务重新读取配置。可以用pg_ctl reload,也可以:
systemctl reload postgresql-16然后给数据库用户设置一个密码,否则远端连接没有密码可用:
su - postgres psql -c "ALTER USER postgres PASSWORD '你的强密码';"3.4 开放防火墙端口:别让数据包到不了 PostgreSQL
很多内网服务器默认开着 firewalld,即使 PostgreSQL 监听了所有地址,外部机器访问也会被防火墙拦截。先看防火墙状态:
systemctl status firewalld如果服务是active (running),需要添加端口放行规则。比如 5432 端口:
firewall-cmd --permanent --add-port=5432/tcp firewall-cmd --reload如果内网安全策略要求只允许特定来源,可以加--add-rich-rule限制来源 IP 或网段:
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=192.168.10.0/24 port port=5432 protocol=tcp accept' firewall-cmd --reload到这里,服务端基本可以接受从内网其他机器过来的连接了。在另外一台内网机器上可以用psql或telnet测试端口连通性:
telnet <数据库服务器IP> 5432如果不能 telnet,大概率还是防火墙没放行;如果能 telnet 但 psql 连不上,那就是 pg_hba 或监听地址的问题。
4 实操踩坑记录与排查速查
4.1 命令找不到:psql 不在 PATH 里
常见的坑,装完 PostgreSQL 后,root 用户执行psql提示command not found。原因是 PostgreSQL 16 的可执行文件在/usr/pgsql-16/bin下,但这个目录没有加到默认 PATH 中。你可以用全路径执行:/usr/pgsql-16/bin/psql。为了以后方便,可以把路径写进用户配置文件,比如在/etc/profile.d/pgsql.sh中写入:
export PATH=$PATH:/usr/pgsql-16/bin然后执行source /etc/profile.d/pgsql.sh,这样所有用户都能直接用psql、pg_dump等命令。
4.2 初始化失败:Permission denied on data directory
如果手动运行 initdb 时用了 root 用户,而没有切换为 postgres 用户,初始化会直接失败。因为 PostgreSQL 不允许 root 用户运行 initdb,数据目录也会被写入 root 属主,后续服务启动时 postgres 用户无权访问。修复方法是把数据目录属主改回 postgres:
chown -R postgres:postgres /var/lib/pgsql/16/data如果是使用系统脚本初始化的,一般不会出现这个问题。但如果你手动指定了一个自定义目录,比如/data/pgsql,记得先创建目录并修改属主:
mkdir -p /data/pgsql chown -R postgres:postgres /data/pgsql su - postgres -c "/usr/pgsql-16/bin/initdb -D /data/pgsql"4.3 依赖包缺失:libpq.so.5 was not found
有时候你下载的 rpm 包不全,或者系统里已经装了旧版本 libpq,安装 server 包时会报缺少libpq.so.5。先看系统里有没有 libpq 相关库文件:
ldconfig -p | grep libpq如果没有,去已下载的postgresql16-libsrpm 里解包确认,或重新用 repotrack 拉取依赖。安装后如果还是找不到,执行ldconfig刷新一下库缓存:
ldconfig再来一个深坑:如果系统里有多个 PostgreSQL 版本的 libpq,比如同时装了 postgresql12-libs 和 postgresql16-libs,libpq.so.5可能被软链到了不同版本。这时候可以通过设置LD_LIBRARY_PATH或调整/etc/ld.so.conf.d下的路径优先级来解决,但更推荐的做法是把旧版本清理干净,避免客户端的psql版本与 server 不匹配导致奇怪问题。
4.4 认证失败:Password authentication failed for user "postgres"
远端连接时最常见的是这个错误。原因通常是pg_hba.conf里的规则认证方式不是密码认证,或者密码设置不对。排查思路:先确认你提交的网段是否匹配了正确的规则行。PostgreSQL 是自上而下匹配第一条规则的,如果你的127.0.0.1/32 ident写在前面,而客户端从内网 IP 连接时没匹配到你新增的192.168.0.0/16规则,可能就落到默认的拒绝规则上。检查pg_hba.conf规则顺序,把具体的、范围小的规则放在前面。另外,如果数据库用户密码用的加密方式为scram-sha-256,而pg_hba.conf中的规则写的是md5,也可能导致认证失败。要么统一改成scram-sha-256,要么确认客户端驱动支持对应认证方式。
4.5 在无外网环境下怎么补依赖包:系统 ISO 和已装实例是两大法宝
实战中总会出现依赖包遗漏的情况。这时候别慌,先看手头有没有两个资源:第一,操作系统安装 ISO 镜像。将 ISO 挂载后,里面通常有大量基础 rpm 包,在Packages目录下,可以按需安装:
mount -o loop /opt/rhel8.iso /mnt find /mnt -name "libicu*.rpm" -o -name "lz4*.rpm"第二,内网其他机器上是否已经装过相同版本的 PostgreSQL。如果有一台已经跑起来的机器,可以用rpm -qa查看它装了什么包,再到另一台机器上对比差异,这样能快速定位缺的是哪个包。在准备阶段如果能提前在一台测试机上完整装一遍,后面拿到生产环境就会顺畅很多。
4.6 服务无法启动:status exited with code 1
初始化完成后systemctl start postgresql-16失败,日志中经常会有FATAL: could not open log file或FATAL: data directory "/var/lib/pgsql/16/data" does not exist这类提示。前者多半是日志目录或数据目录权限不对;后者则是没有执行初始化。看详细日志可以用:
journalctl -u postgresql-16 --no-pager这是排查服务启动问题最直接的方法。还可以看数据目录下的日志文件,通常在/var/lib/pgsql/16/data/log下,里面的 postgresql-*.log 会记录具体错误。最常见的组合是:忘记初始化 + 权限不对,连踩两个坑。
4.7 开机自启与默认配置检查
装好后别忘设置开机自启,否则服务器一重启 PostgreSQL 就没了:
systemctl enable postgresql-16检查是否已启用:
systemctl is-enabled postgresql-16如果返回enabled,就万事大吉。我还在实际项目里遇到过一种情况:服务正常启动了,也能本地连接,但客户端无法连接,最后发现是postgresql.conf里的listen_addresses配置文件权限被改坏,或者配置行里面有不可见字符。这种问题排查起来很烦,建议先将配置恢复成默认,再用 sed 或 vim 重新修改,改完后用下面命令检查一下语法:
/usr/pgsql-16/bin/pg_ctl -D /var/lib/pgsql/16/data -m fast reload如果配置有误会明确提示。这算是一个不错的验收手段。
安装 PostgreSQL 的事,到了这儿基本算落地了。整个过程里最花时间的往往不是安装本身,而是提前准备依赖和排错。我在实际中反复体会到:离线安装前一定要把系统版本、架构、安装版本这三件事钉死,再用repotrack把依赖包一次拉全,到了现场先初始化、再改配置、最后放防火墙,这个顺序走下来一般不会有大问题。如果真遇到特殊情况,优先翻日志而不是瞎试,journalctl和pg_ctl reload是排查服务问题最好用的两个工具。希望这篇记录能帮你把那台不能联网的机器上的 PostgreSQL 顺利跑起来。