
注:本文为安丫科技·刘峰原创。 转载请注明出处,严禁抄袭。
⚠️ 风险提示:
文中代码仅供测试环境学习。
生产环境复杂,严禁盲目套用,后果自负。
如遇紧急故障或需兜底支持,请联系安丫专家团队。
在运维圈,有一个著名的“注意力衰减曲线”:
手动安装第 1 台服务器时,你全神贯注;
安装到第 37 台时,你开始走神;
安装到第 79 台时,你已经分不清 shared_buffers 后面是 2G 还是 2GB。
当周五下班铃声响起,100 台数据库装完了,但没有人敢保证它们的配置完全一致——这不仅是运维的噩梦,更是生产环境最大的“隐形定时炸弹”。
手动操作+人的疲劳,是数据库运维中最昂贵的故障源。
今天,我把生产环境里那套批量部署 PostgreSQL 18 的 Ansible 方案整理了出来。
它不是简单的安装脚本,而是涵盖了多发行版兼容、配置自动化、安全加密与滚动部署的生产级实践。
建议直接收藏,下次开新项目,改个 IP 直接跑,把你的时间留给更有价值的架构设计。
01
先说几个决定你能不能落地的
关键判断
很多人看到 Ansible 方案就直接抄代码,结果跑到一半卡住,又不知道从哪查。
在看代码之前,先把几件事想清楚:为什么是 PG 18? 截至 2026 年 5 月,PostgreSQL 18 是官方当前最新主版本。
更重要的是,PGDG 官方仓库对 Ubuntu 系和 RHEL 系都有明确的安装文档——这意味着我们不需要靠猜,有官方路径可走。
为什么要用 PGDG 仓库,而不是系统自带的包? 因为系统自带的 postgresql-server,在 Rocky Linux 上可能是 13,在 Ubuntu 22.04 上是 14。
你要 18,发行版给你的版本号不一定对得上。
用 PGDG 官方仓库,版本可控,安全补丁也是官方维护的。
为什么 RHEL 系比 Debian 系麻烦一点?
因为 Red Hat 系安装完 PostgreSQL 包之后,数据库集群不会自动初始化,你必须手工跑 initdb,然后再 enable 和 start。
这一步漏了,服务根本起不来,然后你会对着一个空荡荡的报错发呆。
这三个判断搞清楚,后面的代码你才知道它在干嘛,而不是复制粘贴祈祷成功。
02
目录结构:先把架子搭对,后面
才好维护
pg-install/
ansible.cfg
inventory/hosts.ini
group_vars/postgresql.yml
group_vars/postgresql_vault.yml ← 用 ansible-vault 加密,密码不能裸奔进 Git
requirements.yml
site.yml
roles/postgresql/
tasks/main.yml
tasks/debian.yml
tasks/redhat.yml
handlers/main.yml
templates/postgresql.conf.j2
templates/pg_hba.conf.j2
按 Role 管理,是因为半年后你要加监控、加备份、加高可用的时候,不会发现所有东西全堆在一个 2000 行的 playbook 里出不来。
03
控制端准备:
装 community.postgresql
community.postgresql 是 Ansible 官方维护的 PostgreSQL 模块集合,建库、建账号都靠它。
没有它,你只能用 shell 模块拼 SQL,那和写 bash 脚本没有本质区别。
requirements.yml
collections:
- name: community.postgresql
cd pg-install
ansible-galaxy collection install -r requirements.yml
一行命令,依赖装齐。
04
ansible.cfg:并发参数,
100 台不能乱来
ansible.cfg
[defaults]
inventory = inventory/hosts.ini
forks = 50
timeout = 30
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
[privilege_escalation]
become = True
forks = 50 是并发数,意思是最多同时跟 50 台机器通信。
你可能会问:为什么不开到 100,一次全搞定?
因为 100 台机器同时跑 apt update 或者 dnf install,是在同一时间向同一个镜像站发起 100 个请求。
网络一抖,镜像站一限流,你会得到一批随机失败的机器——然后你不知道哪些成功了,哪些没成功,排查成本翻倍。
50 的并发,配合后面说的 serial: 10 滚动策略,才是正确的组合。
05
库存文件:把 100 台塞进一个组
inventory/hosts.ini
[postgresql]
pg001 ansible_host=10.10.1.1
pg002 ansible_host=10.10.1.2
; ...
pg100 ansible_host=10.10.1.100
生产环境如果有跳板机,在 ansible.cfg 里配 ProxyJump,不要每台机器手动处理网络路径。
06
变量:普通配置和敏感信息
必须分开存
这里有一条绝对不能违反的原则:密码明文提交进 Git = 迟早出事,只是时间问题。
group_vars/postgresql.yml(普通配置,可以进 Git)
# ---- 版本与安装源 ----
postgres_major: 18
postgres_use_pgdg_repo: true
# ---- 基础网络 ----
postgres_port: 5432
postgres_listen_addresses: "0.0.0.0" # ⚠️ 生产要改成内网 IP,下面会说
postgres_allowed_subnets:
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
# ---- 是否建库建账号 ----
postgres_create_objects: true
postgres_admin_user: "appadmin"
postgres_databases:
- name: "appdb"
owner: "appadmin"
# ---- 资源参数(保守默认,按实际机器规格调整)----
postgres_shared_buffers: "2GB"
postgres_work_mem: "16MB"
postgres_maintenance_work_mem: "256MB"
postgres_max_connections: 300
postgres_log_min_duration_statement: 1000 # 慢查询阈值,单位毫秒
group_vars/postgresql_vault.yml(敏感信息,加密后才能进 Git)
vault_postgres_admin_password: "ChangeMe_StrongPassword"
加密:
ansible-vault encrypt group_vars/postgresql_vault.yml
加完密之后,这个文件变成一段看不懂的密文。
执行 playbook 时带上 vault 密码就能自动解密。
07
入口 Playbook:滚动推进,
失败即停
site.yml
ansible-vault encrypt group_vars/postgresql_vault.yml
serial: 10 是这里最重要的一个参数。
分批的意义在于:第一批 10 台跑完,你能看到结果是否符合预期,再决定要不要继续。
如果第一批有 3 台失败,你至少还有 90 台是干净的,可以安全回滚和排查。
100 台并发一把梭,出了问题你连「从哪开始错的」都不知道。
08
Role 核心:安装 + 初始化 +
配置 + 启动
主入口:按系统家族分流
roles/postgresql/tasks/main.yml
- name: Load vault vars
ansible.builtin.include_vars:
file: group_vars/postgresql_vault.yml
- name: Install PostgreSQL on Debian family
ansible.builtin.include_tasks: debian.yml
when: ansible_os_family == "Debian"
- name: Install PostgreSQL on RedHat family
ansible.builtin.include_tasks: redhat.yml
when: ansible_os_family == "RedHat"
- name: Deploy postgresql.conf
ansible.builtin.template:
src: postgresql.conf.j2
dest: "{{ postgres_conf_path }}"
owner: postgres
group: postgres
mode: "0644"
notify: Restart PostgreSQL
- name: Deploy pg_hba.conf
ansible.builtin.template:
src: pg_hba.conf.j2
dest: "{{ postgres_hba_path }}"
owner: postgres
group: postgres
mode: "0600"
notify: Reload PostgreSQL
- name: Ensure PostgreSQL service enabled and started
ansible.builtin.service:
name: "{{ postgres_service_name }}"
enabled: true
state: started
# ---- psycopg2:community.postgresql 模块的运行依赖 ----
- name: Install psycopg2 (Debian)
ansible.builtin.apt:
name: python3-psycopg2
state: present
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian" and postgres_create_objects
- name: Install psycopg2 (RedHat)
ansible.builtin.dnf:
name: python3-psycopg2
state: present
when: ansible_os_family == "RedHat" and postgres_create_objects
- name: Create admin role
community.postgresql.postgresql_user:
name: "{{ postgres_admin_user }}"
password: "{{ vault_postgres_admin_password }}"
role_attr_flags: "CREATEDB,LOGIN"
become_user: postgres
when: postgres_create_objects
- name: Create databases
community.postgresql.postgresql_db:
name: "{{ item.name }}"
owner: "{{ item.owner }}"
loop: "{{ postgres_databases }}"
become_user: postgres
when: postgres_create_objects
Debian/Ubuntu 分支
Ubuntu 官方 PGDG 文档推荐使用新格式的 .sources 文件,而不是老的 apt-key 方式。
原因很简单:apt-key 在新版 Ubuntu 上已经是 deprecated 状态,用老方式每次执行都会打警告,时间长了你会开始忽视所有警告——那才是真正的风险。
roles/postgresql/tasks/debian.yml
- name: Install prerequisites
ansible.builtin.apt:
name: [curl, ca-certificates]
state: present
update_cache: true
cache_valid_time: 3600
- name: Create PGDG key directory
ansible.builtin.file:
path: usr/share/postgresql-common/pgdg
state: directory
mode: "0755"
- name: Download PGDG signing key
ansible.builtin.get_url:
url: "https://www.postgresql.org/media/keys/ACCC4CF8.asc"
dest: usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
mode: "0644"
when: postgres_use_pgdg_repo
- name: Configure PGDG apt source
ansible.builtin.copy:
dest: etc/apt/sources.list.d/pgdg.sources
mode: "0644"
content: |
Types: deb deb-src
URIs: https://apt.postgresql.org/pub/repos/apt
Suites: {{ ansible_distribution_release }}-pgdg
Architectures: amd64
Components: main
Signed-By: usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
when: postgres_use_pgdg_repo
- name: Install PostgreSQL packages
ansible.builtin.apt:
name:
- "postgresql-{{ postgres_major }}"
- "postgresql-client-{{ postgres_major }}"
state: present
update_cache: true
cache_valid_time: 3600
- name: Set Debian-family paths
ansible.builtin.set_fact:
postgres_service_name: "postgresql"
postgres_conf_path: "/etc/postgresql/{{ postgres_major }}/main/postgresql.conf"
postgres_hba_path: "/etc/postgresql/{{ postgres_major }}/main/pg_hba.conf"
RHEL/Rocky/Alma 分支
这里有一个坑必须提前说清楚:RHEL 系有一个叫「postgresql module」的东西,会和 PGDG 的包产生冲突。
它是 DNF 的模块流机制,系统默认启用了一个叫 postgresql 的 module,锁定了某个版本。
如果你不把它禁掉,直接装 PGDG 的 postgresql18,要么装不上,要么装上了也是错的版本。
failed_when: false 是因为某些极简化的环境根本就没有 module 机制,这条命令会直接报错退出,但这个错不重要,不该让它中断整个 playbook。
roles/postgresql/tasks/redhat.yml
- name: Install PGDG repo RPM
ansible.builtin.dnf:
name: "https://download.postgresql.org/pub/repos/yum/reporpms/EL-{{ ansible_distribution_major_version }}-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
state: present
disable_gpg_check: false
when: postgres_use_pgdg_repo
- name: Disable built-in postgresql module(防包冲突)
ansible.builtin.command: dnf -y module disable postgresql
register: dnf_module_disable
changed_when: "'Nothing to do' not in (dnf_module_disable.stdout | default(''))"
failed_when: false
when: postgres_use_pgdg_repo
- name: Install PostgreSQL packages from PGDG
ansible.builtin.dnf:
name:
- "postgresql{{ postgres_major }}"
- "postgresql{{ postgres_major }}-server"
- "postgresql{{ postgres_major }}-contrib"
state: present
when: postgres_use_pgdg_repo
- name: Initialize database cluster
ansible.builtin.command: >
usr/pgsql-{{ postgres_major }}/bin/postgresql-{{ postgres_major }}-setup initdb
args:
creates: "/var/lib/pgsql/{{ postgres_major }}/data/PG_VERSION"
when: postgres_use_pgdg_repo
# creates 这个参数是幂等性的关键:
# 如果 PG_VERSION 文件已经存在,这条任务会跳过,不会重复 initdb
- name: Set RHEL PGDG paths
ansible.builtin.set_fact:
postgres_service_name: "postgresql-{{ postgres_major }}"
postgres_conf_path: "/var/lib/pgsql/{{ postgres_major }}/data/postgresql.conf"
postgres_hba_path: "/var/lib/pgsql/{{ postgres_major }}/data/pg_hba.conf"
when: postgres_use_pgdg_repo
# ---- 备选:走发行版自带仓库 ----
- name: Install from distro repo
ansible.builtin.dnf:
name: postgresql-server
state: present
when: not postgres_use_pgdg_repo
- name: Initialize database cluster (distro)
ansible.builtin.command: postgresql-setup --initdb
args:
creates: "/var/lib/pgsql/data/PG_VERSION"
when: not postgres_use_pgdg_repo
- name: Set RHEL distro paths
ansible.builtin.set_fact:
postgres_service_name: "postgresql"
postgres_conf_path: "/var/lib/pgsql/data/postgresql.conf"
postgres_hba_path: "/var/lib/pgsql/data/pg_hba.conf"
when: not postgres_use_pgdg_repo
09
配置模板:只写「能上线」的
基础配置
这里有一个常见的错误:把配置模板写得太完整,每个参数都塞进去,然后发现不同机器规格差异很大,模板反而成了负担。
正确的思路是:模板只管「让它能上线」,按机器规格微调的参数用 host_vars 或更高层的变量覆盖。
roles/postgresql/templates/postgresql.conf.j2
listen_addresses = '{{ postgres_listen_addresses }}'
port = {{ postgres_port }}
max_connections = {{ postgres_max_connections }}
shared_buffers = '{{ postgres_shared_buffers }}'
work_mem = '{{ postgres_work_mem }}'
maintenance_work_mem = '{{ postgres_maintenance_work_mem }}'
logging_collector = on
log_min_duration_statement = {{ postgres_log_min_duration_statement }}
log_line_prefix = '%m [%p] %u@%d %r '
# PG 18 默认就是 scram-sha-256,显式写出来让意图更清晰
password_encryption = scram-sha-256
roles/postgresql/templates/pg_hba.conf.j2
# 本地 postgres 用户走 peer,运维操作用
local all postgres peer
# 本地 socket 其他用户
local all all scram-sha-256
# 允许指定内网段访问
{% for net in postgres_allowed_subnets %}
host all all {{ net }} scram-sha-256
{% endfor %}
10
handlers:配置变更后的正确
处理方式
roles/postgresql/handlers/main.yml
- name: Reload PostgreSQL
ansible.builtin.service:
name: "{{ postgres_service_name }}"
state: reloaded
- name: Restart PostgreSQL
ansible.builtin.service:
name: "{{ postgres_service_name }}"
state: restarted
这里有一个细节很多人忽略:postgresql.conf 的部分参数变更需要 restart,pg_hba.conf 变更只需要 reload。这两个不一样。
reload 是平滑的,不影响现有连接;restart 会断开所有现有连接,在生产环境是有业务影响的。
不必要的 restart,是运维事故的常见来源之一。
11
执行
cd pg-install
# 交互式输入 vault 密码
ansible-playbook site.yml --ask-vault-pass
# 或者用密码文件(适合接 CI/CD)
ansible-playbook site.yml --vault-password-file .vault_pass
执行流程是这样的:
控制端发起
→ 第 1 批(10 台)
→ 下载 PGDG 仓库 key 和配置
→ 安装 postgresql-18 包
→ RHEL 系:跑 initdb
→ 下发 postgresql.conf pg_hba.conf
→ 启动并 enable 服务
→ 建账号、建库
→ 第 1 批全部成功
→ 第 2 批(10 台)
→ ...
→ 第 10 批(10 台)
→ 完成
正常情况下,100 台大概 15 到 20 分钟跑完,具体取决于网络和机器性能。
12
上线前必须做的 3 件事,跳过
必踩坑
代码能跑,不等于生产没风险。
第一件事:listen_addresses = 0.0.0.0
不能裸奔
这个配置意味着 PostgreSQL 会监听所有网卡的所有 IP,包括公网 IP。
如果你的机器有公网 IP,没有防火墙,没有安全组,等于把数据库直接暴露在公网上。
生产环境请改成内网 IP,配合安全组或 firewalld/ufw 做访问控制。
第二件事:时钟、时区、locale,
三件套要一致
100 台机器时区不统一,日志时间对不上,排查问题的时候会把人逼疯。
建议在跑这套 playbook 之前,先跑一个统一 NTP + 时区的 Role,把地基打平。
第三件事:数据目录必须落在独立磁盘
这是最容易被忽略、也是后果最严重的一条。
系统盘打满会发生什么:
/(根分区)写满
├── PostgreSQL 无法写 WAL
│ → 数据库挂起所有写操作
│ → 业务写入立即停止
├── 无法写临时文件
│ → 复杂查询直接报错
└── 系统日志写不进去
→ 出了问题没有任何诊断信息
从【磁盘快满了】到【数据库完全停服】,可能就差最后几个 GB 的空间。
最低要求:数据目录 PGDATA 单独挂载,不和系统盘混用。
13
这套方案能带你走多远
本文给出的是一套可以直接落地的基础版本——装好、配好、能用。
如果你需要进一步到「生产标准交付」,还有这些坑要填:

温馨提示
手动操作 100 台,不可能保证第 100 台和第 1 台配置完全一样。
这不是对人的指责——人的注意力在重复操作中衰减,是认知科学的结论,不是谁的错。
真正的错,是明明可以用 Ansible,却还在一台一台手动登。
代码写完记得进 Git。
下次再开 100 台,改个 hosts.ini,重跑一遍。
写在最后
回到 Ansible 本身,它最大的价值不是替你敲了键盘,而是通过“代码化”的手段,强迫你把运维逻辑标准化。
当 100 台机器的部署过程变成了几行代码,你就拥有了运维领域最重要的资产——可复现性(Reproducibility)。
任何一次手动配置,本质上都是在制造技术债务。
安呀智数据坊,专注企业的数字化基础设施建设。
今天的脚本只是起点。
如果你正在处理以下场景,单节点的自动化还不够,我们需要聊聊更深层的架构:
数据库高可用: 当自动部署完成,如何结合 Patroni 实现自动化故障转移?
备份体系: pgBackRest 如何无缝接入这套自动化流程?
监控接入: 100 台机器,如何通过 Ansible 一键植入监控 Agent?
性能调优: 针对不同硬件规格的自动参数算力分配?
如果你也想把这套运维思路引入你的团队,欢迎后台私信我们。
我们不提供“一次性脚本”,我们提供的是帮你构建一套可持续运行的“数字化运维流水线”。
关注【安呀智数据坊】,转发给那位还在“手动装机”的同事。
帮他一次,也是在帮团队减负。

作者介绍
大家好,我是刘峰,安丫科技创始人 & 数据库技术高级讲师,专注于 PostgreSQL、国产数据库运维与迁移、数据库性能优化 等方向。
作为 PG中国分会官方授权讲师、PostgreSQL ACE 讲师认证专家,我长期活跃在一线项目实战中,拥有 10年以上大型数据库管理与优化经验,曾深度参与电信、金融、政务等多个行业的数据库性能调优与迁移项目。
欢迎关注我,一起深入探索数据库的无限可能,技术交流不设限!
📌 觉得有收获的话,记得点赞、收藏、转发支持一下哦,别忘了关注我获取更多数据库干货~
安呀智数据坊|我们能做什么
无论你是业务系统的技术负责人,还是数据部门的第一响应人,我们都能为你提供可靠的支持:
数据库类型支持
Oracle / MySQL / PostgreSQL / SQL Server 等主流数据库
核心服务内容
性能优化 / 故障处理 / 数据迁移 / 备份恢复 / 版本升级 / 补丁管理
系统性支持
深度巡检 / 高可用架构设计 / 应用层兼容评估 / 运维工具集成
专项能力补充
定制课程培训 / 甲方团队辅导 / 复杂问题协作排查 / 紧急救援支持
📮 如果你有一张删不掉的表、一个跑不动的查询,或者一场说不清的升级风险,欢迎来找我们聊聊。
关键词回复(可见相应文章):
oracle、mysql、pg、postgresql、sql、性能优化、故障处理、数据迁移、备份恢复、版本升级、补丁管理、深度巡检、解决方案、架构设计......

添加微信好友1对1咨询
\ | /
★
动动你的手指
给【安呀智数据坊】加个星标吧~
这样你就不会丢下我啦~
记得加星标呀!









