observability.svc.plus/pgsql-db.yml
2026-02-01 20:53:55 +08:00

182 lines
10 KiB
YAML
Executable File

#!/usr/bin/env ansible-playbook
---
#==============================================================#
# File : pgsql-db.yml
# Desc : create database on existing cluster
# Ctime : 2021-02-27
# Mtime : 2025-12-29
# Path : pgsql-db.yml
# Deps : templates/pg-db.sql
# Docs : https://pigsty.io/docs/pgsql/playbook
# License : Apache-2.0 @ https://pigsty.io/docs/about/license/
# Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com)
#==============================================================#
#--------------------------------------------------------------#
# Usage
#--------------------------------------------------------------#
# 1. Define new database in inventory (cmdb or config)
# `all.children.<pg_cluster>.vars.pg_databases[i]`
#
# 2. Execute this playbook on target cluster with arg dbname
# `pgsql-db.yml -l <pg_cluster> -e dbname=<database.name>
#
# This playbook will:
# 1. create database sql definition on `/pg/tmp/pg-db-{{ database.name }}.sql`
# 2. execute database creation/update sql on cluster leader instance
# 3. register database to grafana datasource when `db.register_datasource`
# 4. update /etc/pgbouncer/database.txt and reload pgbouncer if necessary
#
#--------------------------------------------------------------#
# Utils
#--------------------------------------------------------------#
# Create pgsql database 'dbname' on pgsql cluster 'cls'
# bin/pgsql-db <cls> <dbname>
# bin/pgsql-db pg-meta meta
#--------------------------------------------------------------#
# Example
#--------------------------------------------------------------#
# pg-meta:
# vars:
# pg_databases: # define business databases on this cluster, array of database definition
# - name: meta # REQUIRED, `name` is the only mandatory field of a database definition
# state: create # optional, cloud be create (default), absent (drop db), recreate (drop then recreate)
# baseline: cmdb.sql # optional, database sql baseline path, (relative path among ansible search path, e.g files/)
# pgbouncer: true # optional, add this database to pgbouncer database list? true by default
# schemas: [pigsty] # optional, additional schemas to be created, array of schema names
# extensions: # optional, additional extensions to be installed: array of `{name[,schema]}`
# - { name: postgis , schema: public } # install postgis on schema `public`
# - { name: timescaledb } # install timescaledb extension
# comment: pigsty meta database # optional, comment string for this database
# owner: postgres # optional, database owner, current user if not specified
# template: template1 # optional, template database to use, template1 by default
# strategy: FILE_COPY # optional, clone strategy: FILE_COPY or WAL_LOG (PG15+), default to PG's default
# encoding: UTF8 # optional, inherited from template / cluster if not defined (UTF8)
# locale: C # optional, inherited from template / cluster if not defined (C)
# lc_collate: C # optional, inherited from template / cluster if not defined (C)
# lc_ctype: C # optional, inherited from template / cluster if not defined (C)
# locale_provider: libc # optional, locale provider: libc, icu, builtin (PG15+)
# icu_locale: en-US # optional, icu locale for icu locale provider (PG15+)
# icu_rules: '' # optional, icu rules for icu locale provider (PG16+)
# builtin_locale: C.UTF-8 # optional, builtin locale for builtin locale provider (PG17+)
# tablespace: pg_default # optional, default tablespace, pg_default by default
# is_template: false # optional, mark database as template, allowing clone by any user with CREATEDB privilege
# allowconn: true # optional, allow connection, true by default. false will disable connect at all
# revokeconn: false # optional, revoke public connection privilege. false by default. (leave connect with grant option to owner)
# register_datasource: true # optional, register this database to grafana datasources? true by default
# connlimit: -1 # optional, database connection limit, default -1 disable limit
# pool_auth_user: dbuser_meta # optional, all connection to this pgbouncer database will be authenticated by this user
# pool_mode: transaction # optional, pgbouncer pool mode at database level, default transaction
# pool_size: 64 # optional, pgbouncer pool size at database level, default 64
# pool_size_reserve: 32 # optional, pgbouncer pool size reserve at database level, default 32
# pool_size_min: 0 # optional, pgbouncer pool size min at database level, default 0
# pool_max_db_conn: 100 # optional, max database connections at database level, default 100
# - { name: grafana ,owner: dbuser_grafana ,revokeconn: true ,comment: grafana primary database }
# - { name: bytebase ,owner: dbuser_bytebase ,revokeconn: true ,comment: bytebase primary database }
# - { name: kong ,owner: dbuser_kong ,revokeconn: true ,comment: kong the api gateway database }
# - { name: gitea ,owner: dbuser_gitea ,revokeconn: true ,comment: gitea meta database }
# - { name: wiki ,owner: dbuser_wiki ,revokeconn: true ,comment: wiki meta database }
#--------------------------------------------------------------#
- name: PGSQL DB
become: true
hosts: all
gather_facts: no
tasks:
#----------------------------------------------------------#
# Validate dbname and database definition [preflight]
#----------------------------------------------------------#
- name: preflight
tags: [ preflight , always ]
connection: local
block:
- name: validate dbname parameter
assert:
that:
- dbname is defined
- dbname != ''
#- dbname != 'postgres'
fail_msg: variable 'pg_database' should be specified (-e dbname=<name>)
- name: get database definition
set_fact: db_def={{ pg_databases | json_query(db_def_query) }}
vars: { db_def_query: "[?name=='{{ dbname }}'] | [0]" }
- name: validate database definition
assert:
that:
- db_def is defined
- db_def != None
- db_def != ''
- db_def != {}
fail_msg: define database {{ dbname }} in pg_databases first
- debug:
msg: "{{ db_def }}"
#----------------------------------------------------------#
# Create or Drop Postgres Database [postgres]
#----------------------------------------------------------#
# create or drop database according to database definition
# when db_def.state == 'absent', the database will be dropped
- include_tasks: roles/pgsql/tasks/database.yml
tags: postgres
when: pg_role == 'primary'
vars: { database: "{{ db_def }}" }
#----------------------------------------------------------#
# Create Grafana Datasource [grafana]
#----------------------------------------------------------#
# register grafana database as grafana datasource (skip if state=absent)
- include_tasks: roles/pg_monitor/tasks/register_grafana.yml
ignore_errors: true
tags: [ register, grafana, add_ds ]
when: db_def.state is not defined or db_def.state != 'absent'
vars: { database: "{{ db_def }}" }
#----------------------------------------------------------#
# Remove Grafana Datasource [unregister]
#----------------------------------------------------------#
# unregister grafana database datasource (only when state=absent)
- include_tasks: roles/pg_remove/tasks/grafana.yml
ignore_errors: true
tags: [ unregister, grafana, rm_ds ]
when: db_def.state is defined and db_def.state == 'absent'
vars: { database: "{{ db_def }}" }
#----------------------------------------------------------#
# Refresh Pgbouncer Database Configuration [pgbouncer]
#----------------------------------------------------------#
- name: refresh pgbouncer databases
tags: pgbouncer
when: db_def.pgbouncer is not defined or db_def.pgbouncer|bool
block:
- name: render pgbouncer database.txt
copy:
dest: /etc/pgbouncer/database.txt
owner: "{{ pg_dbsu|default('postgres') }}"
group: postgres
mode: 0600
content: |
# pgbouncer database list
{% for db in pg_databases %}
{% if ('pgbouncer' not in db or db.pgbouncer|bool) and (db.state is not defined or db.state != 'absent') %}
{% if pgbouncer_redirect_to is defined and pgbouncer_redirect_to != '' %}{% set connstr = "host=" + pgbouncer_redirect_to|string %}{% else %}{% set connstr = "host=" + pg_localhost|default('/var/run/postgresql')|string %}{% endif %}
{% if 'pool_auth_user' in db %}{% set connstr = connstr + " auth_user=" + db.pool_auth_user|string %}{% endif %}
{% if 'pool_mode' in db %}{% set connstr = connstr + " pool_mode=" + db.pool_mode|string %}{% endif %}
{% if 'pool_size' in db %}{% set connstr = connstr + " pool_size=" + db.pool_size|string %}{% endif %}
{% if 'pool_size_min' in db %}{% set connstr = connstr + " min_pool_size=" + db.pool_size_min|string %}{% endif %}
{% if 'pool_reserve' in db %}{% set connstr = connstr + " reserve_pool=" + db.pool_reserve|string %}{% endif %}
{% if 'pool_connlimit' in db %}{% set connstr = connstr + " max_db_connections=" + db.pool_connlimit|string %}{% endif %}
{{ "%-27s" | format(db.name) }} = {{ connstr }}
{% endif %}
{% endfor %}
- name: reload pgbouncer
systemd: name=pgbouncer state=reloaded enabled=yes daemon_reload=yes
...