暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

手动装 100 台 PG?这是在玩火!Ansible 自动化部署全实战(附脚本)

安呀智数据坊 2026-05-21
91

注:本文为安丫科技·刘峰原创。 转载请注明出处,严禁抄袭。

⚠️ 风险提示:

文中代码仅供测试环境学习。

生产环境复杂,严禁盲目套用,后果自负。

如遇紧急故障或需兜底支持,请联系安丫专家团队。

在运维圈,有一个著名的“注意力衰减曲线”:

手动安装第 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 核心:安装 + 初始化 + 

 配置 + 启动 

1

 主入口:按系统家族分流

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 件事,跳过

 必踩坑

代码能跑,不等于生产没风险。


1

 第一件事:listen_addresses = 0.0.0.0 

 不能裸奔

这个配置意味着 PostgreSQL 会监听所有网卡的所有 IP,包括公网 IP。

如果你的机器有公网 IP,没有防火墙,没有安全组,等于把数据库直接暴露在公网上。

生产环境请改成内网 IP,配合安全组或 firewalld/ufw 做访问控制。


2

 第二件事:时钟、时区、locale,

 三件套要一致

100 台机器时区不统一,日志时间对不上,排查问题的时候会把人逼疯。

建议在跑这套 playbook 之前,先跑一个统一 NTP + 时区的 Role,把地基打平。


3

 第三件事:数据目录必须落在独立磁盘

这是最容易被忽略、也是后果最严重的一条。

系统盘打满会发生什么:

/(根分区)写满
  ├── 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 等主流数据库

  • 核心服务内容

    性能优化 / 故障处理 / 数据迁移 / 备份恢复 / 版本升级 / 补丁管理

  • 系统性支持

    深度巡检 / 高可用架构设计 / 应用层兼容评估 / 运维工具集成

  • 专项能力补充

    定制课程培训 / 甲方团队辅导 / 复杂问题协作排查 / 紧急救援支持

📮 如果你有一张删不掉的表、一个跑不动的查询,或者一场说不清的升级风险,欢迎来找我们聊聊。

END

关键词回复(可见相应文章):

oracle、mysql、pg、postgresql、sql、性能优化、故障处理、数据迁移、备份恢复、版本升级、补丁管理、深度巡检、解决方案、架构设计......

添加微信好友1对1咨询

\ | /

动动你的手指

【安呀智数据坊】加个星标吧~

这样你就不会丢下我啦~

记得加星标呀!

文章转载自安呀智数据坊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论