-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackup-cluster.yml
190 lines (164 loc) · 7.63 KB
/
backup-cluster.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
---
- name: Deploy PostgreSQL standalone | Pre-checks servers for playbook
hosts: hosts_backup
become: true
become_method: sudo
gather_facts: true
tags: always
any_errors_fatal: true
environment: "{{ proxy_env | default({}) }}"
pre_tasks:
- name: System information
ansible.builtin.debug:
var: system_info
vars:
system_info:
OS: "{{ ansible_distribution | default('N/A') }} {{ ansible_distribution_version | default('N/A') }}"
Kernel: "{{ ansible_kernel | default('N/A') }}"
CPU model: >-
{{ ansible_processor[2] | default('N/A') }},
count: {{ ansible_processor_count | default('N/A') }},
cores: {{ ansible_processor_cores | default('N/A') }}
Memory: "{{ (ansible_memtotal_mb / 1024) | round(2) if ansible_memtotal_mb is defined else 'N/A' }} GB"
Disk space total: >-
{{
(ansible_mounts
| map(attribute='size_total')
| map('int')
| sum / 1024 / 1024 / 1024
)
| round(2) if ansible_mounts is defined else 'N/A'
}} GB
Architecture: "{{ ansible_architecture | default('N/A') }}"
Virtualization type: "{{ ansible_virtualization_type | default('N/A') }}"
Product name: "{{ ansible_product_name | default('N/A') }}"
tags: always
tasks:
- name: Get default IPv4 IP address
debug:
msg: "Default IPv4 IP Address: {{ ansible_default_ipv4.address }}"
- name: Run patronictl list
command: patronictl -c /etc/patroni/patroni.yml list
register: patronictl_output
ignore_errors: true
- name: Fail if patronictl command failed
fail:
msg: "Failed to get Patroni list using patronictl"
when: patronictl_output.rc != 0
- name: Parse patronictl output and set facts
set_fact:
patroni_members: "{{ patronictl_output.stdout_lines | select('search', '\\|.*running.*\\|') | map('regex_replace', '^\\|\\s*(.*?)\\s*\\|\\s*(.*?)\\s*\\|.*$', '\\1:\\2') | list }}"
patroni_leader_name: "{{ patronictl_output.stdout_lines | select('search', '\\|.*Leader.*\\|') | map('regex_replace', '^\\|\\s*(.*?)\\s*\\|.*$', '\\1') | first | default('No leader found') }}"
when: patronictl_output.rc == 0
- name: Parse patronictl output and set facts
set_fact:
patroni_leader_ip: "{{ patroni_members | select('search', '^' + patroni_leader_name + ':') | map('regex_replace', '^.*?:(.*)$', '\\1') | first | default('No leader ip found') }}"
when: patronictl_output.rc == 0 and patroni_leader_name != 'No leader found'
- name: Print the master node
debug:
msg: "The current master node is: {{ patroni_leader_name }} {{ patroni_leader_ip }}"
when: patronictl_output.rc == 0
- name: Create directory for dumps on leader
file:
path: /var/lib/postgresql/dump
state: directory
owner: postgres
group: postgres
mode: '0755'
delegate_to: "{{ patroni_leader_ip }}"
run_once: true
become: yes
become_user: postgres
when: patroni_leader_ip != 'No leader ip found'
- name: Check if database exists using psql
command: psql -U postgres -d postgres -c "SELECT true FROM pg_database WHERE datname='{{ item }}';"
register: database_exists_result
delegate_to: "{{ patroni_leader_ip }}"
become: yes
become_user: postgres
loop: "{{ database_name }}"
ignore_errors: true # Игнорируем ошибки, потому что запрос вернет ошибку, если базы нет.
- name: Fail if any database does not exist
fail:
msg: "Database {{ database_name[item] }} does not exist. Aborting playbook."
when: "'(0 rows)' in database_exists_result.results[item].stdout"
loop: "{{ range(database_name | length) | list }}"
loop_control:
loop_var: item
#-------------------------------
- name: Get size of each database (using awk)
shell: "psql -U postgres -d postgres -c \"SELECT pg_database_size(datname) FROM pg_database WHERE datname='{{ item }}';\" | awk 'NR==3 {print $1}'"
register: db_size_result
loop: "{{ database_name }}"
loop_control:
loop_var: item
ignore_errors: true # чтобы продолжить, даже если база не найдена
- name: Collect database sizes
set_fact:
all_db_sizes: "{{ all_db_sizes | default([]) + [db_size_result.results[item].stdout] }}"
loop: "{{ range(database_name | length) | list }}"
loop_control:
loop_var: item
- name: Convert all database sizes to integers
set_fact:
db_sizes: "{{ all_db_sizes | map('int') | list }}"
when: all_db_sizes is defined
- name: Calculate total database size
set_fact:
total_db_size: "{{ db_sizes | sum(start=0) }}"
when: db_sizes is defined
- name: Get free disk space
shell: "df -B1 /var/lib/postgresql/dump/ | tail -1 | awk '{print $4}'"
register: disk_space
delegate_to: "{{ patroni_leader_ip }}"
become: yes # Требуются права root для выполнения df
become_user: root # Явное указание пользователя root
- name: Convert free disk space to integer
set_fact:
free_disk_space: "{{ disk_space.stdout | int }}"
when: disk_space.stdout is defined
- name: Print database names and sizes in MB
debug:
msg: "Database {{ database_name[item] }} size: {{ (db_sizes[item] / (1024 * 1024)) | round(2) }} MB"
loop: "{{ range(database_name | length) | list }}"
loop_control:
loop_var: item
when: total_db_size > free_disk_space and total_db_size is defined and db_sizes is defined
- name: Fail if not enough disk space
fail:
msg: "Not enough disk space. Total database size: {{ (total_db_size | int / (1024 * 1024)) | round(2) }} MB. Free disk space: {{ (free_disk_space | int / (1024 * 1024)) | round(2) }} MB."
when: total_db_size > free_disk_space and total_db_size is defined
#-------------------------------
- name: Run pg_dump on leader
command: "pg_dump -U postgres -d {{ database }} -f /var/lib/postgresql/dump/{{ database }}_{{ ansible_date_time.year }}-{{ ansible_date_time.month }}-{{ ansible_date_time.day }}_{{ ansible_date_time.hour }}-{{ ansible_date_time.minute }}.dump"
register: pg_dump_output
delegate_to: "{{ patroni_leader_ip }}"
become: yes
become_user: postgres
with_items: "{{ database_name }}"
loop_control:
loop_var: database
when: patroni_leader_ip != 'No leader ip found'
- name: Fail if pg_dump failed
fail:
msg: "Failed to run pg_dump for database {{ database_name[item] }}"
when: pg_dump_output.results[item].rc != 0 and patroni_leader_ip != 'No leader ip found'
with_items: "{{ range(database_name | length) | list }}"
loop_control:
loop_var: item
- name: Print backup information
debug:
msg: "Backup was created on host {{ patroni_leader_ip }}, and saved to file /var/lib/postgresq/dump/{{ database }}_{{ ansible_date_time.year }}-{{ ansible_date_time.month }}-{{ ansible_date_time.day }}_{{ ansible_date_time.hour }}-{{ ansible_date_time.minute }}.dump"
with_items: "{{ database_name }}"
loop_control:
loop_var: database
when: patroni_leader_ip != 'No leader ip found'
- name: Get disk space information
command: df -h /var/lib/postgresql/dump/
register: disk_space_info
delegate_to: "{{ patroni_leader_ip }}"
become: yes # Требуются права root для выполнения df
become_user: root # Явное указание пользователя root
- name: Display disk space information
debug:
msg: "{{ disk_space_info.stdout_lines }}"