diff --git a/automation/roles/cloud_resources/defaults/main.yml b/automation/roles/cloud_resources/defaults/main.yml index 9cf5ad95f6..f5e39eb721 100644 --- a/automation/roles/cloud_resources/defaults/main.yml +++ b/automation/roles/cloud_resources/defaults/main.yml @@ -32,6 +32,9 @@ database_public_allowed_ips: "" # (comma-separated list of IP addresses in CIDR # Load balancer cloud_load_balancer: true # Create a Load Balancer in the Cloud. +cloud_load_balancer_unified: false # Creates a single load balancer with different ports +cloud_load_balancer_replica_port: 5001 +cloud_load_balancer_sync_port: 5002 # Backups (if 'pgbackrest_install' or 'wal_g_install' is 'true') aws_s3_bucket_create: true # if 'cloud_provider=aws' diff --git a/automation/roles/cloud_resources/tasks/azure.yml b/automation/roles/cloud_resources/tasks/azure.yml index 24db509570..22c36e068f 100644 --- a/automation/roles/cloud_resources/tasks/azure.yml +++ b/automation/roles/cloud_resources/tasks/azure.yml @@ -359,11 +359,78 @@ loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" register: azure_load_balancer - when: cloud_load_balancer | bool and + when: cloud_load_balancer | bool and not cloud_load_balancer_unified | bool and (item == 'primary' or (item == 'replica' and server_count | int > 1) or (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + - name: "Azure: Create or modify unified Load Balancer" + azure.azcollection.azure_rm_loadbalancer: + resource_group: "{{ azure_resource_group | default('postgres-cluster-resource-group' ~ '-' ~ server_location) }}" + name: "{{ patroni_cluster_name }}-{{item}}" + location: "{{ server_location }}" + frontend_ip_configurations: + - name: "{{ patroni_cluster_name }}-lb-frontend" + public_ip_address: "{{ database_public_access | bool | ternary(patroni_cluster_name ~ '-lb-public-ip', omit) }}" + subnet: "{{ database_public_access | bool | ternary(omit, network_info.virtualnetworks[0].subnets[0].id) }}" + backend_address_pools: + - name: "{{ patroni_cluster_name }}-lb-backend" + probes: + - name: "{{ patroni_cluster_name }}-primary-probe" + protocol: "Http" + port: "{{ patroni_restapi_port }}" + request_path: "/primary" + interval: 5 + fail_count: 2 + - name: "{{ patroni_cluster_name }}-replica-probe" + protocol: "Http" + port: "{{ patroni_restapi_port }}" + request_path: "/replica" + interval: 5 + fail_count: 2 + - name: "{{ patroni_cluster_name }}-sync-probe" + protocol: "Http" + port: "{{ patroni_restapi_port }}" + request_path: "/sync" + interval: 5 + fail_count: 2 + load_balancing_rules: + - name: "{{ patroni_cluster_name }}-primary-rule" + frontend_ip_configuration: "{{ patroni_cluster_name }}-lb-frontend" + frontend_port: "{{ postgresql_port | default('5432') }}" + backend_address_pool: "{{ patroni_cluster_name }}-lb-backend" + backend_port: "{{ postgresql_port | default('5432') }}" + probe: "{{ patroni_cluster_name }}-primary-probe" + protocol: "Tcp" + idle_timeout: 10 + enable_floating_ip: false + disable_outbound_snat: true + - name: "{{ patroni_cluster_name }}-replica-rule" + frontend_ip_configuration: "{{ patroni_cluster_name }}-lb-frontend" + frontend_port: "{{ cloud_load_balancer_replica_port | default(postgresql_port | default('5432') | int + 1) }}" + backend_address_pool: "{{ patroni_cluster_name }}-lb-backend" + backend_port: "{{ postgresql_port | default('5432') }}" + probe: "{{ patroni_cluster_name }}-replica-probe" + protocol: "Tcp" + idle_timeout: 10 + enable_floating_ip: false + disable_outbound_snat: true + - name: "{{ patroni_cluster_name }}-sync-rule" + frontend_ip_configuration: "{{ patroni_cluster_name }}-lb-frontend" + frontend_port: "{{ cloud_load_balancer_sync_port | default(postgresql_port | default('5432') | int + 2) }}" + backend_address_pool: "{{ patroni_cluster_name }}-lb-backend" + backend_port: "{{ postgresql_port | default('5432') }}" + probe: "{{ patroni_cluster_name }}-sync-probe" + protocol: "Tcp" + idle_timeout: 10 + enable_floating_ip: false + disable_outbound_snat: true + sku: "Standard" + loop: + - "lb" + register: azure_load_balancer + when: cloud_load_balancer | bool and cloud_load_balancer_unified | bool + - name: Extract virtual machine private IPs ansible.builtin.set_fact: private_ips: >- diff --git a/automation/roles/cloud_resources/tasks/hetzner.yml b/automation/roles/cloud_resources/tasks/hetzner.yml index 6b8ad792aa..fca78e50d8 100644 --- a/automation/roles/cloud_resources/tasks/hetzner.yml +++ b/automation/roles/cloud_resources/tasks/hetzner.yml @@ -533,12 +533,14 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" when: cloud_load_balancer | bool and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Configure Load Balancer service" hetzner.hcloud.load_balancer_service: @@ -566,11 +568,77 @@ label: "{{ patroni_cluster_name }}-{{ item }}" vars: database_port: "{{ pgbouncer_listen_port | default(6432) if pgbouncer_install | bool else postgresql_port | default(5432) }}" - when: cloud_load_balancer | bool and + when: cloud_load_balancer | bool and not cloud_load_balancer_unified | bool and (item == 'primary' or (item == 'replica' and server_count | int > 1) or (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + - name: "Hetzner Cloud: Configure Load Balancer primary service" + hetzner.hcloud.load_balancer_service: + api_token: "{{ lookup('ansible.builtin.env', 'HCLOUD_API_TOKEN') }}" + load_balancer: "{{ patroni_cluster_name }}-lb" + listen_port: "{{ hetzner_load_balancer_port | default(database_port) }}" + destination_port: "{{ pgbouncer_listen_port | default(6432) if pgbouncer_install | bool else postgresql_port | default(5432) }}" + protocol: tcp + health_check: + protocol: http + port: "{{ patroni_restapi_port }}" + interval: 5 + timeout: 2 + retries: 3 + http: + path: "/primary" + status_codes: + - "200" + state: present + vars: + database_port: "{{ pgbouncer_listen_port | default(6432) if pgbouncer_install | bool else postgresql_port | default(5432) }}" + when: cloud_load_balancer | bool and cloud_load_balancer_unified | bool + + - name: "Hetzner Cloud: Configure Load Balancer replica service" + hetzner.hcloud.load_balancer_service: + api_token: "{{ lookup('ansible.builtin.env', 'HCLOUD_API_TOKEN') }}" + load_balancer: "{{ patroni_cluster_name }}-lb" + listen_port: "{{ hetzner_load_balancer_replica_port | default(database_port) }}" + destination_port: "{{ pgbouncer_listen_port | default(6432) if pgbouncer_install | bool else postgresql_port | default(5432) }}" + protocol: tcp + health_check: + protocol: http + port: "{{ patroni_restapi_port }}" + interval: 5 + timeout: 2 + retries: 3 + http: + path: "/replica" + status_codes: + - "200" + state: present + vars: + database_port: "{{ cloud_load_balancer_replica_port | default(5433) }}" + when: cloud_load_balancer | bool and cloud_load_balancer_unified | bool and server_count | int > 1 + + - name: "Hetzner Cloud: Configure Load Balancer sync service" + hetzner.hcloud.load_balancer_service: + api_token: "{{ lookup('ansible.builtin.env', 'HCLOUD_API_TOKEN') }}" + load_balancer: "{{ patroni_cluster_name }}-lb" + listen_port: "{{ hetzner_load_balancer_sync_port | default(database_port) }}" + destination_port: "{{ pgbouncer_listen_port | default(6432) if pgbouncer_install | bool else postgresql_port | default(5432) }}" + protocol: tcp + health_check: + protocol: http + port: "{{ patroni_restapi_port }}" + interval: 5 + timeout: 2 + retries: 3 + http: + path: "/sync" + status_codes: + - "200" + state: present + vars: + database_port: "{{ cloud_load_balancer_sync_port | default(5434) }}" + when: cloud_load_balancer | bool and cloud_load_balancer_unified | bool and server_count | int > 1 and synchronous_mode | bool + - name: "Hetzner Cloud: Add Load Balancer to network '{{ server_network }}'" hetzner.hcloud.load_balancer_network: api_token: "{{ lookup('ansible.builtin.env', 'HCLOUD_API_TOKEN') }}" @@ -581,12 +649,14 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" when: cloud_load_balancer | bool and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Disable public interface for Load Balancer" hetzner.hcloud.load_balancer: @@ -598,6 +668,7 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" register: hetzner_load_balancer_disable_public @@ -605,9 +676,10 @@ delay: 5 retries: 3 when: (cloud_load_balancer | bool and not database_public_access | bool) and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Enable public interface for Load Balancer" hetzner.hcloud.load_balancer: @@ -619,6 +691,7 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" register: hetzner_load_balancer_enable_public @@ -626,9 +699,10 @@ delay: 5 retries: 3 when: (cloud_load_balancer | bool and database_public_access | bool) and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Add servers to Load Balancer (use label_selector 'cluster={{ patroni_cluster_name }}')" hetzner.hcloud.load_balancer_target: @@ -641,12 +715,14 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" when: cloud_load_balancer | bool and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Gather information about Load Balancers" hetzner.hcloud.load_balancer_info: @@ -759,12 +835,14 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" when: cloud_load_balancer | bool and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Delete Load Balancer" hetzner.hcloud.load_balancer: @@ -776,12 +854,14 @@ - "primary" - "replica" - "sync" + - "lb" loop_control: label: "{{ patroni_cluster_name }}-{{ item }}" when: cloud_load_balancer | bool and - (item == 'primary' or - (item == 'replica' and server_count | int > 1) or - (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool)) + ((item == 'primary' and not cloud_load_balancer_unified | bool) or + (item == 'replica' and server_count | int > 1 and not cloud_load_balancer_unified | bool) or + (item in ['sync', 'async'] and server_count | int > 1 and synchronous_mode | bool and not cloud_load_balancer_unified | bool) or + (item == 'lb' and cloud_load_balancer_unified | bool)) - name: "Hetzner Cloud: Delete public firewall" hetzner.hcloud.firewall: