这段时间在腾讯云上给团队搭了一套项目环境,核心需求是把 PostgreSQL 跑起来供多个业务模块使用。折腾了一圈,最终选了 Ubuntu 24.04 + Docker 的方式来完成部署。整个过程走下来,发现网上一堆教程都是“复制粘贴”式的,很多关键细节没讲到,尤其是 Ubuntu 24.04 这个版本和 Docker 的组合,踩了不少坑才跑通。所以干脆把这套部署流程从头到尾整理出来,包括我自己的选型思考、每一步操作的原因、参数怎么配置,以及排错经验,希望对正在做同样事情的你有点帮助。
1. 为什么在腾讯云上我坚持用 Docker 部署 PostgreSQL
1.1 容器化和传统安装方式的真实差异
很多人习惯直接在 Ubuntu 上用 apt 装 PostgreSQL,这是我强烈不建议在生产服务器上做的事。apt 安装的 PostgreSQL 通常不是最新稳定版,而且一旦系统升级或者你手动改了配置文件,整个环境就变得不可控了。容器化部署最大的优势在于环境隔离和一致性——你在本地 Ubuntu 上能跑起来的 PostgreSQL,在腾讯云上同样能跑,不会因为系统底包、glibc 版本、依赖库的差异导致各种莫名其妙的问题。
另外,Docker 容器的启动、停止、重启速度比 systemctl 管理原生服务要快得多,这在日常运维中体验非常明显。举个例子,我调整 postgresql.conf 参数后需要重启数据库让配置生效,用 Docker 的方式就一句话docker restart pg-container,两三秒搞定。原生安装你还得先sudo systemctl restart postgresql,然后看日志确认有没有启动失败,处理起来要啰嗦不少。
还有一个很实际的理由:未来不管你换服务器还是做多机部署,镜像一套过去就行,数据放数据卷里,业务代码和数据库配置完全分离。这种“基础设施即代码”的思路,在团队协作时特别有用——新同事加入,直接把 docker-compose.yml 给他,一条命令起来一个一模一样的数据库环境,省去了写一堆初始化文档的时间。
1.2 数据安全与迁移:Volume 解决换服务器的核心痛点
容器化部署最容易被新手忽略的是数据卷的概念。如果没有把 PostgreSQL 的数据目录挂载到宿主机,容器一删除,数据库里的所有数据就彻底没了,而且找不回来——这是很多第一次用 Docker 跑数据库的人踩过的最痛的坑。
我的建议是初始化第一秒就规划好数据目录。在腾讯云服务器上,数据尽量不要放在系统盘里,因为系统盘在实例重装或升级时会有被格式化的风险。我会单独买一块云硬盘挂载到/data目录,然后让 PostgreSQL 容器把数据写到/data/postgresql下面。这样一来,即使整个服务器实例挂了,把云硬盘卸载后挂到新实例上,重新执行 docker run 挂载同一个目录,数据完好无损。这个操作对数据库这种“生命线”级别的服务来说,价值远高于那点挂载配置的成本。
除了数据目录,还要注意容器本身的“一次性”属性。PostgreSQL 容器可以随时删掉重新创建,只要数据卷还在,删除容器不会影响数据库里的内容。这听起来好像没什么,实际操作中意义很大——你升级 PostgreSQL 镜像、调整启动参数、修改端口映射,都只需要用一个新容器替换旧容器,旧容器删掉就行,根本不用操心数据迁移问题。
1.3 不用云数据库 RDS,自己部署的核心原因
肯定有人会问:腾讯云不是有现成的云数据库 PostgreSQL 吗?直接用不就行了,干嘛还要自己折腾?说实话,云数据库确实省心,但有几个场景让我最终选择了自建。
第一,成本问题。云数据库 RDS 的定价比纯云服务器要高不少,尤其是对数据量增长快的业务,存储费用和备份费用都是额外支出。如果只是个人项目或中小团队内部系统,自己部署能把成本压得很低。
第二,控制权和灵活性。云数据库有各种限制,比如某些参数不允许修改、插件安装受限、超级用户权限拿不到。自建的话,postgresql.conf 里每个参数都能调,想装什么扩展就装什么扩展,出了问题也能直接用超级用户登录排查,不会被平台的管控策略卡住。
第三,数据迁移的便利性。云数据库要做跨平台迁移或者迁移回自建环境,过程相对复杂。但自建数据库的数据要带到任何地方都简单——pg_dump 导出,或者直接拷贝整个数据目录,到哪都能恢复。
当然,如果你的业务对可用性的要求极高,比如金融、电商秒杀这些场景,那云数据库的自动容灾和 SLA 保障还是很有价值的。但作为一般业务部署,自己在腾讯云上用 Docker 跑 PostgreSQL,兼顾了成本、灵活性和可控性,是性价比很高的选择。
2. 腾讯云实例与 Ubuntu 24.04 的初始化细节
2.1 实例规格与镜像选择:2C4G 够不够用
腾讯云上创建实例的时候,配置选择直接决定了后面的使用体验。我这次用的是 2核4G 的 Standard S5,对大多数中小型业务来说已经完全够用了。如果你是要跑并发量比较高的业务,我建议至少 4核8G 起步,但如果是团队内部系统、个人项目或者测试环境,2C4G 完全没问题。
系统镜像方面,我选择的是 Ubuntu 24.04 LTS,也就是代码名称 Nobl。选择 LTS 版本一个最重要的原因就是维护周期长,标准支持到 2029 年,安全更新不断档,不像非 LTS 版本一年就要催你升级。另外 Ubuntu 24.04 的软件仓库更新比较及时,对 Docker 和 PostgreSQL 这类常用组件的兼容性也测得多,踩坑概率小。
还有一个容易忽略的是系统盘大小。腾讯云默认给的是 50G 系统盘,我直接改成了 100G,因为 Docker 镜像、容器日志、PostgreSQL 的 WAL 日志、备份文件都会占空间。系统盘不够用再去扩,既麻烦还可能短暂的不可用,不如一开始就留足余量。
登录方式建议直接用密钥对而不是密码登录。腾讯云控制台创建实例的时候可以绑定密钥,日常 SSH 登录就不用输密码了,也更安全。如果实在要用密码,尽量设置一个足够强度的复杂密码,并且不要用默认的 root 账号直接登录,建议创建一个普通用户加上 sudo 权限。这一套下来,服务器的基础环境才算是稳了。
2.2 安全组规则与腾讯云的两层防火墙理解
腾讯云的网络安全策略和普通机房不太一样,它的安全组相当于在虚拟机外面的第一层防火墙。你的 Ubuntu 系统内部可能还有 ufw 或者 iptables,构成了第二层防护。这两层之间的关系是“与”的关系——流量必须同时通过两层放行才能到达你的服务。
对于 PostgreSQL 来说,默认端口是 5432。很多人部署完成后发现宿主机怎么都连不上数据库,腰果大半都出在安全组没放行。腾讯云安全组规则里,入站规则必须添加一条“TCP:5432”的放行规则,而且我建议在“来源”那一栏填你办公网络的公网 IP,不要图省事填0.0.0.0/0。数据库这种东西暴露在公网上,等于把自己的数据开放给全世界的扫描器,分分钟被爆破或者勒索。等我部署好之后,业务服务器的内网 IP 也会加进安全组来源,这样只能内网访问,更安全。
系统内部的防火墙,我的建议是直接关闭 ufw,理由后面具体说。因为 Docker 在操作 iptables 的方式比较特殊,如果开了 ufw 再叠加 Docker,很容易出现规则相互覆盖,造成端口看起来放行了却连不上,或者相反——你以为关了端口结果公网还能访问。在腾讯云这种已经有安全组外置防护的环境下,把防火墙这一层交给云平台,服务器内部就别再叠加了,逻辑简单还不容易出问题。
2.3 系统初始化:时区、源、基础工具一次搞定
拿到新服务器后,我通常先做一套基础初始化流程,避免后面部署的时候各种小问题。Ubuntu 24.04 的 apt 源默认用的是官方镜像,国内访问有时候会比较慢,建议直接换成腾讯云镜像源。操作方式是编辑/etc/apt/sources.list.d/ubuntu.sources,把archive.ubuntu.com和security.ubuntu.com替换成mirrors.cloud.tencent.com,然后执行apt update。如果你的服务器是在腾讯云买的,内网访问腾讯云镜像源速度更快,这点很关键。
时区设置也需要处理。默认情况下 Ubuntu 用的是 UTC 时间,数据库记录日志、时间戳都会比北京时间慢 8 小时,调试问题时很容易产生混淆。执行timedatectl set-timezone Asia/Shanghai把时区调回来,同时确认一下系统时间是否和北京时间一致。PostgreSQL 容器内部也要做同样的时区配置,这个后面启动容器的时候再设置。
基础工具方面,我一般会装vim、curl、wget、git、htop、tree、net-tools这些,日常排查问题的时候都能用上。另外推荐装一个unzip,因为后面如果要从备份文件恢复数据,处理压缩包是避不开的操作。
3. Docker 引擎安装:Ubuntu 24.04 的坑和官方源的正确姿势
3.1 不要用 apt 自带的 docker.io,直接上官方仓库
Ubuntu 24.04 的软件源里面确实有 docker.io 这个包,但我不建议通过这种方式安装 Docker。原因有几个:第一,apt 仓库里的 Docker 版本相对滞后,可能不是最新稳定版;第二,docker.io 包和 Docker 官方仓库安装的引擎在某些插件、网络特性上有差异,遇到问题你查 Stack Overflow 都可能因为版本不一致而对不上号。
正确姿势是添加 Docker 官方 apt 源,然后安装 Docker Engine。在 Ubuntu 24.04 上,执行下面的命令:
# 安装依赖 sudo apt update sudo apt install -y ca-certificates curl gnupg # 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 添加 Docker apt 仓库 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装 Docker Engine sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里有个容易踩的坑:curl 下载 GPG 密钥时,如果网络不好或者 DNS 有问题,有可能下载到空文件或者不完整的文件,然后添加到 apt 源的时候会报错。解决办法是先把密钥文件下载看下大小,如果ls -l看到文件很小,比如只有几字节,基本就是下载出了问题,重新执行一次 curl 就好。
安装完成后执行sudo systemctl enable --now docker让 Docker 开机自启,然后运行docker version确认安装成功。如果看到 Client 和 Server 都有版本号输出,说明 Docker 引擎正常在运行。Server 部分显示不出来的时候,多半是 daemon 没有起来,检查一下/var/log/syslog里 dockerd 相关的报错。
3.2 Ubuntu 24.04 的 iptables-nft 与 Docker 的兼容性问题
Ubuntu 从 22.04 开始就把默认防火墙从 iptables 切换到了 nftables,24.04 延续了这个设定。Docker 引擎的端口映射和网络隔离底层是通过 iptables 规则实现的,而 Ubuntu 24.04 的 iptables 命令实际上是一个指向 nftables 的兼容层。绝大多数情况下这个兼容层工作得很好,但你如果手动写了 iptables 规则,或者开了 ufw,就很可能和 Docker 的网络规则打架。
我遇到过的具体情况是:ufw 的规则和 Docker 的 FORWARD 链规则相互覆盖,导致 Docker 映射出来的端口从外面访问不通。排查了半天,发现是 ufw 默认 DROP 策略把 Docker 的流量也拦了。这个问题的根源在于 Docker 会在 iptables 的 DOCKER 链里插入规则,但 ufw 的规则加载顺序会覆盖掉一部分。
所以我的建议非常明确:在使用腾讯云安全组的前提下,直接在 Ubuntu 内部禁用 ufw,让 Docker 完全接管 iptables 规则。执行:
sudo systemctl stop ufw sudo systemctl disable ufw如果你实在需要在系统层面做防火墙控制,建议用 Docker 的--publish端口映射暴露范围来限制,不要再用 ufw 叠加。比如只映射到某个内网 IP 上:-p 192.168.1.10:5432:5432,这样只有访问该 IP 的流量才会到达 PostgreSQL 容器,比 ufw 规则直观多了。
3.3 让非 root 用户直接管理 Docker
安装完 Docker 后,你会发现直接运行docker ps会报权限错误,提示无法连接 Docker daemon,因为 Docker 要求 root 权限。官方推荐的做法是把你的用户加入 docker 用户组,这样每次不用 sudo 前缀也能执行 docker 命令。
sudo groupadd docker sudo usermod -aG docker $USER newgrp docker执行完后重新登录服务器,运行docker ps就能正常看到容器列表了。这一步看似简单,但很多人会忽略一个安全细节:docker 组的用户其实等同于 root 权限,因为 Docker 客户端可以通过各种方式提权。所以在团队服务器上,不要随便把人加进 docker 组,最好是只给真正需要管理容器的人。
4. PostgreSQL 容器启动参数逐项解析
4.1 数据目录规划与专属 Docker 网络
万事俱备,开始重头戏:启动 PostgreSQL 容器。第一步先规划好数据目录,我习惯在/data下建一个postgresql目录:
sudo mkdir -p /data/postgresql这个目录将作为数据卷挂载到容器内部。PostgreSQL 官方镜像的数据目录是/var/lib/postgresql/data,所以启动命令里会有-v /data/postgresql:/var/lib/postgresql/data这一段。
然后是网络规划。如果你只是跑一个数据库容器,直接用 Docker 默认的 bridge 网络也没问题。但如果后面还有其他容器要一起协作,比如应用容器需要连接数据库,我建议创建一个自定义网络。自定义网络的好处是容器之间可以用服务名互相访问,而不是依赖每次可能变化的 IP 地址。
docker network create app-network这样后续启动其他容器的时候,只要都加入app-network,在应用容器里直接连postgres这个主机名就能访问数据库,非常方便。
4.2 首次启动必设参数:密码、数据目录、时区、字符集
现在正式启动容器。我用的命令是:
docker run -d \ --name postgres \ --restart always \ --network app-network \ -e POSTGRES_USER=myuser \ -e POSTGRES_PASSWORD='Your_Strong_Password' \ -e POSTGRES_DB=mydb \ -e TZ=Asia/Shanghai \ -e PGTZ=Asia/Shanghai \ -p 5432:5432 \ -v /data/postgresql:/var/lib/postgresql/data \ postgres:16逐项解释一下每个参数的含义。-d表示后台运行,--name postgres给容器起名叫 postgres,方便后续操作。--restart always让容器在意外退出或服务器重启时自动拉起,这是生产环境必备的,否则服务器一重启,数据库不会自动启动,业务直接挂掉。
环境变量方面,POSTGRES_USER和POSTGRES_PASSWORD是启动时初始化超级用户用的。这里有个容易忽略的点:这两个变量只在数据目录为空、首次初始化的时候生效。如果你的数据目录里已经有一份旧数据,容器启动时会完全忽略这两个变量,直接用数据目录里的用户信息。所以在数据目录是空的第一次启动时,务必把密码设成强密码,并且记好。
POSTGRES_DB指定初始化时额外创建的数据库。默认情况下镜像会创建一个和POSTGRES_USER同名的数据库,这个变量让你额外创建一个业务数据库。TZ和PGTZ设置时区,不设置的话容器默认 UTC,数据库里now()返回的时间和北京时间差 8 小时,后面查日志、统计数据会非常别扭。
-p 5432:5432把宿主机的 5432 端口映射到容器的 5432 端口。如果担心安全,可以改成-p 127.0.0.1:5432:5432,这样数据库只在服务器本地可访问,外部连不进来;但这样应用容器也得在同一个宿主机上才能连,跨服务器的场景就不适用了。我一般会根据实际需求判断:如果数据库只给同服务器的应用用,就绑定 127.0.0.1;如果有其他服务器需要远程访问,就按实际来源 IP 配置安全组放行。
镜像版本我用的postgres:16。PostgreSQL 16 是目前发布比较久的稳定版了,在性能、逻辑复制、Vacuum 等方面都有了明显改进。如果你之前用的是 14 或 15,建议尽快升级到 16;如果是全新部署,直接上 16 肯定是不会错的选择。
4.3 端口映射与连接测试
启动完成后,先用一条命令确认容器的运行状态:
docker ps | grep postgres看到状态是Up就说明容器正常在运行。接下来可以测试本地连接:
docker exec -it postgres psql -U myuser -d mydb能进入 psql 交互界面,说明容器内部一切正常。然后测试宿主机到容器的连接:
psql -h 127.0.0.1 -p 5432 -U myuser -d mydb这一步如果你本机没有安装 psql 客户端,也可以直接用 Python 或者别的工具连一下试试。
如果在宿主机上连接失败,优先排查两个地方:第一,端口映射是否生效,ss -tlnp | grep 5432看端口有没有监听;第二,PostgreSQL 的监听地址是否正确。默认情况下镜像里的listen_addresses是*,也就是监听所有网卡,如果这个参数被改成了localhost,那么从宿主机用 127.0.0.1 连接反而会失败,注意区分。
最后是从你自己的电脑上远程连接测试。先在腾讯云安全组里确认放行了 5432 端口的入站规则,然后本地用 Navicat、DBeaver 这些工具连服务器的公网 IP,能连上就算是大功告成。
5. 针对腾讯云规格的 PostgreSQL 参数调优
5.1 shared_buffers、effective_cache_size 与内存预算
PostgreSQL 跑起来之后,如果不调优就上线,性能大概率不理想。默认配置是为最小环境设计的,相当于把一台跑车的发动机限速在 30 码。不同的服务器规格需要不同参数配置,在 2C4G 的腾讯云实例上,我建议这样设置。
先看shared_buffers,这是 PostgreSQL 的共享内存缓冲区,用来缓存数据页。官方文档推荐设置为系统内存的 25%,2C4G 就是 1GB。设置太大会导致系统内存不足,触发 swap 反而拖慢性能。
effective_cache_size是一个告诉优化器“操作系统层还有多少缓存可用”的估算值,不是实际分配的内存。一般设置为系统内存的 50%~75%。2C4G 我设置为 3GB。这个值主要影响查询计划器对索引扫描和顺序扫描的选择,设置合理了,优化器更容易选择使用索引,加快查询速度。
还有work_mem,这个参数控制排序和哈希操作的内存上限。注意它不是全局内存,而是每个排序操作都会用这么多,所以并发高的时候总内存消耗会成倍上涨。2C4G 我设置为 16MB,因为并发连接不多所以 16MB 是安全的;如果并发很高,建议更低一点,比如 8MB,避免内存暴涨。
安全修改参数的方式是编辑容器内的postgresql.conf:
docker exec -it postgres bash -c "echo 'shared_buffers = 1GB' >> /var/lib/postgresql/data/postgresql.conf"不过我不太推荐直接 docker exec 进去改文件,因为容器重建之后你得重新执行一遍。更优雅的做法是把postgresql.conf所在目录整体挂载到宿主机,或者在宿主机上写好一个自定义配置文件,启动时用-c参数覆盖。比如:
docker exec -it postgres psql -U myuser -d mydb -c "ALTER SYSTEM SET shared_buffers = '1GB';" docker restart postgresALTER SYSTEM SET会修改 PostgreSQL 的postgresql.auto.conf文件,重启后自动生效,这样既不用进容器改文件,也能保持配置的统一管理。我实际生产环境用的就是这种方式,比手动改 postgresql.conf 规范得多。
5.2 WAL 与 Checkpointer 参数:写入密集场景的核心
如果你是要跑写入比较频繁的业务,WAL 相关的参数会直接影响性能。WAL(Write-Ahead Logging)是 PostgreSQL 的预写日志,相当于操作日志,所有的数据修改先写 WAL 再落盘,保证崩溃时能恢复。
max_wal_size和min_wal_size控制 WAL 文件的大小范围。默认的max_wal_size = 1GB对写入密集场景偏小,会导致 checkpoint 更频繁地触发,而 checkpoint 本身是耗时操作,频繁触发会影响插入和更新性能。2C4G 我设置为max_wal_size = 2GB,min_wal_size = 80MB。
checkpoint_timeout默认是 5 分钟,意思是每 5 分钟至少触发一次 checkpoint。如果 WAL 文件增速很快,5 分钟一到就 checkpoint,有可能跟不上写入速度。我把这个参数调到 15 分钟,配合加大max_wal_size,大幅减少 checkpoint 频率。
wal_buffers默认是 16MB,对于大多数场景够用,一般不需要动。如果你遇到大量写操作争抢 WAL 缓冲区锁,可以考虑提高到 32MB,但这种情况通常是连接数过高,先查连接池再调参数。
另外,如果你的业务对数据丢失零容忍,可以开启synchronous_commit = on,但这会降低写入性能。对自己部署的数据库,通常用默认的on就好,因为 Docker 跑在同一台宿主机上,丢失数据的概率很小。
5.3 连接数、客户端认证与日志参数
max_connections默认是 100,对一般场景够用。但要注意,每个连接在 PostgreSQL 内部都是一个进程,会占用一定内存。2C4G 的实例如果开满 100 个连接,内存压力会很大。我建议把应用层的连接池配好(比如 pgbouncer),把实际活跃连接控制在 20~30 个以内,远比盲目调大max_connections靠谱。
client_encoding和lc_messages建议都设为UTF8,尤其是有中文数据存储需求的时候。如果初始化集群时没有用 UTF8,后面要转编码非常麻烦,数据可能乱码。启动容器的时候可以加一个-e POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=C.UTF-8",确保初始化的数据库就是 UTF8 编码。
日志方面的参考配置是:
logging_collector = on log_directory = 'log' log_filename = 'postgresql-%Y-%m-%d.log' log_statement = 'ddl' log_min_duration_statement = 1000log_statement = 'ddl'只记录 DDL 操作,不至于日志爆炸;log_min_duration_statement = 1000把超过 1 秒的慢查询记录下来,方便后续做性能优化时定位问题。日志文件默认在/var/lib/postgresql/data/log目录,正好也在我们挂载的宿主机数据卷里,备份和查看都很方便。
5.4 这些参数我不建议动
前面推荐的都是有明确收益的调整,但有些参数在 2C4G 这种规格下,最好别随便改。
autovacuum保持默认开启即可,这是 PostgreSQL 自动清理死行、维护数据健康的核心机制,关闭它会让表膨胀得越来越严重,查询越来越慢。fsync保持 on,虽然关了能提升安全性,但一旦断电或系统崩溃,数据文件和 WAL 不一致,整个数据库可能直接无法启动,这种风险绝对不能冒。full_page_writes也不要关,它和 crash recovery 直接相关,关闭后若系统崩溃,可能发生数据页损坏。
还有一点容易被忽略:Docker 容器默认的存储驱动是 overlay2,如果底层文件系统本身性能一般,数据库的 IO 会明显受限。腾讯云 SSD 云硬盘的性能还可以,但不建议买那种低配的 HDD 云硬盘来跑数据库,IO 延迟会让人抓狂。
6. 日常运维:备份、升级与故障恢复
6.1 用 pg_dump 做定时逻辑备份,避免灾难性损失
PostgreSQL 跑起来只是第一步,备份策略才是日常运维的重头戏。最常用的备份工具是pg_dump,它导出的是逻辑数据——即 SQL 语句,可以跨版本恢复。
我习惯把备份任务写到宿主机的一个脚本里,然后用 cron 定时执行。脚本如下:
#!/bin/bash BACKUP_DIR="/data/backup/postgresql" DATE=$(date +%Y%m%d_%H%M%S) DATABASE_NAME="mydb" # 确保备份目录存在 mkdir -p $BACKUP_DIR # 执行备份 docker exec postgres pg_dump -U myuser -d $DATABASE_NAME | gzip > $BACKUP_DIR/${DATABASE_NAME}_${DATE}.sql.gz # 保留最近 7 天备份,清理更早的文件 find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -exec rm -f {} \;给脚本加执行权限,然后crontab -e添加一行:
0 2 * * * /data/scripts/backup_postgres.sh >> /data/scripts/backup.log 2>&1这个方案每天晚上 2 点做一次全量逻辑备份,保留 7 天,足够应对大部分误删数据、升级失败等场景。恢复的时候执行:
gunzip < /data/backup/postgresql/mydb_xxxx.sql.gz | docker exec -i postgres psql -U myuser -d mydb逻辑备份适合中小规模数据,如果数据量上了 TB 级别,就要考虑用物理备份方案了,比如 pg_basebackup 或者第三方工具,这里先不展开。
6.2 容器镜像升级的完整步骤和注意事项
PostgreSQL 出新版本,或者你想从 16 升级到 17,Docker 容器的方式有清晰的步骤。但有一点必须反复强调:绝对不要直接 docker pull 新镜像然后 restart 容器,因为数据目录的版本不兼容,容器可能直接启动失败。
完整升级流程是这样的:
- 备份当前数据:按上面的脚本来一次全量备份。
- 停止应用写入:确保没有业务连接数据库。如果业务无法完全暂停,至少要把连接降到最低。
- 记录当前容器配置:把 docker inspect 里的运行参数记下来,尤其是挂载卷、端口映射、环境变量这些。建议平时就把启动命令写进 docker-compose.yml 或者脚本里,省得到时候手忙脚乱。
- 停止并删除旧容器:
docker stop postgres && docker rm postgres。注意,只要数据卷目录还在,数据就不会丢。 - 用新镜像启动新容器:比如把
postgres:16改成postgres:17,同样的挂载卷和环境变量。启动后看日志,如果报数据版本不兼容,那就继续用旧镜像,或者用 pg_upgrade 走正式的版本升级流程。
PostgreSQL 的跨大版本升级不能靠数据目录直接替换,因为系统表结构会变,数据格式也可能不同。如果是从 16 升 17,个人推荐直接用 pg_dump 导出再导入新库,简单可靠;数据量实在太大,才考虑 pg_upgrade。
6.3 日志查看、容器健康检查和自动重启
日常运维中,日志是最重要的第一手排错信息。PostgreSQL 容器的日志可以用docker logs查看:
docker logs postgres docker logs --tail 100 -f postgres--tail 100只看最近 100 行,-f持续跟踪输出。容器崩溃的时候,先用 docker logs 看日志往往能直接定位到原因,比猜谜语效率高得多。
如果希望 Docker 能更智能地判断容器状态,可以配置健康检查。修改启动命令加一段:
docker run -d \ ... --health-cmd="pg_isready -U myuser -d mydb" \ --health-interval=30s \ --health-timeout=5s \ --health-retries=3 \ postgres:16pg_isready是 PostgreSQL 自带的健康检查工具,能确认数据库是否能够接受连接。配置之后,docker ps里会显示 healthy 状态,容器异常时也能快速被主机发现。
--restart always已经保证了容器会随机器启动,但如果 PostgreSQL 进程挂了而容器没挂,仅靠 restart 策略是不够的。健康检查配合 Docker 的自动重启策略(或者用系统 systemd unit 管理容器服务)才能做到自动拉起。有精力的话可以把容器交给 systemd 管理,用systemctl restart docker-postgres控制,逻辑会更清晰。
7. 踩坑实录:从“容器退出”到“数据丢失”的完整排查链路
7.1 容器一启动就退出:PostgreSQL 日志正在告诉你原因
我遇到的第一个问题是:按照网上教程执行 docker run 之后,容器状态从 Up 变成 Exited,一两秒就退出了。这种情况新手最容易慌,但其实八成是启动参数或者数据目录权限的问题,先看日志:
docker logs postgres日志里出现FATAL: data directory "/var/lib/postgresql/data" has wrong ownership时,说明宿主机挂载目录的属主不对。PostgreSQL 容器内是用 postgres 用户来运行数据库的,UID 是 999,它需要对你挂载出来的数据目录有读写权限。解决办法是:
sudo chown -R 999:999 /data/postgresql还有一种情况是数据目录不干净。如果之前有别的版本或别的方式初始化过这个目录,里面残留了旧文件,PostgreSQL 启动时会报错,提示数据目录非空或者版本不匹配。对付这种问题,把目录清空再启动即可——前提是你已经确认不需要旧数据了。
我强烈建议:第一次启动时先不要急着挂载数据卷,直接用容器默认路径跑通,确认业务没问题后再考虑挂载卷迁移。这样可以避免一上来就把权限、路径问题混在一起,排查成本高。
7.2 连接超时和拒绝访问:安全组、listen_addresses、pg_hba.conf 三座大山
第二个让我折腾最久的坑,是部署完以后从本地电脑怎么都连不上数据库。通过docker ps看容器明明是 Up,在服务器上用 127.0.0.1 也能连上,但换成本地就超时或者访问被拒绝。
这个问题的根源通常是三选一,按排查顺序来:
腾讯云安全组没放行。这是最常见的。控制台 → 实例 → 安全组 → 入站规则,检查 5432 端口是否有 TCP 放行规则。特别注意来源 IP 是不是包含了你本地电脑的公网 IP。很多人在这一步填了
0.0.0.0/0,那当然能连上,但风险极大;如果填了指定 IP,就要确保你的地址没变过。PostgreSQL 只监听了 localhost。默认情况下 postgres 镜像的
listen_addresses是*,也就是说监听所有网卡。但如果你之前用其他方式改过配置,它可能只在 127.0.0.1 上监听。在服务器上执行ss -tlnp | grep 5432,看监听地址是什么。如果是 127.0.0.1:5432,那就说明需要改listen_addresses。pg_hba.conf 的认证规则不允许远程登录。这个文件控制哪些 IP 可以用什么方式认证。默认镜像里的 pg_hba.conf 允许所有地址用
scram-sha-256认证,但如果你想从特定网段连,确认没有规则拦截。检查方法是在容器里执行docker exec postgres cat /var/lib/postgresql/data/pg_hba.conf,找到类似host all all 0.0.0.0/0 scram-sha-256的行。
如果这三项都设置正确,从本地用 psql 或者 DBeaver 连接,理论上能正常通。还连不上,就需要检查云服务器的外网带宽、路由是否有异常了。
7.3 重启后数据没了:永远不要把数据库文件放在容器可写层
最后也是最重要的一坑:有同事在服务器上执行docker rm -f postgres然后重新跑了一个容器,发现数据库里的表全没了。我当时看着他操作屏幕,简直想拍桌子——这就是没做数据卷挂载的后果。
如果你只是在 docker run 的时候用了postgres:16镜像,没有指定-v参数,PostgreSQL 默认把数据写到容器内部的/var/lib/postgresql/data。这个路径在容器被删除时,会连同容器一起消失。Docker 的设计理念是“容器是可丢弃的”,但数据必须是持久的,所以一切状态数据都必须放到卷里。
如果你已经遇到这个问题了,先冷静,能找回来一部分的概率并不为零。如果容器还没被删除,只是停止了,可以先docker start postgres,然后把数据目录拷贝出来:
docker cp postgres:/var/lib/postgresql/data /data/postgresql_data_backup如果容器已经被删了,但 Docker 的 overlay2 层还没被清理(没有执行过docker system prune),理论上还能从/var/lib/docker/overlay2/下通过搜索PG_VERSION文件找到残留数据,但这个过程很痛苦,不是所有场景都能成功恢复。
防患于未然的做法很简单:启动命令里必须有-v /data/postgresql:/var/lib/postgresql/data,并且用docker inspect postgres可以确认挂载是否正确。更方便的做法是把启动命令固化成 docker-compose.yml 文件,每次启动都通过同一个文件来管理,既能保留配置,也天然避免了漏挂载的情况。
这套部署方案我从零到一跑通了,中间踩的每一个坑现在回想起来都值得。数据卷挂载和参数调优这两个点,是我觉得最需要提前规划好的——数据库部署不像普通服务,一旦数据量上去、业务跑起来,再改底层结构代价就大了。如果你也在腾讯云上折腾 Ubuntu 24.04 + Docker + PostgreSQL 的组合,按照这套流程走一遍,至少能少走很多弯路。