active_postgres 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +142 -5
- data/lib/active_postgres/cli.rb +7 -0
- data/lib/active_postgres/components/core.rb +2 -7
- data/lib/active_postgres/components/extensions.rb +40 -35
- data/lib/active_postgres/components/monitoring.rb +256 -4
- data/lib/active_postgres/components/pgbackrest.rb +91 -5
- data/lib/active_postgres/components/pgbouncer.rb +58 -7
- data/lib/active_postgres/components/repmgr.rb +448 -62
- data/lib/active_postgres/configuration.rb +66 -1
- data/lib/active_postgres/connection_pooler.rb +3 -12
- data/lib/active_postgres/credentials.rb +3 -3
- data/lib/active_postgres/direct_executor.rb +1 -2
- data/lib/active_postgres/generators/active_postgres/install_generator.rb +2 -0
- data/lib/active_postgres/generators/active_postgres/templates/postgres.yml.erb +28 -1
- data/lib/active_postgres/health_checker.rb +29 -7
- data/lib/active_postgres/installer.rb +9 -0
- data/lib/active_postgres/overview.rb +351 -0
- data/lib/active_postgres/rollback_manager.rb +4 -8
- data/lib/active_postgres/secrets.rb +23 -5
- data/lib/active_postgres/ssh_executor.rb +44 -19
- data/lib/active_postgres/version.rb +1 -1
- data/lib/active_postgres.rb +1 -0
- data/lib/tasks/postgres.rake +77 -4
- data/lib/tasks/rotate_credentials.rake +4 -16
- data/templates/pg_hba.conf.erb +4 -1
- data/templates/pgbackrest.conf.erb +28 -0
- data/templates/pgbouncer-follow-primary.service.erb +8 -0
- data/templates/pgbouncer-follow-primary.timer.erb +11 -0
- data/templates/pgbouncer.ini.erb +2 -0
- data/templates/pgbouncer_follow_primary.sh.erb +34 -0
- data/templates/postgresql.conf.erb +4 -0
- data/templates/repmgr.conf.erb +10 -3
- data/templates/repmgr_dns_failover.sh.erb +59 -0
- metadata +6 -1
|
@@ -19,6 +19,8 @@ module ActivePostgres
|
|
|
19
19
|
config.all_hosts.each do |host|
|
|
20
20
|
ssh_executor.execute_on_host(host) do
|
|
21
21
|
execute :sudo, 'apt-get', 'remove', '-y', 'pgbackrest'
|
|
22
|
+
execute :sudo, 'rm', '-f', '/etc/cron.d/pgbackrest-backup'
|
|
23
|
+
execute :sudo, 'rm', '-f', '/etc/cron.d/pgbackrest-backup-incremental'
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -27,13 +29,21 @@ module ActivePostgres
|
|
|
27
29
|
puts "pgBackRest is a backup tool and doesn't run as a service."
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
def install_on_standby(host)
|
|
33
|
+
puts " Installing pgBackRest on standby #{host}..."
|
|
34
|
+
install_on_host(host, create_stanza: false)
|
|
35
|
+
end
|
|
36
|
+
|
|
30
37
|
def run_backup(type = 'full')
|
|
31
38
|
puts "Running #{type} backup..."
|
|
32
39
|
postgres_user = config.postgres_user
|
|
33
40
|
|
|
41
|
+
output = nil
|
|
34
42
|
ssh_executor.execute_on_host(config.primary_host) do
|
|
35
|
-
|
|
43
|
+
output = capture(:sudo, '-u', postgres_user, 'pgbackrest', '--stanza=main', "--type=#{type}", 'backup')
|
|
36
44
|
end
|
|
45
|
+
|
|
46
|
+
puts output if output
|
|
37
47
|
end
|
|
38
48
|
|
|
39
49
|
def run_restore(backup_id)
|
|
@@ -45,20 +55,43 @@ module ActivePostgres
|
|
|
45
55
|
execute :sudo, 'systemctl', 'stop', 'postgresql'
|
|
46
56
|
|
|
47
57
|
# Restore
|
|
48
|
-
|
|
58
|
+
output = capture(:sudo, '-u', postgres_user, 'pgbackrest', '--stanza=main', "--set=#{backup_id}", 'restore')
|
|
59
|
+
puts output if output
|
|
49
60
|
|
|
50
61
|
# Start PostgreSQL
|
|
51
62
|
execute :sudo, 'systemctl', 'start', 'postgresql'
|
|
52
63
|
end
|
|
53
64
|
end
|
|
54
65
|
|
|
66
|
+
def run_restore_at(target_time, target_action: 'promote')
|
|
67
|
+
puts "Restoring to #{target_time} (PITR)..."
|
|
68
|
+
postgres_user = config.postgres_user
|
|
69
|
+
|
|
70
|
+
ssh_executor.execute_on_host(config.primary_host) do
|
|
71
|
+
execute :sudo, 'systemctl', 'stop', 'postgresql'
|
|
72
|
+
|
|
73
|
+
output = capture(:sudo, '-u', postgres_user, 'pgbackrest',
|
|
74
|
+
'--stanza=main',
|
|
75
|
+
'--type=time',
|
|
76
|
+
"--target=#{target_time}",
|
|
77
|
+
"--target-action=#{target_action}",
|
|
78
|
+
'restore')
|
|
79
|
+
puts output if output
|
|
80
|
+
|
|
81
|
+
execute :sudo, 'systemctl', 'start', 'postgresql'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
55
85
|
def list_backups
|
|
56
86
|
puts 'Available backups:'
|
|
57
87
|
postgres_user = config.postgres_user
|
|
58
88
|
|
|
89
|
+
output = nil
|
|
59
90
|
ssh_executor.execute_on_host(config.primary_host) do
|
|
60
|
-
|
|
91
|
+
output = capture(:sudo, '-u', postgres_user, 'pgbackrest', 'info')
|
|
61
92
|
end
|
|
93
|
+
|
|
94
|
+
puts output if output
|
|
62
95
|
end
|
|
63
96
|
|
|
64
97
|
private
|
|
@@ -66,7 +99,7 @@ module ActivePostgres
|
|
|
66
99
|
def install_on_host(host, create_stanza: true)
|
|
67
100
|
puts " Installing pgBackRest on #{host}..."
|
|
68
101
|
|
|
69
|
-
pgbackrest_config = config.component_config(:pgbackrest)
|
|
102
|
+
pgbackrest_config = secrets.resolve_value(config.component_config(:pgbackrest))
|
|
70
103
|
postgres_user = config.postgres_user
|
|
71
104
|
secrets_obj = secrets
|
|
72
105
|
_ = pgbackrest_config # Used in ERB template
|
|
@@ -78,7 +111,8 @@ module ActivePostgres
|
|
|
78
111
|
end
|
|
79
112
|
|
|
80
113
|
# Upload configuration
|
|
81
|
-
upload_template(host, 'pgbackrest.conf.erb', '/etc/pgbackrest.conf', binding,
|
|
114
|
+
upload_template(host, 'pgbackrest.conf.erb', '/etc/pgbackrest.conf', binding,
|
|
115
|
+
mode: '640', owner: "root:#{postgres_user}")
|
|
82
116
|
|
|
83
117
|
ssh_executor.execute_on_host(host) do
|
|
84
118
|
execute :sudo, 'rm', '-rf', '/var/lib/pgbackrest', '||', 'true'
|
|
@@ -99,6 +133,58 @@ module ActivePostgres
|
|
|
99
133
|
execute :sudo, '-u', postgres_user, 'pgbackrest', '--stanza=main', 'stanza-create'
|
|
100
134
|
end
|
|
101
135
|
end
|
|
136
|
+
|
|
137
|
+
# Set up scheduled backups on primary only
|
|
138
|
+
if create_stanza
|
|
139
|
+
setup_backup_schedules(host, pgbackrest_config)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def setup_backup_schedules(host, pgbackrest_config)
|
|
144
|
+
schedules = backup_schedules(pgbackrest_config)
|
|
145
|
+
schedules.each do |entry|
|
|
146
|
+
setup_backup_schedule(host, entry[:schedule], entry[:type], entry[:file])
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def backup_schedules(pgbackrest_config)
|
|
151
|
+
schedule_full = pgbackrest_config[:schedule_full] || pgbackrest_config[:schedule]
|
|
152
|
+
schedule_incremental = pgbackrest_config[:schedule_incremental]
|
|
153
|
+
|
|
154
|
+
schedules = []
|
|
155
|
+
if schedule_full
|
|
156
|
+
schedules << { type: 'full', schedule: schedule_full, file: '/etc/cron.d/pgbackrest-backup' }
|
|
157
|
+
end
|
|
158
|
+
if schedule_incremental
|
|
159
|
+
schedules << { type: 'incremental', schedule: schedule_incremental, file: '/etc/cron.d/pgbackrest-backup-incremental' }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
schedules
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def setup_backup_schedule(host, schedule, backup_type = 'full', cron_file = '/etc/cron.d/pgbackrest-backup')
|
|
166
|
+
puts " Setting up #{backup_type} backup schedule: #{schedule}"
|
|
167
|
+
postgres_user = config.postgres_user
|
|
168
|
+
|
|
169
|
+
# Create cron job for scheduled backups
|
|
170
|
+
# /etc/cron.d format requires username after time spec
|
|
171
|
+
cron_content = <<~CRON
|
|
172
|
+
# pgBackRest scheduled backups (managed by active_postgres)
|
|
173
|
+
SHELL=/bin/bash
|
|
174
|
+
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
|
175
|
+
#{schedule} #{postgres_user} pgbackrest --stanza=main --type=#{backup_type} backup
|
|
176
|
+
CRON
|
|
177
|
+
|
|
178
|
+
# Install cron job in /etc/cron.d (system cron directory)
|
|
179
|
+
# Use a temp file + sudo mv to avoid shell redirection permission issues.
|
|
180
|
+
ssh_executor.upload_file(host, cron_content, cron_file, mode: '644', owner: 'root:root')
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def remove_backup_schedule(host)
|
|
184
|
+
ssh_executor.execute_on_host(host) do
|
|
185
|
+
execute :sudo, 'rm', '-f', '/etc/cron.d/pgbackrest-backup'
|
|
186
|
+
execute :sudo, 'rm', '-f', '/etc/cron.d/pgbackrest-backup-incremental'
|
|
187
|
+
end
|
|
102
188
|
end
|
|
103
189
|
end
|
|
104
190
|
end
|
|
@@ -5,7 +5,8 @@ module ActivePostgres
|
|
|
5
5
|
puts 'Installing PgBouncer for connection pooling...'
|
|
6
6
|
|
|
7
7
|
config.all_hosts.each do |host|
|
|
8
|
-
|
|
8
|
+
is_standby = config.standby_hosts.include?(host)
|
|
9
|
+
install_on_host(host, is_standby: is_standby)
|
|
9
10
|
end
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -14,6 +15,11 @@ module ActivePostgres
|
|
|
14
15
|
|
|
15
16
|
config.all_hosts.each do |host|
|
|
16
17
|
ssh_executor.execute_on_host(host) do
|
|
18
|
+
execute :sudo, 'systemctl', 'disable', '--now', 'pgbouncer-follow-primary.timer', '||', 'true'
|
|
19
|
+
execute :sudo, 'rm', '-f', '/usr/local/bin/pgbouncer-follow-primary',
|
|
20
|
+
'/etc/systemd/system/pgbouncer-follow-primary.service',
|
|
21
|
+
'/etc/systemd/system/pgbouncer-follow-primary.timer'
|
|
22
|
+
execute :sudo, 'systemctl', 'daemon-reload'
|
|
17
23
|
execute :sudo, 'systemctl', 'stop', 'pgbouncer'
|
|
18
24
|
execute :sudo, 'apt-get', 'remove', '-y', 'pgbouncer'
|
|
19
25
|
end
|
|
@@ -44,21 +50,42 @@ module ActivePostgres
|
|
|
44
50
|
|
|
45
51
|
def install_on_standby(standby_host)
|
|
46
52
|
puts "Installing PgBouncer on standby #{standby_host}..."
|
|
47
|
-
install_on_host(standby_host)
|
|
53
|
+
install_on_host(standby_host, is_standby: true)
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
private
|
|
51
57
|
|
|
52
|
-
def install_on_host(host)
|
|
58
|
+
def install_on_host(host, is_standby: false)
|
|
53
59
|
puts " Installing PgBouncer on #{host}..."
|
|
54
60
|
|
|
55
61
|
user_config = config.component_config(:pgbouncer)
|
|
56
62
|
|
|
63
|
+
# Determine follow_primary behavior:
|
|
64
|
+
# - Primary: use global follow_primary setting
|
|
65
|
+
# - Standby: check per-standby pgbouncer_follow_primary, default false (use localhost for read replicas)
|
|
66
|
+
if is_standby
|
|
67
|
+
standby_config = config.standby_config_for(host) || {}
|
|
68
|
+
follow_primary = standby_config['pgbouncer_follow_primary'] == true
|
|
69
|
+
else
|
|
70
|
+
follow_primary = user_config[:follow_primary] == true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if follow_primary && !config.component_enabled?(:repmgr)
|
|
74
|
+
raise Error, 'PgBouncer follow_primary requires repmgr to be enabled'
|
|
75
|
+
end
|
|
76
|
+
|
|
57
77
|
max_connections = get_postgres_max_connections(host)
|
|
58
78
|
optimal_pool = ConnectionPooler.calculate_optimal_pool_sizes(max_connections)
|
|
59
79
|
|
|
60
80
|
pgbouncer_config = optimal_pool.merge(user_config)
|
|
81
|
+
# For standbys not following primary, use localhost; otherwise use primary host
|
|
82
|
+
pgbouncer_config[:database_host] = follow_primary ? config.primary_replication_host : '127.0.0.1'
|
|
61
83
|
ssl_enabled = config.component_enabled?(:ssl)
|
|
84
|
+
has_ca_cert = ssl_enabled && secrets.resolve('ssl_chain')
|
|
85
|
+
secrets_obj = secrets
|
|
86
|
+
_ = pgbouncer_config
|
|
87
|
+
_ = has_ca_cert
|
|
88
|
+
_ = secrets_obj
|
|
62
89
|
|
|
63
90
|
puts " Calculated pool settings for max_connections=#{max_connections}"
|
|
64
91
|
|
|
@@ -76,6 +103,8 @@ module ActivePostgres
|
|
|
76
103
|
execute :sudo, 'systemctl', 'enable', 'pgbouncer'
|
|
77
104
|
execute :sudo, 'systemctl', 'restart', 'pgbouncer'
|
|
78
105
|
end
|
|
106
|
+
|
|
107
|
+
install_follow_primary(host, pgbouncer_config) if follow_primary
|
|
79
108
|
end
|
|
80
109
|
|
|
81
110
|
def setup_ssl_certs(host)
|
|
@@ -130,10 +159,7 @@ module ActivePostgres
|
|
|
130
159
|
def fetch_user_hash(backend, user, postgres_user)
|
|
131
160
|
sql = build_user_hash_sql(user)
|
|
132
161
|
|
|
133
|
-
|
|
134
|
-
backend.execute :chmod, '644', '/tmp/get_user_hash.sql'
|
|
135
|
-
user_hash = backend.capture(:sudo, '-u', postgres_user, 'psql', '-t', '-f', '/tmp/get_user_hash.sql').strip
|
|
136
|
-
backend.execute :rm, '-f', '/tmp/get_user_hash.sql'
|
|
162
|
+
user_hash = ssh_executor.run_sql_on_backend(backend, sql, postgres_user: postgres_user).to_s.strip
|
|
137
163
|
|
|
138
164
|
if user_hash && !user_hash.empty?
|
|
139
165
|
puts " ✓ Added #{user} to PgBouncer userlist"
|
|
@@ -167,6 +193,31 @@ module ActivePostgres
|
|
|
167
193
|
warn ' Warning: No users added to userlist - connections may fail'
|
|
168
194
|
end
|
|
169
195
|
end
|
|
196
|
+
|
|
197
|
+
def install_follow_primary(host, pgbouncer_config)
|
|
198
|
+
interval = pgbouncer_config[:follow_primary_interval] || 5
|
|
199
|
+
interval = interval.to_i
|
|
200
|
+
interval = 5 if interval <= 0
|
|
201
|
+
|
|
202
|
+
repmgr_conf = '/etc/repmgr.conf'
|
|
203
|
+
postgres_user = config.postgres_user
|
|
204
|
+
_ = interval
|
|
205
|
+
_ = repmgr_conf
|
|
206
|
+
_ = postgres_user
|
|
207
|
+
|
|
208
|
+
upload_template(host, 'pgbouncer_follow_primary.sh.erb', '/usr/local/bin/pgbouncer-follow-primary', binding,
|
|
209
|
+
mode: '755', owner: 'root:root')
|
|
210
|
+
upload_template(host, 'pgbouncer-follow-primary.service.erb',
|
|
211
|
+
'/etc/systemd/system/pgbouncer-follow-primary.service', binding, mode: '644', owner: 'root:root')
|
|
212
|
+
upload_template(host, 'pgbouncer-follow-primary.timer.erb',
|
|
213
|
+
'/etc/systemd/system/pgbouncer-follow-primary.timer', binding, mode: '644', owner: 'root:root')
|
|
214
|
+
|
|
215
|
+
ssh_executor.execute_on_host(host) do
|
|
216
|
+
execute :sudo, 'systemctl', 'daemon-reload'
|
|
217
|
+
execute :sudo, 'systemctl', 'enable', '--now', 'pgbouncer-follow-primary.timer'
|
|
218
|
+
execute :sudo, 'systemctl', 'start', 'pgbouncer-follow-primary.service'
|
|
219
|
+
end
|
|
220
|
+
end
|
|
170
221
|
end
|
|
171
222
|
end
|
|
172
223
|
end
|