news 2026/6/7 16:48:51

PostgreSQL 主从复制从零搭建:本地高可用数据库怎么远程安全验证?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL 主从复制从零搭建:本地高可用数据库怎么远程安全验证?

PostgreSQL 主从复制从零搭建:本地高可用数据库怎么远程安全验证?

标签:PostgreSQL、Docker、数据库、cpolar、内网穿透

本地服务最怕的不是代码报错,而是数据库一挂,接口、后台任务、管理页面一起停摆。真要给小团队做一套高可用数据库验证环境,光把 PostgreSQL 跑起来不够,还得确认主库写入后,从库能不能稳定读到数据。

这篇不讲“数据库挂了怎么办”的大叙事,直接做一套能落地的本地实验:用 Docker Compose 启动 PostgreSQL 主库、只读从库和 pgAdmin,再用psql查复制状态。到验证阶段,再临时用 cpolar 暴露 pgAdmin 或只读从库端口,让远程同事帮忙看连接、复制状态和查询结果。

划重点:这套方案只用于开发、测试、演示和复制链路验证,不要把生产主库写入口长期暴露出去。

1 什么是 PostgreSQL 主从流复制?

PostgreSQL 的主从流复制,说白了就是主库负责写入,从库持续接收主库产生的 WAL 日志并回放。业务把数据写到主库后,从库跟着同步,适合做只读查询、备份验证、读写分离前的链路演练。

这篇里我们只做一件事:确认“主库写入 → WAL 传输 → 从库回放 → 远程只读验证”这条链路是通的。

这里先不做自动故障切换。故障切换涉及 VIP、代理层、提升从库、应用连接重试,一篇文章塞进去反而容易写乱。先把复制跑稳,后面再扩展 Patroni、repmgr 或 HAProxy 会更清楚。

本次实验的端口安排如下:

服务容器名容器端口宿主机访问
PostgreSQL 主库pg-primary5432127.0.0.1:5432
PostgreSQL 从库pg-replica5432127.0.0.1:5433
pgAdminpgadmin80http://127.0.0.1:5050

提醒一下,主库和从库在 Compose 网络里都使用 5432;只是映射到宿主机时,从库改成了 5433,避免端口冲突。

2 环境准备:目录、Docker 和账号先定好

这一步先把目录和文件放整齐。后面排错时,能一眼看出是主库初始化脚本、从库拉基线脚本,还是 Compose 配置出了问题。

2.1 创建项目目录

在一台已经安装 Docker 和 Docker Compose v2 的机器上执行:

mkdir -p ~/pg-replication-lab/primary/init mkdir -p ~/pg-replication-lab/replica cd ~/pg-replication-lab

检查 Docker Compose 是否可用:

docker compose version

能看到版本号就继续。这里建议直接使用docker compose,不要再用老的docker-compose命令,后面的命令都按 Compose v2 写。

2.2 准备主库初始化脚本

主库启动时要做两件关键事:创建复制账号,并允许这个账号从 Docker 网络内发起 replication 连接。

新建文件primary/init/01-primary.sh

cat > primary/init/01-primary.sh <<'EOF' #!/bin/bash set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<'EOSQL' CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'repl_pass_2026'; CREATE ROLE app_readonly WITH LOGIN PASSWORD 'readonly_pass_2026'; EOSQL cat >> "$PGDATA/pg_hba.conf" <<'EOF_HBA' host replication replicator 0.0.0.0/0 scram-sha-256 host all app_readonly 0.0.0.0/0 scram-sha-256 EOF_HBA EOF chmod +x primary/init/01-primary.sh

这里别把replicator当业务账号用,它只负责从主库拉 WAL。后面远程验证查询时,用app_readonly这个只读账号,不把主库超级用户密码发给别人。

2.3 准备从库初始化脚本

从库第一次启动时,要用pg_basebackup从主库拉一份基线数据。-R参数会写入复制配置,让从库知道以后从哪个主库继续接收 WAL。

新建文件replica/replica-entrypoint.sh

cat > replica/replica-entrypoint.sh <<'EOF' #!/bin/bash set -euo pipefail mkdir -p "$PGDATA" chown -R postgres:postgres "$PGDATA" chmod 700 "$PGDATA" if [ ! -s "$PGDATA/PG_VERSION" ]; then echo "waiting for primary..." until gosu postgres pg_isready -h pg-primary -p 5432 -U postgres; do sleep 2 done echo "running pg_basebackup..." rm -rf "$PGDATA"/* export PGPASSWORD="$REPLICATOR_PASSWORD" gosu postgres pg_basebackup \ -h pg-primary \ -p 5432 \ -D "$PGDATA" \ -U replicator \ -v \ -P \ -R \ -X stream \ -C \ -S replica_slot unset PGPASSWORD fi exec gosu postgres postgres \ -c hot_standby=on \ -c listen_addresses='*' EOF chmod +x replica/replica-entrypoint.sh

这段脚本里有一个容易卡住的点:pg_basebackup必须等主库真正 ready 后再执行,所以前面用了pg_isready循环等待。否则从库容器启动得太快,会直接连不上主库。

3 使用 Docker Compose 搭建主库、从库和 pgAdmin

现在把三个服务编排到一个文件里。主库负责写,从库负责读,pgAdmin 负责图形化验证。

新建docker-compose.yml

cat > docker-compose.yml <<'EOF' services: pg-primary: image: postgres:16 container_name: pg-primary environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: primary_pass_2026 POSTGRES_DB: appdb command: - postgres - -c - wal_level=replica - -c - max_wal_senders=10 - -c - max_replication_slots=10 - -c - hot_standby=on - -c - listen_addresses=* volumes: - pg_primary_data:/var/lib/postgresql/data - ./primary/init:/docker-entrypoint-initdb.d:ro ports: - "127.0.0.1:5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d appdb"] interval: 5s timeout: 5s retries: 20 networks: - pgnet pg-replica: image: postgres:16 container_name: pg-replica environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: replica_local_pass_2026 POSTGRES_DB: appdb REPLICATOR_PASSWORD: repl_pass_2026 PGDATA: /var/lib/postgresql/data entrypoint: ["/replica-entrypoint.sh"] volumes: - pg_replica_data:/var/lib/postgresql/data - ./replica/replica-entrypoint.sh:/replica-entrypoint.sh:ro ports: - "127.0.0.1:5433:5432" depends_on: pg-primary: condition: service_healthy networks: - pgnet pgadmin: image: dpage/pgadmin4:latest container_name: pgadmin environment: PGADMIN_DEFAULT_EMAIL: admin@example.com PGADMIN_DEFAULT_PASSWORD: pgadmin_pass_2026 ports: - "127.0.0.1:5050:80" depends_on: pg-primary: condition: service_healthy networks: - pgnet volumes: pg_primary_data: pg_replica_data: networks: pgnet: driver: bridge EOF

启动服务:

docker compose up -d

看容器状态:

docker compose ps

如果从库一直重启,先看日志,不要急着删数据卷:

docker compose logs -f pg-replica

正常情况下,日志里能看到pg_basebackup的进度,随后从库进入接收 WAL 的状态。这里建议第一次搭建时别开太多服务,先让主从复制跑稳,后面再接应用更轻松。

4 验证主从复制:主库写,从库读

复制链路不是看容器都在运行就算成功。我们要做三层验证:主库能看到从库连接、从库确认自己处于恢复状态、主库写入的数据能在从库查到。

4.1 在主库建表并写入数据

进入主库执行 SQL:

docker exec -it pg-primary psql -U postgres -d appdb

psql里执行:

CREATE TABLE IF NOT EXISTS demo_orders ( id BIGSERIAL PRIMARY KEY, order_no TEXT NOT NULL, amount NUMERIC(10, 2) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); INSERT INTO demo_orders (order_no, amount) VALUES ('ORD-20260607-001', 99.90), ('ORD-20260607-002', 128.50); GRANT CONNECT ON DATABASE appdb TO app_readonly; GRANT USAGE ON SCHEMA public TO app_readonly; GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_readonly;

这里顺手给app_readonly授权,是为了后面验证只读连接。真实项目里建议给业务表单独授权,不要偷懒把写权限也给出去。

4.2 在从库查询数据

从宿主机连从库端口5433

PGPASSWORD=readonly_pass_2026 psql \ -h 127.0.0.1 \ -p 5433 \ -U app_readonly \ -d appdb \ -c "SELECT id, order_no, amount FROM demo_orders ORDER BY id;"

能看到两条订单记录,就说明主库写入已经同步到从库。

再确认从库是只读恢复状态:

docker exec -it pg-replica psql -U postgres -d appdb \ -c "SELECT pg_is_in_recovery();"

返回t表示当前实例是从库。这个检查很重要,别只看端口连通;有些环境里连错端口,会把主库当从库测。

4.3 在主库查看复制连接

回到主库查pg_stat_replication

docker exec -it pg-primary psql -U postgres -d appdb \ -c "SELECT application_name, client_addr, state, sync_state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"

能看到一行从库连接,statestreaming,说明 WAL 正在传输。

如果这里没有记录,按这个顺序查:

  • 从库日志里有没有pg_basebackup或连接认证错误;
  • 主库pg_hba.conf是否允许replicator做 replication 连接;
  • REPLICATOR_PASSWORD是否和主库创建的repl_pass_2026一致;
  • 两个容器是否在同一个 Compose 网络pgnet里。

复制延迟也可以直接查 WAL 位置差异:

docker exec -it pg-primary psql -U postgres -d appdb \ -c "SELECT pg_current_wal_lsn();" docker exec -it pg-replica psql -U postgres -d appdb \ -c "SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();"

本地 Docker 环境里,写入量很小时延迟通常很低。重点不是追求漂亮数字,而是要能定位卡在“主库没发、网络没通、从库没回放”哪一段。

5 用 pgAdmin 或 psql 做图形化验证

命令行验证够直接,但团队协作时,pgAdmin 这种 Web 管理界面更适合让同事快速看结果。

浏览器打开:

http://127.0.0.1:5050

登录信息:

Email: admin@example.com Password: pgadmin_pass_2026

在 pgAdmin 里添加主库连接时,填写:

主库配置
Host name/addresspg-primary
Port5432
Maintenance databaseappdb
Usernamepostgres
Passwordprimary_pass_2026

添加从库连接时,填写:

从库配置
Host name/addresspg-replica
Port5432
Maintenance databaseappdb
Usernameapp_readonly
Passwordreadonly_pass_2026

注意,pgAdmin 容器和 PostgreSQL 容器在同一个 Docker 网络里,所以这里写pg-primarypg-replica,不是写127.0.0.1。如果你是在宿主机用本地 psql 连接,才使用127.0.0.1:5432127.0.0.1:5433

在 pgAdmin 的 Query Tool 里,对从库执行:

SELECT pg_is_in_recovery(); SELECT id, order_no, amount FROM demo_orders ORDER BY id;

再试着执行一条写入:

INSERT INTO demo_orders (order_no, amount) VALUES ('ORD-READONLY-TEST', 1.00);

从库处于恢复状态时,这条写入会失败。这个失败是好事,它证明你现在连的是只读从库,不是误连了主库。

6 用 cpolar 做远程临时验证:只暴露验证入口

本地验证通过后,常见需求是让远程同事也看一眼:从库能不能连、查询结果是不是最新、pgAdmin 里能不能看到状态。

这里 cpolar 的作用很明确:临时把本地验证入口映射出去。不要把它理解成“把数据库长期放到公网”。数据库端口直接暴露风险很高,尤其是主库写入口。

6.1 临时暴露 pgAdmin 页面

如果只是让同事看 pgAdmin 页面,优先开 HTTP 隧道,映射本地5050

cpolar http 5050

cpolar 会输出一个公网 HTTP 地址。把这个地址发给同事后,对方能打开 pgAdmin 登录页,再用你提供的 pgAdmin 账号登录。

安全提醒放前面:

  • pgAdmin 密码要设置强密码,不要使用本文示例密码;
  • 只在验证窗口内开启隧道,用完关闭;
  • 不要把生产库连接信息配置到这个临时 pgAdmin;
  • 同事验证完成后,修改临时密码或删除 pgAdmin 容器。

6.2 临时暴露只读从库端口

如果对方需要用本地数据库客户端连接从库,可以临时开 TCP 隧道,映射宿主机的从库端口5433

cpolar tcp 5433

cpolar 会给出一个公网 TCP 地址和端口。对方连接时使用 cpolar 输出的主机名和端口,数据库账号使用只读账号:

Database: appdb Username: app_readonly Password: readonly_pass_2026

这里别填主库账号,也别映射5432主库端口。验证目标是“远程读取从库数据和复制状态”,不是让远程写入主库。

如果要长期固定地址,cpolar 的免费随机公网地址 24 小时内会变化;固定二级子域名需要基础套餐或以上,固定 TCP 地址需要专业套餐或以上。本文这个场景更推荐短时验证,用完关闭,减少暴露面。

6.3 用完关闭验证入口

前台运行的 cpolar 命令,在对应终端按Ctrl+C就能停止。停止后,再让同事刷新页面或重连数据库,确认外部入口已经不可用。

这一步不是形式主义。数据库验证链路越短越好,暴露时间也越短越好。尤其是主从复制这种基础设施实验,安全边界要从一开始就立住。

7 常见排查:复制没起来先看这几处

这套实验最容易卡的不是 SQL,而是初始化顺序和连接权限。

7.1 从库没有数据

先看从库是否真的处于恢复状态:

docker exec -it pg-replica psql -U postgres -d appdb \ -c "SELECT pg_is_in_recovery();"

如果不是t,说明从库没有按 standby 模式启动。检查replica-entrypoint.sh是否挂载成功,以及数据卷里是否残留了旧数据。

需要从头重建实验环境时,用下面命令清理容器和数据卷:

docker compose down -v docker compose up -d

提醒:down -v会删除主库和从库数据卷,只适合实验环境。生产库不要这么干。

7.2 主库看不到 pg_stat_replication

主库没有复制连接时,重点看认证和网络:

docker compose logs pg-replica

日志里如果出现密码认证失败,检查repl_pass_2026是否前后一致。日志里如果出现主机无法解析,检查 Compose 服务名是不是pg-primary

再检查主库是否开启了复制参数:

docker exec -it pg-primary psql -U postgres -d appdb \ -c "SHOW wal_level; SHOW max_wal_senders; SHOW max_replication_slots;"

wal_level应该是replicamax_wal_sendersmax_replication_slots都要大于 0。

7.3 只读账号查不到表

从库能连但查表失败,多半是主库没有给只读账号授权。回主库补一次:

docker exec -it pg-primary psql -U postgres -d appdb \ -c "GRANT USAGE ON SCHEMA public TO app_readonly; GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;"

以后新建表想自动给只读权限,就保留前面写过的默认权限语句:

ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_readonly;

权限这块别嫌麻烦。远程验证时只给查询权限,能减少很多不必要的风险。

8 和 CloudBeaver 那篇有什么区别?

之前如果你看过 CloudBeaver 相关内容,可以把它理解成“数据库 Web 管理工具”方向:重点是怎么通过浏览器连接、管理多种数据库。

本文不做客户端选型,核心是 PostgreSQL 主从架构与复制验证。pgAdmin 只是验证工具之一,真正要确认的是:

  • 主库是否正确产生并发送 WAL;
  • 从库是否处于恢复状态并回放数据;
  • 只读账号是否能完成远程查询验证;
  • cpolar 是否只在验证阶段短时打开入口。

旧文内链位置先留在这里:CloudBeaver 数据库 Web 管理工具教程。

9 总结

到这里,我们已经用 Docker Compose 搭好了一套 PostgreSQL 主从复制实验环境:主库负责写入,从库持续接收 WAL 并提供只读查询,pgAdmin 和 psql 都能验证复制状态。远程协作验证时,cpolar 只负责短时打开 pgAdmin 或只读从库入口,不碰生产库,也不长期暴露主库写入口。

这套流程里最关键的几步是:

  • 主库提前配置wal_level=replicamax_wal_sendersmax_replication_slots,并在pg_hba.conf里允许复制账号连接;
  • 从库用pg_basebackup -R -X stream拉取基线数据,再通过pg_is_in_recovery()pg_stat_replication双向确认状态;
  • 远程验证只开放 pgAdmin 或只读从库端口,使用强密码、只读账号和临时隧道,用完立刻关闭。

如果只是家庭服务器、小团队测试环境,这套做法已经能把“主库写、从库读、远程验证”的链路跑通。后续要继续升级,可以再接入故障切换、连接代理、备份恢复演练,把它从实验环境慢慢推到更接近生产的形态。

你更想看 PostgreSQL 故障切换、只读账号权限细分,还是 CloudBeaver/pgAdmin 管理界面对比?评论区直接点一个方向,我按实操路线继续写。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/7 16:48:47

ARM嵌入式开发板选购指南:从硬件到生态的全面解析

1. 从“纸上谈兵”到实战落地&#xff1a;为什么选对开发板是嵌入式学习的第一步搞嵌入式开发&#xff0c;尤其是ARM这一块&#xff0c;手里没块开发板&#xff0c;那感觉就像学游泳只在岸上看教程&#xff0c;永远不知道水有多深、浪有多大。我刚开始接触时&#xff0c;也经历…

作者头像 李华
网站建设 2026/6/7 16:48:44

移动芯片设计:从核心堆砌到异构计算与系统级优化的演进

1. 从一场火爆的圆桌讨论说起 今天在深圳会展中心&#xff0c;第十八届国际集成电路研讨会暨展览会&#xff08;IIC China&#xff09;的现场&#xff0c;气氛比深圳的天气还要热。孙昌旭老师主持的“智能手机与平板论坛”会场&#xff0c;从早上九点开始就座无虚席&#xff0c…

作者头像 李华
网站建设 2026/6/7 16:47:52

Guardrails(大模型护栏 / 防护栏)

Guardrails&#xff08;大模型护栏 / 防护栏&#xff09;&#xff0c;简单说就是&#xff1a;给 LLM/AI Agent 加一层 “安全 格式 合规” 的防护网&#xff0c;防止输出乱、有毒、幻觉、泄露隐私。下面用大白话讲清楚&#xff1a;一、它是什么&#xff1f;字面&#xff1a;护…

作者头像 李华
网站建设 2026/6/7 16:46:44

终极免费字体解决方案:如何用Montserrat字体家族提升你的设计品质

终极免费字体解决方案&#xff1a;如何用Montserrat字体家族提升你的设计品质 【免费下载链接】Montserrat 项目地址: https://gitcode.com/gh_mirrors/mo/Montserrat 你是否曾经为设计项目找不到合适的字体而烦恼&#xff1f;或者因为付费字体价格高昂而不得不妥协设计…

作者头像 李华
网站建设 2026/6/7 16:45:32

PDF 拆分工具怎么选?2026 年主流方案对比与选型指南

从标书中单独提取资质文件、从论文里拆出某一章节发给导师、从扫描件合集中分离需要的几页——PDF 拆分的需求频率远高于大多数人的预期。据 Adobe 2025 年发布的文档工作流报告&#xff0c;企业用户每月平均处理 PDF 拆分操作约 12 次&#xff0c;主要集中在投标文件准备、合同…

作者头像 李华