1. 内网装pgsql:先搞清楚你面对的环境和安装路线
前一阵公司内部有个新项目要上线,数据库指定用PostgreSQL(以下简称pgsql)。项目服务器在内网,不能连外网,yum源基本是摆设,直接执行yum install postgresql-server大概率报“Cannot find a valid baseurl for repo”。这种场景在政企、金融、制造业项目里太常见了:不是网络断掉,就是不让你访问外部仓库,要么就是仓库里压根没有pgsql的相关包。这篇文章就把我在这种环境下把pgsql装起来的完整过程拆开讲一遍,从离线包准备到内网安装、启动、开机自启、常见报错处理,全都会覆盖到。适合刚接手内网服务器配置的运维、做项目实施交付的工程师,还有被突然分配“把数据库装好”这个任务的后端开发参考。
拿到这种任务先别急着下载安装包,多数人栽跟头都是因为没想清楚内网到底是什么状态。我这里说的“不能使用yum”其实至少有三种情况:第一种是服务器完全没有yum或者yum配置全部指向无效地址;第二种是yum能用,但内网仓库里只有系统基础包,没有pgsql相关包;第三种是yum源需要走代理才能访问,但代理没配置好。不同情况对应的解决路径完全不一样。如果你只是缺少pgsql的包,那最省事的办法是找公司内部有没有镜像站;如果整个yum就是死的,那只能走纯离线安装,这也是我下面重点讲的场景。
1.1 确认系统版本、内核和yum的真实状态
动手之前,先花两分钟把下面这几个信息弄清楚,后面能少踩一个星期的坑:
cat /etc/os-release uname -m uname -r yum repolist/etc/os-release看的是大版本,比如CentOS 7.9、CentOS 8.5、openEuler 20.03还是Ubuntu 20.04。uname -m确定架构,x86_64还是aarch64,这个直接决定下载包的类型。uname -r看内核版本,有些pgsql扩展对内核版本敏感,尤其是涉及GPU加速、高级存储插件的时候,普通场景不需要太在意。yum repolist则是确认当前仓库到底能不能列出可用软件包,很多时候不是yum不可用,而是仓库配置文件的baseurl被写成了一个内网不存在的地址,修一下就能用。
如果发现是仓库地址问题,可以检查/etc/yum.repos.d/下的文件,看看是不是存在指向外网域名或者已经被禁用的baseurl。内网环境经常有专门的本地镜像源,地址长得像http://192.168.x.x/centos/7.9/os/x86_64,问一下公司基础设施的同事就能拿到。但大多数情况下,即便yum仓库正常,里面也不会有pgsql,因为pgsql不在默认的base仓库里,需要额外添加PostgreSQL官方仓库或者EPEL仓库。内网环境根本访问不了这些外部仓库,所以最终还是得靠离线包。
1.2 rpm、tar源码编译,到底选哪条路
pgsql离线安装通常就两条路:rpm包安装或者源码编译安装。我在不同项目里两种都试过,这里直接说结论:能搞到rpm包就优先用rpm包,实在搞不到再考虑源码编译。
rpm包的好处是安装路径规范、依赖关系由系统管理、升级卸载方便,很多发行版还会自动帮你生成systemd服务脚本。坏处是依赖复杂,一句话概括就是“装一个pg,可能要连带装十个依赖包”,而且pgsql的rpm包对系统基础库版本有要求,比如CentOS 7上装PostgreSQL 16,可能会因为glibc、libstdc++版本不够而装不上。你可以选择比较保守的版本,或者去官方源里找对应系统的包。
源码编译的好处是不受系统包管理器的限制,安装路径自己定,编译参数自己选,很多从零搭建的封闭环境里反而更灵活。坏处也很明显:你需要在内网服务器上提前准备好gcc、make、readline-devel、zlib-devel等一大堆编译工具和库。如果一个内网连基本编译工具链都没有,源码编译根本走不通,最后还是得去外面找rpm包带进来。
所以我的建议是:先确认内网服务器上有没有gcc --version,有没有/usr/bin/make,有没有libreadline.so相关文件。如果有,源码编译可以作为备选;如果没有,那你无论如何都得准备一套完整的rpm包,否则这条路也堵死了。从操作难度和维护成本综合看,rpm包方式明显更稳,下面先重点讲离线rpm包的准备。
2. 在外网准备好一整包可携带的离线依赖
离线安装最难的不是装,而是在一个能够上网的机器上把内网安装所需要的所有包,一个不漏地拉下来。很多新手以为只要把那个postgresql-server的rpm拷过去就完事了,结果到内网一执行rpm -ivh,立刻报缺依赖,然后陷入“缺一个找一个”的死循环。原因很简单:pgsql依赖libpq、libicu、openssl-libs等一堆系统库,这些库在内网机器上可能存在,但版本不对;也可能压根没装。
2.1 优先选哪台机器做“打包机”
准备离线包这件事,最理想的位置不是随便找个能上网的电脑,而是要找到一台和内网服务器系统版本一致的机器。什么意思?如果你要装的目标内网服务器是CentOS 7.9 x86_64,那么你也要找一台CentOS 7.9 x86_64的外网机器来执行下载,不要用CentOS 8去下载CentOS 7的包。这背后的原因在于rpm依赖的so库版本。CentOS 8上的libicu版本可能是60以上,CentOS 7上的libicu是50,如果把CentOS 8上编译好的pgsql包拿到CentOS 7上装,大概率会报依赖libicuuc.so.60找不到。别问我怎么知道的,这种事我踩过不止一次。
如果没有同版本的外网机器,还有一个替代方案:用相同大版本的官方rpm仓库里的包。比如PostgreSQL官方提供的PGDG源,里面有很多不同发行版、不同架构的子目录,你可以通过手动指定URL直接下载CentOS 7的子包。但风险同样存在,因为PGDG里的包不一定区分所有小版本,它编译时的基线可能高于你的系统。所以强烈建议找一台同系统的打包机器,这是最省心的。
2.2 用repotrack拉全依赖,而不是手工copy
确认好外网打包机后,第一件事是配置PostgreSQL官方yum源。以CentOS 7为例:
yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm装好后,仓库里会出现pgdg14、pgdg15等子仓库,具体版本看你的需要。然后安装辅助工具:
yum install -y yum-utils这里重点说一下repotrack和yumdownloader的区别。yumdownloader加--resolve也能解析依赖,但我在实际使用中经常发现它漏掉一些“弱依赖”,比如某些子包、语言包,结果到了内网就是报缺文件。repotrack会把指定包的所有依赖递归拉全,甚至包括一些rpm元数据依赖,虽然包更多更占空间,但至少不会漏。
以pgsql 14为例,我们需要下载postgresql14-server,以及常用的postgresql14-contrib(扩展包)。想覆盖绝大多数场景,最好把postgresql14、postgresql14-server、postgresql14-libs、postgresql14-contrib、postgresql14-plperl一起拉下来。命令如下:
mkdir -p /pg_rpms repotrack -a x86_64 -p /pg_rpms \ postgresql14 \ postgresql14-server \ postgresql14-libs \ postgresql14-contrib \ postgresql14-plperl如果没有配置PGDG源,也可以直接从rpmfind这类镜像站下载单包,但这样依赖关系很难理清。我一般习惯用repotrack,因为最终拉下来的是一个相对封闭的包集合,很多情况下甚至能凑齐内网服务器缺的系统依赖。
2.3 离线包传输和完整性校验
包下载完成后,不要急着拷贝。先做两件事。第一,把所有包打包成一个tar文件,避免小文件在U盘拷贝过程中丢失。第二,生成校验值,方便到内网后验证。
cd / tar czvf pg_rpms.tar.gz pg_rpms md5sum pg_rpms.tar.gz > pg_rpms.md5 sha256sum pg_rpms.tar.gz >> pg_rpms.md5传输用U盘或者内网共享目录都行,但注意不要从Windows直接解压tar包,因为Windows上解压可能会破坏符号链接和可执行权限,最好的方式是整个tar包拷贝到Linux服务器后再解压。到了内网服务器后:
tar xzvf pg_rpms.tar.gz -C / cd /pg_rpms md5sum -c pg_rpms.md5这里有个细节:实际在服务器执行时,pg_rpms.md5文件里的路径是/pg_rpms/...,如果目录结构一致就可以校验。如果不一致,可以手动用md5sum <文件>对比。校验通过后再继续。
3. 内网离线安装的完整操作流程
离线包准备好以后,真正的安装就开始了。这个环节最考验细心程度。我先讲rpm方式,这是最推荐的;再讲源码编译方式,因为总有一些场景rpm包搞不定,比如特殊CPU架构、内网系统版本太老、或者你需要自定义编译参数。
3.1 rpm包方式:从依赖检查到安装落地
进入/pg_rpms后,不要急着执行安装,先用rpm -ivh --test做一次预演。什么意思?rpm命令有一个--test模式,它不是真的安装,而是模拟安装过程并报告依赖错误。这个习惯能帮你提前发现问题,避免装到一半系统环境被改坏。
cd /pg_rpms rpm -ivh --test *.rpm如果输出一堆“failed dependencies”,先看缺的是什么。常见缺的是libpq.so.5、libicuuc.so.50、libreadline.so.6这种系统库。很多系统基础包默认是装了的,但版本对不上,比如内网服务器上的libicu是50版本,而pgsql需要60版本,此时就要把在外网下载的对应依赖rpm包一起装上。这些包都在/pg_rpms里,repotrack已经帮你拉下来了。
确认预演通过后,直接安装:
rpm -ivh *.rpm如果遇到个别包提示“already installed”,可以使用rpm -Uvh --replacepkgs强行覆盖安装,但要注意别覆盖掉系统正在运行的核心库文件。另一种更省事的方式是:
rpm -Uvh --replacepkgs --replacefiles *.rpm这个命令在依赖全部满足的情况下能一次性把所有包处理完,但风险也大,强烈建议不要在生产库服务器上这样搞。更好的顺序是:先装postgresql14-libs,再装postgresql14,再装postgresql14-server,最后装postgresql14-contrib和postgresql14-plperl。手动控制顺序可以更清晰地处理冲突。
安装完成后,验证一下可执行文件是否就位:
/usr/pgsql-14/bin/postgres --versionrpm方式默认把pgsql装到/usr/pgsql-14/,数据目录默认是/var/lib/pgsql/14/data/。如果你不想用默认目录,完全可以初始化到独立数据分区,后面会讲到。
3.2 源码编译方式:configure、make、install全记录
如果rpm包找不到,或者内网系统差异太大,源码编译就是唯一的生路。先提前准备好编译工具链:gcc、make、readline-devel、zlib-devel、openssl-devel、perl-devel、python3-devel。这些依赖也可以在外网机器上用repotrack一次性拉到内网。不要以为源码编译就不需要依赖了,它只是把rpm依赖换成了编译依赖,缺了readline库,configure阶段就会直接报错。
从PostgreSQL官网下载源码包:
wget https://ftp.postgresql.org/pub/source/v14.10/postgresql-14.10.tar.gz tar xzf postgresql-14.10.tar.gz cd postgresql-14.10配置安装路径和编译参数:
./configure --prefix=/opt/pgsql --with-openssl --with-perl --with-python这里--prefix=/opt/pgsql表示把pgsql安装到/opt/pgsql目录,好处是不污染系统原生目录,后期想卸载直接删文件夹。如果你确定不要Perl或Python支持,可以去掉对应--with参数,减少依赖。configure 通过后就开始编译:
make -j$(nproc) make installmake -j$(nproc)是用多核并行编译,能明显加快速度。但老服务器如果内存紧张,并行编译可能因为内存不足而失败,这时改成make -j2更稳。
编译安装默认不带contrib扩展,如果你需要一些常用扩展(比如pg_stat_statements、hstore、postgis等),在源码目录里执行:
make -C contrib install部分扩展可能还需要额外的系统库,比如postgis需要geos、proj,这种复杂的扩展建议在编译前就确认好依赖,否则后面会折腾很久。
编译安装完成后,还需要手动创建系统用户和数据目录:
useradd postgres mkdir -p /data/pgsql chown postgres:postgres /data/pgsql3.3 初始化数据库并调整核心配置
无论是rpm安装还是源码编译,数据库都不是装完就能直接用的,必须先执行初始化。
如果是rpm安装:
/usr/pgsql-14/bin/postgresql-14-setup initdb或者手动初始化:
su - postgres -c "/usr/pgsql-14/bin/initdb -D /var/lib/pgsql/14/data -E UTF8"如果是源码编译安装,因为没有现成的setup脚本,需要手动执行:
su - postgres -c "/opt/pgsql/bin/initdb -D /data/pgsql/data -E UTF8 --locale=C"这里我特别说一下--locale=C的来历。很多人在内网服务器上执行initdb -E UTF8时报错,说invalid locale name,因为系统里根本没有配置en_US.UTF-8或zh_CN.UTF-8这个locale。这种情况下直接指定--locale=C最省事,或者先执行localedef -i en_US -f UTF-8 en_US.UTF-8把locale生成出来再初始化。考虑到内网环境很多基础工具都不全,直接--locale=C是最不容易出问题的选择。
初始化完成后,编辑核心配置文件。rpm安装的配置文件在/var/lib/pgsql/14/data/postgresql.conf,源码编译的在/data/pgsql/data/postgresql.conf。至少要改这几个参数:
listen_addresses = '*' port = 5432 unix_socket_directories = '/var/run/postgresql' max_connections = 200 shared_buffers = 512MBlisten_addresses改成*是为了允许远程连接,否则只监听本地回环地址,其他机器根本连不上。shared_buffers的大小一般是物理内存的1/4,可以后续根据业务调整。改完后还要修改客户端认证文件pg_hba.conf,默认内容是只允许本地连接,对内网环境肯定不够。
如果你知道内网网段是192.168.10.0/24,在pg_hba.conf文件末尾加一行:
host all all 192.168.10.0/24 scram-sha-256认证方式建议使用scram-sha-256,不要用md5,因为pgsql 14及以上默认密码加密方式已经切换成scram。如果用md5,连接时会提示认证方式不匹配。如果实在需要兼容老客户端,再考虑改成md5。
4. 启动服务、开机自启和内网问题排查实录
安装、初始化和基础配置都完成后,接下来就是启动和自启设置。这一块看起来简单,但很多内网项目最后就卡在起不来或者重启后数据库没跟着起来。尤其是源码编译安装的用户,没有现成的systemd脚本,需要自己写。
4.1 用systemd管好PostgreSQL
rpm安装的pgsql会自动生成systemd服务单元。CentOS 7下默认的服务名是postgresql-14,直接执行:
systemctl enable postgresql-14 systemctl start postgresql-14 systemctl status postgresql-14如果服务状态是active (running),说明已经成功跑起来了。
源码编译安装没有现成服务,需要手动创建/etc/systemd/system/postgresql.service,内容可以这样写:
[Unit] Description=PostgreSQL 14 database server After=network.target [Service] User=postgres Group=postgres Type=forking PIDFile=/data/pgsql/data/postmaster.pid ExecStart=/opt/pgsql/bin/pg_ctl -D /data/pgsql/data -l /data/pgsql/logfile start ExecStop=/opt/pgsql/bin/pg_ctl -D /data/pgsql/data stop ExecReload=/opt/pgsql/bin/pg_ctl -D /data/pgsql/data reload Restart=on-failure TimeoutSec=300 [Install] WantedBy=multi-user.target写完后执行:
systemctl daemon-reload systemctl enable postgresql systemctl start postgresql注意PIDFile要指向真实生成的postmaster.pid文件路径。如果数据目录里找不到这个文件,说明数据库根本没有成功启动,此时systemd会显示成一个失败的循环,反复Restart=on-failure。遇到这种情况不要慌,直接看/data/pgsql/logfile日志文件,里面会有具体的报错原因。
还有一种情况:你用root用户直接执行了pg_ctl,但服务里指定了User=postgres。这样会导致数据目录里的文件权限全部变成了root,服务启动时报could not change permissions of directory。解决方法是确保所有数据文件归属postgres用户:
chown -R postgres:postgres /data/pgsql包括日志文件所在的目录也要一并处理。
4.2 高发报错和对应处理
我在内网环境装pgsql时,最常见的启动失败报错就这么几种,整理成一个速查表,建议收藏:
| 报错信息 | 原因分析 | 处理办法 |
|---|---|---|
| FATAL: could not create shared memory segment | 系统内核共享内存参数太小,或shared_buffers设置过大 | 调小shared_buffers为128MB,或执行sysctl -w kernel.shmmax=1073741824并写进/etc/sysctl.conf |
| could not change permissions of directory | 数据目录或日志目录的属主不是postgres | 执行chown -R postgres:postgres /data/pgsql |
| psql: could not connect to server: No such file or directory | 没有启动成功,或socket路径与客户端查找路径不一致 | 查看日志确认启动状态;psql连接时用-h /var/run/postgresql指定socket目录 |
| FATAL: lock file "postmaster.pid" already exists | 上次数据库异常退出留下残留PID文件 | 先确认没有pgsql进程,再删除postmaster.pid后重启 |
| database "postgres" does not exist | 初始化后没有默认数据库?一般不会,但如果自定义了数据目录且手工建库漏掉 | 用initdb重新初始化,或执行createdb postgres |
有针对性的展开几个。could not create shared memory segment这类问题是内网老服务器上最常见的,因为内核参数kernel.shmseg、kernel.shmmax默认值比较保守,而pgsql默认的shared_buffers如果设置成2GB甚至更高,就会超出共享内存限制。临时改一下:
sysctl -w kernel.shmmax=17179869184 sysctl -w kernel.shmall=4194304永久生效就追加到/etc/sysctl.conf后执行sysctl -p。
postmaster.pid残留问题也经常遇到,特别是在服务器异常断电时。残留PID文件里的进程ID已经不存在,但数据库启动时检测到文件存在,就拒绝启动。解决办法是先ps -ef | grep postgres确认没有进程,再删除这个文件。
4.3 内网收敛认证和访问控制
数据库能跑起来只是第一步,内网环境的安全配置同样重要。很多研发同学把数据库起起来后,发现别的机器连不上,第一反应是配置问题,但更多时候是防火墙挡住了端口。在CentOS 7上先确认firewalld是否在运行:
systemctl status firewalld如果运行中,放行5432端口:
firewall-cmd --permanent --add-port=5432/tcp firewall-cmd --reload如果内网比较老,用的是iptables,则需要额外处理:
iptables -A INPUT -p tcp --dport 5432 -j ACCEPT service iptables save数据库密码认证也要处理。初始化后默认的postgres用户密码是未设置的,本地通过peer认证可以直接连。远程要连接,需要先给postgres用户设置密码:
sudo -u postgres /usr/pgsql-14/bin/psql -c "ALTER USER postgres PASSWORD '强密码';"如果你用的是源码编译安装,路径换成/opt/pgsql/bin/psql。注意,pg_hba.conf中如果配置了scram-sha-256,那么这里的密码加密就是安全的。不要再用ident或trust方式,内网环境虽然相对可信,但一旦有测试脚本误连,trsut认证的权限放开会造成数据风险。
生产内网通常还会要求只允许特定应用服务器连接数据库。这时候不要写0.0.0.0/0,最好精确到网段或单个IP。比如只允许10.0.20.5这台应用服务器访问:
host all all 10.0.20.5/32 scram-sha-256pg_hba.conf改动后不需要重启数据库,执行SELECT pg_reload_conf();即可生效,或者用systemctl reload postgresql。
我在实际项目里还遇到过一个情况:内网机器用同一个数据库账号连不上的原因是DNS反向解析问题,导致pgsql处理认证时变慢。解决办法是在postgresql.conf中设置:
log_hostname = off这样就不进行反向DNS解析,连接响应会快很多,尤其是在没有内网DNS的纯IP环境里。
最后再分享一个内网安装极其容易踩到的坑:数据库数据目录所在磁盘空间不足。很多内网机器的根分区只有20G,装完系统已经用了大半,如果再放一个pgsql的数据文件,初期看不出问题,跑一段时间就会卡死。强烈建议在初始化前用df -h看下挂载点,尽量把数据目录放到独立的数据盘或存储挂载点上。rpm安装默认的/var/lib/pgsql如果在根分区,可以考虑移动数据目录,方法是用systemctl stop postgresql-14后,把整个14/data目录移动到/data/pgsql,然后创建软链接:
systemctl stop postgresql-14 mv /var/lib/pgsql/14/data /data/pgsql_data ln -s /data/pgsql_data /var/lib/pgsql/14/data systemctl start postgresql-14这个操作本质上就是把数据目录挪到空间充足的挂载点,很多运维初次接触会担心破坏文件,其实pgsql只认目录路径,软链接完全有效,而且后续备份恢复也方便。
我个人的体会是,内网装pgsql这件事,80%的时间都耗在依赖准备上,真正的安装过程可能十分钟就结束了。如果你在外网打包阶段足够细心,把每一个可能的依赖都拉全,那到内网的安装体验会非常顺滑。反过来,如果贪图省事只拷了一个server包,那在内网来回找依赖的滋味会让你记住很久。做离线的关键原则就是一个字:全。宁可多带两个用不上的包,也不要漏掉一个关键的so库。这个习惯救过我很多次。