├── .ansible-lint ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── missing-capability.md └── workflows │ ├── cla-check.yml │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── alertrules.yaml ├── ansible.cfg ├── backup.yaml ├── createadmin.yaml ├── group_vars ├── all │ ├── 01-maas │ ├── 20-database │ ├── 20-vault │ └── 50-o11y ├── maas_pacemaker │ ├── 01-pacemaker │ └── 01-stonith ├── maas_postgres │ ├── 01-postgres │ ├── 20-pg_hba │ ├── 30-replication │ └── 60-firewall ├── maas_postgres_proxy │ ├── 10-maas_postgres_proxy │ └── 60-firewall ├── maas_proxy │ ├── 10-maas_proxy │ └── 60-firewall ├── maas_rack_controller │ ├── 10-maas_rack_controller │ └── 60-firewall └── maas_region_controller │ ├── 10-maas_region_controller │ └── 60-firewall ├── hosts ├── hosts.yaml ├── restore.yaml ├── roles ├── common │ ├── tasks │ │ ├── TLS.yaml │ │ ├── backup.yaml │ │ ├── maas_supported_os.yaml │ │ ├── restore.yaml │ │ └── vault.yaml │ └── vars │ │ └── main.yaml ├── maas_corosync │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── main.yaml │ │ └── teardown.yaml │ └── templates │ │ └── corosync.conf.j2 ├── maas_firewall │ └── tasks │ │ ├── setup_firewall_rules.yaml │ │ └── teardown.yaml ├── maas_pacemaker │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── main.yaml │ │ ├── o11y.yaml │ │ └── teardown.yaml │ └── templates │ │ ├── configure_pacemaker.sh.j2 │ │ ├── pacemaker_sshd_config.j2 │ │ └── postgresql-part.conf.j2 ├── maas_postgres │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── configure_postgres_secondary.yaml │ │ ├── create_replication_user.yaml │ │ ├── install_postgres.yaml │ │ ├── main.yaml │ │ ├── o11y_pg.yaml │ │ ├── teardown.yaml │ │ └── write_postgres_config.yaml │ └── templates │ │ ├── pg_hba.conf.j2 │ │ ├── pgsql_check.j2 │ │ ├── pgsql_check_conf.j2 │ │ └── postgresql.conf.j2 ├── maas_postgres_proxy │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── main.yaml │ │ └── teardown.yaml │ └── templates │ │ └── haproxy.cfg.j2 ├── maas_proxy │ ├── handlers │ │ └── main.yaml │ ├── tasks │ │ ├── main.yml │ │ └── teardown.yaml │ └── templates │ │ └── haproxy.cfg.j2 ├── maas_rack_controller │ └── tasks │ │ ├── install_maas.yaml │ │ ├── main.yaml │ │ ├── teardown.yaml │ │ └── upgrade_maas.yaml ├── maas_region_controller │ ├── tasks │ │ ├── install_maas.yaml │ │ ├── main.yaml │ │ ├── o11y.yaml │ │ ├── teardown.yaml │ │ └── update_maas.yaml │ └── templates │ │ └── regiond.conf.j2 └── o11y_agent │ ├── handlers │ └── main.yaml │ ├── tasks │ ├── main.yaml │ └── teardown.yaml │ └── templates │ ├── agent-deb.yaml.j2 │ ├── agent-snap.yaml.j2 │ └── grafana-agent.yaml.j2 ├── site.yaml └── teardown.yaml /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /maas-*-alert-rules 3 | /*-alert-rules.yml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAAS-ansible-playbook 2 | An Ansible playbook for installing and configuring MAAS, further documentation is found [here](https://maas.io/docs/ansible-playbooks-reference). 3 | 4 | ## Versions 5 | This playbook has been tested with Ansible version 5.10.0 and above. We recommend using the latest available stable version of Ansible (currently 7.x). The `netaddr` Python library needs to be installed on the machine on which Ansible is used; note that this is not required on remote hosts. 6 | 7 | ## Install 8 | 9 | ``` 10 | git clone git@github.com:canonical/maas-ansible-playbook 11 | ``` 12 | 13 | ## Setup 14 | This playbook has several main roles, each has a corresponding group to assign hosts to. They are as follows: 15 | 16 | - **maas_pacemaker**: This role and group are for a set of hosts to be configured as a Pacemaker cluster to manage HA Postgres. It is recommended that `maas_corosync` is a child group of this group. It is also recommended that these are the same hosts as the `maas_postgres` group. This role is optional, but required for HA Postgres. 17 | 18 | - **maas_corosync**: This role and group are for a set of hosts to be configured to run a Corosync cluster, used for managing quorum of a HA Postgres cluster. This role is optional, but required for HA Postgres. 19 | 20 | - **maas_postgres**: This role and group are for a host to be configured as a Postgres instance for the MAAS stack. If more than one host is assigned this role, Postgres will be configured in HA, which then makes the `maas_corosync` and `maas_pacemaker` roles required. This role is required. 21 | 22 | - **maas_region_controller**: This role and group are for hosts to be configured as MAAS Region Controllers. This role is required at least once. 23 | 24 | - **maas_rack_controller**: This role and group are for hosts to be configured as MAAS Rack Controllers. This role is required at least once. 25 | 26 | - **maas_postgres_proxy**: This role and group are for hosts to be configured as an HAProxy instance in front of the HA Postgres cluster, in order to ensure queries are directed to the primary instance. We recommend this group is assigned to the same hosts as `maas_region_controller`, this will have HAProxy's listener bind to localhost and route queries for that specific region controller. This role is optional, but recommended for HA Postgres. 27 | 28 | - **maas_proxy**: This role and group are for hosts to be configured as an HAProxy instance in front of region controllers for HA stacks. This role is optional. 29 | 30 | ### Host file 31 | 32 | Two formats can be used to define the inventory (`ini` and `yaml`). Below, the same inventory can be found twice: defined in both formats. Only one is to be chosen when running the playbook. 33 | 34 | More information can be found on the [Ansible inventory documentation page](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html). 35 | 36 | #### ini 37 | 38 | ```ini 39 | [maas_corosync] 40 | db01.example.com 41 | db02.example.com 42 | db03.example.com 43 | 44 | [maas_pacemaker:children] 45 | maas_corosync 46 | 47 | [maas_postgres] 48 | db01.example.com 49 | db02.example.com 50 | db03.example.com 51 | 52 | [maas_postgres_proxy] 53 | region01.example.com 54 | region02.example.com 55 | region03.example.com 56 | 57 | [maas_region_controller] 58 | region01.example.com 59 | region02.example.com 60 | region03.example.com 61 | 62 | [maas_rack_controller] 63 | region01.example.com 64 | rack01.example.com 65 | rack02.example.com 66 | 67 | [maas_proxy] 68 | proxy01.example.com 69 | ``` 70 | 71 | #### yaml 72 | 73 | ```yaml 74 | --- 75 | all: 76 | children: 77 | maas_pacemaker: 78 | children: 79 | maas_corosync: 80 | hosts: 81 | db01.example.com 82 | db02.example.com 83 | db03.example.com 84 | maas_postgres: 85 | hosts: 86 | db01.example.com: 87 | db02.example.com: 88 | db03.example.com: 89 | maas_proxy: 90 | hosts: 91 | proxy01.example.com: 92 | maas_postgres_proxy: 93 | hosts: 94 | region01.example.com: 95 | maas_region_controller: 96 | hosts: 97 | region01.example.com: 98 | region02.example.com: 99 | region03.example.com: 100 | maas_rack_controller: 101 | hosts: 102 | region01.example.com: 103 | rack01.example.com: 104 | rack02.example.com: 105 | ``` 106 | 107 | Note: the Pacemaker role requires host-specifc variables that should be defined in the hosts file, they are as follows: 108 | 109 | - `maas_pacemaker_fencing_driver`: The Pacemaker STONITH fencing driver, defaults to `ipmilan`. This driver is used to forcefully remove a member from the Pacemaker cluster when it exhibits erroneous behavior. Pacemaker will list the available drivers with the following command: `stonith_admin --list-installed` 110 | 111 | - `maas_pacemaker_stonith_params`: The parameters specific to the fencing driver selected. These must be defined when using Pacemaker, to see the parameters of a given driver, run `stonith_admin --metadata --agent ` 112 | 113 | ## Run 114 | 115 | This playbook requires a user to set the following set of variables: 116 | 117 | - **maas_version**: The intended version of MAAS to install.\ 118 | Example: `'3.2'` 119 | 120 | - **maas_postgres_password**: The password for the MAAS postgres user.\ 121 | Example: `'my_password'` 122 | 123 | - **maas_installation_type**: The intended type of MAAS installation.\ 124 | Possible values are: `'deb'` or `'snap'` 125 | 126 | - **maas_url**: The MAAS URL where MAAS will be accessed and which rack controllers should use for registering with region controllers.\ 127 | Example: `'http://proxy01.example.com:5240/MAAS'` 128 | 129 | 130 | There are additional optional variables that can be passed to the playbooks: 131 | 132 | - ### Admin credentials (if setting up an admin user) 133 | 134 | - **admin_username**: The username of the admin user\ 135 | Default: `admin` 136 | 137 | - **admin_password**: The password of the admin user\ 138 | Default: `admin` 139 | 140 | - **admin_email**: The email address of the admin user\ 141 | Default: `admin@email.com` 142 | 143 | - **admin_id**: the Launchpad or GitHub id of the admin user to import ssh keys. 144 | (Optional) 145 | 146 | - **maas_proxy_postgres_proxy_enabled**: Use postgres proxy uri\ 147 | Default: `false` 148 | 149 | - **enable_tls**: Whether MAAS should enable TLS\ 150 | Default: `false` (only valid for MAAS >= 3.2) 151 | 152 | - **enable_firewall**: Whether MAAS should configure the firewall\ 153 | Default: `true` 154 | - ### MAAS Vault (only valid for MAAS >= 3.3) 155 | 156 | - **vault_integration**: Whether MAAS Should use Vault for secret storage\ 157 | Default: `false` 158 | - **vault_url**: The URL of the MAAS Vault\ 159 | Default: undefined 160 | - **vault_approle_id**: The approle id of the MAAS Vault\ 161 | Default: undefined 162 | - **vault_wrapped_token**: The wrapped token for the MAAS Vault\ 163 | Default: undefined 164 | - **vault_secrets_path**: The secrets path of the MAAS Vault\ 165 | Default: undefined 166 | - **vault_secret_mount**: The secret mount of the MAAS Vault\ 167 | Default: undefined 168 | 169 | - **http_proxy**: The HTTP Proxy to use\ 170 | Default: proxy not used 171 | 172 | - **https_proxy**: The HTTPS Proxy to use\ 173 | Default: proxy not used 174 | 175 | - ### Observability 176 | 177 | - **o11y_enable**: Whether Observability features should be enabled\ 178 | Default: `false` 179 | - **o11y_prometheus_url**: Prometheus [Remote Write Receiver](https://prometheus.io/docs/prometheus/latest/feature_flags/#remote-write-receiver) endpoint URL\ 180 | Default: undefined 181 | - **o11y_loki_url**: Loki endpoint URL\ 182 | Default: undefined 183 | 184 | When the Observability features are enabled, at least one of the endpoints MUST be defined. 185 | 186 | The following variables are only required when using HA Postgres: 187 | 188 | - **maas_postgres_floating_ip**: The floating IP used internally to the HA Postgres cluster for the purpose of replication. A static reservation for this IP should be made in your network to avoid overlap. 189 | 190 | - **maas_postgres_floating_ip_prefix_len**: The prefix length of the subnet within your network that the floating IP is within. 191 | 192 | ### Deploy the MAAS stack 193 | 194 | ``` 195 | ansible-playbook -i ./hosts\ 196 | --extra-vars="maas_version=3.2 maas_postgres_password=example maas_installation_type=deb maas_url=http://example.com:5240/MAAS"\ 197 | ./site.yaml 198 | ``` 199 | 200 | ### Deploy the MAAS stack with Observability enabled 201 | 202 | ``` 203 | ansible-playbook -i ./hosts \ 204 | --extra-vars="maas_version=3.3 \ 205 | maas_postgres_password=example \ 206 | maas_installation_type=snap \ 207 | maas_url=http://example.com:5240/MAAS \ 208 | o11y_enable=true \ 209 | o11y_prometheus_url=http://prometheus-server:9090/api/v1/write \ 210 | o11y_loki_url=http://loki-server:3100/loki/api/v1/push" \ 211 | ./site.yaml 212 | ``` 213 | 214 | ### Teardown the MAAS stack 215 | 216 | ``` 217 | ansible-playbook -i ./hosts ./teardown.yaml 218 | ``` 219 | 220 | ### Backup the MAAS stack 221 | 222 | Backup MAAS requires the `maas_backup_download_path` variable to be set, it will designate a path local to where the playbook is being run to download backup archives. This path must exist prior to running the playbook. `maas_installation_type` is also required. 223 | 224 | ``` 225 | ansible-playbook -i ./hosts --extra-vars="maas_backup_download_path=/tmp/maas_backups/ maas_installation_type=deb" ./backup.yaml 226 | ``` 227 | 228 | ### Restore from Backup 229 | 230 | Since backup is per-host, it is recommended to set the `maas_backup_file` variable as a host variable, or filter execution to a specific host, but `maas_backup_file` should be set to a gzipped tar file from the backup action and `maas_installation_type` should be set as well. 231 | 232 | The below example assumes `maas_backup_file` is set in `./hosts` 233 | 234 | ``` 235 | ansible-playbook -i ./hosts --extra-vars="maas_installation_type=deb" ./restore.yaml 236 | ``` 237 | 238 | ### Create a new admin user 239 | 240 | For an already existing MAAS installation, a new admin user can be created. The below example assumes the host for which the account is to be created is set under `[maas_region_controller]` in `./hosts`. `user_ssh` allows for upload of a Launchpad (`lp:id`) or GitHub (`gh:id`) public key, or can be left blank (`user_ssh=`). 241 | 242 | ``` 243 | ansible-playbook -i ./hosts --extra-vars="user_name=newuser user_pwd=newpwd user_email=user@email.com user_ssh=lp:id" ./createadmin.yaml 244 | ``` 245 | 246 | ### Export Observability alert rules 247 | 248 | MAAS has a curated collection of alert rules for Prometheus and Loki. You can export these rules using the following command, where `o11y_alertrules_dest` is the directory where the files should me placed. 249 | 250 | ``` 251 | ansible-playbook --extra-vars="o11y_alertrules_dest=/tmp" ./alertrules.yaml 252 | ``` 253 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = hosts 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 }}' -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/common/vars/main.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/maas-ansible-playbook/cce542a4224389d23006fe84c05f67daee7d0930/roles/common/vars/main.yaml -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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_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_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/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_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_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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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_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_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/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_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_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_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_postgres/templates/postgresql.conf.j2: -------------------------------------------------------------------------------- 1 | # ----------------------------- 2 | # PostgreSQL configuration file 3 | # ----------------------------- 4 | # 5 | # This file consists of lines of the form: 6 | # 7 | # name = value 8 | # 9 | # (The "=" is optional.) Whitespace may be used. Comments are introduced with 10 | # "#" anywhere on a line. The complete list of parameter names and allowed 11 | # values can be found in the PostgreSQL documentation. 12 | # 13 | # The commented-out settings shown in this file represent the default values. 14 | # Re-commenting a setting is NOT sufficient to revert it to the default value; 15 | # you need to reload the server. 16 | # 17 | # This file is read on server startup and when the server receives a SIGHUP 18 | # signal. If you edit the file on a running system, you have to SIGHUP the 19 | # server for the changes to take effect, run "pg_ctl reload", or execute 20 | # "SELECT pg_reload_conf()". Some parameters, which are marked below, 21 | # require a server shutdown and restart to take effect. 22 | # 23 | # Any parameter can also be given as a command-line option to the server, e.g., 24 | # "postgres -c log_connections=on". Some parameters can be changed at run time 25 | # with the "SET" SQL command. 26 | # 27 | # Memory units: B = bytes Time units: us = microseconds 28 | # kB = kilobytes ms = milliseconds 29 | # MB = megabytes s = seconds 30 | # GB = gigabytes min = minutes 31 | # TB = terabytes h = hours 32 | # d = days 33 | 34 | 35 | #------------------------------------------------------------------------------ 36 | # FILE LOCATIONS 37 | #------------------------------------------------------------------------------ 38 | 39 | # The default values of these variables are driven from the -D command-line 40 | # option or PGDATA environment variable, represented here as ConfigDir. 41 | 42 | data_directory = '{{ maas_postgres_data_dir }}' # use data in another directory 43 | # (change requires restart) 44 | hba_file = '{{ maas_postgres_config_dir }}pg_hba.conf' # host-based authentication file 45 | # (change requires restart) 46 | ident_file = '{{ maas_postgres_config_dir }}pg_ident.conf' # ident configuration file 47 | # (change requires restart) 48 | 49 | # If external_pid_file is not explicitly set, no extra PID file is written. 50 | external_pid_file = '/var/run/postgresql/{{ maas_postgres_version_number }}-main.pid' # write an extra PID file 51 | 52 | #------------------------------------------------------------------------------ 53 | # CONNECTIONS AND AUTHENTICATION 54 | #------------------------------------------------------------------------------ 55 | 56 | # - Connection Settings - 57 | {% if maas_postgres_primary_listen_all_ifaces|default(false) %} 58 | listen_addresses = '*' 59 | {% else %} 60 | listen_addresses = '{%- if extra_ip is defined -%}{{ extra_ip }}, {% endif -%}{%- if maas_postgres_ipv4 is defined -%}{{ maas_postgres_ipv4 }}, {% endif -%}{%- if maas_postgres_ipv6 is defined -%}{{ maas_postgres_ipv6 }}, {% endif -%}localhost' # what IP address(es) to listen on; 61 | {% endif %} 62 | # comma-separated list of addresses; 63 | # defaults to 'localhost'; use '*' for all 64 | # (change requires restart) 65 | port = 5432 # (change requires restart) 66 | max_connections = 100 # (change requires restart) 67 | #superuser_reserved_connections = 3 # (change requires restart) 68 | unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories 69 | #unix_socket_group = '' # (change requires restart) 70 | #unix_socket_permissions = 0777 # begin with 0 to use octal notation 71 | # (change requires restart) 72 | #bonjour = off # advertise server via Bonjour 73 | # (change requires restart) 74 | #bonjour_name = '' # defaults to the computer name 75 | # (change requires restart) 76 | 77 | # - TCP settings - 78 | # see "man tcp" for details 79 | 80 | #tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; 81 | # 0 selects the system default 82 | #tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; 83 | # 0 selects the system default 84 | #tcp_keepalives_count = 0 # TCP_KEEPCNT; 85 | # 0 selects the system default 86 | #tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; 87 | # 0 selects the system default 88 | 89 | #client_connection_check_interval = 0 # time between checks for client 90 | # disconnection while running queries; 91 | # 0 for never 92 | 93 | # - Authentication - 94 | 95 | #authentication_timeout = 1min # 1s-600s 96 | #password_encryption = scram-sha-256 # scram-sha-256 or md5 97 | #db_user_namespace = off 98 | 99 | # GSSAPI using Kerberos 100 | #krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' 101 | #krb_caseins_users = off 102 | 103 | # - SSL - 104 | 105 | ssl = {{ 'on' if maas_postgres_ssl_enabled else 'off' }} 106 | #ssl_ca_file = '' 107 | ssl_cert_file = '{{ maas_postgres_cert if maas_postgres_cert is defined else "/etc/ssl/certs/ssl-cert-snakeoil.pem" }}' 108 | #ssl_crl_file = '' 109 | #ssl_crl_dir = '' 110 | ssl_key_file = '{{ maas_postgres_key if maas_postgres_key is defined else "/etc/ssl/private/ssl-cert-snakeoil.key" }}' 111 | #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers 112 | #ssl_prefer_server_ciphers = on 113 | #ssl_ecdh_curve = 'prime256v1' 114 | #ssl_min_protocol_version = 'TLSv1.2' 115 | #ssl_max_protocol_version = '' 116 | #ssl_dh_params_file = '' 117 | #ssl_passphrase_command = '' 118 | #ssl_passphrase_command_supports_reload = off 119 | 120 | 121 | #------------------------------------------------------------------------------ 122 | # RESOURCE USAGE (except WAL) 123 | #------------------------------------------------------------------------------ 124 | 125 | # - Memory - 126 | 127 | shared_buffers = 128MB # min 128kB 128 | # (change requires restart) 129 | #huge_pages = try # on, off, or try 130 | # (change requires restart) 131 | #huge_page_size = 0 # zero for system default 132 | # (change requires restart) 133 | #temp_buffers = 8MB # min 800kB 134 | #max_prepared_transactions = 0 # zero disables the feature 135 | # (change requires restart) 136 | # Caution: it is not advisable to set max_prepared_transactions nonzero unless 137 | # you actively intend to use prepared transactions. 138 | #work_mem = 4MB # min 64kB 139 | #hash_mem_multiplier = 1.0 # 1-1000.0 multiplier on hash table work_mem 140 | #maintenance_work_mem = 64MB # min 1MB 141 | #autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem 142 | #logical_decoding_work_mem = 64MB # min 64kB 143 | #max_stack_depth = 2MB # min 100kB 144 | #shared_memory_type = mmap # the default is the first option 145 | # supported by the operating system: 146 | # mmap 147 | # sysv 148 | # windows 149 | # (change requires restart) 150 | dynamic_shared_memory_type = posix # the default is the first option 151 | # supported by the operating system: 152 | # posix 153 | # sysv 154 | # windows 155 | # mmap 156 | # (change requires restart) 157 | #min_dynamic_shared_memory = 0MB # (change requires restart) 158 | 159 | # - Disk - 160 | 161 | #temp_file_limit = -1 # limits per-process temp file space 162 | # in kilobytes, or -1 for no limit 163 | 164 | # - Kernel Resources - 165 | 166 | #max_files_per_process = 1000 # min 64 167 | # (change requires restart) 168 | 169 | # - Cost-Based Vacuum Delay - 170 | 171 | #vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) 172 | #vacuum_cost_page_hit = 1 # 0-10000 credits 173 | #vacuum_cost_page_miss = 2 # 0-10000 credits 174 | #vacuum_cost_page_dirty = 20 # 0-10000 credits 175 | #vacuum_cost_limit = 200 # 1-10000 credits 176 | 177 | # - Background Writer - 178 | 179 | #bgwriter_delay = 200ms # 10-10000ms between rounds 180 | #bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables 181 | #bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round 182 | #bgwriter_flush_after = 512kB # measured in pages, 0 disables 183 | 184 | # - Asynchronous Behavior - 185 | 186 | #backend_flush_after = 0 # measured in pages, 0 disables 187 | #effective_io_concurrency = 1 # 1-1000; 0 disables prefetching 188 | #maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching 189 | #max_worker_processes = 8 # (change requires restart) 190 | #max_parallel_workers_per_gather = 2 # taken from max_parallel_workers 191 | #max_parallel_maintenance_workers = 2 # taken from max_parallel_workers 192 | #max_parallel_workers = 8 # maximum number of max_worker_processes that 193 | # can be used in parallel operations 194 | #parallel_leader_participation = on 195 | #old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate 196 | # (change requires restart) 197 | 198 | 199 | #------------------------------------------------------------------------------ 200 | # WRITE-AHEAD LOG 201 | #------------------------------------------------------------------------------ 202 | 203 | # - Settings - 204 | 205 | {% if maas_ha_postgres_enabled|bool %} 206 | wal_level = replica # minimal, replica, or logical 207 | {% endif %} 208 | # (change requires restart) 209 | #fsync = on # flush data to disk for crash safety 210 | # (turning this off can cause 211 | # unrecoverable data corruption) 212 | #synchronous_commit = on # synchronization level; 213 | # off, local, remote_write, remote_apply, or on 214 | #wal_sync_method = fsync # the default is the first option 215 | # supported by the operating system: 216 | # open_datasync 217 | # fdatasync (default on Linux and FreeBSD) 218 | # fsync 219 | # fsync_writethrough 220 | # open_sync 221 | #full_page_writes = on # recover from partial page writes 222 | # (change requires restart) 223 | #wal_compression = off # enable compression of full-page writes 224 | #wal_init_zero = on # zero-fill new WAL files 225 | #wal_recycle = on # recycle WAL files 226 | #wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers 227 | # (change requires restart) 228 | #wal_writer_delay = 200ms # 1-10000 milliseconds 229 | #wal_writer_flush_after = 1MB # measured in pages, 0 disables 230 | #wal_skip_threshold = 2MB 231 | 232 | #commit_delay = 0 # range 0-100000, in microseconds 233 | #commit_siblings = 5 # range 1-1000 234 | 235 | # - Checkpoints - 236 | 237 | #checkpoint_timeout = 5min # range 30s-1d 238 | #checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 239 | #checkpoint_flush_after = 256kB # measured in pages, 0 disables 240 | #checkpoint_warning = 30s # 0 disables 241 | max_wal_size = 2GB 242 | min_wal_size = 80MB 243 | 244 | # - Archiving - 245 | 246 | #archive_mode = off # enables archiving; off, on, or always 247 | # (change requires restart) 248 | #archive_command = '' # command to use to archive a logfile segment 249 | # placeholders: %p = path of file to archive 250 | # %f = file name only 251 | # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' 252 | #archive_timeout = 0 # force a logfile segment switch after this 253 | # number of seconds; 0 disables 254 | 255 | # - Archive Recovery - 256 | 257 | # These are only used in recovery mode. 258 | 259 | #restore_command = '' # command to use to restore an archived logfile segment 260 | # placeholders: %p = path of file to restore 261 | # %f = file name only 262 | # e.g. 'cp /mnt/server/archivedir/%f %p' 263 | #archive_cleanup_command = '' # command to execute at every restartpoint 264 | #recovery_end_command = '' # command to execute at completion of recovery 265 | 266 | # - Recovery Target - 267 | 268 | # Set these only when performing a targeted recovery. 269 | 270 | {% if maas_ha_postgres_enabled|bool %} 271 | wal_log_hints = on # also do full page writes of non-critical updates 272 | #recovery_target = 'immediate' # 'immediate' to end recovery as soon as a 273 | # consistent state is reached 274 | # (change requires restart) 275 | #recovery_target_name = '' # the named restore point to which recovery will proceed 276 | # (change requires restart) 277 | #recovery_target_time = '' # the time stamp up to which recovery will proceed 278 | # (change requires restart) 279 | #recovery_target_xid = '' # the transaction ID up to which recovery will proceed 280 | # (change requires restart) 281 | #recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed 282 | # (change requires restart) 283 | #recovery_target_inclusive = on # Specifies whether to stop: 284 | # just after the specified recovery target (on) 285 | # just before the recovery target (off) 286 | # (change requires restart) 287 | recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID 288 | # (change requires restart) 289 | #recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' 290 | # (change requires restart) 291 | {% endif %} 292 | 293 | #------------------------------------------------------------------------------ 294 | # REPLICATION 295 | #------------------------------------------------------------------------------ 296 | 297 | # - Sending Servers - 298 | 299 | # Set these on the primary and on any standby that will send replication data. 300 | 301 | {% if maas_ha_postgres_enabled|bool %} 302 | max_wal_senders = {{ groups['maas_postgres']|length + 10 }} # max number of walsender processes 303 | max_replication_slots = {{ groups['maas_postgres']|length + 10 }} # max number of replication slots 304 | {% endif %} 305 | # (change requires restart) 306 | # (change requires restart) 307 | #wal_keep_size = 0 # in megabytes; 0 disables 308 | #max_slot_wal_keep_size = -1 # in megabytes; -1 disables 309 | #wal_sender_timeout = 60s # in milliseconds; 0 disables 310 | #track_commit_timestamp = off # collect timestamp of transaction commit 311 | # (change requires restart) 312 | 313 | # - Standby Servers - 314 | 315 | # These settings are ignored on a primary server. 316 | 317 | {% if maas_ha_postgres_enabled|bool %} 318 | primary_conninfo = '{{ maas_postgres_primary_conninfo }}' # connection string to sending server 319 | primary_slot_name = '{{ maas_postgres_replication_slot ~ "_" ~ inventory_hostname|regex_replace('[^A-Za-z0-9_]', '_') }}' # replication slot on sending server 320 | hot_standby = 'on' # "off" disallows queries during recovery 321 | hot_standby_feedback = 'on' 322 | {% else %} 323 | hot_standby = 'off' # "off" disallows queries during recovery 324 | {% endif %} 325 | # (change requires restart) 326 | #max_standby_archive_delay = 30s # max delay before canceling queries 327 | # when reading WAL from archive; 328 | # -1 allows indefinite delay 329 | #max_standby_streaming_delay = 30s # max delay before canceling queries 330 | # when reading streaming WAL; 331 | # -1 allows indefinite delay 332 | #wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name 333 | # is not set 334 | #wal_receiver_status_interval = 10s # send replies at least this often 335 | # 0 disables 336 | #hot_standby_feedback = off # send info from standby to prevent 337 | # query conflicts 338 | #wal_receiver_timeout = 60s # time that receiver waits for 339 | # communication from primary 340 | # in milliseconds; 0 disables 341 | #wal_retrieve_retry_interval = 5s # time to wait before retrying to 342 | # retrieve WAL after a failed attempt 343 | #recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery 344 | 345 | # - Subscribers - 346 | 347 | # These settings are ignored on a publisher. 348 | 349 | #max_logical_replication_workers = 4 # taken from max_worker_processes 350 | # (change requires restart) 351 | #max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers 352 | 353 | 354 | #------------------------------------------------------------------------------ 355 | # QUERY TUNING 356 | #------------------------------------------------------------------------------ 357 | 358 | # - Planner Method Configuration - 359 | 360 | #enable_async_append = on 361 | #enable_bitmapscan = on 362 | #enable_gathermerge = on 363 | #enable_hashagg = on 364 | #enable_hashjoin = on 365 | #enable_incremental_sort = on 366 | #enable_indexscan = on 367 | #enable_indexonlyscan = on 368 | #enable_material = on 369 | #enable_memoize = on 370 | #enable_mergejoin = on 371 | #enable_nestloop = on 372 | #enable_parallel_append = on 373 | #enable_parallel_hash = on 374 | #enable_partition_pruning = on 375 | #enable_partitionwise_join = off 376 | #enable_partitionwise_aggregate = off 377 | #enable_seqscan = on 378 | #enable_sort = on 379 | #enable_tidscan = on 380 | 381 | # - Planner Cost Constants - 382 | 383 | #seq_page_cost = 1.0 # measured on an arbitrary scale 384 | #random_page_cost = 4.0 # same scale as above 385 | #cpu_tuple_cost = 0.01 # same scale as above 386 | #cpu_index_tuple_cost = 0.005 # same scale as above 387 | #cpu_operator_cost = 0.0025 # same scale as above 388 | #parallel_setup_cost = 1000.0 # same scale as above 389 | #parallel_tuple_cost = 0.1 # same scale as above 390 | #min_parallel_table_scan_size = 8MB 391 | #min_parallel_index_scan_size = 512kB 392 | #effective_cache_size = 4GB 393 | 394 | #jit_above_cost = 100000 # perform JIT compilation if available 395 | # and query more expensive than this; 396 | # -1 disables 397 | #jit_inline_above_cost = 500000 # inline small functions if query is 398 | # more expensive than this; -1 disables 399 | #jit_optimize_above_cost = 500000 # use expensive JIT optimizations if 400 | # query is more expensive than this; 401 | # -1 disables 402 | 403 | # - Genetic Query Optimizer - 404 | 405 | #geqo = on 406 | #geqo_threshold = 12 407 | #geqo_effort = 5 # range 1-10 408 | #geqo_pool_size = 0 # selects default based on effort 409 | #geqo_generations = 0 # selects default based on effort 410 | #geqo_selection_bias = 2.0 # range 1.5-2.0 411 | #geqo_seed = 0.0 # range 0.0-1.0 412 | 413 | # - Other Planner Options - 414 | 415 | #default_statistics_target = 100 # range 1-10000 416 | #constraint_exclusion = partition # on, off, or partition 417 | #cursor_tuple_fraction = 0.1 # range 0.0-1.0 418 | #from_collapse_limit = 8 419 | #jit = on # allow JIT compilation 420 | #join_collapse_limit = 8 # 1 disables collapsing of explicit 421 | # JOIN clauses 422 | #plan_cache_mode = auto # auto, force_generic_plan or 423 | # force_custom_plan 424 | 425 | 426 | #------------------------------------------------------------------------------ 427 | # REPORTING AND LOGGING 428 | #------------------------------------------------------------------------------ 429 | 430 | # - Where to Log - 431 | 432 | #log_destination = 'stderr' # Valid values are combinations of 433 | # stderr, csvlog, syslog, and eventlog, 434 | # depending on platform. csvlog 435 | # requires logging_collector to be on. 436 | 437 | # This is used when logging to stderr: 438 | {% if maas_ha_postgres_enabled|bool %} 439 | logging_collector = 'on' # Enable capturing of stderr and csvlog 440 | {% endif %} 441 | # into log files. Required to be on for 442 | # csvlogs. 443 | # (change requires restart) 444 | 445 | # These are only used if logging_collector is on: 446 | #log_directory = 'log' # directory where log files are written, 447 | # can be absolute or relative to PGDATA 448 | log_filename = 'postgresql.log' # log file name pattern, 449 | # can include strftime() escapes 450 | #log_file_mode = 0600 # creation mode for log files, 451 | # begin with 0 to use octal notation 452 | #log_rotation_age = 1d # Automatic rotation of logfiles will 453 | # happen after that time. 0 disables. 454 | #log_rotation_size = 10MB # Automatic rotation of logfiles will 455 | # happen after that much log output. 456 | # 0 disables. 457 | #log_truncate_on_rotation = off # If on, an existing log file with the 458 | # same name as the new log file will be 459 | # truncated rather than appended to. 460 | # But such truncation only occurs on 461 | # time-driven rotation, not on restarts 462 | # or size-driven rotation. Default is 463 | # off, meaning append to existing files 464 | # in all cases. 465 | 466 | # These are relevant when logging to syslog: 467 | #syslog_facility = 'LOCAL0' 468 | #syslog_ident = 'postgres' 469 | #syslog_sequence_numbers = on 470 | #syslog_split_messages = on 471 | 472 | # This is only relevant when logging to eventlog (Windows): 473 | # (change requires restart) 474 | #event_source = 'PostgreSQL' 475 | # - When to Log - 476 | 477 | #log_min_messages = warning # values in order of decreasing detail: 478 | # debug5 479 | # debug4 480 | # debug3 481 | # debug2 482 | # debug1 483 | # info 484 | # notice 485 | # warning 486 | # error 487 | # log 488 | # fatal 489 | # panic 490 | 491 | #log_min_error_statement = error # values in order of decreasing detail: 492 | # debug5 493 | # debug4 494 | # debug3 495 | # debug2 496 | # debug1 497 | # info 498 | # notice 499 | # warning 500 | # error 501 | # log 502 | # fatal 503 | # panic (effectively off) 504 | 505 | #log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements 506 | # and their durations, > 0 logs only 507 | # statements running at least this number 508 | # of milliseconds 509 | 510 | #log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements 511 | # and their durations, > 0 logs only a sample of 512 | # statements running at least this number 513 | # of milliseconds; 514 | # sample fraction is determined by log_statement_sample_rate 515 | 516 | #log_statement_sample_rate = 1.0 # fraction of logged statements exceeding 517 | # log_min_duration_sample to be logged; 518 | # 1.0 logs all such statements, 0.0 never logs 519 | 520 | 521 | #log_transaction_sample_rate = 0.0 # fraction of transactions whose statements 522 | # are logged regardless of their duration; 1.0 logs all 523 | # statements from all transactions, 0.0 never logs 524 | 525 | # - What to Log - 526 | #debug_print_parse = off 527 | #debug_print_rewritten = off 528 | #debug_print_plan = off 529 | #debug_pretty_print = on 530 | #log_autovacuum_min_duration = -1 # log autovacuum activity; 531 | # -1 disables, 0 logs all actions and 532 | # their durations, > 0 logs only 533 | # actions running at least this number 534 | # of milliseconds. 535 | #log_checkpoints = off 536 | #log_connections = off 537 | #log_disconnections = off 538 | #log_duration = off 539 | #log_error_verbosity = default # terse, default, or verbose messages 540 | #log_hostname = off 541 | #log_line_prefix = '%m [%p] ' # special values: 542 | # %a = application name 543 | # %u = user name 544 | # %d = database name 545 | # %r = remote host and port 546 | # %h = remote host 547 | # %b = backend type 548 | # %p = process ID 549 | # %P = process ID of parallel group leader 550 | # %t = timestamp without milliseconds 551 | # %m = timestamp with milliseconds 552 | # %n = timestamp with milliseconds (as a Unix epoch) 553 | # %Q = query ID (0 if none or not computed) 554 | # %i = command tag 555 | # %e = SQL state 556 | # %c = session ID 557 | # %l = session line number 558 | # %s = session start timestamp 559 | # %v = virtual transaction ID 560 | # %x = transaction ID (0 if none) 561 | # %q = stop here in non-session 562 | # processes 563 | # %% = '%' 564 | # e.g. '<%u%%%d> ' 565 | #log_lock_waits = off # log lock waits >= deadlock_timeout 566 | #log_recovery_conflict_waits = off # log standby recovery conflict waits 567 | # >= deadlock_timeout 568 | #log_parameter_max_length = -1 # when logging statements, limit logged 569 | # bind-parameter values to N bytes; 570 | # -1 means print in full, 0 disables 571 | #log_parameter_max_length_on_error = 0 # when logging an error, limit logged 572 | # bind-parameter values to N bytes; 573 | # -1 means print in full, 0 disables 574 | #log_statement = 'none' # none, ddl, mod, all 575 | #log_replication_commands = off 576 | #log_temp_files = -1 # log temporary files equal or larger 577 | # than the specified size in kilobytes; 578 | # -1 disables, 0 logs all temp files 579 | log_timezone = 'Etc/UTC' 580 | 581 | 582 | #------------------------------------------------------------------------------ 583 | # PROCESS TITLE 584 | #------------------------------------------------------------------------------ 585 | 586 | #cluster_name = '' # added to process titles if nonempty 587 | # (change requires restart) 588 | #update_process_title = on 589 | 590 | 591 | #------------------------------------------------------------------------------ 592 | # STATISTICS 593 | #------------------------------------------------------------------------------ 594 | 595 | # - Query and Index Statistics Collector - 596 | 597 | #track_activities = on 598 | #track_activity_query_size = 1024 # (change requires restart) 599 | #track_counts = on 600 | #track_io_timing = off 601 | #track_wal_io_timing = off 602 | #track_functions = none # none, pl, all 603 | #stats_temp_directory = 'pg_stat_tmp' 604 | 605 | 606 | # - Monitoring - 607 | 608 | #compute_query_id = auto 609 | #log_statement_stats = off 610 | #log_parser_stats = off 611 | #log_planner_stats = off 612 | #log_executor_stats = off 613 | 614 | 615 | #------------------------------------------------------------------------------ 616 | # AUTOVACUUM 617 | #------------------------------------------------------------------------------ 618 | 619 | #autovacuum = on # Enable autovacuum subprocess? 'on' 620 | # requires track_counts to also be on. 621 | #autovacuum_max_workers = 3 # max number of autovacuum subprocesses 622 | # (change requires restart) 623 | #autovacuum_naptime = 1min # time between autovacuum runs 624 | #autovacuum_vacuum_threshold = 50 # min number of row updates before 625 | # vacuum 626 | #autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts 627 | # before vacuum; -1 disables insert 628 | # vacuums 629 | #autovacuum_analyze_threshold = 50 # min number of row updates before 630 | # analyze 631 | #autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum 632 | #autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table 633 | # size before insert vacuum 634 | #autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze 635 | #autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum 636 | # (change requires restart) 637 | #autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age 638 | # before forced vacuum 639 | # (change requires restart) 640 | #autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for 641 | # autovacuum, in milliseconds; 642 | # -1 means use vacuum_cost_delay 643 | #autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for 644 | # autovacuum, -1 means use 645 | # vacuum_cost_limit 646 | 647 | 648 | #------------------------------------------------------------------------------ 649 | # CLIENT CONNECTION DEFAULTS 650 | #------------------------------------------------------------------------------ 651 | 652 | # - Statement Behavior - 653 | 654 | #client_min_messages = notice # values in order of decreasing detail: 655 | # debug5 656 | # debug4 657 | # debug3 658 | # debug2 659 | # debug1 660 | # log 661 | # notice 662 | # warning 663 | # error 664 | #search_path = '"$user", public' # schema names 665 | #row_security = on 666 | #default_table_access_method = 'heap' 667 | #default_tablespace = '' # a tablespace name, '' uses the default 668 | #default_toast_compression = 'pglz' # 'pglz' or 'lz4' 669 | #temp_tablespaces = '' # a list of tablespace names, '' uses 670 | # only default tablespace 671 | #check_function_bodies = on 672 | #default_transaction_isolation = 'read committed' 673 | #default_transaction_read_only = off 674 | #default_transaction_deferrable = off 675 | #session_replication_role = 'origin' 676 | #statement_timeout = 0 # in milliseconds, 0 is disabled 677 | #lock_timeout = 0 # in milliseconds, 0 is disabled 678 | #idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled 679 | #idle_session_timeout = 0 # in milliseconds, 0 is disabled 680 | #vacuum_freeze_table_age = 150000000 681 | 682 | #vacuum_freeze_min_age = 50000000 683 | #vacuum_failsafe_age = 1600000000 684 | #vacuum_multixact_freeze_table_age = 150000000 685 | #vacuum_multixact_freeze_min_age = 5000000 686 | #vacuum_multixact_failsafe_age = 1600000000 687 | #bytea_output = 'hex' # hex, escape 688 | #xmlbinary = 'base64' 689 | #xmloption = 'content' 690 | #gin_pending_list_limit = 4MB 691 | 692 | # - Locale and Formatting - 693 | 694 | datestyle = 'iso, mdy' 695 | #intervalstyle = 'postgres' 696 | timezone = 'Etc/UTC' 697 | #timezone_abbreviations = 'Default' # Select the set of available time zone 698 | # abbreviations. Currently, there are 699 | # Default 700 | # Australia (historical usage) 701 | # India 702 | # You can create your own file in 703 | # share/timezonesets/. 704 | #extra_float_digits = 1 # min -15, max 3; any value >0 actually 705 | # selects precise output mode 706 | #client_encoding = sql_ascii # actually, defaults to database 707 | # encoding 708 | 709 | # These settings are initialized by initdb, but they can be changed. 710 | lc_messages = 'C.UTF-8' # locale for system error message 711 | # strings 712 | lc_monetary = 'C.UTF-8' # locale for monetary formatting 713 | lc_numeric = 'C.UTF-8' # locale for number formatting 714 | lc_time = 'C.UTF-8' # locale for time formatting 715 | 716 | # default configuration for text search 717 | default_text_search_config = 'pg_catalog.english' 718 | 719 | # - Shared Library Preloading - 720 | 721 | #local_preload_libraries = '' 722 | #session_preload_libraries = '' 723 | #shared_preload_libraries = '' # (change requires restart) 724 | #jit_provider = 'llvmjit' # JIT library to use 725 | 726 | # - Other Defaults - 727 | 728 | #dynamic_library_path = '$libdir' 729 | #extension_destdir = '' # prepend path when loading extensions 730 | # and shared objects (added by Debian) 731 | #gin_fuzzy_search_limit = 0 732 | 733 | #------------------------------------------------------------------------------ 734 | # LOCK MANAGEMENT 735 | #------------------------------------------------------------------------------ 736 | 737 | #deadlock_timeout = 1s 738 | #max_locks_per_transaction = 64 # min 10 739 | # (change requires restart) 740 | #max_pred_locks_per_transaction = 64 # min 10 741 | # (change requires restart) 742 | #max_pred_locks_per_relation = -2 # negative values mean 743 | # (max_pred_locks_per_transaction 744 | # / -max_pred_locks_per_relation) - 1 745 | #max_pred_locks_per_page = 2 # min 0 746 | 747 | 748 | #------------------------------------------------------------------------------ 749 | # VERSION AND PLATFORM COMPATIBILITY 750 | #------------------------------------------------------------------------------ 751 | 752 | # - Previous PostgreSQL Versions - 753 | 754 | #array_nulls = on 755 | #backslash_quote = safe_encoding # on, off, or safe_encoding 756 | #escape_string_warning = on 757 | #lo_compat_privileges = off 758 | #quote_all_identifiers = off 759 | #standard_conforming_strings = on 760 | #synchronize_seqscans = on 761 | 762 | # - Other Platforms and Clients - 763 | 764 | #transform_null_equals = off 765 | 766 | 767 | #------------------------------------------------------------------------------ 768 | # ERROR HANDLING 769 | #------------------------------------------------------------------------------ 770 | 771 | #exit_on_error = off # terminate session on any error? 772 | #restart_after_crash = on # reinitialize after backend crash? 773 | #data_sync_retry = off # retry or panic on failure to fsync 774 | # data? 775 | # (change requires restart) 776 | #recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) 777 | 778 | 779 | #------------------------------------------------------------------------------ 780 | # CONFIG FILE INCLUDES 781 | #------------------------------------------------------------------------------ 782 | 783 | # These options allow settings to be loaded from files other than the 784 | # default postgresql.conf. Note that these are directives, not variable 785 | # assignments, so they can usefully be given more than once. 786 | 787 | #include_dir = '...' # include files ending in '.conf' from 788 | # a directory, e.g., 'conf.d' 789 | #include_if_exists = '...' # include file only if it exists 790 | #include = '...' # include file 791 | 792 | 793 | #------------------------------------------------------------------------------ 794 | # CUSTOMIZED OPTIONS 795 | #------------------------------------------------------------------------------ 796 | 797 | # Add settings for extensions here 798 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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_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/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_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_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_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 | -------------------------------------------------------------------------------- /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/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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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