--- # tasks/main.yml - name: Apply new Patroni configuration ansible.builtin.uri: url: "http://{{ patroni_host }}:{{ patroni_api_port }}/config" method: PATCH body: "{{ new_config | to_json }}" body_format: json status_code: 200 headers: Content-Type: "application/json" register: apply_result changed_when: apply_result.status == 200 notify: "config applied" - name: Wait for config propagation # noqa: no-handler ansible.builtin.wait_for: timeout: 30 delay: 5 when: apply_result is changed - name: Check for pending restarts # noqa: no-handler when: apply_result is changed run_once: true block: - name: Get cluster status with retry ansible.builtin.uri: url: "http://{{ patroni_host }}:{{ patroni_api_port }}/cluster" method: GET return_content: yes status_code: 200 register: cluster_status until: cluster_status.json is defined retries: 3 delay: 2 delegate_to: localhost run_once: true - name: Check restart flags ansible.builtin.set_fact: needs_restart: >- {{ (cluster_status.json.members | map(attribute='pending_restart', default=false) | select('equalto', true) | list | length > 0) or (cluster_status.json.members | map(attribute='tags.pending_restart', default=false) | select('equalto', true) | list | length > 0) }} node_names: "{{ cluster_status.json.members | map(attribute='name') | list }}" node_info: >- {% set info = {} %} {% for member in cluster_status.json.members %} {% set _ = info.update({member.name: {'role': member.role}}) %} {% endfor %} {{ info }} run_once: true rescue: - name: Set no restart needed ansible.builtin.set_fact: needs_restart: false run_once: true - name: Display restart warning ansible.builtin.debug: msg: | {% if needs_restart %} {% if autorestart %} ================================================ ПРЕДУПРЕЖДЕНИЕ: АВТОМАТИЧЕСКИЙ ПЕРЕЗАПУСК КЛАСТЕРА ================================================ Следующие ноды будут перезапущены: {% for node in node_names %} - {{ node }} ({{ node_info[node].role | default('UNKNOWN') }}) {% endfor %} Для отмены нажмите Ctrl+C в течение 10 секунд {% else %} ============================================ ВНИМАНИЕ: НЕОБХОДИМ РУЧНОЙ ПЕРЕЗАПУСК КЛАСТЕРА ============================================ Выполните на одной из нод: patronictl restart -c /etc/patrony.yml {{ node_names | join(' ') }} Ноды для перезапуска: {% for node in node_names %} - {{ node }} ({{ node_info[node].role | default('UNKNOWN') }}) {% endfor %} {% endif %} {% else %} ================================ ПЕРЕЗАГРУЗКА НЕ ТРЕБУЕТСЯ ================================ {% endif %} delegate_to: localhost run_once: true when: - needs_restart is defined - node_names is defined - node_info is defined - name: Confirm automatic restart ansible.builtin.pause: prompt: "Подтвердите автоматический перезапуск кластера (Enter - продолжить, Ctrl+C - отмена)" seconds: 10 when: - needs_restart | default(false) - autorestart | default(false) delegate_to: localhost run_once: true - name: Execute cluster restart when: - needs_restart | default(false) - autorestart | bool - cluster_status is defined - cluster_status.json is defined - cluster_status.json.members is defined run_once: true block: - name: Find nodes needing restart ansible.builtin.set_fact: nodes_to_restart: >- {% set nodes = [] %}{% for member in cluster_status.json.members %}{% if member.pending_restart is defined and member.pending_restart or member.tags.pending_restart is defined and member.tags.pending_restart %}{% set _ = nodes.append(member) %}{% endif %}{% endfor %}{{ nodes }} - name: Restart nodes via API ansible.builtin.uri: url: "http://{{ item.host }}:{{ patroni_api_port }}/restart" method: POST body_format: json body: restart_pending: true timeout: 60 status_code: [200, 503] loop: "{{ nodes_to_restart | default([]) }}" loop_control: label: "{{ item.name }}" register: restart_results ignore_errors: yes changed_when: > restart_results.status == 200 or restart_results.status == 503 - name: Wait for cluster stabilization block: - name: Check cluster status until stable ansible.builtin.uri: url: "http://{{ patroni_host }}:{{ patroni_api_port }}/cluster" method: GET return_content: yes status_code: 200 register: cluster_health until: > cluster_health.json.members | selectattr('state', 'match', '^(running|streaming)$') | list | length == cluster_health.json.members | length retries: 12 delay: 10 delegate_to: localhost run_once: true - name: Show restart results ansible.builtin.debug: msg: | ======================== РЕЗУЛЬТАТЫ ПЕРЕЗАГРУЗКИ ======================== Нода: {{ item.item.name }} Роль: {{ item.item.role }} Статус: {% if item.status == 200 %}Успешно перезапущена{% elif item.status == 503 %}Перезапуск в процессе{% else %}Ошибка (код {{ item.status }}){% endif %} Время выполнения: {{ item.elapsed }} сек {% if item.item.pending_restart_reason is defined %} Причина перезагрузки: {% for param, values in item.item.pending_restart_reason.items() %} - {{ param }}: было {{ values.old_value }}, стало {{ values.new_value }} {% endfor %} {% endif %} ------------------------ loop: "{{ restart_results.results | default([]) }}" loop_control: label: "" run_once: true - name: Archive old configurations # noqa: no-handler when: apply_result is changed run_once: true block: - name: Find old config files ansible.builtin.find: path: "{{ config_dir }}" pattern: "*-config.yaml" age: "10s" register: old_configs delegate_to: localhost connection: local - name: Remove excess configs ansible.builtin.file: path: "{{ item.path }}" state: absent loop: "{{ (old_configs.files | sort(attribute='mtime'))[:-10] }}" when: old_configs.matched > 10 delegate_to: localhost connection: local notify: "Log cleanup"