├── roles ├── common │ ├── vars │ │ └── main.yaml │ └── tasks │ │ ├── vault.yaml │ │ ├── TLS.yaml │ │ ├── maas_supported_os.yaml │ │ ├── backup.yaml │ │ └── restore.yaml ├── maas_proxy │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── teardown.yaml │ │ └── main.yml │ └── templates │ │ └── haproxy.cfg.j2 ├── maas_corosync │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── teardown.yaml │ │ └── main.yaml │ └── templates │ │ └── corosync.conf.j2 ├── maas_pacemaker │ ├── templates │ │ ├── postgresql-part.conf.j2 │ │ ├── pacemaker_sshd_config.j2 │ │ └── configure_pacemaker.sh.j2 │ ├── handlers │ │ └── main.yaml │ └── tasks │ │ ├── teardown.yaml │ │ ├── o11y.yaml │ │ └── main.yaml ├── maas_postgres_proxy │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── teardown.yaml │ │ └── main.yaml │ └── templates │ │ └── haproxy.cfg.j2 ├── o11y_agent │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── teardown.yaml │ │ └── main.yaml │ └── templates │ │ ├── agent-snap.yaml.j2 │ │ ├── agent-deb.yaml.j2 │ │ └── grafana-agent.yaml.j2 ├── maas_rack_controller │ └── tasks │ │ ├── teardown.yaml │ │ ├── upgrade_maas.yaml │ │ ├── main.yaml │ │ └── install_maas.yaml ├── maas_region_controller │ ├── templates │ │ └── regiond.conf.j2 │ └── tasks │ │ ├── teardown.yaml │ │ ├── o11y.yaml │ │ ├── update_maas.yaml │ │ ├── main.yaml │ │ └── install_maas.yaml ├── maas_postgres │ ├── tasks │ │ ├── write_postgres_config.yaml │ │ ├── create_replication_user.yaml │ │ ├── main.yaml │ │ ├── teardown.yaml │ │ ├── o11y_pg.yaml │ │ ├── install_postgres.yaml │ │ └── configure_postgres_secondary.yaml │ ├── templates │ │ ├── pgsql_check.j2 │ │ ├── pgsql_check_conf.j2 │ │ ├── pg_hba.conf.j2 │ │ └── postgresql.conf.j2 │ └── handlers │ │ └── main.yaml └── maas_firewall │ └── tasks │ ├── teardown.yaml │ └── setup_firewall_rules.yaml ├── ansible.cfg ├── .gitignore ├── group_vars ├── maas_postgres │ ├── 60-firewall │ ├── 30-replication │ ├── 20-pg_hba │ └── 01-postgres ├── maas_postgres_proxy │ ├── 60-firewall │ └── 10-maas_postgres_proxy ├── maas_proxy │ ├── 60-firewall │ └── 10-maas_proxy ├── all │ ├── 20-vault │ ├── 01-maas │ ├── 50-o11y │ └── 20-database ├── maas_pacemaker │ ├── 01-stonith │ └── 01-pacemaker ├── maas_rack_controller │ ├── 60-firewall │ └── 10-maas_rack_controller └── maas_region_controller │ ├── 60-firewall │ └── 10-maas_region_controller ├── .ansible-lint ├── hosts.yaml ├── .github ├── workflows │ ├── cla-check.yml │ └── main.yml └── ISSUE_TEMPLATE │ ├── missing-capability.md │ └── bug_report.md ├── hosts ├── createadmin.yaml ├── restore.yaml ├── backup.yaml ├── alertrules.yaml ├── teardown.yaml ├── site.yaml ├── README.md └── LICENSE /roles/common/vars/main.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = hosts 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /maas-*-alert-rules 3 | /*-alert-rules.yml 4 | -------------------------------------------------------------------------------- /group_vars/maas_postgres/60-firewall: -------------------------------------------------------------------------------- 1 | --- 2 | maas_pg_tcp_ports: 3 | - 5432 4 | - "{{ 9187 if o11y_enable else (false) }}" 5 | 6 | maas_pg_udp_ports: [] 7 | -------------------------------------------------------------------------------- /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | # .ansible-lint 3 | 4 | exclude_paths: 5 | - .github/ 6 | 7 | # skip_list to skip error when package state=latest 8 | skip_list: 9 | - '403' 10 | -------------------------------------------------------------------------------- /group_vars/maas_postgres_proxy/60-firewall: -------------------------------------------------------------------------------- 1 | --- 2 | maas_pgproxy_tcp_ports: 3 | - 5432 4 | - "{{ 5051 if 'maas_postgres' in group_names else (false) }}" 5 | 6 | maas_pgproxy_udp_ports: 7 | -------------------------------------------------------------------------------- /roles/maas_proxy/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Restart HAProxy" 3 | ansible.builtin.systemd: 4 | name: "haproxy.service" 5 | state: 'restarted' 6 | enabled: true 7 | -------------------------------------------------------------------------------- /roles/maas_corosync/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Restart Corosync" 3 | ansible.builtin.systemd: 4 | name: "corosync.service" 5 | state: restarted 6 | no_block: false 7 | -------------------------------------------------------------------------------- /group_vars/maas_proxy/60-firewall: -------------------------------------------------------------------------------- 1 | --- 2 | maas_proxy_tcp_ports: 3 | - "{{ 5432 if 'maas_postgres' in group_names else (false) }}" 4 | - "{{ maas_proxy_port }}" 5 | 6 | maas_proxy_udp_ports: 7 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/templates/postgresql-part.conf.j2: -------------------------------------------------------------------------------- 1 | # Directory for PostgreSQL temp stat files 2 | d /run/postgresql/{{ maas_postgres_version_number }}-main.pg_stat_tmp 0700 postgres postgres - - 3 | -------------------------------------------------------------------------------- /roles/maas_postgres_proxy/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Restart Postgres HAProxy" 3 | ansible.builtin.systemd: 4 | name: "haproxy.service" 5 | state: 'restarted' 6 | enabled: true 7 | -------------------------------------------------------------------------------- /roles/o11y_agent/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Start grafana agent" 3 | ansible.builtin.systemd: 4 | name: telemetry 5 | enabled: true 6 | state: "restarted" 7 | daemon-reload: true 8 | -------------------------------------------------------------------------------- /hosts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | all: 3 | children: 4 | maas_postgres: 5 | maas_pacemaker: 6 | children: 7 | maas_corosync: 8 | maas_postgres_proxy: 9 | maas_proxy: 10 | maas_region_controller: 11 | maas_rack_controller: 12 | -------------------------------------------------------------------------------- /.github/workflows/cla-check.yml: -------------------------------------------------------------------------------- 1 | name: cla-check 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | cla-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check if CLA signed 10 | uses: canonical/has-signed-canonical-cla@v1 11 | -------------------------------------------------------------------------------- /hosts: -------------------------------------------------------------------------------- 1 | # Define hosts for MAAS here 2 | 3 | [maas_corosync] 4 | 5 | [maas_pacemaker:children] 6 | maas_corosync 7 | 8 | [maas_postgres] 9 | 10 | [maas_postgres_proxy] 11 | 12 | [maas_proxy] 13 | 14 | [maas_region_controller] 15 | 16 | [maas_rack_controller] 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Ansible Lint 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Run ansible-lint 10 | uses: ansible-community/ansible-lint-action@v6.11.0 11 | -------------------------------------------------------------------------------- /roles/maas_rack_controller/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Uninstall MAAS - snap 4 | community.general.snap: 5 | name: maas 6 | state: absent 7 | 8 | - name: Uninstall MAAS - deb 9 | ansible.builtin.apt: 10 | name: 11 | - "maas-rack-controller" 12 | purge: true 13 | state: absent 14 | -------------------------------------------------------------------------------- /group_vars/maas_proxy/10-maas_proxy: -------------------------------------------------------------------------------- 1 | --- 2 | maas_proxy_port: "{{ 5240 if 'maas_region_controller' not in group_names else 5050 }}" 3 | maas_proxy_bind_addr_v4: "0.0.0.0" 4 | maas_proxy_bind_addr_v6: "::" 5 | maas_proxy_upstreams: [] # empty or undefined will use the default ips of each maas_region_controller 6 | maas_proxy_state: "install" 7 | -------------------------------------------------------------------------------- /group_vars/all/20-vault: -------------------------------------------------------------------------------- 1 | --- 2 | vault_integration: false # Use MAAS vault for secret storage 3 | 4 | # MAAS Vault configuration 5 | vault_url: "" # The URL of the MAAS Vault 6 | vault_approle_id: "" # The approle ID used for MAAS Vault 7 | vault_wrapped_token: "" 8 | vault_secrets_path: "" 9 | vault_secret_mount: "" 10 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/templates/pacemaker_sshd_config.j2: -------------------------------------------------------------------------------- 1 | Match User hacluster 2 | {% for host in groups['maas_pacemaker'] %} 3 | Match Address {{ hostvars[host]["ansible_default_ipv4"]["address"] if "ansible_default_ipv4" in hostvars[host] else hostvars[host]["ansible_default_ipv6"]["address"] }} 4 | PasswordAuthentication yes 5 | {% endfor %} 6 | Match All 7 | 8 | -------------------------------------------------------------------------------- /group_vars/maas_pacemaker/01-stonith: -------------------------------------------------------------------------------- 1 | --- 2 | maas_pacemaker_fencing_driver: "fence_ipmilan" # should be overriden depending on the type of host pacemaker is running on 3 | maas_pacemaker_tmp_cib: "/tmp/stonith_cfg" 4 | maas_pacemaker_fence_name: "maas_postgres_fence" 5 | maas_pacemaker_stonith_params: {} # To be filled out by user based on what fencing driver is used 6 | -------------------------------------------------------------------------------- /group_vars/maas_rack_controller/60-firewall: -------------------------------------------------------------------------------- 1 | --- 2 | maas_rack_tcp_ports: 3 | - 53 # dns 4 | - 514 # rsyslog 5 | - 5248 # rack http port 6 | - 3128 7 | - 8000 8 | - "{{ maas_promtail_port if o11y_enable else (false) }}" 9 | 10 | maas_rack_udp_ports: 11 | - 53 # dns 12 | - 67:69 # tftp, dhcp 13 | - 123 # ntp 14 | - 514 # rsyslog 15 | - 5248 # rack http port 16 | -------------------------------------------------------------------------------- /roles/maas_proxy/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Uninstall HAProxy" 3 | ansible.builtin.apt: 4 | name: "haproxy" 5 | state: absent 6 | purge: true 7 | 8 | - name: "Remove HAProxy artifacts" 9 | ansible.builtin.file: 10 | state: absent 11 | name: "{{ item }}" 12 | with_items: 13 | - /etc/haproxy/haproxy.cfg 14 | - /var/lib/haproxy 15 | -------------------------------------------------------------------------------- /roles/maas_postgres_proxy/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Uninstall HAProxy" 3 | ansible.builtin.apt: 4 | name: haproxy 5 | state: absent 6 | purge: true 7 | 8 | - name: "Remove HAProxy artifacts" 9 | ansible.builtin.file: 10 | state: absent 11 | name: "{{ item }}" 12 | with_items: 13 | - /etc/haproxy/haproxy.cfg 14 | - /var/lib/haproxy 15 | -------------------------------------------------------------------------------- /roles/maas_corosync/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Uninstall Corosync" 3 | ansible.builtin.apt: 4 | name: "corosync" 5 | state: absent 6 | purge: true 7 | autoremove: true 8 | 9 | - name: "Remove Corosync artifacts" 10 | ansible.builtin.file: 11 | state: absent 12 | name: "{{ item }}" 13 | with_items: 14 | - "/etc/corosync/corosync.conf" 15 | -------------------------------------------------------------------------------- /roles/maas_corosync/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install Corosync" 3 | ansible.builtin.apt: 4 | name: "corosync" 5 | state: present 6 | update_cache: true 7 | 8 | - name: "Write Corosync config" 9 | ansible.builtin.template: 10 | src: "corosync.conf.j2" 11 | dest: "/etc/corosync/corosync.conf" 12 | mode: 0644 13 | notify: 14 | - "Restart Corosync" 15 | -------------------------------------------------------------------------------- /group_vars/maas_postgres/30-replication: -------------------------------------------------------------------------------- 1 | --- 2 | maas_postgres_primary_address: "" 3 | maas_postgres_primary_conninfo: "user={{ maas_postgres_replication_user }} password=''{{ maas_postgres_replication_password|default(maas_postgres_password) }}'' host={{ maas_postgres_floating_ip|default('127.0.0.1') }} port=5432 application_name={{ inventory_hostname }}" 4 | maas_postgres_replication_slot: "maas_replication" 5 | -------------------------------------------------------------------------------- /roles/maas_region_controller/templates/regiond.conf.j2: -------------------------------------------------------------------------------- 1 | database_host: "{{ 'localhost' if 'maas_postgres_proxy' in group_names else (maas_postgres_primary_host_v6 if maas_postgres_primary_host_v6 else maas_postgres_primary_host_v4) }}" 2 | database_name: "{{ maas_postgres_database }}" 3 | database_pass: "{{ maas_postgres_password }}" 4 | database_port: {{ 5432 }} 5 | database_user: "{{ maas_postgres_user }}" 6 | maas_url: "{{ maas_url }}" 7 | -------------------------------------------------------------------------------- /roles/o11y_agent/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Set the grafana agent service status 4 | ansible.builtin.service: 5 | name: telemetry 6 | state: stopped 7 | when: ansible_facts.services['telemetry.service'] is defined 8 | 9 | - name: "Remove o11y agent artifacts" 10 | ansible.builtin.file: 11 | state: absent 12 | name: "{{ item }}" 13 | with_items: "{{ o11y_grafana_agent_dirs|union([o11y_grafana_agent_unit]) }}" 14 | -------------------------------------------------------------------------------- /group_vars/maas_rack_controller/10-maas_rack_controller: -------------------------------------------------------------------------------- 1 | # MAAS installation setup 2 | maas_snap_channel: "stable" # if using snap, then the channel. ie: stable, beta, edge 3 | maas_deb_state: "present" # if using deb, the state for install 4 | maas_package_name: "{{ 'maas' if (not maas_install_deb | bool) else 'maas-rack-controller' }}" 5 | 6 | maas_log_dir: "{{ '/var/log/maas' if maas_install_deb|bool else '/var/snap/maas/common/log' }}" 7 | maas_promtail_port: 5238 8 | -------------------------------------------------------------------------------- /roles/common/tasks/vault.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Enable the use of vault on MAAS 3 | 4 | - name: Configure MAAS Vault 5 | ansible.builtin.command: > 6 | maas config-vault configure {{ vault_url }} {{ vault_approle_id }} 7 | {{ vault_wrapped_token }} {{ vault_secrets_path }} 8 | --secrets-mount {{ vault_secret_mount }} 9 | changed_when: false 10 | 11 | - name: Migrate MAAS secrets 12 | ansible.builtin.command: maas config-vault migrate-secrets 13 | changed_when: false 14 | -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/write_postgres_config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Write postgresql.conf" 3 | ansible.builtin.template: 4 | src: "postgresql.conf.j2" 5 | dest: "{{ maas_postgres_config_dir }}postgresql.conf" 6 | mode: 0644 7 | owner: 'postgres' 8 | group: 'postgres' 9 | notify: 10 | - "Stop Postgres Service To Load New Configuration" 11 | - "Start Postgres Service To Load New Configuration" 12 | 13 | - name: "Flush Handlers" 14 | ansible.builtin.meta: "flush_handlers" 15 | -------------------------------------------------------------------------------- /roles/maas_region_controller/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Installs MAAS 3 | 4 | - name: Uninstall MAAS - Snap 5 | community.general.snap: 6 | name: maas 7 | state: absent 8 | 9 | - name: Uninstall MAAS - Deb 10 | ansible.builtin.apt: 11 | name: "maas-region-api" 12 | purge: true 13 | state: absent 14 | 15 | - name: Remove MAAS Files 16 | ansible.builtin.file: 17 | path: "{{ item }}" 18 | state: absent 19 | with_items: 20 | - "/var/snap/maas/" 21 | - "/var/lib/maas/" 22 | - "/etc/maas/" 23 | -------------------------------------------------------------------------------- /roles/maas_firewall/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Remove iptables rules as part of teardown 3 | 4 | - name: Set policy for INPUT chain to accept 5 | ansible.builtin.iptables: 6 | chain: INPUT 7 | policy: ACCEPT 8 | 9 | - name: Set policy for FORWARD chain to accept 10 | ansible.builtin.iptables: 11 | chain: FORWARD 12 | policy: ACCEPT 13 | 14 | - name: Flush all other rules 15 | ansible.builtin.iptables: 16 | flush: true 17 | 18 | - name: Remove persistent iptables package 19 | ansible.builtin.apt: 20 | name: "iptables-persistent" 21 | state: absent 22 | -------------------------------------------------------------------------------- /createadmin.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Add (admin) user to already existing installation 3 | 4 | - hosts: maas_region_controller 5 | tasks: 6 | - name: "Create admin" 7 | ansible.builtin.expect: 8 | command: maas createadmin 9 | responses: 10 | "(?i)Username: ": "{{ user_name }}" 11 | "(?i)Password: ": "{{ user_pwd }}" 12 | "(?i)Again: ": "{{ user_pwd }}" 13 | "(?i)Email:": "{{ user_email }}" 14 | "(?i)Import SSH keys": "{{ user_ssh if user_ssh is defined else '' }}" 15 | become: true 16 | no_log: true 17 | gather_facts: true 18 | -------------------------------------------------------------------------------- /group_vars/maas_region_controller/60-firewall: -------------------------------------------------------------------------------- 1 | --- 2 | maas_region_tcp_ports: 3 | - 53 # dns 4 | - 514 # rsyslog 5 | - 3128 6 | - 5240 # MAAS port 7 | - 5241:5247 8 | - 5250:5270 # region workers (RPC) 9 | - 5432 # postgres 10 | - 5443 # MAAS https port 11 | - 8000 12 | - "{{ maas_proxy_port if 'maas_proxy' in group_names else (false) }}" 13 | - "{{ maas_proxy_postgres_port if 'maas_proxy' in group_names else (false) }}" 14 | - "{{ maas_promtail_port if o11y_enable else (false) }}" 15 | 16 | maas_region_udp_ports: 17 | - 53 # dns 18 | - 123 # ntp 19 | - 514 # rsyslog 20 | - 5240 # MAAS port 21 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Ensure Pacemaker is started" 3 | ansible.builtin.systemd: 4 | name: "pacemaker.service" 5 | state: started 6 | enabled: false # if the system is rebooted, the user may not want this member to rejoin automatically 7 | no_block: false 8 | 9 | - name: "Restart sshd" 10 | ansible.builtin.systemd: 11 | name: "sshd.service" 12 | state: restarted 13 | no_block: false 14 | 15 | - name: "Start HA clusters metrics agent" 16 | ansible.builtin.systemd: 17 | name: ha_cluster_exporter 18 | enabled: true 19 | state: "restarted" 20 | daemon-reload: true 21 | -------------------------------------------------------------------------------- /roles/maas_region_controller/tasks/o11y.yaml: -------------------------------------------------------------------------------- 1 | - name: Set the TCP port of the Promtail Push API 2 | ansible.builtin.command: maas {{ admin_username }} maas set-config name=promtail_port value={{ maas_promtail_port }} 3 | when: o11y_loki_url|length > 0 4 | changed_when: false 5 | 6 | - name: Enable syslog forwarding 7 | ansible.builtin.command: maas {{ admin_username }} maas set-config name=promtail_enabled value=true 8 | when: o11y_loki_url|length > 0 9 | 10 | - name: Enable Cluster metrics endpoint 11 | ansible.builtin.command: maas {{ admin_username }} maas set-config name=prometheus_enabled value=true 12 | when: o11y_prometheus_url|length > 0 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/missing-capability.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Missing capability 3 | about: Report missing playbook capabilities 4 | title: Missing - lacking feature 5 | labels: bug, enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | *NOTE: This is not a feature request! This is to report a blind-spot in the play-books that limits expected usage* 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Additional context** 19 | Add any other context feature here. 20 | -------------------------------------------------------------------------------- /roles/maas_rack_controller/tasks/upgrade_maas.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update MAAS - Snap 3 | ansible.builtin.command: snap refresh --channel={{ maas_version }}/{{ maas_snap_channel }} maas 4 | when: maas_installation_type | lower == 'snap' 5 | 6 | - name: Add MAAS apt Repository 7 | ansible.builtin.apt_repository: 8 | repo: "ppa:maas/{{ maas_version }}" 9 | when: maas_installation_type | lower == 'deb' 10 | 11 | - name: Update MAAS Rack Controller - Deb 12 | ansible.builtin.apt: 13 | name: 14 | - "maas-rack-controller" 15 | state: latest 16 | update_cache: true 17 | cache_valid_time: 3600 18 | when: (maas_installation_type | lower == 'deb') 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a problem with the playbooks 4 | title: Bug - Something doesn't work 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Reproducing** 14 | Provide the following, limited to relevant sections 15 | 1. Hosts file 16 | 2. Execution command 17 | 3. Playbook output 18 | Depending on the problem, it may also be helpful to attach the following: 19 | - MAAS Output 20 | 21 | **Expected behaviour** 22 | A short description of the expected behaviour. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /group_vars/maas_region_controller/10-maas_region_controller: -------------------------------------------------------------------------------- 1 | maas_package_name: "{{ 'maas' if (not maas_install_deb | bool) else 'maas-region-api' }}" 2 | 3 | maas_port: 5240 # The port MAAS uses to communicate on 4 | maas_https_port: 5443 # The https port MAAS uses to communicate on 5 | 6 | # administrator account details 7 | admin_username: "admin" 8 | admin_password: "admin" 9 | admin_email: "admin@email.com" 10 | 11 | maas_proxy_port: "{{ 5240 if 'maas_region_controller' not in group_names else 5050 }}" 12 | maas_proxy_postgres_port: "{{ 5432 if 'maas_postgres' not in group_names else 5051 }}" 13 | 14 | maas_promtail_port: 5238 15 | maas_log_dir: "{{ '/var/log/maas' if maas_install_deb|bool else '/var/snap/maas/common/log' }}" 16 | -------------------------------------------------------------------------------- /group_vars/maas_postgres_proxy/10-maas_postgres_proxy: -------------------------------------------------------------------------------- 1 | --- 2 | maas_proxy_postgres_bind_addr_v4: "{{ '127.0.0.1' if 'maas_region_controller' in group_names else ((hostvars[groups['maas_postgres_proxy'][0]]['ansible_default_ipv4']['address']) if hostvars[groups['maas_postgres_proxy'][0]]['ansible_default_ipv4']['address'] is defined else '') }}" 3 | maas_proxy_postgres_bind_addr_v6: "{{ '::1' if 'maas_region_controller' in group_names else ((hostvars[groups['maas_postgres_proxy'][0]]['ansible_default_ipv6']['address']) if hostvars[groups['maas_postgres_proxy'][0]]['ansible_default_ipv6']['address'] is defined else '') }}" 4 | maas_proxy_postgres_port: "{{ 5432 if 'maas_postgres' not in group_names else 5051 }}" 5 | 6 | maas_proxy_postgres_upstreams: [] 7 | 8 | maas_proxy_state: "install" 9 | -------------------------------------------------------------------------------- /roles/maas_postgres/templates/pgsql_check.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PG_HOST="{{ postgres_host|default('localhost') }}" 4 | PG_PORT="{{ postgres_port|default('5432') }}" 5 | PG_DATABASE="{{ postgres_database|default('maasdb') }}" 6 | PG_USER="{{ postgres_user|default('maas') }}" 7 | export PGPASSWORD="{{ maas_postgres_password }}" 8 | 9 | CHECK="$(psql -t -h ${PG_HOST} -U ${PG_USER} -w -p ${PG_PORT} -d ${PG_DATABASE} -c 'SELECT pg_is_in_recovery()' 2> /dev/null | xargs)" 10 | 11 | if [ "$CHECK" == "t" ]; then 12 | echo -e 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 9\r\n\r\nSecondary' 13 | elif [ "$CHECK" == "f" ]; then 14 | echo -e 'HTTP/1.1 204 NO CONTENT\r\nContent-Length: 0\r\n\r\n' 15 | else 16 | echo -e 'HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\n\r\nDB Down' 17 | fi 18 | 19 | sleep 1 20 | -------------------------------------------------------------------------------- /roles/maas_proxy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install HAProxy" 3 | ansible.builtin.apt: 4 | name: "haproxy" 5 | state: "{{ 'present' if maas_proxy_state | lower == 'install' else maas_proxy_state }}" 6 | 7 | - name: "Stop HAProxy" 8 | ansible.builtin.systemd: 9 | name: "haproxy.service" 10 | state: 'stopped' 11 | 12 | - name: "Write HAProxy Config" 13 | ansible.builtin.template: 14 | src: haproxy.cfg.j2 15 | dest: /etc/haproxy/haproxy.cfg 16 | mode: '0644' 17 | owner: haproxy 18 | group: haproxy 19 | notify: 20 | - "Restart HAProxy" 21 | 22 | - name: "Setup firewall" 23 | ansible.builtin.include_role: 24 | name: maas_firewall 25 | tasks_from: setup_firewall_rules 26 | when: ('maas_region_controller' not in group_names and 'maas_rack_controller' not in group_names) and (enable_firewall) 27 | -------------------------------------------------------------------------------- /roles/maas_postgres/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Stop Postgres Service To Load New Configuration" 3 | ansible.builtin.systemd: 4 | name: "postgresql@{{ maas_postgres_version_number }}-main.service" 5 | state: stopped 6 | no_block: false # wait for clean stop 7 | 8 | - name: "Start Postgres Service To Load New Configuration" 9 | ansible.builtin.systemd: 10 | name: "postgresql@{{ maas_postgres_version_number }}-main.service" 11 | enabled: true 12 | state: started 13 | no_block: false 14 | 15 | - name: "Restart xinetd" 16 | ansible.builtin.systemd: 17 | name: "xinetd.service" 18 | state: restarted 19 | enabled: true 20 | 21 | - name: "Start Postgres metrics agent" 22 | ansible.builtin.systemd: 23 | name: postgres_exporter 24 | enabled: true 25 | state: "restarted" 26 | daemon-reload: true 27 | -------------------------------------------------------------------------------- /restore.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: 3 | - maas_postgres_primary 4 | - maas_region_controller 5 | - maas_rack_controller 6 | tasks: 7 | - name: Restore from backup 8 | ansible.builtin.include_role: 9 | name: common 10 | tasks_from: restore 11 | become: true 12 | gather_facts: true 13 | 14 | - hosts: maas_postgres_primary 15 | tasks: 16 | - name: Restore from database dump 17 | ansible.builtin.include_role: 18 | name: maas_postgres 19 | tasks_from: restore 20 | become: true 21 | gather_facts: true 22 | 23 | - hosts: 24 | - maas_postgres_primary 25 | - maas_region_controller 26 | - maas_rack_controller 27 | tasks: 28 | - name: Remove Backup Directory 29 | ansible.builtin.file: 30 | path: /tmp/maas_backup 31 | state: absent 32 | become: true 33 | gather_facts: true 34 | -------------------------------------------------------------------------------- /roles/maas_postgres_proxy/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install HAProxy" 3 | ansible.builtin.apt: 4 | name: haproxy 5 | state: "{{ 'present' if maas_proxy_state | lower == 'install' else maas_proxy_state }}" 6 | 7 | - name: "Stop Postgres HAProxy" 8 | ansible.builtin.systemd: 9 | name: "haproxy.service" 10 | state: 'stopped' 11 | 12 | - name: "Write HAProxy Config" 13 | ansible.builtin.template: 14 | src: haproxy.cfg.j2 15 | dest: /etc/haproxy/haproxy.cfg 16 | mode: '0644' 17 | owner: haproxy 18 | group: haproxy 19 | notify: 20 | - "Restart Postgres HAProxy" 21 | 22 | - name: "Setup firewall" 23 | ansible.builtin.include_role: 24 | name: maas_firewall 25 | tasks_from: setup_firewall_rules 26 | when: ('maas_region_controller' not in group_names and 'maas_rack_controller' not in group_names) and ( enable_firewall ) 27 | -------------------------------------------------------------------------------- /group_vars/maas_postgres/20-pg_hba: -------------------------------------------------------------------------------- 1 | --- 2 | maas_postgres_hba_entries: 3 | local: 4 | - type: 'local' 5 | database: 'all' 6 | user: 'all' 7 | address: '' 8 | method: 'peer' 9 | - type: 'host' 10 | database: 'all' 11 | user: 'all' 12 | address: '127.0.0.1/32' 13 | method: '{{ maas_postgres_hash }}' 14 | - type: 'host' 15 | database: 'all' 16 | user: 'all' 17 | address: '::1/128' 18 | method: '{{ maas_postgres_hash }}' 19 | ipv4: 20 | - type: 'host' 21 | database: '{{ maas_postgres_database }}' 22 | user: 'maas' 23 | address: "{{ maas_postgres_v4_subnet }}" 24 | method: '{{ maas_postgres_hash }}' 25 | ipv6: 26 | - type: 'host' 27 | database: '{{ maas_postgres_database }}' 28 | user: 'maas' 29 | address: "{{ maas_postgres_v6_subnet }}" 30 | method: '{{ maas_postgres_hash }}' -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/create_replication_user.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "Create Replication User" 4 | community.postgresql.postgresql_user: 5 | name: "{{ maas_postgres_replication_user }}" 6 | password: "{{ maas_postgres_replication_password }}" 7 | role_attr_flags: replication 8 | state: "{{ 'present' if maas_postgres_action | lower == 'install' else maas_postgres_action }}" 9 | become: true 10 | become_user: postgres 11 | 12 | - name: "Create Replication Slots" 13 | community.postgresql.postgresql_slot: 14 | name: "{{ maas_postgres_replication_slot ~ '_' ~ item|regex_replace('[^A-Za-z0-9_]', '_') }}" 15 | db: "{{ maas_postgres_database }}" 16 | state: "{{ 'present' if maas_postgres_action | lower == 'install' else maas_postgres_action }}" 17 | become: true 18 | become_user: postgres 19 | register: maas_postgres_enable_sync 20 | loop: "{{ groups['maas_postgres'] }}" 21 | -------------------------------------------------------------------------------- /backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: maas_postgres_primary 3 | tasks: 4 | - name: Backup Postgres 5 | ansible.builtin.include_role: 6 | name: maas_postgres 7 | tasks_from: backup 8 | become: true 9 | gather_facts: true 10 | tags: 11 | - maas_postgres_primary 12 | 13 | - hosts: 14 | - maas_postgres_primary 15 | - maas_region_controller 16 | - maas_rack_controller 17 | tasks: 18 | - name: Archive and Download Backups 19 | ansible.builtin.include_role: 20 | name: common 21 | tasks_from: backup 22 | become: true 23 | gather_facts: true 24 | 25 | - hosts: 26 | - maas_postgres_primary 27 | tasks: 28 | - name: Remove DB dump 29 | ansible.builtin.file: 30 | path: "{{ item }}" 31 | state: absent 32 | with_items: 33 | - "{{ maas_postgres_backup_path }}" 34 | - "{{ maas_postgres_backup_dir }}" 35 | become: true 36 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Stop Cluster services" 3 | ansible.builtin.command: pcs cluster stop --all 4 | changed_when: false 5 | when: "'pacemaker' in ansible_facts.packages" 6 | 7 | - name: "Uninstall Pacemaker Packages" 8 | ansible.builtin.apt: 9 | name: 10 | - pacemaker 11 | - pacemaker-common 12 | - pcs 13 | - fence-agents 14 | - resource-agents-paf 15 | state: absent 16 | purge: true 17 | autoremove: true 18 | 19 | - name: "Remove Pacemaker artifacts" 20 | ansible.builtin.file: 21 | state: absent 22 | name: "{{ item }}" 23 | with_items: 24 | - /tmp/configure_pacemaker.sh 25 | - /tmp/pacemaker_auth 26 | - /etc/tmpfiles.d/postgresql-part.conf 27 | - /etc/ssh/sshd_config.d/pacemaker_sshd.conf 28 | - "{{ maas_pacemaker_tmp_cib|d(false) }}" 29 | - /etc/systemd/system/ha_cluster_exporter.service 30 | - "{{ ha_exp_dir }}" 31 | -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install Postgres" 3 | ansible.builtin.include_tasks: 4 | file: install_postgres.yaml 5 | 6 | - name: "Create Replication User" 7 | ansible.builtin.include_tasks: 8 | file: create_replication_user.yaml 9 | when: maas_ha_postgres_enabled and (inventory_hostname == groups["maas_postgres"][0]) 10 | 11 | - name: "Configure Postgres as a secondary" 12 | ansible.builtin.include_tasks: 13 | file: configure_postgres_secondary.yaml 14 | when: maas_ha_postgres_enabled|bool 15 | 16 | - name: "Configure Postgres metrics agent" 17 | ansible.builtin.include_tasks: 18 | file: o11y_pg.yaml 19 | when: o11y_enable 20 | 21 | - name: "Setup firewall" 22 | ansible.builtin.include_role: 23 | name: maas_firewall 24 | tasks_from: setup_firewall_rules 25 | when: ('maas_region_controller' not in group_names) and ('maas_pacemaker' not in group_names) and ( enable_firewall ) 26 | -------------------------------------------------------------------------------- /roles/maas_corosync/templates/corosync.conf.j2: -------------------------------------------------------------------------------- 1 | system { 2 | allow_knet_handle_fallback: yes 3 | } 4 | 5 | totem { 6 | version: 2 7 | secauth: off 8 | cluster_name: postgres_pacemaker 9 | transport: knet 10 | } 11 | 12 | logging { 13 | fileline: off 14 | to_stderr: yes 15 | to_logfile: yes 16 | logfile: /var/log/corosync/corosync.log 17 | to_syslog: yes 18 | debug: off 19 | logger_subsys { 20 | subsys: QUORUM 21 | debug: off 22 | } 23 | } 24 | 25 | 26 | nodelist { 27 | {% for pacemaker_host in groups['maas_corosync'] %} 28 | node { 29 | name: {{ pacemaker_host }} 30 | ring0_addr: {{ hostvars[pacemaker_host]['ansible_default_ipv4']['address'] if 'ansible_default_ipv4' in hostvars[pacemaker_host] else hostvars[pacemaker_host]['ansible_default_ipv6']['address'] }} 31 | nodeid: {{ loop.index }} 32 | } 33 | {% endfor %} 34 | } 35 | 36 | quorum { 37 | provider: corosync_votequorum 38 | two_node: 1 39 | wait_for_all: 1 40 | last_man_standing: 1 41 | auto_tie_breker: 0 42 | } 43 | -------------------------------------------------------------------------------- /group_vars/maas_pacemaker/01-pacemaker: -------------------------------------------------------------------------------- 1 | maas_paf_version: "2.3.0" 2 | maas_paf_revision: "1" 3 | maas_paf_deb_url: "https://github.com/ClusterLabs/PAF/releases/download/v{{ maas_paf_version }}/resource-agents-paf_{{ maas_paf_version }}-{{ maas_paf_revision }}_all.deb" 4 | maas_paf_deb_dest: "/tmp/resource-agents-paf_{{maas_paf_version }}-{{ maas_paf_revision }}_all.deb" 5 | 6 | maas_pacemaker_self_address: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] if 'ansible_default_ipv4' in hostvars[inventory_hostname] else hostvars[inventory_hostname]['ansible_default_ipv6']['address'] }}" 7 | 8 | maas_pacemaker_noproxy_list_v4: "{{ groups['maas_pacemaker'] | map('extract', hostvars, 'ansible_default_ipv4') | map(attribute='address') | list }}" 9 | 10 | maas_pacemaker_noproxy_list_v6: "{{ groups['maas_pacemaker'] | map('extract', hostvars, 'ansible_default_ipv4') | map(attribute='address') | list }}" 11 | 12 | maas_pacemaker_noproxy_list: "{% if maas_pacemaker_noproxy_list_v4 %}{{ maas_pacemaker_noproxy_list_v4 | join(',') }}{% endif %}{% if maas_pacemaker_noproxy_list_v4 and maas_pacemaker_noproxy_list_v6 %},{% endif %}{% if maas_pacemaker_noproxy_list_v6 %}{{ maas_pacemaker_noproxy_list_v6 }}{% endif %}" 13 | -------------------------------------------------------------------------------- /roles/common/tasks/TLS.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # handle TLS setup, config, and enabling 3 | 4 | - name: Set default file locations if none given 5 | ansible.builtin.set_fact: 6 | tls_key_path: "{{ tls_key_path | default('/etc/ssl/private/maas_tls.pem') }}" 7 | tls_cert_path: "{{ tls_cert_path | default('/etc/ssl/crt/maas_tls.crt') }}" 8 | 9 | - name: Check if a certificate exists 10 | ansible.builtin.stat: 11 | path: "{{ tls_cert_path }}" 12 | register: tls_cert_stat 13 | 14 | - name: Check if a key exists 15 | ansible.builtin.stat: 16 | path: "{{ tls_key_path }}" 17 | register: tls_key_stat 18 | 19 | - name: Generate an OpenSSL private key with the default values (4096 bits, RSA) 20 | community.crypto.openssl_privatekey: 21 | path: "{{ tls_key_path }}" 22 | when: generate_tls_cred and not tls_key_stat.stat.exists 23 | 24 | - name: Generate a Self Signed OpenSSL certificate 25 | community.crypto.x509_certificate: 26 | path: "{{ tls_cert_path }}" 27 | privatekey_path: "{{ tls_key_path }}" 28 | provider: selfsigned 29 | when: generate_tls_cred and not tls_cert_stat.stat.exists 30 | 31 | - name: Configure TLS 32 | ansible.builtin.command: maas config-tls enable {{ tls_key_path }} {{ tls_cert_path }} 33 | changed_when: false 34 | -------------------------------------------------------------------------------- /group_vars/all/01-maas: -------------------------------------------------------------------------------- 1 | # Global variable file for Ansible playbooks 2 | 3 | # Standard MAAS Variables 4 | enable_tls: false # Use TLS for MAAS communication 5 | 6 | enable_firewall: true # Run the firewall setup tasks. 7 | 8 | # Installation variables 9 | maas_install_deb: "{{ maas_installation_type == 'deb' }}" 10 | maas_snap_channel: "stable" # if using snap, then the channel. ie: stable, beta, edge 11 | maas_deb_state: "present" # if using deb, the state for install 12 | maas_state: "install" # whether to install, or upgrade maas 13 | 14 | maas_installmetric_file: "{{ '/var/snap/maas/common/maas' if maas_installation_type | lower == 'snap' else '/var/lib/maas' }}/.ansible" 15 | maas_secret_file: "{{ '/var/snap/maas/common' if maas_installation_type | lower == 'snap' else '/var/lib' }}/maas/secret" 16 | 17 | proxy_env: 18 | 19 | # Operating system releases and the minimum version of MAAS they can run 20 | supported_distributions: 21 | Ubuntu: 22 | "22.04": # Jammy supports MAAS >= 3.3 23 | max: "any" 24 | min: "3.3" 25 | "20.04": # Focal supports MAAS >= 2.9 and < 3.3 26 | max: "3.3" 27 | min: "2.9" 28 | "0.0": # Versions below Focal are used for MAAS < 2.9 29 | max: "2.9" 30 | min: "any" 31 | -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set postgres number for uninstall 3 | ansible.builtin.set_fact: 4 | maas_postgres_version_number: "{{ 14 if ansible_distribution_major_version | float >= 22 and maas_version is version('3.2', '>=') else 12 }}" 5 | 6 | - name: Setting config dirs for uninstall 7 | ansible.builtin.set_fact: 8 | maas_postgres_config_dir: "/etc/postgresql/{{ maas_postgres_version_number }}/main/" 9 | 10 | - name: Stop Postgres metrics agent 11 | ansible.builtin.service: 12 | name: postgres_exporter 13 | state: stopped 14 | when: ansible_facts.services['postgres_exporter.service'] is defined 15 | 16 | - name: "Remove PostgreSQL artifacts" 17 | ansible.builtin.file: 18 | state: absent 19 | name: "{{ item }}" 20 | with_items: 21 | - /opt/pgsql_check 22 | - /etc/xinetd.d/pgsql_check 23 | - /etc/systemd/system/postgres_exporter.service 24 | - "{{ o11y_postgres_exporter_dir }}" 25 | 26 | - name: "Uninstall PostgreSQL and configuration dependencies" 27 | ansible.builtin.apt: 28 | name: 29 | - "{{ maas_postgres_deb_name | default('postgresql-' ~ maas_postgres_version_number) }}" 30 | - "acl" 31 | - "python3-psycopg2" 32 | - "xinetd" 33 | state: absent 34 | purge: true 35 | -------------------------------------------------------------------------------- /roles/common/tasks/maas_supported_os.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # If the distibution is not in the supported dictionary 3 | - name: "Verify Distro supported" 4 | ansible.builtin.assert: 5 | fail_msg: "MAAS cannot be installed on {{ ansible_distribution }}!" 6 | that: 7 | - ansible_distribution in supported_distributions 8 | 9 | # fetch the highest release number in the dictionary that is equal or less than the host release number 10 | - name: "Fetch highest supported distro version" 11 | ansible.builtin.set_fact: 12 | closest_distro_version: "{{ supported_distributions[ansible_distribution][supported_distributions[ansible_distribution].keys() 13 | | community.general.version_sort | reject('>', ansible_distribution_version | string) | last] }}" 14 | 15 | # Make sure our maas version is within the minimum and maximum supported version numbers 16 | - name: "Verify version supports installed MAAS" 17 | ansible.builtin.assert: 18 | fail_msg: "MAAS {{ maas_version }} is not compatible with {{ ansible_distribution }}-{{ ansible_distribution_version }}" 19 | that: 20 | - (maas_version is version(closest_distro_version["max"] | string | replace("any", "9999"), "<")) and 21 | (maas_version is version(closest_distro_version["min"] | string | replace("any", "0.0"), ">=")) 22 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/tasks/o11y.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Create ha_cluster_exporter directory" 3 | ansible.builtin.file: 4 | path: "{{ ha_exp_dir }}" 5 | state: "directory" 6 | mode: 0755 7 | 8 | - name: "Download latest ha_cluster_exporter release" 9 | ansible.builtin.get_url: 10 | url: "{{ ha_exp_pkg }}" 11 | dest: "{{ ha_exp_dir ~ '/ha_cluster_exporter.gz' }}" 12 | 13 | - name: "Extract ha_cluster_exporter" 14 | ansible.builtin.command: 15 | cmd: gunzip "{{ ha_exp_dir }}/ha_cluster_exporter.gz" 16 | creates: "{{ ha_exp_dir ~ '/ha_cluster_exporter' }}" 17 | 18 | - name: "Make agent executable" 19 | ansible.builtin.file: 20 | path: "{{ ha_exp_dir ~ '/ha_cluster_exporter' }}" 21 | mode: 0755 22 | 23 | - name: "Create a service file" 24 | ansible.builtin.copy: 25 | dest: /etc/systemd/system/ha_cluster_exporter.service 26 | mode: 0644 27 | content: | 28 | [Unit] 29 | Description=Prometheus exporter for Pacemaker HA clusters metrics 30 | After=network.target 31 | [Service] 32 | Type=simple 33 | ExecStart={{ ha_exp_dir }}/ha_cluster_exporter $ARGS 34 | ExecReload=/bin/kill -HUP $MAINPID 35 | Restart=always 36 | [Install] 37 | WantedBy=multi-user.target 38 | notify: "Start HA clusters metrics agent" 39 | -------------------------------------------------------------------------------- /roles/common/tasks/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Generate List of Archiveable Directories 3 | ansible.builtin.set_fact: 4 | archive_list: "{{ archive_list | default([]) + [item] }}" 5 | loop: 6 | - "{{ ('maas_postgres_primary' in group_names) | ternary('{{ maas_postgres_backup_dir }}', None) }}" 7 | - "{{ (('maas_region_controller' in group_names) or ('maas_rack_controller' in group_names)) | ternary('{{ maas_config_backup_path }}', None) }}" 8 | - "{{ (('maas_region_controller' in group_names) or ('maas_rack_controller' in group_names)) | ternary('{{ maas_runtime_backup_path }}', None) }}" 9 | when: item 10 | 11 | - name: Bundle Backup Assets 12 | community.general.archive: 13 | path: "{{ archive_list }}" 14 | mode: 0644 15 | exclude_path: 16 | - "{{ maas_exclude_backup_path }}" 17 | dest: "{{ maas_backup_dest_path }}" 18 | 19 | # ansible.builtin.fetch has the ability to be oom killed on large files, so we're using scp instead 20 | - name: Download Backup 21 | ansible.builtin.command: scp {{ ansible_user }}@{{ inventory_hostname }}:{{ maas_backup_dest_path }} {{ maas_backup_download_path }} 22 | delegate_to: localhost 23 | become: false # don't need sudo locally 24 | changed_when: false 25 | 26 | - name: Remove Backup from Remote Host 27 | ansible.builtin.file: 28 | path: "{{ maas_backup_dest_path }}" 29 | state: absent 30 | -------------------------------------------------------------------------------- /roles/maas_rack_controller/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Playbook to generate a MAAS region 3 | - name: Verify MAAS Version supported on the host OS 4 | ansible.builtin.include_role: 5 | name: common 6 | tasks_from: maas_supported_os 7 | when: maas_installation_type | lower != 'snap' 8 | 9 | - name: "Check MAAS Url supplied by user or region controller setup" 10 | ansible.builtin.fail: 11 | msg: "MAAS Url not found" 12 | when: maas_url | length == 0 13 | - name: Check installed packages 14 | ansible.builtin.package_facts: 15 | manager: "auto" 16 | 17 | 18 | - name: Check installed snaps 19 | ansible.builtin.shell: "\ 20 | set -o pipefail && \ 21 | snap list | awk '{ print $1}'" 22 | args: 23 | executable: /bin/bash 24 | register: snaps_installed 25 | changed_when: false 26 | 27 | - name: Determine MAAS installation status 28 | ansible.builtin.set_fact: 29 | maas_installed: "{{ (maas_installation_type | lower == 'snap' and 'maas' in snaps_installed.stdout.split('\n') ) 30 | or (maas_installation_type | lower == 'deb' and maas_package_name in ansible_facts.packages) }}" 31 | 32 | - name: "Install MAAS rack controller" 33 | ansible.builtin.import_tasks: install_maas.yaml 34 | when: (not maas_installed) 35 | 36 | - name: "Update MAAS rack controller" 37 | ansible.builtin.import_tasks: upgrade_maas.yaml 38 | when: (maas_installed) 39 | 40 | - name: "Setup firewall" 41 | ansible.builtin.include_role: 42 | name: maas_firewall 43 | tasks_from: setup_firewall_rules 44 | when: enable_firewall 45 | -------------------------------------------------------------------------------- /roles/maas_postgres/templates/pgsql_check_conf.j2: -------------------------------------------------------------------------------- 1 | {%- if (maas_postgres_ipv4 is defined) and (maas_postgres_ipv4 != "127.0.0.1") -%} 2 | service pgsql_check_v4 3 | { 4 | flags = REUSE 5 | socket_type = stream 6 | port = {{ maas_pgsql_check_port|default(23267) }} 7 | bind = {{ maas_postgres_ipv4 }} 8 | wait = no 9 | user = nobody 10 | server = /opt/pgsql_check 11 | log_on_failure += USERID 12 | {% if groups.get("maas_postgres_proxy") %} 13 | only_from ={% for host in groups["maas_postgres_proxy"] %} {{ hostvars[host]['ansible_default_ipv4']['address'] if 'ansible_default_ipv4' in hostvars[host] else ''}}{% endfor %} 14 | {% endif %} 15 | disable = no 16 | per_source = UNLIMITED 17 | } 18 | {% endif %} 19 | 20 | 21 | {% if (maas_postgres_ipv6 is defined) and (maas_postgres_ipv6 != "::1") %} 22 | service pgsql_check_v6 23 | { 24 | flags = REUSE 25 | socket_type = stream 26 | port = {{ maas_pgsql_check_port|default(23267) }} 27 | bind = {{ maas_postgres_ipv6 }} 28 | wait = no 29 | user = nobody 30 | server = /opt/pgsql_check 31 | log_on_failure += USERID 32 | {% if groups.get("maas_postgres_proxy") %} 33 | only_from ={% for host in groups["maas_postgres_proxy"] %} {{ hostvars[host]['ansible_default_ipv6']['address'] if 'ansible_default_ipv6' in hostvars[host] else ''}}{% endfor %} 34 | {% endif %} 35 | disable = no 36 | per_source = UNLIMITED 37 | } 38 | {%- endif -%} 39 | -------------------------------------------------------------------------------- /roles/maas_region_controller/tasks/update_maas.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update MAAS - Snap 3 | ansible.builtin.command: snap refresh --channel={{ maas_version }}/{{ maas_snap_channel }} maas 4 | when: maas_installation_type | lower == 'snap' 5 | 6 | - name: Add MAAS apt Repository 7 | ansible.builtin.apt_repository: 8 | repo: "ppa:maas/{{ maas_version }}" 9 | when: maas_installation_type | lower == 'deb' 10 | 11 | - name: Update MAAS - Deb 12 | ansible.builtin.apt: 13 | name: 14 | - "maas-region-api" 15 | state: latest 16 | update_cache: true 17 | cache_valid_time: 3600 18 | when: maas_installation_type | lower == 'deb' and '"maas_rack_controller" not in group_names' 19 | 20 | - name: Migrate MAAS database 21 | ansible.builtin.command: "{{ 'maas' if maas_installation_type | lower == 'snap' else 'maas-region' }} migrate" 22 | changed_when: false 23 | run_once: true 24 | 25 | - name: Refresh MAAS API 26 | ansible.builtin.command: "maas refresh" 27 | changed_when: false 28 | 29 | - name: Wait For MAAS To Create Secret File 30 | ansible.builtin.wait_for: 31 | path: "{{ maas_secret_file }}" 32 | state: present 33 | 34 | - name: Read MAAS Secret For Rack Controllers 35 | ansible.builtin.command: cat "{{ maas_secret_file }}" 36 | register: maas_rack_secret_tmp 37 | changed_when: false 38 | 39 | - name: Save MAAS Secret 40 | ansible.builtin.set_fact: 41 | maas_rack_secret: "{{ maas_rack_secret_tmp.stdout }}" 42 | cacheable: true 43 | run_once: true 44 | delegate_to: "{{ item }}" 45 | delegate_facts: true 46 | with_items: "{{ groups['maas_rack_controller'] }}" 47 | -------------------------------------------------------------------------------- /group_vars/all/50-o11y: -------------------------------------------------------------------------------- 1 | --- 2 | # O11y variables 3 | o11y_enable: false 4 | 5 | o11y_prometheus_url: "" 6 | o11y_loki_url: "" 7 | 8 | o11y_prometheus_alert_repos: "https://github.com/canonical/maas-prometheus-alert-rules.git" 9 | o11y_loki_alert_repos: "https://github.com/canonical/maas-loki-alert-rules.git" 10 | o11y_enabled_roles: 11 | - maas_postgres 12 | - maas_corosync 13 | - maas_pacemaker 14 | - maas_region_controller 15 | - maas_rack_controller 16 | 17 | o11y_grafana_agent_dir: "/opt/grafana-agent" 18 | o11y_grafana_agent_wal_dir: "/var/lib/grafana-agent/wal" 19 | o11y_grafana_agent_pos_dir: "/var/lib/grafana-agent/positions" 20 | o11y_grafana_agent_unit: "/etc/systemd/system/telemetry.service" 21 | 22 | o11y_grafana_agent_dirs: 23 | - "{{ o11y_grafana_agent_dir }}" 24 | - "{{ o11y_grafana_agent_wal_dir }}" 25 | - "{{ o11y_grafana_agent_pos_dir }}" 26 | 27 | grafana_agent_pkg: "https://github.com/grafana/agent/releases/download/v{{'0.37.4' if maas_version is version('3.5', '>=') else '0.32.1'}}/grafana-agent-linux-{{ ubuntu_arch }}.zip" 28 | 29 | o11y_postgres_exporter_dir: "/opt/postgres-exporter" 30 | pg_exp_pkg: "https://github.com/prometheus-community/postgres_exporter/releases/download/v0.11.1/postgres_exporter-0.11.1.linux-{{ ubuntu_arch }}.tar.gz" 31 | pg_exp_conn: "DATA_SOURCE_NAME='postgresql://{{ maas_postgres_user }}:{{ maas_postgres_password }}@\ 32 | {{ inventory_hostname }}:5432/{{ maas_postgres_database }}?sslmode=disable'" 33 | 34 | ha_exp_pkg: "https://github.com/ClusterLabs/ha_cluster_exporter/releases/download/1.3.1/ha_cluster_exporter-{{ ubuntu_arch }}.gz" 35 | ha_exp_dir: "/opt/ha_cluster_exporter" 36 | -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/o11y_pg.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Create postgres-exporter directory" 3 | ansible.builtin.file: 4 | path: "{{ o11y_postgres_exporter_dir }}" 5 | state: "directory" 6 | mode: 0755 7 | 8 | - name: Install unzip 9 | ansible.builtin.apt: 10 | name: unzip 11 | state: "present" 12 | 13 | - name: "Download and unzip latest postgres-exporter release" 14 | ansible.builtin.unarchive: 15 | src: "{{ pg_exp_pkg }}" 16 | dest: "{{ o11y_postgres_exporter_dir }}" 17 | remote_src: true 18 | creates: "{{ o11y_postgres_exporter_dir ~ '/postgres_exporter' }}" 19 | extra_opts: 20 | - "--strip-components" 21 | - "1" 22 | 23 | - name: "Set env variable for data source" 24 | ansible.builtin.copy: 25 | dest: "{{ o11y_postgres_exporter_dir ~ '/postgres_exporter.env' }}" 26 | mode: 0644 27 | content: "{{ pg_exp_conn }}" 28 | 29 | - name: "Create a service file" 30 | ansible.builtin.copy: 31 | dest: /etc/systemd/system/postgres_exporter.service 32 | mode: 0644 33 | content: | 34 | [Unit] 35 | Description=Postgres Exporter service 36 | Requires=postgresql@{{ maas_postgres_version_number }}-main.service 37 | After=postgresql@{{ maas_postgres_version_number }}-main.service 38 | [Service] 39 | User=postgres 40 | Group=postgres 41 | Type=simple 42 | EnvironmentFile={{ o11y_postgres_exporter_dir }}/postgres_exporter.env 43 | ExecStart={{ o11y_postgres_exporter_dir }}/postgres_exporter $ARGS 44 | ExecReload=/bin/kill -HUP $MAINPID 45 | Restart=always 46 | [Install] 47 | WantedBy=multi-user.target 48 | notify: "Start Postgres metrics agent" 49 | -------------------------------------------------------------------------------- /roles/maas_proxy/templates/haproxy.cfg.j2: -------------------------------------------------------------------------------- 1 | global 2 | maxconn 100 3 | 4 | defaults 5 | log global 6 | mode tcp 7 | retries 2 8 | timeout client 30m 9 | timeout connect 4s 10 | timeout server 30m 11 | timeout check 5s 12 | 13 | {% if (maas_proxy_bind_addr_v4 is defined) and (maas_proxy_bind_addr_v4) %} 14 | 15 | frontend maas_region_controller-v4 16 | mode tcp 17 | bind {{ maas_proxy_bind_addr_v4 }}:{{ maas_proxy_port }} 18 | use_backend maas_region_controllers 19 | 20 | {% endif %} 21 | 22 | {% if (maas_proxy_bind_addr_v6 is defined) and (maas_proxy_bind_addr_v6) %} 23 | 24 | frontend maas_region_controller-v6 25 | mode tcp 26 | bind {{ maas_proxy_bind_addr_v6 }}:{{ maas_proxy_port }} 27 | use_backend maas_region_controllers 28 | 29 | {% endif %} 30 | 31 | backend maas_region_controllers 32 | balance roundrobin 33 | {% if maas_proxy_upstreams %} 34 | {% for upstream in maas_proxy_upstreams %} 35 | server {{ upstream['hostname'] }} {{ upstream['ip'] }}:{{ upstream['port'] }} 36 | {% endfor %} 37 | {% else %} 38 | {% for upstream in groups['maas_region_controller'] %} 39 | {% if ('ansible_default_ipv4' in hostvars[upstream]) and ('address' in hostvars[upstream]['ansible_default_ipv4']) %} 40 | server {{ hostvars[upstream]['ansible_hostname'] }} {{ hostvars[upstream]['ansible_default_ipv4']['address'] }}:5240 41 | {% endif %} 42 | {% if ('ansible_default_ipv6' in hostvars[upstream]) and ('address' in hostvars[upstream]['ansible_default_ipv6']) %} 43 | server {{ hostvars[upstream]['ansible_hostname'] }} {{ hostvars[upstream]['ansible_default_ipv6']['address'] }}:5240 44 | {% endif %} 45 | {% endfor %} 46 | {% endif %} 47 | -------------------------------------------------------------------------------- /alertrules.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Downloads Prometheus and Loki alert rules 3 | 4 | - name: "Create Prometheus and Loki alert rules bundle" 5 | hosts: localhost 6 | connection: local 7 | tasks: 8 | - name: "Ensure required variables have been defined" 9 | ansible.builtin.assert: 10 | quiet: true 11 | fail_msg: "Required variable has not been defined" 12 | that: 13 | - o11y_prometheus_alert_repos is defined 14 | - o11y_loki_alert_repos is defined 15 | - o11y_alertrules_dest is defined 16 | 17 | - name: "Install dependencies" 18 | ansible.builtin.pip: 19 | name: 20 | - "pyyaml" 21 | 22 | - name: "Fetch Prometheus alert rules" 23 | ansible.builtin.git: 24 | repo: "{{ o11y_prometheus_alert_repos }}" 25 | dest: "maas-prometheus-alert-rules" 26 | version: HEAD # noqa git-latest 27 | 28 | - name: "Bundle Prometheus rules" 29 | ansible.builtin.make: 30 | chdir: "maas-prometheus-alert-rules" 31 | target: groups 32 | 33 | - name: "Fetch Loki alert rules" 34 | ansible.builtin.git: 35 | repo: "{{ o11y_loki_alert_repos }}" 36 | dest: "maas-loki-alert-rules" 37 | version: HEAD # noqa git-latest 38 | 39 | - name: "Bundle Loki rules" 40 | ansible.builtin.make: 41 | chdir: "maas-loki-alert-rules" 42 | target: groups 43 | 44 | - name: "Move files" 45 | ansible.builtin.copy: 46 | src: "{{ item.src }}" 47 | dest: "{{ item.dest }}" 48 | mode: 0644 49 | loop: 50 | - src: maas-loki-alert-rules/rules/bundle.yml 51 | dest: "{{ o11y_alertrules_dest }}/loki-alert-rules.yml" 52 | - src: maas-prometheus-alert-rules/group.yml 53 | dest: "{{ o11y_alertrules_dest }}/prometheus-alert-rules.yml" 54 | -------------------------------------------------------------------------------- /teardown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # will reset the system state to that prior to maas install 3 | 4 | - hosts: all 5 | become: true 6 | gather_facts: true 7 | tasks: 8 | - name: "Collect facts about installed packages" 9 | ansible.builtin.package_facts: 10 | manager: auto 11 | strategy: all 12 | 13 | - name: "Collect facts about system services" 14 | ansible.builtin.service_facts: 15 | 16 | - name: "Remove iptables rules" 17 | ansible.builtin.include_role: 18 | name: maas_firewall 19 | tasks_from: teardown 20 | 21 | - name: "Uninstall MAAS region" 22 | ansible.builtin.include_role: 23 | name: "maas_region_controller" 24 | tasks_from: teardown 25 | 26 | - name: "Uninstall MAAS rack" 27 | ansible.builtin.include_role: 28 | name: maas_rack_controller 29 | tasks_from: teardown 30 | 31 | - name: "Uninstall HAProxy" 32 | ansible.builtin.include_role: 33 | name: maas_proxy 34 | tasks_from: teardown 35 | 36 | - name: "Uninstall Pacemaker" 37 | ansible.builtin.include_role: 38 | name: maas_pacemaker 39 | tasks_from: teardown 40 | 41 | - name: "Uninstall Corosync" 42 | ansible.builtin.include_role: 43 | name: maas_corosync 44 | tasks_from: teardown 45 | 46 | - name: "Uninstall postgres" 47 | ansible.builtin.include_role: 48 | name: maas_postgres 49 | tasks_from: teardown 50 | 51 | - name: "Uninstall o11y" 52 | ansible.builtin.include_role: 53 | name: o11y_agent 54 | tasks_from: teardown 55 | when: group_names|intersect(o11y_enabled_roles) 56 | 57 | - name: "Remove unused packages from the cache" 58 | ansible.builtin.apt: 59 | autoclean: true 60 | 61 | - name: Reload the SystemD to re-read configurations 62 | ansible.builtin.systemd: 63 | daemon-reload: true 64 | -------------------------------------------------------------------------------- /roles/maas_rack_controller/tasks/install_maas.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Install MAAS - Snap 4 | community.general.snap: 5 | name: maas 6 | channel: '{{ maas_version }}/{{ maas_snap_channel }}' 7 | when: (maas_installation_type | lower == 'snap') and ('maas_region_controller' not in group_names) 8 | 9 | - name: Add MAAS apt Repository 10 | ansible.builtin.apt_repository: 11 | repo: "ppa:maas/{{ maas_version }}" 12 | when: (maas_installation_type | lower == 'deb') and ('maas_region_controller' not in group_names) 13 | 14 | - name: Create MAAS Unix Group 15 | ansible.builtin.group: 16 | name: maas 17 | state: present 18 | 19 | - name: Create MAAS Unix User 20 | ansible.builtin.user: 21 | name: maas 22 | group: maas 23 | state: present 24 | 25 | - name: Install MAAS Rack Controller - Deb 26 | ansible.builtin.apt: 27 | name: 28 | - "maas-rack-controller" 29 | state: "{{ maas_deb_state | default('present') }}" 30 | update_cache: true 31 | cache_valid_time: 3600 32 | when: (maas_installation_type | lower == 'deb') 33 | 34 | - name: Initialise MAAS Rack Controller 35 | ansible.builtin.command: maas init rack --maas-url={{ maas_url }} --secret={{ maas_rack_secret }} 36 | when: (maas_installation_type | lower == 'snap') and ('maas_region_controller' not in group_names) 37 | 38 | - name: Initialise MAAS Rack Controller 39 | ansible.builtin.command: maas-rack register --url={{ maas_url }} --secret={{ maas_rack_secret }} 40 | when: (maas_installation_type | lower == 'deb') 41 | 42 | - name: Enable TLS 43 | ansible.builtin.include_role: 44 | name: common 45 | tasks_from: TLS 46 | # TLS is available only in MAAS 3.2 or higher 47 | when: maas_version is version("3.2", '>=') and enable_tls 48 | 49 | - name: Create Ansible installation metric file 50 | ansible.builtin.file: 51 | path: "{{ maas_installmetric_file }}" 52 | state: touch 53 | mode: 0644 54 | -------------------------------------------------------------------------------- /roles/common/tasks/restore.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Temporary Unpack Directory 3 | ansible.builtin.file: 4 | path: /tmp/maas_backup/ 5 | owner: root 6 | group: root 7 | mode: '0755' 8 | state: directory 9 | 10 | - name: Unpack Backup Archive 11 | ansible.builtin.unarchive: 12 | src: "{{ maas_backup_file }}" 13 | dest: /tmp/maas_backup/ 14 | 15 | - name: Stop Region Controller 16 | ansible.builtin.systemd: 17 | name: maas-regiond.service 18 | state: stopped 19 | when: ('maas_region_controller' in group_names) and maas_install_deb | bool 20 | 21 | - name: Stop Rack Controller 22 | ansible.builtin.systemd: 23 | name: maas-rackd.service 24 | state: stopped 25 | when: ('maas_rack_controller' in group_names) and maas_install_deb | bool 26 | 27 | - name: Stop MAAS snap 28 | ansible.builtin.command: snap stop maas 29 | when: (('maas_region_controller' in group_names) or ('maas_rack_controller' in group_names)) and (not maas_install_deb | bool) 30 | 31 | - name: Restore Config 32 | ansible.builtin.command: "mv /tmp/maas_backup/etc/maas {{ maas_restore_config_path }}" 33 | when: ('maas_region_controller' in group_names) or ('maas_rack_controller' in group_names) 34 | 35 | - name: Restore Runtime Data 36 | ansible.builtin.command: "mv /tmp/maas_backup/var/lib/maas {{ maas_restore_runtime_path }}" 37 | when: ('maas_region_controller' in group_names) or ('maas_rack_controller' in group_names) 38 | 39 | - name: Start Region Controller 40 | ansible.builtin.systemd: 41 | name: maas-regiond.service 42 | state: started 43 | when: ('maas_region_controller' in group_names) and maas_install_deb | bool 44 | 45 | - name: Start Rack Controller 46 | ansible.builtin.systemd: 47 | name: maas-rackd.service 48 | state: started 49 | when: ('maas_rack_controller' in group_names) and maas_install_deb | bool 50 | 51 | - name: Start MAAS snap 52 | ansible.builtin.command: snap start maas 53 | when: (('maas_region_controller' in group_names) or ('maas_rack_controller' in group_names)) and (not maas_install_deb | bool) 54 | -------------------------------------------------------------------------------- /group_vars/maas_postgres/01-postgres: -------------------------------------------------------------------------------- 1 | # postgres version number 2 | maas_postgres_version_number: "{{ 14 if ansible_distribution_major_version | float >= 22 and maas_version is version('3.2', '>=') else 12 }}" 3 | # latest compatible deb version of postgres 4 | maas_postgres_deb_name: "postgresql-{{ maas_postgres_version_number }}" 5 | 6 | # action to perform on Postgres db, overriden by backup and restore 7 | maas_postgres_action: "install" 8 | 9 | maas_postgres_bin_dir: "/usr/lib/postgresql/{{ maas_postgres_version_number }}/bin/" 10 | maas_postgres_config_dir: "/etc/postgresql/{{ maas_postgres_version_number }}/main/" 11 | 12 | maas_postgres_data_dir: "/var/lib/postgresql/{{ maas_postgres_version_number }}/main/" 13 | maas_postgres_log_dir: "/var/log/postgresql/postgresql-{{ maas_postgres_version_number }}-main.log" 14 | 15 | maas_postgres_ssl_enabled: true 16 | 17 | maas_postgres_hash: "{{ 'md5' if maas_postgres_version_number|int <= 12 else 'scram-sha-256' }}" 18 | 19 | maas_postgres_ipv4: "{{ ansible_default_ipv4['address'] if 'address' in ansible_default_ipv4 else '127.0.0.1' }}" 20 | maas_postgres_ipv6: "{{ ansible_default_ipv6['address'] if 'address' in ansible_default_ipv6 else '::1' }}" 21 | maas_postgres_ipv4_netmask: "{{ ansible_default_ipv4['netmask'] if 'netmask' in ansible_default_ipv4 else '255.255.255.255' }}" 22 | maas_postgres_ipv6_prefixlen: "{{ ansible_default_ipv6['prefixlen'] if 'prefixlen' in ansible_default_ipv6 else '128' }}" 23 | maas_postgres_v4_cidr: "{{ maas_postgres_ipv4 }}/{{ maas_postgres_ipv4_netmask }}" 24 | maas_postgres_v6_cidr: "{{ maas_postgres_ipv6 }}/{{ maas_postgres_ipv6_prefixlen }}" 25 | maas_postgres_v4_subnet: "{{ maas_postgres_v4_cidr | ansible.utils.ipaddr('network') }}/{{ maas_postgres_v4_cidr | ansible.utils.ipaddr('prefix') }}" 26 | maas_postgres_v6_subnet: "{{ maas_postgres_v6_cidr | ansible.utils.ipaddr('network') }}/{{ maas_postgres_v6_cidr | ansible.utils.ipaddr('prefix') }}" 27 | maas_postgres_floating_ip_iface: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['interface'] if 'ansible_default_ipv4' in hostvars[inventory_hostname]\ 28 | else hostvars[inventory_hostname]['ansible_default_ipv6']['interface'] }}" 29 | -------------------------------------------------------------------------------- /roles/maas_postgres_proxy/templates/haproxy.cfg.j2: -------------------------------------------------------------------------------- 1 | global 2 | maxconn 100 3 | 4 | defaults 5 | log global 6 | mode tcp 7 | retries 2 8 | timeout client 30m 9 | timeout connect 4s 10 | timeout server 30m 11 | timeout check 5s 12 | 13 | listen stats 14 | bind :9090 15 | balance 16 | mode http 17 | stats enable 18 | stats uri /stats 19 | stats refresh 10s 20 | 21 | {% if (maas_proxy_postgres_bind_addr_v4 is defined) and (maas_proxy_postgres_bind_addr_v4) %} 22 | 23 | frontend maas-postgres-v4 24 | mode tcp 25 | bind {{ maas_proxy_postgres_bind_addr_v4 }}:{{ maas_proxy_postgres_port }} 26 | use_backend maas-postgres 27 | 28 | {% endif %} 29 | 30 | {% if (maas_proxy_postgres_bind_addr_v6 is defined) and (maas_proxy_postgres_bind_addr_v6) %} 31 | 32 | frontend maas-postgres-v6 33 | mode tcp 34 | bind {{ maas_proxy_postgres_bind_addr_v6 }}:{{ maas_proxy_postgres_port }} 35 | use_backend maas-postgres 36 | 37 | {% endif %} 38 | 39 | backend maas-postgres 40 | option httpchk 41 | http-check send meth HEAD uri / 42 | http-check expect status 204 43 | {% if maas_proxy_postgres_upstreams %} 44 | {% for upstream in maas_proxy_postgres_upstreams %} 45 | server {{ upstream['hostname'] }} {{ upstream['ip'] }}:{{ upstream['port'] }} maxconn 100 check port {{ maas_pgsql_check_port|default(23267) }} 46 | {% endfor %} 47 | {% else %} 48 | {%- for pg in groups['maas_postgres'] %} 49 | {%- if ('ansible_default_ipv4' in hostvars[pg]) and ('address' in hostvars[pg]['ansible_default_ipv4']) %} 50 | server {{ hostvars[pg]['ansible_hostname'] }} {{ hostvars[pg]['ansible_default_ipv4']['address'] }}:5432 maxconn 100 check port {{ maas_pgsql_check_port|default(23267) }} inter 1s rise 2 fall 3 51 | {% endif -%} 52 | {%- if ('ansible_default_ipv6' in hostvars[pg]) and ('address' in hostvars[pg]['ansible_default_ipv6']) %} 53 | server {{ hostvars[pg]['ansible_hostname'] }} {{ hostvars[pg]['ansible_default_ipv6']['address'] }}:5432 maxconn 100 check port {{ maas_pgsql_check_port|default(23267) }} inter 1s rise 2 fall 3 54 | {% endif -%} 55 | {% endfor %} 56 | {% endif %} 57 | -------------------------------------------------------------------------------- /roles/o11y_agent/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install grafana agents 3 | - name: "Ensure o11y can be enabled in this host" 4 | ansible.builtin.assert: 5 | quiet: true 6 | fail_msg: "o11y is not supported in this host" 7 | that: 8 | - (group_names|intersect(o11y_enabled_roles)|length>0) 9 | when: o11y_enable 10 | 11 | - name: Agent directories 12 | ansible.builtin.file: 13 | path: "{{ item }}" 14 | state: "{{ 'directory' if o11y_enable else 'absent' }}" 15 | mode: 0755 16 | loop: "{{ o11y_grafana_agent_dirs }}" 17 | 18 | - name: Install unzip 19 | ansible.builtin.apt: 20 | name: unzip 21 | state: "present" 22 | 23 | - name: Download and unzip grafana agent 24 | ansible.builtin.unarchive: 25 | src: "{{ grafana_agent_pkg }}" 26 | dest: "{{ o11y_grafana_agent_dir }}" 27 | remote_src: true 28 | when: o11y_enable 29 | 30 | - name: Make agent executable 31 | ansible.builtin.file: 32 | path: "{{ o11y_grafana_agent_dir }}/grafana-agent-linux-{{ ubuntu_arch }}" 33 | mode: "a+x" 34 | when: o11y_enable 35 | 36 | - name: Creating a service file 37 | ansible.builtin.copy: 38 | dest: "{{ o11y_grafana_agent_unit }}" 39 | mode: 0644 40 | content: | 41 | [Unit] 42 | Description=Grafana-agent Service 43 | Requires=network.target 44 | After=network.target 45 | [Service] 46 | Type=simple 47 | ExecStart={{ o11y_grafana_agent_dir }}/grafana-agent-linux-{{ ubuntu_arch }} -config.file={{ o11y_grafana_agent_dir }}/agent.yaml 48 | Restart=on-abnormal 49 | [Install] 50 | WantedBy=multi-user.target 51 | when: o11y_enable 52 | 53 | - name: Agent configuration 54 | ansible.builtin.template: 55 | src: "grafana-agent.yaml.j2" 56 | dest: "{{ o11y_grafana_agent_dir }}/agent.yaml" 57 | mode: 0644 58 | when: o11y_enable and maas_version is version("3.5",'<') 59 | notify: 60 | - "Start grafana agent" 61 | 62 | - name: Agent configuration for MAAS 3.5 63 | ansible.builtin.template: 64 | src: "agent-{{ maas_installation_type }}.yaml.j2" 65 | dest: "{{ o11y_grafana_agent_dir }}/agent.yaml" 66 | mode: 0644 67 | when: o11y_enable and maas_version is version("3.5",'>=') 68 | notify: 69 | - "Start grafana agent" 70 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/templates/configure_pacemaker.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pcs cluster cib {{ maas_pacemaker_tmp_cib }}\ 4 | && stat {{ maas_pacemaker_tmp_cib }}\ 5 | && pcs -f {{ maas_pacemaker_tmp_cib }} resource defaults update migration-threshold=5\ 6 | && pcs -f {{ maas_pacemaker_tmp_cib }} resource defaults update resource-stickiness=10\ 7 | {% for host in groups['maas_pacemaker'] %} 8 | && pcs -f {{ maas_pacemaker_tmp_cib }}\ 9 | stonith create {{ maas_pacemaker_fence_name }}_{{ host }}\ 10 | {{ hostvars[host]['maas_pacemaker_fencing_driver'] }} 11 | {%- for key, value in hostvars[host]['maas_pacemaker_stonith_params'].items() %} {{ key }}={{ value }}{%- endfor -%}\ 12 | && pcs -f {{ maas_pacemaker_tmp_cib }} constraint location {{ maas_pacemaker_fence_name }}_{{ host }} avoids {{ host }}=INFINITY\ 13 | {% endfor %} 14 | && pcs -f {{ maas_pacemaker_tmp_cib }} resource create pgsql-pri-ip ocf:heartbeat:IPaddr2\ 15 | ip="{{ maas_postgres_floating_ip }}"\ 16 | cidr_netmask="{{ maas_postgres_floating_ip_prefix_len }}"\ 17 | op monitor interval=10s\ 18 | && pcs -f {{ maas_pacemaker_tmp_cib }} resource create pgsqld ocf:heartbeat:pgsqlms\ 19 | bindir="{{ maas_postgres_bin_dir }}"\ 20 | pgdata="{{ maas_postgres_config_dir }}"\ 21 | datadir="{{ maas_postgres_data_dir }}"\ 22 | op start timeout=60s\ 23 | op stop timeout=60s\ 24 | op promote timeout=30s\ 25 | op demote timeout=120s\ 26 | op monitor interval=15s timeout=10s role="Master"\ 27 | op monitor interval=16s timeout=10s role="Slave"\ 28 | op notify timeout=60s\ 29 | promotable notify=true\ 30 | && pcs cluster cib-push scope=configuration {{ maas_pacemaker_tmp_cib }}\ 31 | && pcs resource status pgsqld\ 32 | && pcs resource meta pgsqld master-max=1\ 33 | && pcs constraint colocation add pgsql-pri-ip with Master pgsqld-clone INFINITY\ 34 | && pcs constraint order promote pgsqld-clone then start pgsql-pri-ip symmetrical=false kind=Mandatory\ 35 | && pcs constraint order demote pgsqld-clone then stop pgsql-pri-ip symmetrical=false kind=Mandatory\ 36 | && pcs resource enable pgsql-pri-ip\ 37 | && pcs resource enable pgsqld\ 38 | && rm {{ maas_pacemaker_tmp_cib }} 39 | -------------------------------------------------------------------------------- /roles/maas_region_controller/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Playbook to generate a MAAS region 3 | - name: Verify MAAS Version supported on the host OS 4 | ansible.builtin.include_role: 5 | name: common 6 | tasks_from: maas_supported_os 7 | when: maas_installation_type | lower != 'snap' 8 | 9 | - name: Check installed packages 10 | ansible.builtin.package_facts: 11 | manager: "auto" 12 | 13 | - name: Check installed snaps 14 | ansible.builtin.shell: "\ 15 | set -o pipefail && \ 16 | snap list | awk '{ print $1}'" 17 | args: 18 | executable: /bin/bash 19 | register: snaps_installed 20 | changed_when: false 21 | 22 | - name: Determine MAAS installation status 23 | ansible.builtin.set_fact: 24 | maas_installed: 25 | "{{ (maas_installation_type | lower == 'snap' and 'maas' in snaps_installed.stdout.split('\n') ) 26 | or (maas_installation_type | lower == 'deb' and maas_package_name in ansible_facts.packages) }}" 27 | 28 | - name: Install MAAS Region 29 | ansible.builtin.import_tasks: install_maas.yaml 30 | when: (not maas_installed) 31 | 32 | - name: Update MAAS Region 33 | ansible.builtin.import_tasks: update_maas.yaml 34 | when: (maas_installed) 35 | 36 | - name: "Get admin user API key" 37 | ansible.builtin.command: maas apikey --username {{ admin_username }} 38 | register: maas_admin_api_key 39 | changed_when: false 40 | 41 | - name: "Login in MAAS" 42 | ansible.builtin.command: maas login {{ admin_username }} {{ maas_url }} {{ maas_admin_api_key.stdout }} 43 | environment: 44 | http_proxy: "" 45 | https_proxy: "" 46 | no_proxy: "localhost,hostvars[inventory_hostname]['ansible_env'].SSH_CONNECTION.split(' ')[2]" 47 | changed_when: true 48 | register: login 49 | until: login is not failed 50 | retries: 5 51 | delay: 2 52 | 53 | - name: setup o11y 54 | ansible.builtin.import_tasks: o11y.yaml 55 | when: o11y_enable 56 | 57 | - name: Use MAAS Vault 58 | ansible.builtin.include_role: 59 | name: common 60 | tasks_from: vault 61 | # Vault requires MAAS 3.3 or greater 62 | when: vault_integration is defined and vault_integration and maas_version is version("3.3", '>=') 63 | 64 | - name: "Setup firewall" 65 | ansible.builtin.include_role: 66 | name: maas_firewall 67 | tasks_from: setup_firewall_rules 68 | when: enable_firewall 69 | -------------------------------------------------------------------------------- /group_vars/all/20-database: -------------------------------------------------------------------------------- 1 | --- 2 | maas_proxy_postgres_proxy_enabled: false 3 | 4 | maas_postgres_user: "maas" 5 | maas_postgres_database: "maasdb" 6 | 7 | maas_ha_postgres_enabled: "{{ groups['maas_postgres']|length > 1 }}" 8 | maas_postgres_uri_login: "{{ maas_postgres_user }}:{{ maas_postgres_password }}" 9 | 10 | maas_postgres_proxy_self: "{{ inventory_hostname in groups['maas_postgres_proxy']|d([]) }}" 11 | maas_postgres_proxy_host: "{{ inventory_hostname if maas_postgres_proxy_self|bool else groups['maas_postgres_proxy']|first|d('') }}" 12 | 13 | maas_postgres_proxy_host_v4: "{{ '127.0.0.1' if maas_postgres_proxy_self|bool else hostvars[maas_postgres_proxy_host]['ansible_default_ipv4']['address']|d('') }}" 14 | maas_postgres_proxy_host_v6: "{{ '::1' if maas_postgres_proxy_self|bool else hostvars[maas_postgres_proxy_host]['ansible_default_ipv6']['address']|d('') }}" 15 | maas_postgres_proxy_uri: "postgres://{{ maas_postgres_uri_login }}@{{ maas_postgres_proxy_host_v4|d(maas_postgres_proxy_host_v6, True) }}:5432/{{ maas_postgres_database }}" 16 | 17 | maas_postgres_primary_host_v4: "{{ hostvars[groups['maas_postgres'][0]]['ansible_default_ipv4']['address']|d('') }}" 18 | maas_postgres_primary_host_v6: "{{ hostvars[groups['maas_postgres'][0]]['ansible_default_ipv6']['address']|d('') }}" 19 | maas_postgres_primary_uri: "postgres://{{ maas_postgres_uri_login }}@{{ maas_postgres_primary_host_v4|d(maas_postgres_primary_host_v6, True) }}:5432/{{ maas_postgres_database }}" 20 | 21 | maas_postgres_uri: "{{ maas_postgres_proxy_uri if groups['maas_postgres_proxy']|d(False) else maas_postgres_primary_uri }}" 22 | 23 | # postgres version number 24 | maas_postgres_version_number: "{{ 14 if ansible_distribution_major_version | float >= 22 and maas_use_version is version('3.2', '>=') else 12 }}" 25 | maas_postgres_replication_user: "replicator" 26 | maas_postgres_backup_dir: "/tmp/maas_backup/" 27 | maas_postgres_backup_path: "{{ maas_postgres_backup_dir }}dump.sql.gz" 28 | maas_config_backup_path: "{{ '/etc/maas/' if maas_install_deb | bool else '' }}" 29 | maas_runtime_backup_path: "{{ '/var/lib/maas/' if maas_install_deb | bool else '' }}" 30 | maas_exclude_backup_path: "{{ '/var/lib/maas/boot-resources/' if maas_install_deb | bool else '' }}" 31 | maas_backup_dest_path: "/tmp/{{ inventory_hostname }}_maas_backup_{{ ansible_date_time.iso8601 }}.tgz" 32 | maas_restore_config_path: "{{ '/etc/maas' if maas_install_deb | bool else '' }}" 33 | maas_restore_runtime_path: "{{ '/var/lib/maas' if maas_install_deb | bool else '' }}" 34 | -------------------------------------------------------------------------------- /roles/maas_firewall/tasks/setup_firewall_rules.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # firewall config for maas. First lock down everything except port 22 3 | - name: Install persistent iptables package 4 | ansible.builtin.apt: 5 | name: "iptables-persistent" 6 | state: present 7 | update_cache: true 8 | cache_valid_time: 3600 9 | 10 | - name: Allow outgoing connection port 22 (ssh) 11 | ansible.builtin.iptables: 12 | chain: OUTPUT 13 | protocol: tcp 14 | destination_port: 22 15 | jump: ACCEPT 16 | 17 | - name: Allow local traffic 18 | ansible.builtin.iptables: 19 | chain: INPUT 20 | in_interface: lo 21 | jump: ACCEPT 22 | 23 | - name: Allow incoming port 22 24 | ansible.builtin.iptables: 25 | chain: INPUT 26 | protocol: tcp 27 | destination_port: 22 28 | jump: ACCEPT 29 | 30 | - name: Allow related and established connections 31 | ansible.builtin.iptables: 32 | chain: INPUT 33 | ctstate: ESTABLISHED,RELATED 34 | jump: ACCEPT 35 | 36 | - name: Allow port 53 tcp/udp 37 | ansible.builtin.iptables: 38 | chain: INPUT 39 | protocol: tcp 40 | destination_port: 53 41 | jump: ACCEPT 42 | 43 | - name: Allow port 53 tcp/udp 44 | ansible.builtin.iptables: 45 | chain: INPUT 46 | protocol: udp 47 | destination_port: 53 48 | jump: ACCEPT 49 | 50 | - name: Open tcp ports 51 | ansible.builtin.iptables: 52 | chain: INPUT 53 | protocol: tcp 54 | destination_port: "{{ item }}" 55 | jump: ACCEPT 56 | with_items: 57 | - '{{ maas_pg_tcp_ports | default([]) | select() }}' 58 | - '{{ maas_pgproxy_tcp_ports | default([]) | select() }}' 59 | - '{{ maas_proxy_tcp_ports | default([]) | select() }}' 60 | - "{{ maas_rack_tcp_ports | default([]) | select() }}" 61 | - "{{ maas_region_tcp_ports | default([]) | select() }}" 62 | 63 | - name: Open udp ports 64 | ansible.builtin.iptables: 65 | chain: INPUT 66 | protocol: udp 67 | destination_port: "{{ item }}" 68 | jump: ACCEPT 69 | with_items: 70 | - "{{ maas_pg_udp_ports | default([]) | select() }}" 71 | - "{{ maas_pgproxy_udp_ports | default([]) | select() }}" 72 | - "{{ maas_proxy_udp_ports | default([]) | select() }}" 73 | - "{{ maas_rack_udp_ports | default([]) | select() }}" 74 | - "{{ maas_region_udp_ports | default([]) | select() }}" 75 | 76 | - name: Set policy for INPUT chain to drop (otherwise) 77 | ansible.builtin.iptables: 78 | chain: INPUT 79 | policy: DROP 80 | 81 | - name: Set policy for FORWARD chain to drop 82 | ansible.builtin.iptables: 83 | chain: FORWARD 84 | policy: DROP 85 | 86 | - name: Save iptables rules 87 | ansible.builtin.shell: iptables-save > /etc/iptables/rules.v4 88 | changed_when: false 89 | -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/install_postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install PostgreSQL and configuration dependencies" 3 | ansible.builtin.apt: 4 | name: 5 | - "python3-psycopg2" 6 | - "acl" 7 | - "{{ maas_postgres_deb_name }}" 8 | update_cache: true 9 | cache_valid_time: 3600 10 | state: "{{ 'present' if maas_postgres_action | lower == 'install' else maas_postgres_action }}" 11 | register: postgres_installed 12 | 13 | - name: "Install xinetd" 14 | ansible.builtin.apt: 15 | name: "xinetd" 16 | state: present 17 | when: maas_ha_postgres_enabled|bool 18 | 19 | - name: "Generate Replication Password" 20 | ansible.builtin.command: openssl rand -base64 14 21 | register: maas_postgres_replication_password_output 22 | when: (maas_postgres_replication_password is undefined) and (maas_ha_postgres_enabled|bool) and (postgres_installed.changed) 23 | 24 | - name: "Save Replication Password" 25 | ansible.builtin.set_fact: 26 | maas_postgres_replication_password: "{{ maas_postgres_replication_password_output.stdout }}" 27 | cacheable: true 28 | run_once: true 29 | delegate_to: "{{ item }}" 30 | delegate_facts: true 31 | loop: "{{ groups['maas_postgres'] }}" 32 | when: (maas_ha_postgres_enabled|bool) and (postgres_installed.changed) 33 | 34 | - name: "Write pg_hba.conf" 35 | ansible.builtin.template: 36 | src: "pg_hba.conf.j2" 37 | dest: "{{ maas_postgres_config_dir }}pg_hba.conf" 38 | mode: 0640 39 | owner: postgres 40 | group: postgres 41 | 42 | - name: "Write postgresql.conf" 43 | ansible.builtin.include_tasks: 44 | file: write_postgres_config.yaml 45 | 46 | - name: "Create MAAS Postgres User" 47 | community.postgresql.postgresql_user: 48 | name: "{{ maas_postgres_user }}" 49 | password: "{{ maas_postgres_password }}" 50 | login_user: "postgres" 51 | state: present 52 | become: true 53 | become_user: postgres 54 | 55 | - name: "Create MAAS Postgres Database" 56 | community.postgresql.postgresql_db: 57 | name: "{{ maas_postgres_database }}" 58 | state: "{{ 'present' if maas_postgres_action | lower == 'install' else maas_postgres_action }}" 59 | owner: "{{ maas_postgres_user }}" 60 | become: true 61 | become_user: postgres 62 | 63 | - name: "Write pgsql_check script" 64 | ansible.builtin.template: 65 | src: pgsql_check.j2 66 | dest: /opt/pgsql_check 67 | mode: 0755 68 | owner: root 69 | group: root 70 | when: maas_ha_postgres_enabled|bool 71 | 72 | - name: "Add pgsql_check_v4 service entry" 73 | ansible.builtin.lineinfile: 74 | dest: /etc/services 75 | line: "pgsql_check_v4 {{ maas_pgsql_check_port|default(23267) }}/tcp # postgres healthcheck" 76 | when: maas_postgres_ipv4 is defined 77 | 78 | - name: "Add pgsql_check_v6 service entry" 79 | ansible.builtin.lineinfile: 80 | dest: /etc/services 81 | line: "pgsql_check_v6 {{ maas_pgsql_check_port|default(23267) }}/tcp # postgres healthcheck" 82 | when: maas_postgres_ipv6 is defined 83 | 84 | - name: "Write pgsql_check xinetd config file" 85 | ansible.builtin.template: 86 | src: pgsql_check_conf.j2 87 | dest: /etc/xinetd.d/pgsql_check 88 | mode: 0644 89 | owner: root 90 | group: root 91 | when: maas_ha_postgres_enabled|bool 92 | notify: "Restart xinetd" 93 | -------------------------------------------------------------------------------- /roles/maas_postgres/tasks/configure_postgres_secondary.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Add temporary primary IP" 3 | ansible.builtin.shell: > 4 | ip addr add\ 5 | {{ maas_postgres_floating_ip }}/{{ maas_postgres_floating_ip_prefix_len }}\ 6 | dev {{ maas_postgres_floating_ip_iface }} 7 | when: (inventory_hostname == groups['maas_postgres'][0]) and (maas_postgres_floating_ip is defined) 8 | register: ip_addr_result 9 | failed_when: 10 | - ip_addr_result.rc != 0 11 | - '"RTNETLINK answers: File exists" not in ip_addr_result.stderr' 12 | 13 | - name: "Add floating IP to postgres' bind addresses" 14 | ansible.builtin.include_tasks: 15 | file: write_postgres_config.yaml 16 | vars: 17 | extra_ip: "{{ maas_postgres_floating_ip }}" 18 | when: (inventory_hostname == groups['maas_postgres'][0]) and (maas_postgres_floating_ip is defined) 19 | 20 | - name: "Stop Postgres To Clear Out Data" 21 | ansible.builtin.systemd: 22 | name: "postgresql@{{ maas_postgres_version_number }}-main.service" 23 | state: stopped 24 | no_block: false 25 | when: not (inventory_hostname == groups['maas_postgres'][0]) 26 | 27 | - name: "Remove Previous Data Directory" 28 | ansible.builtin.file: 29 | path: "{{ maas_postgres_data_dir }}" 30 | state: 'absent' 31 | when: not (inventory_hostname == groups['maas_postgres'][0]) 32 | 33 | - name: "Create a New Data Directory" 34 | ansible.builtin.file: 35 | path: "{{ maas_postgres_data_dir }}" 36 | state: directory 37 | owner: postgres 38 | group: postgres 39 | mode: '0700' 40 | when: not (inventory_hostname == groups['maas_postgres'][0]) 41 | 42 | - name: "Create Base Backup" 43 | ansible.builtin.expect: 44 | command: > 45 | pg_basebackup -h {{ maas_postgres_floating_ip }} 46 | -U {{ maas_postgres_replication_user }} 47 | -p 5432 -D {{ maas_postgres_data_dir }} -Fp -Xs -R 48 | -S {{ maas_postgres_replication_slot ~ "_" ~ inventory_hostname|regex_replace('[^A-Za-z0-9_]', '_') }} 49 | responses: 50 | '(?i)password': "{{ maas_postgres_replication_password }}" 51 | when: not (inventory_hostname == groups['maas_postgres'][0]) 52 | become: true 53 | become_user: postgres 54 | 55 | - name: "Create standby.signal" 56 | ansible.builtin.file: 57 | path: "{{ maas_postgres_data_dir }}/standby.signal" 58 | owner: postgres 59 | group: postgres 60 | mode: 0644 61 | when: not (inventory_hostname == groups['maas_postgres'][0]) 62 | notify: 63 | - "Start Postgres Service To Load New Configuration" 64 | 65 | - name: "Flush Handlers" 66 | ansible.builtin.meta: "flush_handlers" 67 | 68 | - name: "Ensure application_name Preserved" 69 | community.postgresql.postgresql_set: 70 | name: "primary_conninfo" 71 | value: "{{ maas_postgres_primary_conninfo }}" 72 | become: true 73 | become_user: postgres 74 | when: not (inventory_hostname == groups['maas_postgres'][0]) 75 | 76 | - name: "Disable Postgres in systemd for pacemaker management" 77 | ansible.builtin.systemd: 78 | name: "postgresql@{{ maas_postgres_version_number }}-main.service" 79 | enabled: false 80 | 81 | - name: "Remove temporary primary IP" 82 | ansible.builtin.command: > 83 | ip addr del {{ maas_postgres_floating_ip }}/{{ maas_postgres_floating_ip_prefix_len }} dev {{ maas_postgres_floating_ip_iface }} 84 | when: (inventory_hostname == groups['maas_postgres'][0]) and (maas_postgres_floating_ip is defined) 85 | 86 | - name: "Disable auto-start" 87 | ansible.builtin.copy: 88 | dest: "{{ maas_postgres_config_dir }}start.conf" 89 | content: "disabled" 90 | mode: 0644 91 | owner: postgres 92 | group: postgres 93 | -------------------------------------------------------------------------------- /site.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # This playbook should deploy everything 4 | 5 | - hosts: all 6 | tasks: 7 | - name: "Ensure required variables have been defined" 8 | ansible.builtin.assert: 9 | quiet: true 10 | fail_msg: "Required variable has not been defined" 11 | that: 12 | - maas_url is defined 13 | - maas_version is defined 14 | - maas_installation_type is defined 15 | - maas_postgres_password is defined 16 | 17 | - name: "Ensure maas_version is a version string" 18 | ansible.builtin.assert: 19 | fail_msg: "'{{ maas_version }}' is not a valid version number" 20 | that: 21 | - maas_version is regex('\d+\.\d+(\.\d+)?') 22 | 23 | - name: "Ensure o11y can be enabled" 24 | ansible.builtin.assert: 25 | quiet: true 26 | fail_msg: "Prometheus or Loki endpoints must be defined" 27 | that: 28 | - (o11y_prometheus_url|length > 0 or o11y_loki_url|length > 0) 29 | when: o11y_enable 30 | 31 | - name: "Define proxy environment if proxies given" 32 | ansible.builtin.set_fact: 33 | proxy_env: "{{ proxy_env | combine({'http_proxy' : http_proxy | d(omit)}) | 34 | combine({'https_proxy' : https_proxy | d(omit)}) }}" 35 | 36 | - name: "Show proxy environment if in use" 37 | ansible.builtin.debug: 38 | msg: "Playbooks using proxy settings: {{ proxy_env }}" 39 | with_dict: "{{ proxy_env }}" 40 | 41 | - name: "Ensure dist is up to date" 42 | ansible.builtin.apt: 43 | upgrade: "dist" 44 | update_cache: true 45 | cache_valid_time: 3600 46 | become: true 47 | 48 | - name: "Discover host architecture" 49 | ansible.builtin.command: dpkg --print-architecture 50 | register: arch_out 51 | changed_when: false 52 | 53 | - name: "Set architechture facts" 54 | ansible.builtin.set_fact: 55 | ubuntu_arch: "{{ arch_out.stdout }}" 56 | 57 | - hosts: maas_postgres 58 | roles: 59 | - role: maas_postgres 60 | - role: o11y_agent 61 | become: true 62 | gather_facts: true 63 | tags: 64 | - maas_postgres_primary 65 | - maas_postgres_secondary 66 | - maas_postgres 67 | - maas_ha_postgres 68 | environment: "{{ proxy_env }}" 69 | 70 | - hosts: maas_corosync 71 | roles: 72 | - role: maas_corosync 73 | become: true 74 | gather_facts: true 75 | tags: 76 | - maas_corosync 77 | - maas_ha_postgres 78 | environment: "{{ proxy_env }}" 79 | 80 | - hosts: maas_pacemaker 81 | roles: 82 | - role: maas_pacemaker 83 | become: true 84 | gather_facts: true 85 | tags: 86 | - maas_pacemaker 87 | - maas_ha_postgres 88 | environment: "{{ proxy_env }}" 89 | 90 | - hosts: maas_postgres_proxy 91 | roles: 92 | - role: maas_postgres_proxy 93 | become: true 94 | gather_facts: true 95 | tags: 96 | - maas_postgres_proxy 97 | environment: "{{ proxy_env }}" 98 | 99 | # proxies are provisioned prior to rack controllers so the rack controller 100 | # will request to register to region controllers through the proxy 101 | - hosts: maas_proxy 102 | roles: 103 | - role: maas_proxy 104 | become: true 105 | gather_facts: true 106 | tags: 107 | - maas_proxy 108 | environment: "{{ proxy_env }}" 109 | 110 | - hosts: maas_region_controller 111 | roles: 112 | - role: maas_region_controller 113 | - role: o11y_agent 114 | become: true 115 | gather_facts: true 116 | tags: 117 | - maas_region_controller 118 | - maas_controller 119 | environment: "{{ proxy_env }}" 120 | 121 | - hosts: maas_rack_controller 122 | roles: 123 | - role: maas_rack_controller 124 | - role: o11y_agent 125 | become: true 126 | gather_facts: true 127 | tags: 128 | - maas_rack_controller 129 | - maas_controller 130 | environment: "{{ proxy_env }}" 131 | -------------------------------------------------------------------------------- /roles/maas_postgres/templates/pg_hba.conf.j2: -------------------------------------------------------------------------------- 1 | # PostgreSQL Client Authentication Configuration File 2 | # =================================================== 3 | # DO NOT DISABLE! 4 | # If you change this first entry you will need to make sure that the 5 | # database superuser can access the database using some other method. 6 | # Noninteractive access to all databases is required during automatic 7 | # maintenance (custom daily cronjobs, replication, and similar tasks). 8 | # 9 | # Database administrative login by Unix domain socket 10 | local all postgres peer 11 | 12 | # TYPE DATABASE USER ADDRESS METHOD 13 | {% for hba_entry in maas_postgres_hba_entries['local'] %} 14 | {{ hba_entry['type'] }} {{ hba_entry['database'] }} {{ hba_entry['user'] }} {{ hba_entry['address'] }} {{ hba_entry['method'] }} 15 | 16 | {% endfor %} 17 | 18 | {%- if (ansible_all_ipv4_addresses) and 19 | ('address' in ansible_default_ipv4) and 20 | (maas_postgres_hba_entries['ipv4']) -%} 21 | {%- for hba_entry in maas_postgres_hba_entries['ipv4'] -%} 22 | {{ hba_entry['type'] }} {{ hba_entry['database'] }} {{ hba_entry['user'] }} {{ hba_entry['address'] }} {{ hba_entry['method'] }} 23 | {% endfor -%} 24 | {% endif %} 25 | {%- if (ansible_all_ipv6_addresses) and 26 | ('address' in ansible_default_ipv6) and 27 | (maas_postgres_hba_entries['ipv6']) -%} 28 | {%- for hba_entry in maas_postgres_hba_entries['ipv6'] -%} 29 | {{ hba_entry['type'] }} {{ hba_entry['database'] }} {{ hba_entry['user'] }} {{ hba_entry['address'] }} {{ hba_entry['method'] }} 30 | {% endfor -%} 31 | {% endif %} 32 | 33 | 34 | {% if maas_ha_postgres_enabled|bool %} 35 | {% if maas_postgres_floating_ip is defined %} 36 | host replication replicator {{ maas_postgres_floating_ip }}/{{ 32 if maas_postgres_floating_ip|ansible.utils.ipv4 else 128 }} reject 37 | {% endif %} 38 | 39 | {% for host in groups['maas_postgres'] %} 40 | {% if host != inventory_hostname %} 41 | {%- if ('ansible_default_ipv4' in hostvars[host]) and ('address' in hostvars[host]['ansible_default_ipv4']) -%} 42 | {% if "/" in hostvars[host]['ansible_default_ipv4']['address'] %} 43 | host replication replicator {{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ maas_postgres_hash }} 44 | {% else %} 45 | host replication replicator {{ hostvars[host]['ansible_default_ipv4']['address'] }}/32 {{ maas_postgres_hash }} 46 | {% endif %} 47 | {% endif %} 48 | {%- if ('ansible_default_ipv6' in hostvars[host]) and ('address' in hostvars[host]['ansible_default_ipv6']) -%} 49 | {% if "/" in hostvars[host]['ansible_default_ipv6']['address'] %} 50 | host replication replicator {{ hostvars[host]['ansible_default_ipv6']['address'] }} {{ maas_postgres_hash }} 51 | {% else %} 52 | host replication replicator {{ hostvars[host]['ansible_default_ipv6']['address'] }}/128 {{ maas_postgres_hash }} 53 | {% endif %} 54 | {% endif %} 55 | {% endif %} 56 | {% endfor %} 57 | # don't allow replication of self 58 | {% if ansible_default_ipv4 and ('address' in ansible_default_ipv4) %} 59 | {% if "/" in ansible_default_ipv4['address'] %} 60 | host replication replicator {{ ansible_default_ipv4['address'] }} reject 61 | {% else %} 62 | host replication replicator {{ ansible_default_ipv4['address'] }}/32 reject 63 | {% endif %} 64 | {% endif %} 65 | {% if ansible_default_ipv6 and ('address' in ansible_default_ipv6) %} 66 | {% if "/" in ansible_default_ipv6['address'] %} 67 | host replication replicator {{ ansible_default_ipv6['address'] }} reject 68 | {% else %} 69 | host replication replicator {{ ansible_default_ipv6['address'] }}/128 reject 70 | {% endif %} 71 | {% endif %} 72 | 73 | host replication replicator {{ ansible_hostname }} reject 74 | {% if inventory_hostname | ansible.utils.ipv4 %} 75 | host replication replicator {{ inventory_hostname }}/32 reject 76 | {% elif inventory_hostname | ansible.utils.ipv6 %} 77 | host replication replicator {{ inventory_hostname }}/128 reject 78 | {% else %} 79 | host replication replicator {{ inventory_hostname }} reject 80 | {% endif %} 81 | {% endif %} 82 | -------------------------------------------------------------------------------- /roles/maas_pacemaker/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install Pacemaker packages" 3 | ansible.builtin.apt: 4 | name: "{{ item }}" 5 | state: present 6 | update-cache: true 7 | with_items: 8 | - pacemaker 9 | - pcs 10 | - fence-agents 11 | - resource-agents-paf 12 | register: install_pacemaker 13 | notify: "Ensure Pacemaker is started" 14 | 15 | - name: "Add temp file config for pacemaker-managed postgres" 16 | ansible.builtin.template: 17 | src: "postgresql-part.conf.j2" 18 | dest: "/etc/tmpfiles.d/postgresql-part.conf" 19 | owner: "root" 20 | group: "root" 21 | mode: 0644 22 | 23 | - name: "Setup NOPROXY for cluster members" 24 | ansible.builtin.lineinfile: 25 | dest: "{{ item }}" 26 | line: "NOPROXY={{ groups['maas_pacemaker'] | join(',') }},{{ maas_pacemaker_noproxy_list }},{{ maas_postgres_floating_ip }}" 27 | with_items: 28 | - "/etc/default/pacemaker" 29 | - "/etc/default/pcsd" 30 | when: proxy_env 31 | 32 | - name: "Generate Pacemaker user Password" 33 | ansible.builtin.command: openssl rand -base64 14 34 | register: maas_pacemaker_user_password_output 35 | when: maas_pacemaker_user_password is undefined 36 | 37 | - name: "Save Pacemaker user Password" 38 | ansible.builtin.set_fact: 39 | maas_pacemaker_user_password: "{{ maas_pacemaker_user_password_output.stdout }}" 40 | cacheable: true 41 | run_once: true 42 | delegate_to: "{{ item }}" 43 | delegate_facts: true 44 | loop: "{{ groups['maas_pacemaker'] }}" 45 | 46 | - name: "Set pacemaker user password" 47 | ansible.builtin.user: 48 | name: "hacluster" 49 | password: "{{ maas_pacemaker_user_password | password_hash }}" 50 | 51 | - name: "Configure ssh for pacemaker" 52 | ansible.builtin.template: 53 | src: pacemaker_sshd_config.j2 54 | dest: /etc/ssh/sshd_config.d/pacemaker_sshd.conf 55 | owner: "root" 56 | group: "root" 57 | mode: 0644 58 | notify: "Restart sshd" 59 | 60 | - name: "Flush handlers" 61 | ansible.builtin.meta: flush_handlers 62 | 63 | - name: "Override /etc/hosts for External Address" 64 | ansible.builtin.lineinfile: 65 | dest: /etc/hosts 66 | line: "{{ maas_pacemaker_self_address }} {{ inventory_hostname }}" 67 | 68 | - name: "Auth cluster" 69 | ansible.builtin.shell: 70 | cmd: "pcs host auth {{ groups['maas_pacemaker'] | join(' ') }} -u hacluster -p '{{ maas_pacemaker_user_password }}' && touch /tmp/pacemaker_auth" 71 | creates: "/tmp/pacemaker_auth" 72 | executable: /bin/bash 73 | register: pacemaker_auth 74 | until: pacemaker_auth is not failed 75 | retries: 3 76 | delay: 2 77 | environment: 78 | no_proxy: "{{ groups['maas_pacemaker'] | join(',') }},{{ maas_pacemaker_noproxy_list }},{{ maas_postgres_floating_ip }}" 79 | NO_PROXY: "{{ groups['maas_pacemaker'] | join(',') }},{{ maas_pacemaker_noproxy_list }},{{ maas_postgres_floating_ip }}" 80 | 81 | - name: "Add Pacemaker Configuration Script" 82 | ansible.builtin.template: 83 | src: configure_pacemaker.sh.j2 84 | dest: /tmp/configure_pacemaker.sh 85 | owner: root 86 | group: root 87 | mode: 0755 88 | 89 | - name: "Dump CIB file" 90 | ansible.builtin.command: 91 | cmd: "pcs cluster cib {{ maas_pacemaker_tmp_cib }}" 92 | creates: "{{ maas_pacemaker_tmp_cib }}" 93 | 94 | - name: "Configure Pacemaker Resources" 95 | ansible.builtin.command: 96 | cmd: "/tmp/configure_pacemaker.sh" 97 | removes: "{{ maas_pacemaker_tmp_cib }}" 98 | run_once: true 99 | 100 | - name: "Configure HA metrics agent" 101 | ansible.builtin.include_tasks: 102 | file: o11y.yaml 103 | when: o11y_enable 104 | 105 | - name: "Setup firewall" 106 | ansible.builtin.include_role: 107 | name: maas_firewall 108 | tasks_from: setup_firewall_rules 109 | vars: 110 | maas_pg_tcp_ports: 111 | - 5432 112 | - 2224 113 | - "{{ maas_pgsql_check_port|default(23267) }}" 114 | maas_pg_udp_ports: 115 | - 5405 116 | when: enable_firewall 117 | -------------------------------------------------------------------------------- /roles/maas_region_controller/tasks/install_maas.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Installs MAAS 3 | 4 | - name: Install MAAS - Snap 5 | community.general.snap: 6 | name: maas 7 | channel: '{{ maas_version }}/{{ maas_snap_channel }}' 8 | state: "{{ 'present' if maas_state | lower == 'install' else maas_state }}" 9 | register: maas_region_new_installation 10 | when: maas_installation_type | lower == 'snap' 11 | 12 | - name: Add MAAS apt Repository 13 | ansible.builtin.apt_repository: 14 | repo: "ppa:maas/{{ maas_version }}" 15 | when: maas_installation_type | lower == 'deb' 16 | 17 | - name: Install Chrony 18 | ansible.builtin.apt: 19 | name: "chrony" 20 | state: "{{ maas_deb_state | default('present') }}" 21 | update_cache: true 22 | 23 | - name: Install MAAS Region Controller - Deb 24 | ansible.builtin.apt: 25 | name: 26 | - "maas-region-api" 27 | state: "{{ maas_deb_state | default('present') }}" 28 | update_cache: true 29 | cache_valid_time: 3600 30 | register: maas_region_new_installation 31 | when: (maas_installation_type | lower == 'deb') 32 | 33 | - name: Update regiond.conf 34 | ansible.builtin.template: 35 | src: regiond.conf.j2 36 | dest: /etc/maas/regiond.conf 37 | mode: 0644 38 | owner: maas 39 | group: maas 40 | when: maas_installation_type | lower == 'deb' 41 | 42 | - name: Initialise MAAS Controller - Snap 43 | ansible.builtin.command: > 44 | maas init 45 | {{ 'region+rack' if 'maas_rack_controller' in group_names else 'region' }} 46 | --maas-url={{ maas_url }} 47 | --database-uri {{ maas_postgres_uri }} 48 | when: maas_installation_type | lower == 'snap' and maas_region_new_installation is defined 49 | throttle: 1 50 | 51 | - name: Wait For Postgres Primary To Be Elected 52 | ansible.builtin.uri: 53 | url: http://{{ item }}:{{ maas_pgsql_check_port|default(23267) }} 54 | status_code: 55 | - 200 56 | - 204 57 | use_proxy: false 58 | register: pg_healthcheck 59 | until: pg_healthcheck is not failed 60 | retries: 5 61 | delay: 2 62 | loop: "{{ groups['maas_postgres'] }}" 63 | loop_control: 64 | index_var: idx 65 | when: maas_ha_postgres_enabled|bool 66 | 67 | - name: Migrate MAAS database 68 | ansible.builtin.command: "{{ 'maas' if maas_installation_type | lower == 'snap' else 'maas-region' }} migrate" 69 | changed_when: false 70 | register: pg_migrate 71 | until: pg_migrate is not failed 72 | retries: 1 73 | delay: 2 74 | run_once: true 75 | 76 | # MAAS region controller only needs to be initialized in this case if rbac or candid are in use, otherwise the regiond.conf write handles init 77 | - name: Initialise MAAS Controller - Deb 78 | ansible.builtin.expect: 79 | command: > 80 | maas init --rbac-url={{ maas_rbac_url | default('') | quote }} 81 | --candid-agent-file={{ maas_candid_auth_file | default('') | quote }} 82 | responses: 83 | "(?i)Username: ": "{{ admin_username }}" 84 | "(?i)Password: ": "{{ admin_password }}" 85 | "(?i)Again: ": "{{ admin_password }}" 86 | "(?i)Email: ": "{{ admin_email }}" 87 | "(?i)Import SSH keys ": "{{ admin_id if admin_id is defined else '' }}" 88 | when: maas_installation_type | lower == 'deb' and maas_region_new_installation is defined 89 | 90 | - name: Starting MAAS region service 91 | ansible.builtin.systemd: 92 | name: maas-regiond.service 93 | state: started 94 | no_block: false 95 | when: maas_installation_type | lower == 'deb' 96 | 97 | - name: Add an administrator to MAAS 98 | ansible.builtin.command: maas createadmin \ 99 | --username={{ admin_username }} --password={{ admin_password }} \ 100 | --email={{ admin_email }} --ssh-import={{ admin_id if admin_id is defined else '' }} 101 | when: not maas_region_new_installation or maas_installation_type | lower == 'snap' 102 | run_once: true 103 | 104 | - name: Enable TLS 105 | ansible.builtin.include_role: 106 | name: common 107 | tasks_from: TLS 108 | # TLS is available only in MAAS 3.2 or higher 109 | when: maas_version is version("3.2", '>=') and enable_tls 110 | 111 | - name: Wait For MAAS To Create Secret File 112 | ansible.builtin.wait_for: 113 | path: "{{ maas_secret_file }}" 114 | state: present 115 | 116 | - name: Read MAAS Secret For Rack Controllers 117 | ansible.builtin.command: cat "{{ maas_secret_file }}" 118 | register: maas_rack_secret_tmp 119 | changed_when: false 120 | 121 | - name: Save MAAS Secret 122 | ansible.builtin.set_fact: 123 | maas_rack_secret: "{{ maas_rack_secret_tmp.stdout }}" 124 | cacheable: true 125 | run_once: true 126 | delegate_to: "{{ item }}" 127 | delegate_facts: true 128 | loop: "{{ groups['maas_rack_controller'] }}" 129 | when: ('maas_rack_controller' not in group_names) 130 | 131 | - name: Create Ansible installation metric file 132 | ansible.builtin.file: 133 | path: "{{ maas_installmetric_file }}" 134 | state: touch 135 | mode: 0644 136 | -------------------------------------------------------------------------------- /roles/o11y_agent/templates/agent-snap.yaml.j2: -------------------------------------------------------------------------------- 1 | server: 2 | log_level: info 3 | 4 | {% if o11y_prometheus_url|length > 0 %} 5 | metrics: 6 | wal_directory: {{ o11y_grafana_agent_wal_dir }} 7 | global: 8 | scrape_interval: 30s 9 | external_labels: 10 | host: {{ ansible_hostname }} 11 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 12 | maas_az: {{ maas_availability_zone | default('default') }} 13 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 14 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 15 | {% endif %} 16 | remote_write: 17 | - url: {{o11y_prometheus_url}} 18 | name: prom-export 19 | configs: 20 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 21 | - name: 'maas' 22 | scrape_configs: 23 | - job_name: 'maas-metrics' 24 | static_configs: 25 | - targets: ['{{ ansible_hostname }}:5239'] 26 | {% if 'maas_region_controller' in group_names %} 27 | - job_name: 'maas-cluster-metrics' 28 | static_configs: 29 | - targets: ['{{ ansible_hostname }}:5240'] 30 | metrics_path: '/MAAS/metrics' 31 | {% endif %} 32 | {% endif %} 33 | {% if 'maas_postgres' in group_names %} 34 | - name: 'postgres' 35 | scrape_configs: 36 | - job_name: 'database-metrics' 37 | static_configs: 38 | - targets: ['{{ ansible_hostname }}:9187'] 39 | {% endif %} 40 | {% if 'maas_pacemaker' in group_names %} 41 | - name: 'ha-cluster' 42 | scrape_configs: 43 | - job_name: 'cluster-metrics' 44 | static_configs: 45 | - targets: ['{{ ansible_hostname }}:9664'] 46 | {% endif %} 47 | {% endif %} 48 | 49 | {% if o11y_loki_url|length > 0 %} 50 | logs: 51 | positions_directory: {{ o11y_grafana_agent_pos_dir }} 52 | configs: 53 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 54 | - name: 'maas' 55 | clients: 56 | - url: {{ o11y_loki_url }} 57 | scrape_configs: 58 | - job_name: maas-audit 59 | syslog: 60 | listen_address: localhost:{{ maas_promtail_port }} 61 | labels: 62 | job: "maas-audit" 63 | maas_az: {{ maas_availability_zone | default('default') }} 64 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 65 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 66 | maas_module: "audit" 67 | relabel_configs: 68 | - source_labels: ['__syslog_message_hostname'] 69 | target_label: 'host' 70 | - source_labels: ['__syslog_message_app_name'] 71 | target_label: 'maas_module' 72 | pipeline_stages: 73 | - regex: 74 | expression: "^(?s) \\[(?P\\S+?)\\] ((?P\\S+): )?(?P.*)$" 75 | - output: 76 | source: content 77 | - labels: 78 | severity: 79 | machine: 80 | 81 | - job_name: maas-console 82 | journal: 83 | matches: SYSLOG_IDENTIFIER=maas-regiond, SYSLOG_IDENTIFIER=maas-rackd 84 | labels: 85 | host: {{ ansible_hostname }} 86 | maas_az: {{ maas_availability_zone | default('default') }} 87 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 88 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 89 | job: "maas-console" 90 | 91 | {% if 'maas_rack_controller' in group_names %} 92 | - job_name: maas-dhcpd 93 | journal: 94 | matches: SYSLOG_IDENTIFIER=maas-dhcpd, SYSLOG_IDENTIFIER=maas-dhcpd6 95 | labels: 96 | host: {{ ansible_hostname }} 97 | maas_az: {{ maas_availability_zone | default('default') }} 98 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 99 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 100 | job: "maas-dhcpd" 101 | {% endif %} 102 | 103 | - job_name: maas-metadata 104 | journal: 105 | matches: SYSLOG_IDENTIFIER=maas-http 106 | labels: 107 | host: {{ ansible_hostname }} 108 | maas_az: {{ maas_availability_zone | default('default') }} 109 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 110 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 111 | job: "maas-metadata" 112 | 113 | - job_name: maas-named 114 | journal: 115 | matches: SYSLOG_IDENTIFIER=named 116 | labels: 117 | host: {{ ansible_hostname }} 118 | maas_az: {{ maas_availability_zone | default('default') }} 119 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 120 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 121 | job: "maas-named" 122 | 123 | - job_name: maas-ntpd 124 | journal: 125 | matches: SYSLOG_IDENTIFIER=chronyd 126 | labels: 127 | host: {{ ansible_hostname }} 128 | maas_az: {{ maas_availability_zone | default('default') }} 129 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 130 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 131 | job: "maas-ntpd" 132 | 133 | - job_name: maas-proxy 134 | journal: 135 | matches: SYSLOG_IDENTIFIER=maas-proxy 136 | labels: 137 | host: {{ ansible_hostname }} 138 | maas_az: {{ maas_availability_zone | default('default') }} 139 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 140 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 141 | job: "maas-proxy" 142 | 143 | - job_name: maas-temporal 144 | journal: 145 | matches: SYSLOG_IDENTIFIER=maas-temporal 146 | labels: 147 | host: {{ ansible_hostname }} 148 | maas_az: {{ maas_availability_zone | default('default') }} 149 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 150 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 151 | job: "maas-temporal" 152 | 153 | - job_name: maas-apiserver 154 | journal: 155 | matches: SYSLOG_IDENTIFIER=maas-apiserver 156 | labels: 157 | host: {{ ansible_hostname }} 158 | maas_az: {{ maas_availability_zone | default('default') }} 159 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 160 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 161 | job: "maas-apiserver" 162 | 163 | - job_name: maas-agent 164 | journal: 165 | matches: SYSLOG_IDENTIFIER=maas-agent 166 | labels: 167 | host: {{ ansible_hostname }} 168 | maas_az: {{ maas_availability_zone | default('default') }} 169 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 170 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 171 | job: "maas-agent" 172 | {% endif %} 173 | {% if 'maas_postgres' in group_names %} 174 | - name: 'postgres' 175 | clients: 176 | - url: {{ o11y_loki_url }} 177 | scrape_configs: 178 | - job_name: database-logs 179 | static_configs: 180 | - labels: 181 | __path__: {{ maas_postgres_data_dir ~ 'log/postgresql.log' }} 182 | host: {{ ansible_hostname }} 183 | job: "database-logs" 184 | {% endif %} 185 | {% if 'maas_pacemaker' in group_names %} 186 | - name: 'ha-cluster' 187 | clients: 188 | - url: {{ o11y_loki_url }} 189 | scrape_configs: 190 | - job_name: pacemaker-logs 191 | static_configs: 192 | - labels: 193 | __path__: /var/log/pacemaker/pacemaker.log 194 | host: {{ ansible_hostname }} 195 | job: "pacemaker-logs" 196 | - job_name: corosync-logs 197 | static_configs: 198 | - labels: 199 | __path__: /var/log/corosync/corosync.log 200 | host: {{ ansible_hostname }} 201 | job: "corosync-logs" 202 | {% endif %} 203 | {% endif %} 204 | -------------------------------------------------------------------------------- /roles/o11y_agent/templates/agent-deb.yaml.j2: -------------------------------------------------------------------------------- 1 | server: 2 | log_level: info 3 | 4 | {% if o11y_prometheus_url|length > 0 %} 5 | metrics: 6 | wal_directory: {{ o11y_grafana_agent_wal_dir }} 7 | global: 8 | scrape_interval: 30s 9 | external_labels: 10 | host: {{ ansible_hostname }} 11 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 12 | maas_az: {{ maas_availability_zone | default('default') }} 13 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 14 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 15 | {% endif %} 16 | remote_write: 17 | - url: {{o11y_prometheus_url}} 18 | name: prom-export 19 | configs: 20 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 21 | - name: 'maas' 22 | scrape_configs: 23 | - job_name: 'maas-metrics' 24 | static_configs: 25 | - targets: ['{{ ansible_hostname }}:5239'] 26 | {% if 'maas_region_controller' in group_names %} 27 | - job_name: 'maas-cluster-metrics' 28 | static_configs: 29 | - targets: ['{{ ansible_hostname }}:5240'] 30 | metrics_path: '/MAAS/metrics' 31 | {% endif %} 32 | {% endif %} 33 | {% if 'maas_postgres' in group_names %} 34 | - name: 'postgres' 35 | scrape_configs: 36 | - job_name: 'database-metrics' 37 | static_configs: 38 | - targets: ['{{ ansible_hostname }}:9187'] 39 | {% endif %} 40 | {% if 'maas_pacemaker' in group_names %} 41 | - name: 'ha-cluster' 42 | scrape_configs: 43 | - job_name: 'cluster-metrics' 44 | static_configs: 45 | - targets: ['{{ ansible_hostname }}:9664'] 46 | {% endif %} 47 | {% endif %} 48 | 49 | {% if o11y_loki_url|length > 0 %} 50 | logs: 51 | positions_directory: {{ o11y_grafana_agent_pos_dir }} 52 | configs: 53 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 54 | - name: 'maas' 55 | clients: 56 | - url: {{ o11y_loki_url }} 57 | scrape_configs: 58 | - job_name: maas-audit 59 | syslog: 60 | listen_address: localhost:{{ maas_promtail_port }} 61 | labels: 62 | job: "maas-audit" 63 | maas_az: {{ maas_availability_zone | default('default') }} 64 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 65 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 66 | maas_module: "audit" 67 | relabel_configs: 68 | - source_labels: ['__syslog_message_hostname'] 69 | target_label: 'host' 70 | - source_labels: ['__syslog_message_app_name'] 71 | target_label: 'maas_module' 72 | pipeline_stages: 73 | - regex: 74 | expression: "^(?s) \\[(?P\\S+?)\\] ((?P\\S+): )?(?P.*)$" 75 | - output: 76 | source: content 77 | - labels: 78 | severity: 79 | machine: 80 | 81 | - job_name: maas-console 82 | journal: 83 | matches: _SYSTEMD_UNIT=maas-regiond.service, _SYSTEMD_UNIT=maas-rackd.service 84 | labels: 85 | host: {{ ansible_hostname }} 86 | maas_az: {{ maas_availability_zone | default('default') }} 87 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 88 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 89 | job: "maas-console" 90 | 91 | {% if 'maas_rack_controller' in group_names %} 92 | - job_name: maas-dhcpd 93 | journal: 94 | matches: _SYSTEMD_UNIT=dhcpd.service, _SYSTEMD_UNIT=dhcpd6.service 95 | labels: 96 | host: {{ ansible_hostname }} 97 | maas_az: {{ maas_availability_zone | default('default') }} 98 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 99 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 100 | job: "maas-dhcpd" 101 | {% endif %} 102 | 103 | - job_name: maas-metadata 104 | journal: 105 | matches: _SYSTEMD_UNIT=maas-http.service 106 | labels: 107 | host: {{ ansible_hostname }} 108 | maas_az: {{ maas_availability_zone | default('default') }} 109 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 110 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 111 | job: "maas-metadata" 112 | 113 | - job_name: maas-named 114 | journal: 115 | matches: _SYSTEMD_UNIT=named.service 116 | labels: 117 | host: {{ ansible_hostname }} 118 | maas_az: {{ maas_availability_zone | default('default') }} 119 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 120 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 121 | job: "maas-named" 122 | 123 | - job_name: maas-ntpd 124 | journal: 125 | matches: _SYSTEMD_UNIT=chrony.service 126 | labels: 127 | host: {{ ansible_hostname }} 128 | maas_az: {{ maas_availability_zone | default('default') }} 129 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 130 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 131 | job: "maas-ntpd" 132 | 133 | - job_name: maas-proxy 134 | journal: 135 | matches: _SYSTEMD_UNIT=maas-proxy.service 136 | labels: 137 | host: {{ ansible_hostname }} 138 | maas_az: {{ maas_availability_zone | default('default') }} 139 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 140 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 141 | job: "maas-proxy" 142 | 143 | - job_name: maas-temporal 144 | journal: 145 | matches: _SYSTEMD_UNIT=maas-temporal.service 146 | labels: 147 | host: {{ ansible_hostname }} 148 | maas_az: {{ maas_availability_zone | default('default') }} 149 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 150 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 151 | job: "maas-temporal" 152 | 153 | - job_name: maas-apiserver 154 | journal: 155 | matches: _SYSTEMD_UNIT=maas-apiserver.service 156 | labels: 157 | host: {{ ansible_hostname }} 158 | maas_az: {{ maas_availability_zone | default('default') }} 159 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 160 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 161 | job: "maas-apiserver" 162 | 163 | - job_name: maas-agent 164 | journal: 165 | matches: _SYSTEMD_UNIT=maas-agent.service 166 | labels: 167 | host: {{ ansible_hostname }} 168 | maas_az: {{ maas_availability_zone | default('default') }} 169 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 170 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 171 | job: "maas-agent" 172 | {% endif %} 173 | {% if 'maas_postgres' in group_names %} 174 | - name: 'postgres' 175 | clients: 176 | - url: {{ o11y_loki_url }} 177 | scrape_configs: 178 | - job_name: database-logs 179 | static_configs: 180 | - labels: 181 | __path__: {{ maas_postgres_data_dir ~ 'log/postgresql.log' }} 182 | host: {{ ansible_hostname }} 183 | job: "database-logs" 184 | {% endif %} 185 | {% if 'maas_pacemaker' in group_names %} 186 | - name: 'ha-cluster' 187 | clients: 188 | - url: {{ o11y_loki_url }} 189 | scrape_configs: 190 | - job_name: pacemaker-logs 191 | static_configs: 192 | - labels: 193 | __path__: /var/log/pacemaker/pacemaker.log 194 | host: {{ ansible_hostname }} 195 | job: "pacemaker-logs" 196 | - job_name: corosync-logs 197 | static_configs: 198 | - labels: 199 | __path__: /var/log/corosync/corosync.log 200 | host: {{ ansible_hostname }} 201 | job: "corosync-logs" 202 | {% endif %} 203 | {% endif %} 204 | -------------------------------------------------------------------------------- /roles/o11y_agent/templates/grafana-agent.yaml.j2: -------------------------------------------------------------------------------- 1 | server: 2 | log_level: info 3 | 4 | {% if o11y_prometheus_url|length > 0 %} 5 | metrics: 6 | wal_directory: {{ o11y_grafana_agent_wal_dir }} 7 | global: 8 | scrape_interval: 30s 9 | external_labels: 10 | host: {{ ansible_hostname }} 11 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 12 | maas_az: {{ maas_availability_zone | default('default') }} 13 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 14 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 15 | {% endif %} 16 | remote_write: 17 | - url: {{o11y_prometheus_url}} 18 | name: prom-export 19 | configs: 20 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 21 | - name: 'maas' 22 | scrape_configs: 23 | - job_name: 'maas-metrics' 24 | static_configs: 25 | - targets: ['{{ ansible_hostname }}:5239'] 26 | {% if 'maas_region_controller' in group_names %} 27 | - job_name: 'maas-cluster-metrics' 28 | static_configs: 29 | - targets: ['{{ ansible_hostname }}:5240'] 30 | metrics_path: '/MAAS/metrics' 31 | {% endif %} 32 | {% endif %} 33 | {% if 'maas_postgres' in group_names %} 34 | - name: 'postgres' 35 | scrape_configs: 36 | - job_name: 'database-metrics' 37 | static_configs: 38 | - targets: ['{{ ansible_hostname }}:9187'] 39 | {% endif %} 40 | {% if 'maas_pacemaker' in group_names %} 41 | - name: 'ha-cluster' 42 | scrape_configs: 43 | - job_name: 'cluster-metrics' 44 | static_configs: 45 | - targets: ['{{ ansible_hostname }}:9664'] 46 | {% endif %} 47 | {% endif %} 48 | 49 | {% if o11y_loki_url|length > 0 %} 50 | logs: 51 | positions_directory: {{ o11y_grafana_agent_pos_dir }} 52 | configs: 53 | {% if group_names|intersect(['maas_region_controller', 'maas_rack_controller']) %} 54 | - name: 'maas' 55 | clients: 56 | - url: {{ o11y_loki_url }} 57 | scrape_configs: 58 | - job_name: maas-audit 59 | syslog: 60 | listen_address: localhost:{{ maas_promtail_port }} 61 | labels: 62 | job: "maas-audit" 63 | maas_az: {{ maas_availability_zone | default('default') }} 64 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 65 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 66 | maas_module: "audit" 67 | relabel_configs: 68 | - source_labels: ['__syslog_message_hostname'] 69 | target_label: 'host' 70 | - source_labels: ['__syslog_message_app_name'] 71 | target_label: 'maas_module' 72 | pipeline_stages: 73 | - regex: 74 | expression: "^(?s) \\[(?P\\S+?)\\] ((?P\\S+): )?(?P.*)$" 75 | - output: 76 | source: content 77 | - labels: 78 | severity: 79 | machine: 80 | 81 | - job_name: maas-console 82 | static_configs: 83 | - labels: 84 | __path__: {{ maas_log_dir }}/{regiond,rackd}.log 85 | host: {{ ansible_hostname }} 86 | maas_az: {{ maas_availability_zone | default('default') }} 87 | maas_region: {{ 'True' if 'maas_region_controller' in group_names else 'False' }} 88 | maas_rack: {{ 'True' if 'maas_rack_controller' in group_names else 'False' }} 89 | job: "maas-console" 90 | pipeline_stages: 91 | - regex: 92 | expression: "^(?s)(?P