├── .gitignore ├── .gitreview ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── Vagrantfile ├── bindep.txt ├── defaults └── main.yml ├── doc ├── Makefile ├── requirements.txt └── source │ ├── _static │ └── .gitkeep │ ├── conf.py │ ├── configure-haproxy.rst │ └── index.rst ├── examples └── playbook.yml ├── files └── haproxy.default ├── handlers └── main.yml ├── manual-test.rc ├── meta ├── main.yml └── openstack-ansible.yml ├── releasenotes ├── notes │ ├── .placeholder │ ├── backend_config_per_host-14cec3ec5f708934.yaml │ ├── bind_interface-8f7a123d4ab1219a.yaml │ ├── cert_per_ip-e473f853dbe4047d.yaml │ ├── certbot-auto-5ccf2184fb554c90.yaml │ ├── certbot_ha-83b56aed3f360dba.yaml │ ├── custom-stick-tables-1c790fe223bb0d5d.yaml │ ├── default_backend_override-9840dc75ff9d1a9c.yaml │ ├── disable-sslv3-303acdcc6b593180.yaml │ ├── h2_initial_support-99a3277939942405.yaml │ ├── haproxy-backend-arguments-3b1dca299c7a8296.yaml │ ├── haproxy-bind-override-9562bab32b964de2.yaml │ ├── haproxy-frontend-only-haproxy_raw-d55ad3baa8d006f3.yaml │ ├── haproxy-hatop-4d6525a52f93a69e.yaml │ ├── haproxy-maps-787084d7f161c27e.yaml │ ├── haproxy-selinux-all-ports-4094eed48f2bfbca.yaml │ ├── haproxy_allowlist-95aa9b911baeacd9.yaml │ ├── haproxy_custom_errorfiles-500674edcaa6cbb6.yaml │ ├── haproxy_frontend_raw-0811d5d445a66b41.yaml │ ├── haproxy_letsencrypt-4a13c7911a20b993.yaml │ ├── haproxy_pki_create_certificates-28dd48424855f463.yaml │ ├── haproxy_rise_fall-64ba2e6d7e206973.yaml │ ├── haproxy_ssl_path-7130354314aee961.yaml │ ├── haproxy_sysctl_location-e18310fd96597a6f.yaml │ ├── haproxy_tuning_params_released-45eb40104747561a.yaml │ ├── implement_tuning_params-e627c774c0d0c9d2.yaml │ ├── letsencrypt-ssl-certification-129a80cb88d8e6ff.yaml │ ├── non_inventory_hosts-c0fa4c185a01e78b.yaml │ ├── package-list-name-changes-a26d94a44c24de2f.yaml │ ├── pki-temp-certs-path-d63091df0234df2e.yaml │ ├── refresh-interval-configuration-option-884d64aa259ecc3c.yaml │ ├── remove-haproxy-repo-vars-051a47bbfaf6d1da.yaml │ ├── rename_haproxy_vip_binds-dda8197aaf607b53.yaml │ ├── separated-haproxy-config-b38d200ee0baaeac.yaml │ ├── stick-table-9ef4bd94a4a000b3.yaml │ ├── tls12-only-a22d5f3f8198617f.yaml │ └── tls_variables-91160d4e38085de4.yaml └── source │ ├── _static │ └── .placeholder │ ├── _templates │ └── .placeholder │ ├── conf.py │ ├── index.rst │ ├── newton.rst │ ├── ocata.rst │ ├── pike.rst │ ├── queens.rst │ ├── rocky.rst │ ├── stein.rst │ ├── train.rst │ ├── unreleased.rst │ ├── ussuri.rst │ ├── victoria.rst │ └── zed.rst ├── run_tests.sh ├── tasks ├── haproxy_install.yml ├── haproxy_post_install.yml ├── haproxy_pre_install.yml ├── haproxy_service_config.yml ├── haproxy_service_config_external.yml ├── haproxy_ssl_letsencrypt.yml └── main.yml ├── templates ├── haproxy.cfg.j2 ├── letsencrypt_pre_hook_certbot_distro.j2 ├── letsencrypt_renew_certbot_distro.j2 ├── map.j2 ├── service-redirect.j2 └── service.j2 ├── tests ├── ansible-role-requirements.yml ├── host_vars │ └── localhost.yml ├── inventory ├── test-vars.yml └── test.yml ├── tox.ini ├── vars ├── debian.yml ├── main.yml └── redhat.yml └── zuul.d └── project.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Add patterns in here to exclude files created by tools integrated with this 2 | # repository, such as test frameworks from the project's recommended workflow, 3 | # rendered documentation and package builds. 4 | # 5 | # Don't add patterns to exclude files created by preferred personal tools 6 | # (editors, IDEs, your operating system itself even). These should instead be 7 | # maintained outside the repository, for example in a ~/.gitignore file added 8 | # with: 9 | # 10 | # git config --global core.excludesfile '~/.gitignore' 11 | 12 | # Compiled source # 13 | ################### 14 | *.com 15 | *.class 16 | *.dll 17 | *.exe 18 | *.o 19 | *.so 20 | *.pyc 21 | build/ 22 | dist/ 23 | doc/build/ 24 | 25 | # Packages # 26 | ############ 27 | # it's better to unpack these files and commit the raw source 28 | # git has its own built in compression methods 29 | *.7z 30 | *.dmg 31 | *.gz 32 | *.iso 33 | *.jar 34 | *.rar 35 | *.tar 36 | *.zip 37 | 38 | # Logs and databases # 39 | ###################### 40 | *.log 41 | *.sql 42 | *.sqlite 43 | logs/* 44 | 45 | # OS generated files # 46 | ###################### 47 | ._* 48 | .ansible 49 | .tox 50 | *.egg-info 51 | .eggs 52 | 53 | # Generated by pbr while building docs 54 | ###################################### 55 | AUTHORS 56 | ChangeLog 57 | 58 | # Files created by releasenotes build 59 | releasenotes/build 60 | 61 | # Test temp files 62 | tests/common 63 | tests/*.retry 64 | 65 | # Vagrant artifacts 66 | .vagrant 67 | 68 | # Git clones 69 | openstack-ansible-ops 70 | previous 71 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/openstack-ansible-haproxy_server.git 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | The source repository for this project can be found at: 2 | 3 | https://opendev.org/openstack/openstack-ansible-haproxy_server 4 | 5 | Pull requests submitted through GitHub are not monitored. 6 | 7 | To start contributing to OpenStack, follow the steps in the contribution guide 8 | to set up and use Gerrit: 9 | 10 | https://docs.openstack.org/contributors/code-and-documentation/quick-start.html 11 | 12 | Bugs should be filed on Launchpad: 13 | 14 | https://bugs.launchpad.net/openstack-ansible 15 | 16 | For more specific information about contributing to this repository, see the 17 | openstack-ansible contributor guide: 18 | 19 | https://docs.openstack.org/openstack-ansible/latest/contributor/contributing.html 20 | -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | OpenStack-Ansible HAProxy server 3 | ================================ 4 | 5 | This Ansible role install the HAProxy Load Balancer service. 6 | 7 | Documentation for the project can be found at: 8 | https://docs.openstack.org/openstack-ansible-haproxy_server/latest 9 | 10 | Release notes for the project can be found at: 11 | https://docs.openstack.org/releasenotes/openstack-ansible-haproxy_server/ 12 | 13 | The project source code repository is located at: 14 | https://opendev.org/openstack/openstack-ansible-haproxy_server/ 15 | 16 | The project home is at: 17 | https://launchpad.net/openstack-ansible 18 | 19 | The project bug tracker is located at: 20 | https://bugs.launchpad.net/openstack-ansible 21 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # Note: 2 | # This file is maintained in the openstack-ansible-tests repository. 3 | # https://opendev.org/openstack/openstack-ansible-tests/src/Vagrantfile 4 | # 5 | # If you need to perform any change on it, you should modify the central file, 6 | # then, an OpenStack CI job will propagate your changes to every OSA repository 7 | # since every repo uses the same Vagrantfile 8 | 9 | # Verify whether required plugins are installed. 10 | required_plugins = [ "vagrant-disksize" ] 11 | required_plugins.each do |plugin| 12 | if not Vagrant.has_plugin?(plugin) 13 | raise "The vagrant plugin #{plugin} is required. Please run `vagrant plugin install #{plugin}`" 14 | end 15 | end 16 | 17 | Vagrant.configure(2) do |config| 18 | config.vm.provider "virtualbox" do |v| 19 | v.memory = 6144 20 | v.cpus = 2 21 | # https://github.com/hashicorp/vagrant/issues/9524 22 | v.customize ["modifyvm", :id, "--audio", "none"] 23 | end 24 | 25 | config.vm.synced_folder ".", "/vagrant", type: "rsync" 26 | 27 | config.vm.provision "shell", 28 | privileged: false, 29 | inline: <<-SHELL 30 | cd /vagrant 31 | ./run_tests.sh 32 | SHELL 33 | 34 | config.vm.define "centos8" do |centos8| 35 | centos8.vm.box = "centos/8" 36 | end 37 | 38 | config.vm.define "debian10" do |debian10| 39 | debian10.vm.box = "debian/buster64" 40 | end 41 | 42 | config.vm.define "ubuntu2004" do |focal| 43 | focal.disksize.size = "40GB" 44 | focal.vm.box = "ubuntu/focal64" 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | # This file facilitates OpenStack-CI package installation 2 | # before the execution of any tests. 3 | # 4 | # See the following for details: 5 | # - https://docs.openstack.org/infra/bindep/ 6 | # - https://opendev.org/openstack-infra/bindep 7 | # 8 | # Even if the role does not make use of this facility, it 9 | # is better to have this file empty, otherwise OpenStack-CI 10 | # will fall back to installing its default packages which 11 | # will potentially be detrimental to the tests executed. 12 | # 13 | # Note: 14 | # This file is maintained in the openstack-ansible-tests repository. 15 | # https://opendev.org/openstack/openstack-ansible-tests/src/bindep.txt 16 | # If you need to remove or add extra dependencies, you should modify 17 | # the central file instead and once your change is accepted then update 18 | # this file as well. The purpose of this file is to ensure that Python and 19 | # Ansible have all their necessary binary requirements on the test host before 20 | # tox executes. Any binary requirements needed by services/roles should be 21 | # installed by those roles in their applicable package install tasks, not through 22 | # using this file. 23 | # 24 | 25 | # The gcc compiler 26 | gcc 27 | 28 | # Base requirements for Ubuntu 29 | git-core [platform:dpkg] 30 | libssl-dev [platform:dpkg] 31 | libffi-dev [platform:dpkg] 32 | python3 [platform:dpkg] 33 | python3-apt [platform:dpkg] 34 | python3-dev [platform:dpkg] 35 | 36 | # Base requirements for RPM distros 37 | gcc-c++ [platform:rpm] 38 | git [platform:rpm] 39 | libffi-devel [platform:rpm] 40 | openssl-devel [platform:rpm] 41 | python3-dnf [platform:fedora] 42 | python3-devel [platform:rpm] 43 | 44 | # For SELinux 45 | libselinux-python3 [platform:redhat] 46 | libsemanage-python3 [platform:redhat] 47 | iptables [platform:redhat] 48 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # Validate Certificates when downloading hatop. May be set to "no" when proxy server 17 | # is intercepting the certificates. 18 | haproxy_hatop_download_validate_certs: true 19 | 20 | # Set the package install state for distribution packages 21 | # Options are 'present' and 'latest' 22 | haproxy_package_state: "latest" 23 | 24 | ## Haproxy Configuration 25 | haproxy_rise: 3 26 | haproxy_fall: 3 27 | haproxy_interval: 12000 28 | 29 | ## Haproxy Stats 30 | haproxy_stats_enabled: false 31 | haproxy_stats_bind_address: 127.0.0.1 32 | haproxy_stats_port: 1936 33 | haproxy_stats_ssl: "{{ haproxy_ssl }}" 34 | # haproxy_stats_ssl_cert_path: "{{ haproxy_ssl_cert_path }}/somecustomstatscert.pem" 35 | # haproxy_stats_ssl_client_cert_ca: "{{ haproxy_ssl_cert_path }}/somecustomrootca.pem" 36 | haproxy_username: admin 37 | haproxy_stats_password: secrete 38 | haproxy_stats_refresh_interval: 60 39 | # Prometheus stats are supported from HAProxy v2 40 | # Stats must be enabled above before this can be used 41 | haproxy_stats_prometheus_enabled: false 42 | 43 | # Default haproxy backup nodes to empty list so this doesn't have to be 44 | # defined for each service. 45 | haproxy_backup_nodes: [] 46 | 47 | # Configuration lines to write directly into all frontends 48 | haproxy_frontend_extra_raw: [] 49 | haproxy_frontend_redirect_extra_raw: "{{ haproxy_frontend_extra_raw }}" 50 | 51 | # Default values for enabling HTTP/2 support 52 | # Note, that while HTTP/2 will be enabled on frontends that are covered with TLS, 53 | # backends can be configured to use HTTP/2 regardless of TLS. 54 | haproxy_frontend_h2: true 55 | haproxy_backend_h2: false 56 | 57 | haproxy_service_configs: [] 58 | # Example: 59 | # haproxy_service_configs: 60 | # - haproxy_service_name: haproxy_all 61 | # haproxy_backend_nodes: "{{ groups['haproxy_all'][0] }}" 62 | # # haproxy_backup_nodes: "{{ groups['haproxy_all'][1:] }}" 63 | # haproxy_port: 80 64 | # haproxy_balance_type: http 65 | # haproxy_backend_options: 66 | # - "forwardfor" 67 | # - "httpchk" 68 | # - "httplog" 69 | # haproxy_backend_server_options: 70 | # - "inter 3000" # a contrived example, there are many server config options possible 71 | # haproxy_acls: 72 | # allow_list: 73 | # rule: "src 127.0.0.1/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8" 74 | # backend_name: "mybackend" 75 | # haproxy_frontend_h2: True 76 | # haproxy_backend_h2: False 77 | # haproxy_frontend_acls: 78 | # letsencrypt-acl: 79 | # rule: "path_beg /.well-known/acme-challenge/" 80 | # backend_name: letsencrypt 81 | # haproxy_stick_table: 82 | # - "stick-table type ipv6 size 256k expire 10s store http_err_rate(10s)" 83 | # - "http-request track-sc0 src" 84 | # - "http-request deny deny_status 429 if { sc_http_err_rate(0) gt 20 } !{ src 10.0.0.0/8 } !{ src 172.16.0.0/12 } !{ src 192.168.0.0/16 }" 85 | # # https://www.haproxy.com/blog/haproxy-exposes-a-prometheus-metrics-endpoint/ 86 | # - haproxy_service_name: prometheus-metrics 87 | # haproxy_port: 8404 88 | # haproxy_bind: 89 | # - '127.0.0.1' 90 | # haproxy_allowlist_networks: "{{ haproxy_allowlist_networks }}" 91 | # haproxy_frontend_only: True 92 | # haproxy_balance_type: "http" 93 | # haproxy_frontend_raw: 94 | # - 'http-request use-service prometheus-exporter if { path /metrics }' 95 | # haproxy_service_enabled: True 96 | 97 | # HAProxy maps (unrelated keys are omitted but are required as the previous service example) 98 | # Example: 99 | # haproxy_service_configs: 100 | # - state: present # state 'absent' will remove map entries defined in this service 101 | # haproxy_service_enabled: true # haproxy_service_enabled 'false' will remove map entries defined in this service 102 | # haproxy_service_name: "one" 103 | # haproxy_maps: 104 | # - 'use_backend %[req.hdr(host),lower,map(/etc/haproxy/route.map)]' 105 | # haproxy_map_entries: 106 | # - name: 'route' # this service contributes entries to the map called 'route' 107 | # order: 10 # prefix the name of the map fragment wih this string to control ordering of the assembled map 108 | # entries: 109 | # - compute.example.com nova-api 110 | # - dashboard.example.com horizon 111 | # - haproxy_service_name: "two" 112 | # - haproxy_service_name: "three" 113 | # haproxy_map_entries: 114 | # - name: 'route' # this service contributes to the map called 'route' 115 | # entries: 116 | # - s3.example.com radosgw 117 | # - sso.example.com keycloak 118 | # - name: 'rate' # and also to the map called 'rate' 119 | # state: present # individual map entries can be removed with state 'absent' 120 | # entries: 121 | # - /api/foo 20 122 | # - /api/bar 40 123 | # 124 | # Results: 125 | # 126 | # /etc/haproxy/route.map 127 | # s3.example.com radosgw 128 | # sso.example.com keycloak 129 | # compute.example.com nova-api 130 | # dashboard.example.com horizon 131 | # 132 | # /etc/haproxy/rate.map 133 | # /api/foo 20 134 | # /api/bar 40 135 | 136 | galera_monitoring_user: monitoring 137 | haproxy_bind_on_non_local: false 138 | 139 | ## haproxy SSL 140 | haproxy_ssl: true 141 | haproxy_ssl_all_vips: false 142 | haproxy_ssl_dh_param: 2048 143 | haproxy_ssl_cert_path: /etc/haproxy/ssl 144 | haproxy_ssl_temp_path: "{{ haproxy_ssl_cert_path }}" 145 | haproxy_ssl_bind_options: "ssl-min-ver TLSv1.2 prefer-client-ciphers" 146 | haproxy_ssl_server_options: "ssl-min-ver TLSv1.2" 147 | # TLS v1.2 and below 148 | haproxy_ssl_cipher_suite_tls12: >- 149 | {{ haproxy_ssl_cipher_suite | default(ssl_cipher_suite_tls12 | default('ECDH+AESGCM:ECDH+CHACHA20:ECDH+AES256:ECDH+AES128:!aNULL:!SHA1:!AESCCM')) }} 150 | # TLS v1.3 151 | haproxy_ssl_cipher_suite_tls13: "{{ ssl_cipher_suite_tls13 | default('TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256') }}" 152 | 153 | # haproxy self signed certificate 154 | 155 | # Storage location for SSL certificate authority 156 | haproxy_pki_dir: "{{ openstack_pki_dir | default('/etc/pki/haproxy-ca') }}" 157 | 158 | # Delegated host for operating the certificate authority 159 | haproxy_pki_setup_host: "{{ openstack_pki_setup_host | default('localhost') }}" 160 | 161 | # Create a certificate authority if one does not already exist 162 | haproxy_pki_create_ca: "{{ openstack_pki_authorities is not defined | bool }}" 163 | haproxy_pki_regen_ca: "" 164 | haproxy_pki_authorities: 165 | - name: "HAProxyRoot" 166 | country: "GB" 167 | state_or_province_name: "England" 168 | organization_name: "Example Corporation" 169 | organizational_unit_name: "IT Security" 170 | cn: "HAProxy Root CA" 171 | provider: selfsigned 172 | basic_constraints: "CA:TRUE" 173 | key_usage: 174 | - digitalSignature 175 | - cRLSign 176 | - keyCertSign 177 | not_after: "+3650d" 178 | - name: "HAProxyIntermediate" 179 | country: "GB" 180 | state_or_province_name: "England" 181 | organization_name: "Example Corporation" 182 | organizational_unit_name: "IT Security" 183 | cn: "HAProxy Intermediate CA" 184 | provider: ownca 185 | basic_constraints: "CA:TRUE,pathlen:0" 186 | key_usage: 187 | - digitalSignature 188 | - cRLSign 189 | - keyCertSign 190 | not_after: "+3650d" 191 | signed_by: "HAProxyRoot" 192 | 193 | # Installation details for certificate authorities 194 | haproxy_pki_install_ca: 195 | - name: "HAProxyRoot" 196 | condition: "{{ haproxy_pki_create_ca }}" 197 | 198 | # HAProxy server certificate 199 | haproxy_pki_keys_path: "{{ haproxy_pki_dir ~ '/certs/private/' }}" 200 | haproxy_pki_certs_path: "{{ haproxy_pki_dir ~ '/certs/certs/' }}" 201 | haproxy_pki_intermediate_cert_name: "{{ openstack_pki_service_intermediate_cert_name | default('HAProxyIntermediate') }}" 202 | haproxy_pki_intermediate_cert_path: >- 203 | {{ haproxy_pki_dir ~ '/roots/' ~ haproxy_pki_intermediate_cert_name ~ '/certs/' ~ haproxy_pki_intermediate_cert_name ~ '.crt' }} 204 | haproxy_pki_regen_cert: "" 205 | haproxy_pki_certificates: "{{ _haproxy_pki_certificates }}" 206 | 207 | # SSL certificate creation 208 | haproxy_pki_create_certificates: "{{ haproxy_user_ssl_cert is not defined and haproxy_user_ssl_key is not defined }}" 209 | 210 | # Installation details for SSL certificates 211 | haproxy_pki_install_certificates: "{{ _haproxy_pki_install_certificates }}" 212 | 213 | # activate letsencrypt option 214 | haproxy_ssl_letsencrypt_enable: false 215 | haproxy_ssl_letsencrypt_certbot_binary: "certbot" 216 | haproxy_ssl_letsencrypt_certbot_backend_port: 8888 217 | haproxy_ssl_letsencrypt_pre_hook_timeout: 5 218 | haproxy_ssl_letsencrypt_certbot_bind_address: "{{ ansible_host }}" 219 | haproxy_ssl_letsencrypt_certbot_challenge: "http-01" 220 | haproxy_ssl_letsencrypt_email: "example@example.com" 221 | haproxy_ssl_letsencrypt_config_path: "/etc/letsencrypt/live" 222 | haproxy_ssl_letsencrypt_setup_extra_params: "" 223 | haproxy_ssl_letsencrypt_acl: 224 | letsencrypt-acl: 225 | rule: "path_beg /.well-known/acme-challenge/" 226 | backend_name: letsencrypt 227 | # Use alternative CA that supports ACME, can be a public or private CA 228 | # haproxy_ssl_letsencrypt_certbot_server: "https://acme-staging-v02.api.letsencrypt.org/directory" 229 | haproxy_ssl_letsencrypt_domains: 230 | - "{{ external_lb_vip_address }}" 231 | 232 | # hatop extra package URL and checksum 233 | haproxy_hatop_download_url: "https://github.com/jhunt/hatop/archive/refs/tags/v0.8.2.tar.gz" 234 | haproxy_hatop_download_checksum: "sha256:7fac1f593f92b939cfce34175b593e43862eee8e25db251d03a910b37721fc5d" 235 | 236 | # Install hatop 237 | haproxy_hatop_install: true 238 | 239 | # The location where the extra packages are downloaded to 240 | haproxy_hatop_download_path: "/opt/cache/files" 241 | 242 | ## haproxy default 243 | # Set the number of retries to perform on a server after a connection failure 244 | haproxy_retries: "3" 245 | # Set the maximum inactivity time on the client side 246 | haproxy_client_timeout: "50s" 247 | # Set the maximum time to wait for a connection attempt to a server to succeed 248 | haproxy_connect_timeout: "10s" 249 | # Set the maximum allowed time to wait for a complete HTTP request 250 | haproxy_http_request_timeout: "5s" 251 | # Set the maximum inactivity time on the server side 252 | haproxy_server_timeout: "50s" 253 | # Set the HTTP keepalive mode to use 254 | # Disable persistent connections by default because they can cause issues when the server side closes the connection 255 | # at the same time a request is sent. 256 | haproxy_keepalive_mode: "httpclose" 257 | 258 | ## haproxy tuning params 259 | haproxy_maxconn: 4096 260 | 261 | # Parameters below should only be specified if necessary, defaults are programmed in the template 262 | # haproxy_tuning_params: 263 | # tune.bufsize: 384000 264 | # tune.chksize: 16384 265 | # tune.comp_maxlevel: 1 266 | # tune.http_maxhdr: 101 267 | # tune.maxaccept: 64 268 | # tune.ssl_cachesize: 20000 269 | # tune.ssl_lifetime: 300 270 | haproxy_tuning_params: {} 271 | 272 | # Add extra VIPs to all services 273 | extra_lb_vip_addresses: [] 274 | 275 | # Add extra TLS VIPs to all services 276 | extra_lb_tls_vip_addresses: [] 277 | 278 | # Option to override which address haproxy binds to for external vip. 279 | haproxy_bind_external_lb_vip_address: "{{ external_lb_vip_address }}" 280 | 281 | # Option to override which address haproxy binds to for internal vip. 282 | haproxy_bind_internal_lb_vip_address: "{{ internal_lb_vip_address }}" 283 | 284 | # Option to define if you need haproxy to bind on specific interface. 285 | haproxy_bind_external_lb_vip_interface: 286 | haproxy_bind_internal_lb_vip_interface: 287 | 288 | # Option to override haproxy frontend binds 289 | # Example: 290 | # haproxy_vip_binds: 291 | # - address: '*' 292 | # interface: bond0 293 | # type: external 294 | # - address: '192.168.0.10' 295 | # pki_san_records: 296 | # - internal.cloud 297 | haproxy_vip_binds: "{{ haproxy_tls_vip_binds | default(_haproxy_vip_binds) }}" 298 | 299 | # Make the log socket available to the chrooted filesystem 300 | haproxy_log_socket: "/dev/log" 301 | haproxy_log_mount_point: "/var/lib/haproxy/dev/log" 302 | 303 | # Ansible group name which should be used for distrtibuting self signed SSL Certificates 304 | haproxy_ansible_group_name: haproxy_all 305 | 306 | ## security.txt 307 | # When security risks in web services are discovered by independent security 308 | # researchers who understand the severity of the risk, they often lack the 309 | # channels to disclose them properly. As a result, security issues may be 310 | # left unreported. security.txt defines a standard to help organizations 311 | # define the process for security researchers to disclose security 312 | # vulnerabilities securely. For more information see https://securitytxt.org/ 313 | # This content will be hosted at /security.txt and /.well-known/security.txt 314 | haproxy_security_txt_dir: "/etc/haproxy" 315 | haproxy_security_txt_headers: | 316 | HTTP/1.0 200 OK 317 | Cache-Control: no-cache 318 | Connection: close 319 | Content-Type: text/plain; charset=utf-8 320 | 321 | haproxy_security_txt_content: "" 322 | # haproxy_security_txt_content: | 323 | # # Please see https://securitytxt.org/ for details of the specification of this file 324 | 325 | # Allows to copy any static file to the destination hosts 326 | haproxy_static_files_default: 327 | - dest: "{{ haproxy_security_txt_dir }}/security.txt" 328 | content: "{{ haproxy_security_txt_headers + '\n' + haproxy_security_txt_content }}" 329 | condition: "{{ haproxy_security_txt_content is truthy }}" 330 | haproxy_static_files_extra: [] 331 | haproxy_static_files: "{{ haproxy_static_files_default + haproxy_static_files_extra }}" 332 | 333 | haproxy_sysctl_file: "{{ openstack_sysctl_file | default('/etc/sysctl.conf') }}" 334 | 335 | # Allows to define custom errorfiles in the format: 336 | # - code: 504 337 | # path: /path/to/504.http 338 | # You can use haproxy_static_files_extra to add those files. 339 | # See https://github.com/haproxy/haproxy/tree/master/examples/errorfiles for examples 340 | # 341 | # An example combination of haproxy_static_files_extra and haproxy_errorfiles: 342 | # haproxy_static_files_extra: 343 | # - dest: /etc/haproxy/500.http 344 | # content: | 345 | # HTTP/1.0 500 Internal Server Error 346 | # Cache-Control: no-cache 347 | # Connection: close 348 | # Content-Type: text/html 349 | # 350 | #

500 Internal Server Error

351 | # This Server Made a Boo Boo 352 | # 353 | # - dest: /etc/haproxy/504.http 354 | # content: "{{ lookup('file', '/etc/openstack_deploy/haproxy/504.http') }}" 355 | # 356 | # haproxy_errorfiles: 357 | # - code: 500 358 | # path: /etc/haproxy/500.http 359 | # - code: 504 360 | # path: /etc/haproxy/504.http 361 | # 362 | haproxy_errorfiles: [] 363 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/openstack-ansible-haproxy_server.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/openstack-ansible-haproxy_server.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/openstack-ansible-haproxy_server" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/openstack-ansible-haproxy_server" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | 194 | livehtml: html 195 | sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 196 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | # WARNING: 6 | # This file is maintained in the openstack-ansible-tests repository. 7 | # https://opendev.org/openstack/openstack-ansible-tests/src/branch/master/sync/doc/requirements.txt 8 | # If you need to modify this file, update the one in the 9 | # openstack-ansible-tests repository. Once it merges there, the changes will 10 | # automatically be proposed to all the repositories which use it. 11 | 12 | sphinx>=2.0.0,!=2.1.0 # BSD 13 | sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD 14 | openstackdocstheme>=2.2.1 # Apache-2.0 15 | reno>=3.1.0 # Apache-2.0 16 | doc8>=0.6.0 # Apache-2.0 17 | -------------------------------------------------------------------------------- /doc/source/_static/ .gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/openstack-ansible-haproxy_server/abf058c556f223f2102ab2491f2bb7b9e6f048df/doc/source/_static/ .gitkeep -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is execfile()d with the current directory set to its 17 | # containing dir. 18 | # 19 | # Note that not all possible configuration values are present in this 20 | # autogenerated file. 21 | # 22 | # All configuration values have a default; values that are commented out 23 | # serve to show the default. 24 | 25 | import openstackdocstheme 26 | 27 | # If extensions (or modules to document with autodoc) are in another directory, 28 | # add these directories to sys.path here. If the directory is relative to the 29 | # documentation root, use os.path.abspath to make it absolute, like shown here. 30 | # sys.path.insert(0, os.path.abspath('.')) 31 | 32 | # -- General configuration ------------------------------------------------ 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | 'openstackdocstheme', 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.extlinks', 44 | 'sphinxcontrib.rsvgconverter', 45 | ] 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ['_templates'] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # source_suffix = ['.rst', '.md'] 53 | source_suffix = '.rst' 54 | 55 | # The encoding of source files. 56 | # source_encoding = 'utf-8-sig' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # General information about the project. 62 | author = 'OpenStack-Ansible Contributors' 63 | category = 'Miscellaneous' 64 | copyright = '2014-2016, OpenStack-Ansible Contributors' 65 | description = 'OpenStack-Ansible deploys OpenStack environments using Ansible.' 66 | project = 'OpenStack-Ansible' 67 | role_name = 'haproxy_server' 68 | target_name = 'openstack-ansible-' + role_name 69 | title = 'OpenStack-Ansible Documentation: ' + role_name + ' role' 70 | 71 | # References variable for substitutions 72 | current_series = openstackdocstheme.ext._get_series_name() 73 | dev_docs_prefix = "https://docs.openstack.org/openstack-ansible/{}/%s".format( 74 | current_series 75 | ) 76 | 77 | # Format: Reference name: (string containing %s for substitution, linkname) 78 | extlinks = {'dev_docs': (dev_docs_prefix, '')} 79 | 80 | # openstackdocstheme options 81 | openstackdocs_repo_name = 'openstack/' + target_name 82 | openstackdocs_pdf_link = True 83 | openstackdocs_bug_project = project.lower() 84 | openstackdocs_bug_tag = '' 85 | 86 | # The language for content autogenerated by Sphinx. Refer to documentation 87 | # for a list of supported languages. 88 | # 89 | # This is also used if you do content translation via gettext catalogs. 90 | # Usually you set "language" from the command line for these cases. 91 | language = 'en' 92 | 93 | # There are two options for replacing |today|: either, you set today to some 94 | # non-false value, then it is used: 95 | # today = '' 96 | # Else, today_fmt is used as the format for a strftime call. 97 | # today_fmt = '%B %d, %Y' 98 | 99 | # List of patterns, relative to source directory, that match files and 100 | # directories to ignore when looking for source files. 101 | exclude_patterns = [] 102 | 103 | # The reST default role (used for this markup: `text`) to use for all 104 | # documents. 105 | # default_role = None 106 | 107 | # If true, '()' will be appended to :func: etc. cross-reference text. 108 | # add_function_parentheses = True 109 | 110 | # If true, the current module name will be prepended to all description 111 | # unit titles (such as .. function::). 112 | # add_module_names = True 113 | 114 | # If true, sectionauthor and moduleauthor directives will be shown in the 115 | # output. They are ignored by default. 116 | # show_authors = False 117 | 118 | # The name of the Pygments (syntax highlighting) style to use. 119 | pygments_style = 'native' 120 | 121 | # A list of ignored prefixes for module index sorting. 122 | # modindex_common_prefix = [] 123 | 124 | # If true, keep warnings as "system message" paragraphs in the built documents. 125 | # keep_warnings = False 126 | 127 | # If true, `todo` and `todoList` produce output, else they produce nothing. 128 | todo_include_todos = False 129 | 130 | 131 | # -- Options for HTML output ---------------------------------------------- 132 | 133 | # The theme to use for HTML and HTML Help pages. See the documentation for 134 | # a list of builtin themes. 135 | html_theme = 'openstackdocs' 136 | 137 | # Theme options are theme-specific and customize the look and feel of a theme 138 | # further. For a list of options available for each theme, see the 139 | # documentation. 140 | # html_theme_options = {} 141 | 142 | # Add any paths that contain custom themes here, relative to this directory. 143 | # html_theme_path = [] 144 | 145 | # The name for this set of Sphinx documents. If None, it defaults to 146 | # " v documentation". 147 | # html_title = None 148 | 149 | # A shorter title for the navigation bar. Default is the same as html_title. 150 | # html_short_title = None 151 | 152 | # The name of an image file (relative to this directory) to place at the top 153 | # of the sidebar. 154 | # html_logo = None 155 | 156 | # The name of an image file (within the static path) to use as favicon of the 157 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 158 | # pixels large. 159 | # html_favicon = None 160 | 161 | # Add any paths that contain custom static files (such as style sheets) here, 162 | # relative to this directory. They are copied after the builtin static files, 163 | # so a file named "default.css" will overwrite the builtin "default.css". 164 | html_static_path = ['_static'] 165 | 166 | # Add any extra paths that contain custom files (such as robots.txt or 167 | # .htaccess) here, relative to this directory. These files are copied 168 | # directly to the root of the documentation. 169 | # html_extra_path = [] 170 | 171 | # If true, SmartyPants will be used to convert quotes and dashes to 172 | # typographically correct entities. 173 | # html_use_smartypants = True 174 | 175 | # Custom sidebar templates, maps document names to template names. 176 | # html_sidebars = {} 177 | 178 | # Additional templates that should be rendered to pages, maps page names to 179 | # template names. 180 | # html_additional_pages = {} 181 | 182 | # If false, no module index is generated. 183 | # html_domain_indices = True 184 | 185 | # If false, no index is generated. 186 | # html_use_index = True 187 | 188 | # If true, the index is split into individual pages for each letter. 189 | # html_split_index = False 190 | 191 | # If true, links to the reST sources are added to the pages. 192 | # html_show_sourcelink = True 193 | 194 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 195 | # html_show_sphinx = True 196 | 197 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 198 | # html_show_copyright = True 199 | 200 | # If true, an OpenSearch description file will be output, and all pages will 201 | # contain a tag referring to it. The value of this option must be the 202 | # base URL from which the finished HTML is served. 203 | # html_use_opensearch = '' 204 | 205 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 206 | # html_file_suffix = None 207 | 208 | # Language to be used for generating the HTML full-text search index. 209 | # Sphinx supports the following languages: 210 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 211 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 212 | # html_search_language = 'en' 213 | 214 | # A dictionary with options for the search language support, empty by default. 215 | # Now only 'ja' uses this config value 216 | # html_search_options = {'type': 'default'} 217 | 218 | # The name of a javascript file (relative to the configuration directory) that 219 | # implements a search results scorer. If empty, the default will be used. 220 | # html_search_scorer = 'scorer.js' 221 | 222 | # Output file base name for HTML help builder. 223 | htmlhelp_basename = target_name + '-docs' 224 | 225 | # -- Options for LaTeX output --------------------------------------------- 226 | 227 | latex_elements = { 228 | # The paper size ('letterpaper' or 'a4paper'). 229 | # 'papersize': 'letterpaper', 230 | 231 | # The font size ('10pt', '11pt' or '12pt'). 232 | # 'pointsize': '10pt', 233 | 234 | # Additional stuff for the LaTeX preamble. 235 | # 'preamble': '', 236 | 237 | # Latex figure (float) alignment 238 | # 'figure_align': 'htbp', 239 | } 240 | 241 | # Grouping the document tree into LaTeX files. List of tuples 242 | # (source start file, target name, title, 243 | # author, documentclass [howto, manual, or own class]). 244 | latex_documents = [ 245 | (master_doc, 'doc-' + target_name + '.tex', 246 | title.replace("_", r"\_"), author, 'manual'), 247 | ] 248 | 249 | latex_use_xindy = False 250 | 251 | # The name of an image file (relative to this directory) to place at the top of 252 | # the title page. 253 | # latex_logo = None 254 | 255 | # For "manual" documents, if this is true, then toplevel headings are parts, 256 | # not chapters. 257 | # latex_use_parts = False 258 | 259 | # If true, show page references after internal links. 260 | # latex_show_pagerefs = False 261 | 262 | # If true, show URL addresses after external links. 263 | # latex_show_urls = False 264 | 265 | # Documents to append as an appendix to all manuals. 266 | # latex_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | # latex_domain_indices = True 270 | 271 | 272 | # -- Options for manual page output --------------------------------------- 273 | 274 | # One entry per manual page. List of tuples 275 | # (source start file, name, description, authors, manual section). 276 | man_pages = [ 277 | (master_doc, target_name, 278 | title, [author], 1) 279 | ] 280 | 281 | # If true, show URL addresses after external links. 282 | # man_show_urls = False 283 | 284 | 285 | # -- Options for Texinfo output ------------------------------------------- 286 | 287 | # Grouping the document tree into Texinfo files. List of tuples 288 | # (source start file, target name, title, author, 289 | # dir menu entry, description, category) 290 | texinfo_documents = [ 291 | (master_doc, target_name, 292 | title, author, project, 293 | description, category), 294 | ] 295 | 296 | # Documents to append as an appendix to all manuals. 297 | # texinfo_appendices = [] 298 | 299 | # If false, no module index is generated. 300 | # texinfo_domain_indices = True 301 | 302 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 303 | # texinfo_show_urls = 'footnote' 304 | 305 | # If true, do not generate a @detailmenu in the "Top" node's menu. 306 | # texinfo_no_detailmenu = False 307 | # -- Options for PDF output -------------------------------------------------- 308 | 309 | pdf_documents = [ 310 | (master_doc, target_name, 311 | title, author) 312 | ] 313 | 314 | locale_dirs = ['locale/'] 315 | -------------------------------------------------------------------------------- /doc/source/configure-haproxy.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Configuring HAProxy (optional) 3 | ============================== 4 | 5 | HAProxy provides load balancing services and SSL termination when hardware 6 | load balancers are not available for high availability architectures deployed 7 | by OpenStack-Ansible. The default HAProxy configuration provides highly- 8 | available load balancing services via keepalived if there is more than one 9 | host in the ``haproxy_hosts`` group. 10 | 11 | .. important:: 12 | 13 | Ensure you review the services exposed by HAProxy and limit access 14 | to these services to trusted users and networks only. For more details, 15 | refer to the :dev_docs:`Securing network access to OpenStack services ` section. 16 | 17 | .. note:: 18 | 19 | For a successful installation, you require a load balancer. You may 20 | prefer to make use of hardware load balancers instead of HAProxy. If hardware 21 | load balancers are in use, then implement the load balancing configuration for 22 | services prior to executing the deployment. 23 | 24 | To deploy HAProxy within your OpenStack-Ansible environment, define target 25 | hosts to run HAProxy: 26 | 27 | .. code-block:: yaml 28 | 29 | haproxy_hosts: 30 | infra1: 31 | ip: 172.29.236.101 32 | infra2: 33 | ip: 172.29.236.102 34 | infra3: 35 | ip: 172.29.236.103 36 | 37 | There is an example configuration file already provided in 38 | ``/etc/openstack_deploy/conf.d/haproxy.yml.example``. Rename the file to 39 | ``haproxy.yml`` and configure it with the correct target hosts to use HAProxy 40 | in an OpenStack-Ansible deployment. 41 | 42 | Making HAProxy highly-available 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 44 | 45 | If multiple hosts are found in the inventory, deploy 46 | HAProxy in a highly-available manner by installing Keepalived. 47 | 48 | To make Keepalived work, edit at least the following variables 49 | in ``user_variables.yml``: 50 | 51 | .. code-block:: yaml 52 | 53 | haproxy_keepalived_external_vip_cidr: 192.168.0.4/25 54 | haproxy_keepalived_internal_vip_cidr: 172.29.236.54/16 55 | haproxy_keepalived_external_interface: br-flat 56 | haproxy_keepalived_internal_interface: br-mgmt 57 | 58 | - ``haproxy_keepalived_internal_interface`` and 59 | ``haproxy_keepalived_external_interface`` represent the interfaces on the 60 | deployed node where the keepalived nodes bind the internal and external 61 | vip. By default, use ``br-mgmt``. 62 | 63 | - On the interface listed above, ``haproxy_keepalived_internal_vip_cidr`` and 64 | ``haproxy_keepalived_external_vip_cidr`` represent the internal and 65 | external (respectively) vips (with their prefix length). 66 | 67 | - Set additional variables to adapt Keepalived in your deployment. 68 | Refer to the ``user_variables.yml`` for more descriptions. 69 | 70 | To always deploy (or upgrade to) the latest stable version of Keepalived. 71 | Edit the ``/etc/openstack_deploy/user_variables.yml``: 72 | 73 | .. code-block:: yaml 74 | 75 | keepalived_use_latest_stable: True 76 | 77 | The HAProxy nodes have group vars applied that define the configuration 78 | of Keepalived. This configuration is stored in 79 | ``group_vars/haproxy_all/keepalived.yml``. It contains the variables 80 | needed for the Keepalived role (master and backup nodes). 81 | 82 | Keepalived pings a public and private IP address to check its status. The 83 | default address is ``193.0.14.129``. To change this default, 84 | set the ``keepalived_external_ping_address`` and 85 | ``keepalived_internal_ping_address`` variables in the 86 | ``user_variables.yml`` file. 87 | 88 | .. note:: 89 | 90 | The Keepalived test works with IPv4 addresses only. 91 | 92 | You can adapt Keepalived to your environment by either using our override 93 | mechanisms (per host with userspace ``host_vars``, per group with 94 | userspace``group_vars``, or globally using the userspace 95 | ``user_variables.yml`` file) 96 | 97 | If you wish to deploy multiple HAProxy hosts without Keepalived and 98 | provide your own means for failover between them, edit 99 | ``/etc/openstack_deploy/user_variables.yml`` to skip the deployment 100 | of Keepalived. 101 | To do this, set the following: 102 | 103 | .. code-block:: yaml 104 | 105 | haproxy_use_keepalived: False 106 | 107 | Configuring Keepalived ping checks 108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 109 | 110 | OpenStack-Ansible configures Keepalived with a check script that pings an 111 | external resource and uses that ping to determine if a node has lost network 112 | connectivity. If the pings fail, Keepalived fails over to another node and 113 | HAProxy serves requests there. 114 | 115 | The destination address, ping count and ping interval are configurable via 116 | Ansible variables in ``/etc/openstack_deploy/user_variables.yml``: 117 | 118 | .. code-block:: yaml 119 | 120 | keepalived_external_ping_address: # Public IP address to ping 121 | keepalived_internal_ping_address: # Private IP address to ping 122 | keepalived_ping_count: # ICMP packets to send (per interval) 123 | keepalived_ping_interval: # How often ICMP packets are sent 124 | 125 | By default, OpenStack-Ansible configures Keepalived to ping one of the root 126 | DNS servers operated by RIPE. You can change this IP address to a different 127 | external address or another address on your internal network. 128 | 129 | If external connectivity fails, it is important that internal services can 130 | still access an HAProxy instance. In a situation, when ping to some external 131 | host fails and internal ping is not separated, all Keepalived instances enter 132 | the fault state despite internal connectivity being still available. Separate 133 | ping check for internal and external connectivity ensures that when one 134 | instance fails the other VIP remains in operation. 135 | 136 | Securing HAProxy communication with SSL certificates 137 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 138 | 139 | The OpenStack-Ansible project provides the ability to secure HAProxy 140 | communications with self-signed or user-provided SSL certificates. By default, 141 | self-signed certificates are used with HAProxy. However, you can 142 | provide your own certificates by using the following Ansible variables: 143 | 144 | .. code-block:: yaml 145 | 146 | haproxy_user_ssl_cert: # Path to certificate 147 | haproxy_user_ssl_key: # Path to private key 148 | haproxy_user_ssl_ca_cert: # Path to CA certificate 149 | 150 | Refer to `Securing services with SSL certificates`_ for more information on 151 | these configuration options and how you can provide your own 152 | certificates and keys to use with HAProxy. User provided certificates should 153 | be folded and formatted at 64 characters long. Single line certificates 154 | will not be accepted by HAProxy and will result in SSL validation failures. 155 | Please have a look here for information on `converting your certificate to 156 | various formats `_. 157 | 158 | Using Certificates from Let’s Encrypt 159 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 160 | 161 | If you want to use Let’s Encrypt `_ 162 | you can activate the feature by providing the following configuration in 163 | ``/etc/openstack_deploy/user_variables.yml``. Note that this requires 164 | that ``external_lb_vip_address`` in 165 | ``/etc/openstack_deploy/openstack_user_config.yml`` is set to the 166 | external DNS address. 167 | 168 | The following variables must be set for the HAProxy hosts. 169 | 170 | .. code-block:: yaml 171 | 172 | haproxy_ssl_letsencrypt_enable: True 173 | haproxy_ssl_letsencrypt_email: example@example.com 174 | haproxy_interval: 2000 175 | 176 | The following variables serve as an example for how to configure a 177 | single HAProxy providing SSL termination for a service on the same 178 | host, served from 127.0.0.1:80. An additional HAProxy backend is 179 | configured which will receive the acme-challenge requests when 180 | certificates are renewed. 181 | 182 | .. code-block:: yaml 183 | 184 | haproxy_service_configs: 185 | # the external facing service which serves the apache test site, with a acl for LE requests 186 | - haproxy_service_name: test 187 | haproxy_redirect_http_port: 80 #redirect port 80 to port ssl 188 | haproxy_redirect_scheme: "https if !{ ssl_fc } !{ path_beg /.well-known/acme-challenge/ }" #redirect all non-ssl traffic to ssl except acme-challenge 189 | haproxy_port: 443 190 | haproxy_frontend_acls: #use a frontend ACL specify the backend to use for acme-challenge 191 | letsencrypt-acl: 192 | rule: "path_beg /.well-known/acme-challenge/" 193 | backend_name: letsencrypt 194 | haproxy_ssl: True 195 | haproxy_backend_nodes: #apache is running on locally on 127.0.0.1:80 serving a dummy site 196 | - name: local-test-service 197 | ip_addr: 127.0.0.1 198 | haproxy_balance_type: http 199 | haproxy_backend_port: 80 200 | haproxy_backend_options: 201 | - "httpchk HEAD /" # request to use for health check for the example service 202 | 203 | # an internal only service for acme-challenge whose backend is certbot on the haproxy host 204 | - haproxy_service_name: letsencrypt 205 | haproxy_backend_nodes: 206 | - name: localhost 207 | ip_addr: {{ ansible_host }} #certbot binds to the internal IP 208 | backend_rise: 1 #quick rise and fall time for multinode deployment to succeed 209 | backend_fall: 2 210 | haproxy_bind: 211 | - 127.0.0.1 #bind to 127.0.0.1 as the local internal address will be used by certbot 212 | haproxy_port: 8888 #certbot is configured with http-01-port to be 8888 213 | haproxy_balance_type: http 214 | 215 | 216 | It is possible to use an HA configuration of HAProxy with certificates 217 | initialised and renewed using certbot by setting haproxy_backend_nodes 218 | for the Let’s Encrypt service to include all HAProxy internal addresses. 219 | Each HAProxy instance will be checking for certbot running on its own 220 | node plus each of the others, and direct any incoming acme-challenge 221 | requests to the HAProxy instance which is performing a renewal. 222 | 223 | Domains which will be covered by Let’s Encrypt certificate are defined 224 | with ``haproxy_ssl_letsencrypt_domains`` variable, which can be set to 225 | a list. By default certificate will be issued only for 226 | ``external_lb_vip_address``. 227 | 228 | Another important aspect is defining a list of frontends, for which 229 | issued certificate will be used. 230 | By default, it is goind to be used only for VIPs with type ``external``. 231 | You can control and define type by overriding a variable ``haproxy_vip_binds``. 232 | 233 | It is necessary to configure certbot to bind to the HAProxy node local 234 | internal IP address via the haproxy_ssl_letsencrypt_certbot_bind_address 235 | variable in a H/A setup. 236 | 237 | .. _Securing services with SSL certificates: https://docs.openstack.org/openstack-ansible/latest/user/security/index.html 238 | 239 | Configuring additional services 240 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 241 | 242 | Additional HAProxy service entries can be configured by setting 243 | ``haproxy_extra_services`` in ``/etc/openstack_deploy/user_variables.yml`` 244 | 245 | For more information on the service dict syntax, please reference 246 | ``playbooks/vars/configs/haproxy_config.yml`` 247 | 248 | An example HTTP service could look like: 249 | 250 | .. code-block:: yaml 251 | 252 | haproxy_extra_services: 253 | - haproxy_service_name: extra-web-service 254 | haproxy_backend_nodes: "{{ groups['service_group'] | default([]) }}" 255 | haproxy_ssl: "{{ haproxy_ssl }}" 256 | haproxy_port: 10000 257 | haproxy_balance_type: http 258 | # If backend connections should be secured with SSL (default False) 259 | haproxy_backend_ssl: True 260 | haproxy_backend_ca: /path/to/ca/cert.pem 261 | # Or to use system CA for validation 262 | # haproxy_backend_ca: True 263 | # Or if certificate validation should be disabled 264 | # haproxy_backend_ca: False 265 | 266 | Additionally, you can specify HAProxy services that are not managed 267 | in the Ansible inventory by manually specifying their hostnames/IP Addresses: 268 | 269 | .. code-block:: yaml 270 | 271 | haproxy_extra_services: 272 | - haproxy_service_name: extra-non-inventory-service 273 | haproxy_backend_nodes: 274 | - name: nonInvHost01 275 | ip_addr: 172.0.1.1 276 | - name: nonInvHost02 277 | ip_addr: 172.0.1.2 278 | - name: nonInvHost03 279 | ip_addr: 172.0.1.3 280 | haproxy_ssl: "{{ haproxy_ssl }}" 281 | haproxy_port: 10001 282 | haproxy_balance_type: http 283 | 284 | Adding additional global VIP addresses 285 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 286 | 287 | In some cases, you might need to add additional internal VIP addresses 288 | to the load balancer front end. You can use the HAProxy role to add 289 | additional VIPs to all front ends by setting them in the 290 | ``extra_lb_vip_addresses`` or ``extra_lb_tls_vip_addresses`` variables. 291 | 292 | The following example shows extra VIP addresses defined in the 293 | ``user_variables.yml`` file: 294 | 295 | .. code-block:: yaml 296 | 297 | extra_lb_vip_addresses: 298 | - 10.0.0.10 299 | - 192.168.0.10 300 | 301 | The following example shows extra VIP addresses with TLS enabled 302 | defined in the ``user_variables.yml`` file: 303 | 304 | .. code-block:: yaml 305 | 306 | extra_lb_tls_vip_addresses: 307 | - 10.0.0.10 308 | - 192.168.0.10 309 | 310 | Controlling HAProxy front-end binding 311 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 312 | 313 | HAProxy frontend can bind either to some specific IP (VIP) address or 314 | ethernet interface. A variable which controls this behaviour is 315 | ``haproxy_vip_binds``. It is used for the service, unless ``haproxy_bind`` 316 | is defined on the service level. In that case ``service.haproxy_bind`` 317 | has prescedence over ``haproxy_vip_binds``. 318 | 319 | ``haproxy_vip_binds`` is generated by the role from other "convenience" 320 | variables, like 321 | `haproxy_bind_external_lb_vip_address`, 322 | `haproxy_bind_external_lb_vip_interface`, 323 | `haproxy_bind_internal_lb_vip_address` and 324 | `haproxy_bind_internal_lb_vip_interface`. 325 | 326 | Though you still can override ``haproxy_vip_binds`` to fine-control 327 | the binding process of HAProxy instance. 328 | 329 | Overriding the address HAProxy will bind to 330 | ------------------------------------------- 331 | 332 | In some cases you may want to override the default of having HAProxy 333 | bind to the addresses specified in ``external_lb_vip_address`` and 334 | ``internal_lb_vip_address``. For example if those are hostnames and you 335 | want HAProxy to bind to IP addresses while preserving the names for TLS- 336 | certificates and endpoint URIs. 337 | 338 | This can be set in the ``user_variables.yml`` file: 339 | 340 | .. code-block:: yaml 341 | 342 | haproxy_bind_external_lb_vip_address: 10.0.0.10 343 | haproxy_bind_internal_lb_vip_address: 192.168.0.10 344 | 345 | Binding HAProxy to interface 346 | ---------------------------- 347 | 348 | In some cases it might be more convenient to bind HAProxy to the interface 349 | rather then a specific IP address. For example, this is handy if you decide 350 | to balance load between HAProxy instances using DNS RR, where each HAProxy 351 | will have it's own VIP which will failover to others. 352 | 353 | Binding to the interface can be set by providing following variables 354 | in the ``user_variables.yml`` file: 355 | 356 | .. code-block:: yaml 357 | 358 | haproxy_bind_external_lb_vip_address: "*" 359 | haproxy_bind_internal_lb_vip_address: "*" 360 | haproxy_bind_external_lb_vip_interface: bond0 361 | haproxy_bind_internal_lb_vip_interface: br-mgmt 362 | 363 | Adding Access Control Lists to HAProxy front end 364 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 365 | 366 | Adding ACL rules in HAProxy is easy. You just need to define haproxy_acls and 367 | add the rules in the variable 368 | 369 | Here is an example that shows how to achieve the goal 370 | 371 | .. code-block:: yaml 372 | 373 | 374 | - haproxy_service_name: influxdb-relay 375 | haproxy_acls: 376 | write_queries: 377 | rule: "path_sub -i write" 378 | read_queries: 379 | rule: "path_sub -i query" 380 | backend_name: "influxdb" 381 | 382 | This will add two acl rules ``path_sub -i write`` and ``path_sub -i query`` to 383 | the front end and use the backend specified in the rule. If no backend is specified 384 | it will use a default ``haproxy_service_name`` backend. 385 | 386 | If a frontend service directs to multiple backend services using ACLs, and a 387 | backend service does not require its own corresponding front-end, the 388 | `haproxy_backend_only` option can be specified: 389 | 390 | .. code-block:: yaml 391 | 392 | - haproxy_service_name: influxdb 393 | haproxy_backend_only: true # Directed by the 'influxdb-relay' service above 394 | haproxy_backend_nodes: 395 | - name: influxdb-service 396 | ip_addr: 10.100.10.10 397 | 398 | Adding prometheus metrics to HAProxy 399 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 400 | 401 | Since HAProxy 2.0 it's possible to exposes prometheus metrics. 402 | https://www.haproxy.com/blog/haproxy-exposes-a-prometheus-metrics-endpoint 403 | if you need to create a frontend for it you can use the `haproxy_frontend_only` 404 | option: 405 | 406 | .. code-block:: yaml 407 | 408 | - haproxy_service_name: prometheus-metrics 409 | haproxy_port: 8404 410 | haproxy_bind: 411 | - '127.0.0.1' 412 | haproxy_whitelist_networks: "{{ haproxy_whitelist_networks }}" 413 | haproxy_frontend_only: True 414 | haproxy_frontend_raw: 415 | - 'http-request use-service prometheus-exporter if { path /metrics }' 416 | haproxy_service_enabled: True 417 | haproxy_balance_type: 'http' 418 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | OpenStack-Ansible HAProxy server 3 | ================================ 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | configure-haproxy.rst 9 | 10 | This Ansible role installs the HAProxy Load Balancer service. 11 | 12 | To clone or view the source code for this repository, visit the role repository 13 | for `haproxy_server `_. 14 | 15 | Default variables 16 | ~~~~~~~~~~~~~~~~~ 17 | 18 | .. literalinclude:: ../../defaults/main.yml 19 | :language: yaml 20 | :start-after: under the License. 21 | 22 | Required variables 23 | ~~~~~~~~~~~~~~~~~~ 24 | 25 | None. 26 | 27 | Dependencies 28 | ~~~~~~~~~~~~ 29 | 30 | None. 31 | 32 | Example playbook 33 | ~~~~~~~~~~~~~~~~ 34 | 35 | .. literalinclude:: ../../examples/playbook.yml 36 | :language: yaml 37 | -------------------------------------------------------------------------------- /examples/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install haproxy 3 | hosts: haproxy 4 | user: root 5 | roles: 6 | - role: haproxy_server 7 | tags: 8 | - haproxy-server 9 | vars: 10 | haproxy_service_configs: 11 | - haproxy_service_name: group_name 12 | haproxy_backend_nodes: "{{ groups['group_name'][0] }}" 13 | haproxy_backup_nodes: "{{ groups['group_name'][1:] }}" 14 | haproxy_port: 80 15 | haproxy_balance_type: http 16 | haproxy_backend_options: 17 | - "forwardfor" 18 | - "httpchk" 19 | - "httplog" 20 | haproxy_backend_arguments: 21 | - "http-check expect string OK" 22 | -------------------------------------------------------------------------------- /files/haproxy.default: -------------------------------------------------------------------------------- 1 | # Set ENABLED to 1 if you want the init script to start haproxy. 2 | ENABLED=1 3 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - name: Regen pem # noqa: no-changed-when 17 | ansible.builtin.shell: >- 18 | cat {{ item_base_path ~ '.crt' }} $(test -f {{ item_base_path ~ '-ca.crt' }} && 19 | echo {{ item_base_path ~ '-ca.crt' }}) {{ item_base_path ~ '.key' }} > {{ target_base_path ~ '.pem' }} 20 | vars: 21 | item_interface: "{{ item['interface'] | default('') }}" 22 | item_name: "{{ ('interface' in item and item['interface'] is truthy) | ternary(item['address'] ~ '-' ~ item_interface, item['address']) }}" 23 | item_base_path: "{{ haproxy_ssl_temp_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ item_name }}" 24 | target_base_path: "{{ haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ item_name }}" 25 | with_items: "{{ haproxy_vip_binds }}" 26 | listen: 27 | - haproxy cert installed 28 | 29 | - name: Regenerate maps 30 | vars: 31 | all_changed_results: "{{ (map_create.results + map_delete.results) | select('changed') }}" 32 | ansible.builtin.assemble: 33 | src: "/etc/haproxy/map.conf.d/{{ item }}" 34 | dest: "/etc/haproxy/{{ item }}.map" 35 | mode: "0640" 36 | owner: haproxy 37 | group: haproxy 38 | with_items: "{{ all_changed_results | map(attribute='item') | flatten | selectattr('name', 'defined') | map(attribute='name') | unique }}" 39 | 40 | - name: Regenerate haproxy configuration 41 | ansible.builtin.assemble: 42 | src: "/etc/haproxy/conf.d" 43 | dest: "/etc/haproxy/haproxy.cfg" 44 | validate: /usr/sbin/haproxy -c -f %s 45 | mode: "0640" 46 | owner: haproxy 47 | group: haproxy 48 | tags: 49 | - haproxy-general-config 50 | 51 | - name: Get package facts 52 | ansible.builtin.package_facts: 53 | manager: auto 54 | listen: Restart rsyslog 55 | 56 | - name: Restart rsyslog 57 | ansible.builtin.service: 58 | name: "rsyslog" 59 | state: "restarted" 60 | enabled: true 61 | daemon_reload: true 62 | when: 63 | - "'rsyslog' in ansible_facts.packages" 64 | 65 | - name: Reload haproxy 66 | ansible.builtin.service: 67 | name: "haproxy" 68 | state: "reloaded" 69 | enabled: true 70 | daemon_reload: true 71 | listen: 72 | - Regen pem 73 | - Regenerate maps 74 | - Regenerate haproxy configuration 75 | -------------------------------------------------------------------------------- /manual-test.rc: -------------------------------------------------------------------------------- 1 | export VIRTUAL_ENV=$(pwd) 2 | export ANSIBLE_HOST_KEY_CHECKING=False 3 | export ANSIBLE_SSH_CONTROL_PATH=/tmp/%%h-%%r 4 | 5 | # TODO (odyssey4me) These are only here as they are non-standard folder 6 | # names for Ansible 1.9.x. We are using the standard folder names for 7 | # Ansible v2.x. We can remove this when we move to Ansible 2.x. 8 | export ANSIBLE_ACTION_PLUGINS=${HOME}/.ansible/plugins/action 9 | export ANSIBLE_CALLBACK_PLUGINS=${HOME}/.ansible/plugins/callback 10 | export ANSIBLE_FILTER_PLUGINS=${HOME}/.ansible/plugins/filter 11 | export ANSIBLE_LOOKUP_PLUGINS=${HOME}/.ansible/plugins/lookup 12 | 13 | # This is required as the default is the current path or a path specified 14 | # in ansible.cfg 15 | export ANSIBLE_LIBRARY=${HOME}/.ansible/plugins/library 16 | 17 | # This is required as the default is '/etc/ansible/roles' or a path 18 | # specified in ansible.cfg 19 | export ANSIBLE_ROLES_PATH=${HOME}/.ansible/roles:$(pwd)/.. 20 | 21 | export ANSIBLE_SSH_ARGS="-o ControlMaster=no \ 22 | -o UserKnownHostsFile=/dev/null \ 23 | -o StrictHostKeyChecking=no \ 24 | -o ServerAliveInterval=64 \ 25 | -o ServerAliveCountMax=1024 \ 26 | -o Compression=no \ 27 | -o TCPKeepAlive=yes \ 28 | -o VerifyHostKeyDNS=no \ 29 | -o ForwardX11=no \ 30 | -o ForwardAgent=yes" 31 | 32 | echo "Run manual functional tests by executing the following:" 33 | echo "# ./.tox/functional/bin/ansible-playbook -i tests/inventory tests/test.yml" 34 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | galaxy_info: 17 | author: rcbops 18 | description: Installation and setup of HAProxy 19 | role_name: haproxy_server 20 | namespace: openstack 21 | company: Rackspace 22 | license: Apache2 23 | min_ansible_version: "2.10" 24 | platforms: 25 | - name: Debian 26 | versions: 27 | - bullseye 28 | - name: Ubuntu 29 | versions: 30 | - focal 31 | - jammy 32 | - name: EL 33 | versions: 34 | - "9" 35 | galaxy_tags: 36 | - cloud 37 | - python 38 | - development 39 | - openstack 40 | dependencies: [] 41 | -------------------------------------------------------------------------------- /meta/openstack-ansible.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2017, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # (c) 2017, Jean-Philippe Evrard 17 | 18 | maturity_info: 19 | status: incubated 20 | created_during: newton 21 | -------------------------------------------------------------------------------- /releasenotes/notes/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/openstack-ansible-haproxy_server/abf058c556f223f2102ab2491f2bb7b9e6f048df/releasenotes/notes/.placeholder -------------------------------------------------------------------------------- /releasenotes/notes/backend_config_per_host-14cec3ec5f708934.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - HAProxy services that use backend nodes that are not 4 | in the Ansible inventory can now have the ``backend_port`` 5 | specified in the list, along with ``name`` or ``ip_addr`` settings. 6 | This allow to have the service bound to different port on different 7 | backend servers. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/bind_interface-8f7a123d4ab1219a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | features: 4 | - | 5 | Added variables ``haproxy_bind_external_lb_vip_interface`` and 6 | ``haproxy_bind_internal_lb_vip_interface`` that allows deployer to bind 7 | haproxy on the specific interface only. 8 | - | 9 | Added variable ``haproxy_tls_vip_binds`` that allows to fully override 10 | haproxy bindings, that are generated by the role if some assumptions are 11 | not valid for some scenarios. It is list of mappings, that include address 12 | and interface. Interface key is optional and can be ommited. 13 | -------------------------------------------------------------------------------- /releasenotes/notes/cert_per_ip-e473f853dbe4047d.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | The following variables have been deprecated and will have no effect: 5 | 6 | * ``haproxy_ssl_cert_path`` 7 | * ``haproxy_ssl_key`` 8 | * ``haproxy_ssl_pem`` 9 | * ``haproxy_ssl_ca_cert`` 10 | 11 | These variables were responsible for the path haproxy looked for 12 | certificates on the destination hosts. 13 | 14 | Variables were replaced in favor of ``haproxy_ssl_cert_path`` since the exact 15 | path to certificates will be dynamically set based on the VIP that is used 16 | for the frontend 17 | -------------------------------------------------------------------------------- /releasenotes/notes/certbot-auto-5ccf2184fb554c90.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | Certbot-auto is deprecated since 2020. 5 | It was removed from haproxy_server role. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/certbot_ha-83b56aed3f360dba.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Multiple HAProxy nodes can now be deployed in an HA configuration with 5 | keepalived and LetsEncrypt certificates. Certbot can be treated as a 6 | backend service for haproxy and acme-challenge requests from LetsEncrypt 7 | can be directed to whichever HAProxy server is running a certificate 8 | renewal. New variables are defined for frontend ACLs and options 9 | to be passed to Certbot which enable this new feature, but the majority 10 | of the required configuration is done via the existing HAProxy service 11 | setup. An example is provided in the documentation. 12 | -------------------------------------------------------------------------------- /releasenotes/notes/custom-stick-tables-1c790fe223bb0d5d.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | A new variable ``haproxy_stick_table`` can be defined to apply a 5 | customised stick-table to all backends on the loadbalancer. In addition, 6 | ``haproxy_stick_table`` can be set in each service definition to have a 7 | customised stick-table for a particular backend. 8 | upgrade: 9 | - | 10 | A default stick-table was previously applied to all backends by default 11 | but did not have any specific purpose. This is now removed, and the variable 12 | ``haproxy_stick_table`` should be used to supply a list of config lines 13 | to be applied to each backend to control stick-table functionality. 14 | -------------------------------------------------------------------------------- /releasenotes/notes/default_backend_override-9840dc75ff9d1a9c.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | A new key ``haproxy_default_backend`` can be defined for each service 5 | configured in the haproxy loadbalancer. Configuring this variable writes 6 | a value for the ``default_backend`` directive into the config for the 7 | service frontend. It can be useful to provide a specific default backend 8 | as a fall-through option when other backends are selected using ACLs, 9 | and to also allow the name of the default backend to be different from 10 | ``haproxy_service_name``. 11 | -------------------------------------------------------------------------------- /releasenotes/notes/disable-sslv3-303acdcc6b593180.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - SSLv3 is now disabled in the haproxy daemon configuration by default. 4 | -------------------------------------------------------------------------------- /releasenotes/notes/h2_initial_support-99a3277939942405.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Added new keys ``haproxy_frontend_h2`` and ``haproxy_backend_h2`` 5 | per service definition to enable HTTP/2 for a specified service. 6 | 7 | This also add new variables to control default behavoir for 8 | frontends and backends: 9 | 10 | * ``haproxy_frontend_h2: true`` 11 | * ``haproxy_backend_h2: false`` 12 | 13 | Please mention, that double stack of HTTP/1.1 and HTTP/2 is only available 14 | for TLS protected frontends. In case frontend is just TCP 15 | haproxy_frontend_h2 will be ignored. 16 | 17 | At the same time ``haproxy_backend_h2`` will be respected regardless of 18 | TLS/plain TCP configuration. 19 | upgrade: 20 | - | 21 | HTTP/2 is enabled by default for frontends that are covered with TLS. 22 | You can disable this behaviour by setting ``haproxy_frontend_h2: false`` 23 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy-backend-arguments-3b1dca299c7a8296.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - The new option `haproxy_backend_arguments` can be utilized to add 4 | arbitrary options to a HAProxy backend like tcp-check or http-check. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy-bind-override-9562bab32b964de2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - Setting the haproxy_bind list on a service is now used as an override to the 4 | other VIPs defined in the environment. Previously it was being treated as 5 | an append to the other VIPs so there was no path to override the VIP binds 6 | for a service. For example, haproxy_bind could be used to bind a service to 7 | the internal VIP only. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy-frontend-only-haproxy_raw-d55ad3baa8d006f3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - It is now possible to have a service which only have a frontend. 4 | by using `haproxy_frontend_only` inside your service. 5 | - Add the possibility to have a haproxy_frontend_raw entry to control 6 | haproxy config for the frontend, the entry will be literally copied in 7 | to the service. You can set a list under the key `haproxy_frontend_raw` 8 | 9 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy-hatop-4d6525a52f93a69e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Variable ``haproxy_hatop_downloader`` has been removed, Deployers 5 | supposed to use ``haproxy_hatop_download_url`` override if needed 6 | to install in deployments with limited internet connection. 7 | features: 8 | - | 9 | Added new variable ``haproxy_hatop_install``, that allows to conditionally 10 | enable or disable hatop installation. 11 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy-maps-787084d7f161c27e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | A new key `haproxy_map_entries` is now able to be configured for each 5 | haproxy service definition to allow arbitrary entries to be placed in 6 | any number of haproxy map files which may then be referenced in other 7 | directives in the haproxy config file such as ``use_backend`` or 8 | ``http-request``. The complete map files are constructed from the 9 | fragments defined across all the service definitions and are assembled 10 | into a complete map file in alphanumeric sort order, or optionally with 11 | a user defined ordering. 12 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy-selinux-all-ports-4094eed48f2bfbca.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - The haproxy daemon is now able to bind to any port on CentOS 7. The 4 | ``haproxy_connect_any`` SELinux boolean is now set to ``on``. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_allowlist-95aa9b911baeacd9.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | HAProxy ``haproxy_whitelist_networks`` key inside 5 | ``haproxy_service_configs`` dictionary has been replaced with 6 | ``haproxy_allowlist_networks``. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_custom_errorfiles-500674edcaa6cbb6.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Added support for defining custom error files using haproxy_errorfiles. 5 | These files can be distributed alongside haproxy_static_files_extra. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_frontend_raw-0811d5d445a66b41.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixed inconsistency in ``haproxy_frontend_raw`` key naming between 5 | documentation and service template. Previously, template generation 6 | was expecting ``haproxy_raw`` instead of the ``haproxy_frontend_raw``. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_letsencrypt-4a13c7911a20b993.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | There's no need in providing neither `http-01-address` nor `http-01-port` 5 | options with `haproxy_ssl_letsencrypt_setup_extra_params`, as they are now 6 | configured with corresponding variables 7 | `haproxy_ssl_letsencrypt_certbot_bind_address` and 8 | `haproxy_ssl_letsencrypt_certbot_backend_port` 9 | features: 10 | - | 11 | Added variable `haproxy_ssl_letsencrypt_certbot_challenge` which is default 12 | to `http-01`. As for now really tested in only `http-01` but we keep door 13 | open for adding support for more challanges, like `dns-01`. For `http-01` 14 | all required arguments are passed, but oth other challanges you might want 15 | to use `haproxy_ssl_letsencrypt_setup_extra_params` to pass missing 16 | arguments. 17 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_pki_create_certificates-28dd48424855f463.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | ``haproxy_pki_create_certificates`` was implemented. It allows users to 5 | explicitly disable certificates generation with PKI role but keep using 6 | it for certificates distribution. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_rise_fall-64ba2e6d7e206973.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Variables ``haproxy_fall`` and ``haproxy_rise`` are now respected again 5 | and will be used for defining amount of checks before haproxy will mark 6 | backend as UP or DOWN. Keys ``backend_rise`` and ``haproxy_fall`` that are 7 | set inside service definition are still respected and will have 8 | prescedence over global ones. 9 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_ssl_path-7130354314aee961.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - HAProxy services can now override the path of the certificate with 4 | ``haproxy_ssl_path`` if set under the service definition. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_sysctl_location-e18310fd96597a6f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Allow the definition of a custom sysctl config path through 5 | `openstack_sysctl_file` and/or `haproxy_sysctl_file`. 6 | Defaults to `/etc/sysctl.conf` to retain backwards compatibility. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/haproxy_tuning_params_released-45eb40104747561a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | If you have defined ``haproxy_tuning_params`` in your deployment, make sure 5 | that before upgrade all keys are valid haproxy options. For example, 6 | instead of ``chksize: 16384`` you should set ``tune.chksize: 16384``. 7 | Otherwise invalid config will be generated and haproxy will fail on 8 | startup. 9 | No upgrade scripts are provided for this change as well as no backwards 10 | compatability. 11 | other: 12 | - | 13 | Restriction on parameters that can be passed to ``haproxy_tuning_params`` 14 | has been released. This means, that any tuning parameter can be passed in 15 | key/value format. 16 | fixes: 17 | - | 18 | By default we increase ``tune.maxrewrite`` as otherwise while using CSP 19 | headers, their size could exceed allowed buffer. 20 | Also deployers can override this value if needed. 21 | -------------------------------------------------------------------------------- /releasenotes/notes/implement_tuning_params-e627c774c0d0c9d2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Haproxy-server role allows to set up tunable parameters. 4 | For doing that it is necessary to set up a dictionary of options in the 5 | config files, mentioning those which have to be changed (defaults for the 6 | remaining ones are programmed in the template). Also "maxconn" global option 7 | made to be tunable. 8 | upgrade: 9 | - The ``haproxy_bufsize`` variable has been removed and made a part of the 10 | ``haproxy_tuning_params`` dictionary. 11 | -------------------------------------------------------------------------------- /releasenotes/notes/letsencrypt-ssl-certification-129a80cb88d8e6ff.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | If Horizon dashboard of OSA installation has a public FQDN, is it 5 | now possible to use LetsEncrypt certification service. Certificate 6 | will be generated within HAProxy installation and a cron entry to 7 | renew the certificate daily will be setup. 8 | Note that there is no certificate distribution implementation at 9 | this time, so this will only work for a single haproxy-server 10 | environment. 11 | -------------------------------------------------------------------------------- /releasenotes/notes/non_inventory_hosts-c0fa4c185a01e78b.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - HAProxy services that use backend nodes that are not 4 | in the Ansible inventory can now be specified manually 5 | by setting ``haproxy_backend_nodes`` to a list of 6 | ``name`` and ``ip_addr`` settings. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/package-list-name-changes-a26d94a44c24de2f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - The variable ``haproxy_pre_packages`` has been renamed to 4 | ``haproxy_required_distro_packages``. 5 | - The variable ``haproxy_packages`` has been renamed to 6 | ``haproxy_distro_packages``. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/pki-temp-certs-path-d63091df0234df2e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | If directory is defined instead of certificate files, haproxy will attempt 5 | to treat all files within as a pem bundled certs. And will fail its 6 | configuration test. 7 | To avoid this a new variable haproxy_ssl_temp_path were introduced. When it 8 | is defined certificates from the pki being put into that directory and then 9 | combined into pem in the correct directory. 10 | 11 | Such an approach allows us to put additional certificates to the directory 12 | outside of the haproxy_server role and keep the directory clean. This also 13 | eliminates the need to list all additional custom certificates and sum them 14 | with the ones calculate by this role. 15 | 16 | Additionally added a cleanup/move of the certs if haproxy_ssl_temp_path set 17 | to be different from haproxy_ssl_cert_path which allows a transition from 18 | old setup. 19 | -------------------------------------------------------------------------------- /releasenotes/notes/refresh-interval-configuration-option-884d64aa259ecc3c.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Deployers can set a refresh interval for haproxy's stats page by setting 5 | the ``haproxy_stats_refresh_interval`` variable. The default value is 6 | ``60``, which causes haproxy to refresh the stats page every 60 seconds. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-haproxy-repo-vars-051a47bbfaf6d1da.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | The following variables have been removed from the ``haproxy_server`` role 5 | as they are no longer necessary or used. 6 | - haproxy_repo 7 | - haproxy_gpg_keys 8 | - haproxy_required_distro_packages 9 | -------------------------------------------------------------------------------- /releasenotes/notes/rename_haproxy_vip_binds-dda8197aaf607b53.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | Variable ``haproxy_tls_vip_binds`` has been renamed to 5 | ``haproxy_vip_binds`` to better reflect variable purpose. 6 | Old variable name is still respected but it's usage is 7 | descouraged and old naming will be removed in the future. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/separated-haproxy-config-b38d200ee0baaeac.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | ``haproxy_service_configs`` format was simplified. 5 | Now it's just a list of dicts. Usage of ``service`` key 6 | in ``haproxy_service_configs`` elements is deprecated and will be 7 | removed in 2023.2 release. 8 | 9 | -------------------------------------------------------------------------------- /releasenotes/notes/stick-table-9ef4bd94a4a000b3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Added new variable haproxy_stick_table_enabled to haproxy_service_configs, 5 | that allows you to conditionally enable or disable the default stick-table. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/tls12-only-a22d5f3f8198617f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | security: 3 | - | 4 | The default TLS version has been set to force-tlsv12. This only allows 5 | version 1.2 of the protocol to be used when terminating or creating TLS 6 | connections. You can change the value with the haproxy_ssl_bind_options 7 | variable. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/tls_variables-91160d4e38085de4.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | The HAProxy role now supports TLS v1.3 by default, alongside TLS v1.2. 5 | deprecations: 6 | - | 7 | The variable 'haproxy_ssl_cipher_suite' is deprecated in favour of 8 | 'haproxy_ssl_cipher_suite_tls12' which will continue to manage 9 | configuration of ciphers for TLS v1.2 and earlier. 10 | -------------------------------------------------------------------------------- /releasenotes/source/_static/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/openstack-ansible-haproxy_server/abf058c556f223f2102ab2491f2bb7b9e6f048df/releasenotes/source/_static/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/_templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/openstack-ansible-haproxy_server/abf058c556f223f2102ab2491f2bb7b9e6f048df/releasenotes/source/_templates/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is execfile()d with the current directory set to its 17 | # containing dir. 18 | # 19 | # Note that not all possible configuration values are present in this 20 | # autogenerated file. 21 | # 22 | # All configuration values have a default; values that are commented out 23 | # serve to show the default. 24 | 25 | # If extensions (or modules to document with autodoc) are in another directory, 26 | # add these directories to sys.path here. If the directory is relative to the 27 | # documentation root, use os.path.abspath to make it absolute, like shown here. 28 | # sys.path.insert(0, os.path.abspath('.')) 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | # needs_sphinx = '1.0' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | 'openstackdocstheme', 40 | 'reno.sphinxext', 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix of source filenames. 47 | source_suffix = '.rst' 48 | 49 | # The encoding of source files. 50 | # source_encoding = 'utf-8-sig' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | author = 'OpenStack-Ansible Contributors' 57 | category = 'Miscellaneous' 58 | copyright = '2014-2016, OpenStack-Ansible Contributors' 59 | description = 'OpenStack-Ansible deploys OpenStack environments using Ansible.' 60 | project = 'OpenStack-Ansible' 61 | role_name = 'haproxy_server' 62 | target_name = 'openstack-ansible-' + role_name 63 | title = 'OpenStack-Ansible Release Notes: ' + role_name + 'role' 64 | 65 | # Release notes do not need a version number in the title, 66 | # they cover multiple versions. 67 | # The full version, including alpha/beta/rc tags. 68 | release = '' 69 | # The short X.Y version. 70 | version = '' 71 | 72 | # openstackdocstheme options 73 | openstackdocs_repo_name = 'openstack/' + target_name 74 | openstackdocs_bug_project = project.lower() 75 | openstackdocs_bug_tag = '' 76 | 77 | # The language for content autogenerated by Sphinx. Refer to documentation 78 | # for a list of supported languages. 79 | # language = None 80 | 81 | # There are two options for replacing |today|: either, you set today to some 82 | # non-false value, then it is used: 83 | # today = '' 84 | # Else, today_fmt is used as the format for a strftime call. 85 | # today_fmt = '%B %d, %Y' 86 | 87 | # List of patterns, relative to source directory, that match files and 88 | # directories to ignore when looking for source files. 89 | exclude_patterns = [] 90 | 91 | # The reST default role (used for this markup: `text`) to use for all 92 | # documents. 93 | # default_role = None 94 | 95 | # If true, '()' will be appended to :func: etc. cross-reference text. 96 | # add_function_parentheses = True 97 | 98 | # If true, the current module name will be prepended to all description 99 | # unit titles (such as .. function::). 100 | # add_module_names = True 101 | 102 | # If true, sectionauthor and moduleauthor directives will be shown in the 103 | # output. They are ignored by default. 104 | # show_authors = False 105 | 106 | # The name of the Pygments (syntax highlighting) style to use. 107 | pygments_style = 'native' 108 | 109 | # A list of ignored prefixes for module index sorting. 110 | # modindex_common_prefix = [] 111 | 112 | # If true, keep warnings as "system message" paragraphs in the built documents. 113 | # keep_warnings = False 114 | 115 | 116 | # -- Options for HTML output ---------------------------------------------- 117 | 118 | # The theme to use for HTML and HTML Help pages. See the documentation for 119 | # a list of builtin themes. 120 | html_theme = 'openstackdocs' 121 | 122 | # Theme options are theme-specific and customize the look and feel of a theme 123 | # further. For a list of options available for each theme, see the 124 | # documentation. 125 | # html_theme_options = {} 126 | 127 | # Add any paths that contain custom themes here, relative to this directory. 128 | # html_theme_path = [] 129 | 130 | # The name for this set of Sphinx documents. If None, it defaults to 131 | # " v documentation". 132 | # html_title = None 133 | 134 | # A shorter title for the navigation bar. Default is the same as html_title. 135 | # html_short_title = None 136 | 137 | # The name of an image file (relative to this directory) to place at the top 138 | # of the sidebar. 139 | # html_logo = None 140 | 141 | # The name of an image file (within the static path) to use as favicon of the 142 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 143 | # pixels large. 144 | # html_favicon = None 145 | 146 | # Add any paths that contain custom static files (such as style sheets) here, 147 | # relative to this directory. They are copied after the builtin static files, 148 | # so a file named "default.css" will overwrite the builtin "default.css". 149 | html_static_path = ['_static'] 150 | 151 | # Add any extra paths that contain custom files (such as robots.txt or 152 | # .htaccess) here, relative to this directory. These files are copied 153 | # directly to the root of the documentation. 154 | # html_extra_path = [] 155 | 156 | # If true, SmartyPants will be used to convert quotes and dashes to 157 | # typographically correct entities. 158 | # html_use_smartypants = True 159 | 160 | # Custom sidebar templates, maps document names to template names. 161 | # html_sidebars = {} 162 | 163 | # Additional templates that should be rendered to pages, maps page names to 164 | # template names. 165 | # html_additional_pages = {} 166 | 167 | # If false, no module index is generated. 168 | # html_domain_indices = True 169 | 170 | # If false, no index is generated. 171 | # html_use_index = True 172 | 173 | # If true, the index is split into individual pages for each letter. 174 | # html_split_index = False 175 | 176 | # If true, links to the reST sources are added to the pages. 177 | # html_show_sourcelink = True 178 | 179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 180 | # html_show_sphinx = True 181 | 182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 183 | # html_show_copyright = True 184 | 185 | # If true, an OpenSearch description file will be output, and all pages will 186 | # contain a tag referring to it. The value of this option must be the 187 | # base URL from which the finished HTML is served. 188 | # html_use_opensearch = '' 189 | 190 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 191 | # html_file_suffix = None 192 | 193 | # Output file base name for HTML help builder. 194 | htmlhelp_basename = target_name + '-docs' 195 | 196 | 197 | # -- Options for LaTeX output --------------------------------------------- 198 | 199 | latex_elements = { 200 | # The paper size ('letterpaper' or 'a4paper'). 201 | # 'papersize': 'letterpaper', 202 | 203 | # The font size ('10pt', '11pt' or '12pt'). 204 | # 'pointsize': '10pt', 205 | 206 | # Additional stuff for the LaTeX preamble. 207 | # 'preamble': '', 208 | } 209 | 210 | # Grouping the document tree into LaTeX files. List of tuples 211 | # (source start file, target name, title, 212 | # author, documentclass [howto, manual, or own class]). 213 | latex_documents = [ 214 | (master_doc, target_name + '.tex', 215 | title, author, 'manual'), 216 | ] 217 | 218 | # The name of an image file (relative to this directory) to place at the top of 219 | # the title page. 220 | # latex_logo = None 221 | 222 | # For "manual" documents, if this is true, then toplevel headings are parts, 223 | # not chapters. 224 | # latex_use_parts = False 225 | 226 | # If true, show page references after internal links. 227 | # latex_show_pagerefs = False 228 | 229 | # If true, show URL addresses after external links. 230 | # latex_show_urls = False 231 | 232 | # Documents to append as an appendix to all manuals. 233 | # latex_appendices = [] 234 | 235 | # If false, no module index is generated. 236 | # latex_domain_indices = True 237 | 238 | 239 | # -- Options for manual page output --------------------------------------- 240 | 241 | # One entry per manual page. List of tuples 242 | # (source start file, name, description, authors, manual section). 243 | man_pages = [ 244 | (master_doc, target_name, 245 | title, [author], 1) 246 | ] 247 | 248 | # If true, show URL addresses after external links. 249 | # man_show_urls = False 250 | 251 | 252 | # -- Options for Texinfo output ------------------------------------------- 253 | 254 | # Grouping the document tree into Texinfo files. List of tuples 255 | # (source start file, target name, title, author, 256 | # dir menu entry, description, category) 257 | texinfo_documents = [ 258 | (master_doc, target_name, 259 | title, author, project, 260 | description, category), 261 | ] 262 | 263 | # Documents to append as an appendix to all manuals. 264 | # texinfo_appendices = [] 265 | 266 | # If false, no module index is generated. 267 | # texinfo_domain_indices = True 268 | 269 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 270 | # texinfo_show_urls = 'footnote' 271 | 272 | # If true, do not generate a @detailmenu in the "Top" node's menu. 273 | # texinfo_no_detailmenu = False 274 | 275 | # -- Options for Internationalization output ------------------------------ 276 | locale_dirs = ['locale/'] 277 | -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | OpenStack-Ansible Release Notes 3 | ================================ 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | unreleased 9 | zed 10 | victoria 11 | ussuri 12 | train 13 | stein 14 | rocky 15 | queens 16 | pike 17 | ocata 18 | newton 19 | -------------------------------------------------------------------------------- /releasenotes/source/newton.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Newton Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/newton 7 | -------------------------------------------------------------------------------- /releasenotes/source/ocata.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Ocata Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/ocata 7 | -------------------------------------------------------------------------------- /releasenotes/source/pike.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Pike Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/pike 7 | -------------------------------------------------------------------------------- /releasenotes/source/queens.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Queens Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/queens 7 | -------------------------------------------------------------------------------- /releasenotes/source/rocky.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Rocky Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/rocky 7 | -------------------------------------------------------------------------------- /releasenotes/source/stein.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Stein Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/stein 7 | -------------------------------------------------------------------------------- /releasenotes/source/train.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Train Series Release Notes 3 | ========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/train 7 | -------------------------------------------------------------------------------- /releasenotes/source/unreleased.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Current Series Release Notes 3 | ============================== 4 | 5 | .. release-notes:: 6 | -------------------------------------------------------------------------------- /releasenotes/source/ussuri.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Ussuri Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/ussuri 7 | -------------------------------------------------------------------------------- /releasenotes/source/victoria.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Victoria Series Release Notes 3 | ============================= 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/victoria 7 | -------------------------------------------------------------------------------- /releasenotes/source/zed.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Zed Series Release Notes 3 | ======================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/zed 7 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2015, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # PURPOSE: 17 | # This script clones the openstack-ansible-tests repository to the 18 | # tests/common folder in order to be able to re-use test components 19 | # for role testing. This is intended to be the thinnest possible 20 | # shim for test execution outside of OpenStack CI. 21 | 22 | # WARNING: 23 | # This file is maintained in the openstack-ansible-tests repository. 24 | # https://opendev.org/openstack/openstack-ansible-tests/src/run_tests.sh 25 | # If you need to modify this file, update the one in the openstack-ansible-tests 26 | # repository and then update this file as well. The purpose of this file is to 27 | # prepare the host and then execute all the tox tests. 28 | # 29 | 30 | ## Shell Opts ---------------------------------------------------------------- 31 | set -xeu 32 | 33 | ## Vars ---------------------------------------------------------------------- 34 | 35 | WORKING_DIR="$(readlink -f $(dirname $0))" 36 | OSA_PROJECT_NAME="$(sed -n 's|^project=openstack/\(.*\).git$|\1|p' $(pwd)/.gitreview)" 37 | 38 | COMMON_TESTS_PATH="${WORKING_DIR}/tests/common" 39 | TESTING_HOME=${TESTING_HOME:-$HOME} 40 | ZUUL_TESTS_CLONE_LOCATION="/home/zuul/src/opendev.org/openstack/openstack-ansible-tests" 41 | 42 | # Use .gitreview as the key to determine the appropriate 43 | # branch to clone for tests. 44 | TESTING_BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' "${WORKING_DIR}/.gitreview") 45 | if [[ "${TESTING_BRANCH}" == "" ]]; then 46 | TESTING_BRANCH="master" 47 | fi 48 | 49 | ## Main ---------------------------------------------------------------------- 50 | 51 | # Source distribution information 52 | source /etc/os-release || source /usr/lib/os-release 53 | 54 | # Figure out the appropriate package install command 55 | case ${ID,,} in 56 | centos|rhel|fedora|rocky) pkg_mgr_cmd="dnf install -y" ;; 57 | ubuntu|debian) pkg_mgr_cmd="apt-get install -y" ;; 58 | *) echo "unsupported distribution: ${ID,,}"; exit 1 ;; 59 | esac 60 | 61 | # Install git so that we can clone the tests repo if git is not available 62 | which git &>/dev/null || eval sudo "${pkg_mgr_cmd}" git 63 | 64 | # Clone the tests repo for access to the common test script 65 | if [[ ! -d "${COMMON_TESTS_PATH}" ]]; then 66 | # The tests repo doesn't need a clone, we can just 67 | # symlink it. 68 | if [[ "${OSA_PROJECT_NAME}" == "openstack-ansible-tests" ]]; then 69 | ln -s "${WORKING_DIR}" "${COMMON_TESTS_PATH}" 70 | 71 | # In zuul v3 any dependent repository is placed into 72 | # /home/zuul/src/opendev.org, so we check to see 73 | # if there is a tests checkout there already. If so, we 74 | # symlink that and use it. 75 | elif [[ -d "${ZUUL_TESTS_CLONE_LOCATION}" ]]; then 76 | ln -s "${ZUUL_TESTS_CLONE_LOCATION}" "${COMMON_TESTS_PATH}" 77 | 78 | # Otherwise we're clearly not in zuul or using a previously setup 79 | # repo in some way, so just clone it from upstream. 80 | else 81 | git clone -b "${TESTING_BRANCH}" \ 82 | https://opendev.org/openstack/openstack-ansible-tests \ 83 | "${COMMON_TESTS_PATH}" 84 | fi 85 | fi 86 | 87 | # Execute the common test script 88 | source tests/common/run_tests_common.sh 89 | -------------------------------------------------------------------------------- /tasks/haproxy_install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - name: Install HAProxy Packages 17 | ansible.builtin.package: 18 | name: "{{ haproxy_distro_packages }}" 19 | state: "{{ haproxy_package_state }}" 20 | update_cache: "{{ (ansible_facts['pkg_mgr'] == 'apt') | ternary('yes', omit) }}" 21 | cache_valid_time: "{{ (ansible_facts['pkg_mgr'] == 'apt') | ternary(cache_timeout, omit) }}" 22 | register: install_packages 23 | until: install_packages is success 24 | retries: 5 25 | delay: 2 26 | 27 | - name: Install HATop Utility 28 | block: 29 | - name: Ensure haproxy_hatop_download_path exists on haproxy 30 | ansible.builtin.file: 31 | path: "{{ haproxy_hatop_download_path }}/{{ haproxy_hatop_download_url | basename | replace('.tar.gz', '') }}" 32 | state: directory 33 | mode: "0755" 34 | 35 | - name: Download hatop package 36 | ansible.builtin.get_url: 37 | url: "{{ haproxy_hatop_download_url }}" 38 | dest: "{{ haproxy_hatop_download_path }}/{{ haproxy_hatop_download_url | basename }}" 39 | validate_certs: "{{ haproxy_hatop_download_validate_certs }}" 40 | checksum: "{{ haproxy_hatop_download_checksum }}" 41 | mode: "0644" 42 | register: fetch_url 43 | until: fetch_url is success 44 | retries: 3 45 | delay: 10 46 | 47 | - name: Unarchive HATop 48 | ansible.builtin.unarchive: 49 | src: "{{ haproxy_hatop_download_path }}/{{ haproxy_hatop_download_url | basename }}" 50 | dest: "{{ haproxy_hatop_download_path }}/{{ haproxy_hatop_download_url | basename | replace('.tar.gz', '') }}" 51 | remote_src: true 52 | extra_opts: 53 | - --strip-components=1 54 | 55 | - name: Copy HATop binary 56 | ansible.builtin.copy: 57 | src: "{{ haproxy_hatop_download_path }}/{{ haproxy_hatop_download_url | basename | replace('.tar.gz', '') }}/bin/hatop" 58 | dest: /usr/local/bin/hatop 59 | mode: "0755" 60 | remote_src: true 61 | when: haproxy_hatop_install | bool 62 | -------------------------------------------------------------------------------- /tasks/haproxy_post_install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - name: Make haproxy bindable on non local addresses 17 | ansible.posix.sysctl: 18 | name: "{{ item }}" 19 | value: 1 20 | sysctl_set: true 21 | sysctl_file: "{{ haproxy_sysctl_file }}" 22 | state: present 23 | when: haproxy_bind_on_non_local | bool 24 | with_items: 25 | - "net.ipv4.ip_nonlocal_bind" 26 | - "net.ipv6.ip_nonlocal_bind" 27 | tags: 28 | - haproxy-non-local-bind-config 29 | 30 | # NOTE (noonedeadpunk) Debian/Ubuntu haproxy packages configure rsyslog 31 | # to handle log collection and log file rotation. This is not needed since 32 | # journald is used for this purpose 33 | - name: Delete rsyslog and logrotate configs 34 | ansible.builtin.file: 35 | path: "{{ item }}" 36 | state: absent 37 | with_items: 38 | - /etc/rsyslog.d/49-haproxy.conf 39 | - /etc/logrotate.d/haproxy 40 | - /etc/rsyslog.d/10-haproxy-local-logging.conf 41 | notify: Restart rsyslog 42 | tags: 43 | - haproxy-logging-config 44 | 45 | - name: Drop base haproxy config 46 | ansible.builtin.template: 47 | src: "haproxy.cfg.j2" 48 | dest: "/etc/haproxy/conf.d/00-haproxy" 49 | mode: "0640" 50 | owner: haproxy 51 | group: haproxy 52 | notify: Regenerate haproxy configuration 53 | tags: 54 | - haproxy-base-config 55 | 56 | - name: Including haproxy_service_config tasks 57 | ansible.builtin.include_tasks: haproxy_service_config.yml 58 | args: 59 | apply: 60 | tags: 61 | - haproxy-service-config 62 | tags: 63 | - haproxy-service-config 64 | 65 | - name: Create log directory if it does not exist 66 | ansible.builtin.file: 67 | path: "{{ haproxy_log_mount_point | dirname }}" 68 | state: directory 69 | mode: "0755" 70 | owner: "haproxy" 71 | group: "haproxy" 72 | 73 | # NOTE(jrosser) The next task fails on Centos without this, 74 | # an empty directory rather than a file is made and the bind mount fails 75 | - name: Ensure empty file is availble to bind mount log socket 76 | ansible.builtin.file: 77 | state: touch 78 | path: "{{ haproxy_log_mount_point }}" 79 | access_time: preserve 80 | modification_time: preserve 81 | mode: "0666" 82 | 83 | - name: Make log socket available to chrooted filesystem 84 | ansible.posix.mount: 85 | src: "{{ haproxy_log_socket }}" 86 | path: "{{ haproxy_log_mount_point }}" 87 | opts: bind 88 | state: mounted 89 | fstype: none 90 | 91 | - name: Prevent SELinux from preventing haproxy from binding to arbitrary ports 92 | ansible.posix.seboolean: 93 | name: haproxy_connect_any 94 | state: true 95 | persistent: true 96 | tags: 97 | - haproxy-service-config 98 | notify: 99 | - Reload haproxy 100 | when: 101 | - ansible_facts['selinux']['status'] == "enabled" 102 | -------------------------------------------------------------------------------- /tasks/haproxy_pre_install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2015, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # NOTE(cloudnull): 17 | # While the haproxy distro packages provide for an haproxy 18 | # group this group is being created upfront to support 19 | # log aggregation links as well as ensure common user 20 | # functionality across various distros that we support. 21 | - name: Create the haproxy system group 22 | ansible.builtin.group: 23 | name: "haproxy" 24 | state: "present" 25 | system: "yes" 26 | tags: 27 | - haproxy-group 28 | 29 | # NOTE(cloudnull): 30 | # While the haproxy distro packages provide for an haproxy 31 | # user this user is being created upfront to support 32 | # log aggregation links as well as ensure common user 33 | # functionality across various distros that we support. 34 | - name: Create the haproxy system user 35 | ansible.builtin.user: 36 | name: "haproxy" 37 | group: "haproxy" 38 | comment: "haproxy user" 39 | shell: "/bin/false" 40 | system: "yes" 41 | createhome: "yes" 42 | home: "/var/lib/haproxy" 43 | tags: 44 | - haproxy-user 45 | 46 | - name: Create haproxy conf.d dir 47 | ansible.builtin.file: 48 | path: "{{ item }}" 49 | state: directory 50 | mode: "0755" 51 | owner: haproxy 52 | group: haproxy 53 | with_items: 54 | - /etc/haproxy/conf.d 55 | - "{{ haproxy_ssl_cert_path }}" 56 | - "{{ haproxy_ssl_temp_path }}" 57 | 58 | - name: Cleanup haproxy_ssl_cert_path if temp_path is used 59 | when: "haproxy_ssl_cert_path != haproxy_ssl_temp_path" 60 | block: 61 | - name: Find crt and key files in the cert_path 62 | ansible.builtin.find: 63 | paths: "{{ haproxy_ssl_cert_path }}" 64 | patterns: '*.crt,*.key' 65 | register: old_certs 66 | 67 | - name: Copy cert files to the temp_path 68 | vars: 69 | filename: "{{ item | basename }}" 70 | ansible.builtin.copy: 71 | remote_src: true 72 | src: "{{ item }}" 73 | dest: "{{ [haproxy_ssl_temp_path, filename] | path_join }}" 74 | mode: "0644" 75 | loop: "{{ old_certs['files'] | map(attribute='path') }}" 76 | 77 | - name: Remove file from the old place 78 | ansible.builtin.file: 79 | path: "{{ item }}" 80 | state: absent 81 | loop: "{{ old_certs['files'] | map(attribute='path') }}" 82 | 83 | - name: Copy static files 84 | ansible.builtin.copy: 85 | content: "{{ item.content }}" 86 | dest: "{{ item.dest }}" 87 | mode: "0644" 88 | owner: haproxy 89 | group: haproxy 90 | when: 91 | - (item.condition | default(True)) 92 | loop: "{{ haproxy_static_files }}" 93 | no_log: true 94 | -------------------------------------------------------------------------------- /tasks/haproxy_service_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # NOTE(damiandabrowski): Deprecated haproxy_service_configs format 17 | # conversion will be removed in 2024.1. 18 | - name: Define blank _haproxy_service_configs_simplified variable 19 | ansible.builtin.set_fact: 20 | _haproxy_service_configs_simplified: [] 21 | 22 | - name: Append services to _haproxy_service_configs_simplified list 23 | ansible.builtin.set_fact: 24 | _haproxy_service_configs_simplified: "{{ _haproxy_service_configs_simplified + [(item.service is defined) | ternary(item.service, item)] }}" 25 | loop: "{{ haproxy_service_configs }}" 26 | 27 | ########################################################################### 28 | # Service frontends and backends assembled from fragments into haproxy.conf 29 | ########################################################################### 30 | 31 | - name: Create haproxy service config files 32 | ansible.builtin.template: 33 | src: service.j2 34 | dest: "/etc/haproxy/conf.d/{{ service.haproxy_service_name }}" 35 | owner: root 36 | group: haproxy 37 | mode: "0640" 38 | # NOTE(damiandabrowski): _haproxy_service_configs_simplified should be replaced 39 | # with haproxy_service_configs in 2024.1. 40 | loop: "{{ _haproxy_service_configs_simplified }}" 41 | loop_control: 42 | loop_var: service 43 | when: 44 | - (service.haproxy_backend_nodes is defined and 45 | service.haproxy_backend_nodes | length > 0) or 46 | (service.haproxy_backup_nodes is defined and 47 | service.haproxy_backup_nodes | length > 0) or 48 | service.haproxy_frontend_only | default('False') 49 | - (service.haproxy_service_enabled | default('True')) | bool 50 | - (service.state is not defined or service.state != 'absent') 51 | notify: Regenerate haproxy configuration 52 | 53 | - name: Remove haproxy service config files for absent services 54 | ansible.builtin.file: 55 | path: "/etc/haproxy/conf.d/{{ service.haproxy_service_name }}" 56 | state: absent 57 | notify: Regenerate haproxy configuration 58 | # NOTE(damiandabrowski): _haproxy_service_configs_simplified should be replaced 59 | # with haproxy_service_configs in 2024.1. 60 | loop: "{{ _haproxy_service_configs_simplified }}" 61 | loop_control: 62 | loop_var: service 63 | when: 64 | - ((service.haproxy_service_enabled | default('True')) | bool) is falsy or (service.state is defined and service.state == 'absent') 65 | 66 | ########################################################################### 67 | # Map files assembled from fragments from each service into .map 68 | ########################################################################### 69 | 70 | - name: Create haproxy map fragment directories 71 | ansible.builtin.file: 72 | state: directory 73 | path: "/etc/haproxy/map.conf.d/{{ item }}" 74 | owner: root 75 | group: haproxy 76 | mode: "0750" 77 | # NOTE(damiandabrowski): _haproxy_service_configs_simplified should be replaced 78 | # with haproxy_service_configs in 2024.1. 79 | loop: >- 80 | {{ 81 | _haproxy_service_configs_simplified | selectattr('haproxy_map_entries', 'defined') | map(attribute='haproxy_map_entries') | flatten | 82 | map(attribute='name') | unique 83 | }} 84 | 85 | # create map entries when the service is enabled and an existing map fragment is not absent 86 | - name: Create haproxy map files 87 | vars: 88 | map_file: "/etc/haproxy/map.conf.d/{{ item.1.name }}/{{ item.1.order | default('00') }}-{{ item.0.haproxy_service_name }}.map" 89 | ansible.builtin.template: 90 | src: map.j2 91 | dest: "{{ map_file }}" 92 | owner: root 93 | group: haproxy 94 | mode: "0640" 95 | # NOTE(damiandabrowski): _haproxy_service_configs_simplified should be replaced 96 | # with haproxy_service_configs in 2024.1. 97 | with_subelements: 98 | - "{{ _haproxy_service_configs_simplified | selectattr('haproxy_map_entries', 'defined') }}" 99 | - haproxy_map_entries 100 | when: 101 | - (item.0.haproxy_service_enabled | default(True)) | bool 102 | - item.1.state | default('present') != 'absent' 103 | notify: Regenerate maps 104 | register: map_create 105 | 106 | # remove map entries when the service is not enabled, the service is absent or the map is absent 107 | - name: Delete unused map entries 108 | ansible.builtin.file: 109 | state: absent 110 | path: "/etc/haproxy/map.conf.d/{{ item.1.name }}/{{ item.1.order | default('00') }}-{{ item.0.haproxy_service_name }}.map" 111 | when: 112 | - (item.0.haproxy_service_enabled | default('True')) | bool is falsy or 113 | (item.0.state is defined and item.0.state == 'absent') or 114 | (item.1.state | default('present') == 'absent') 115 | # NOTE(damiandabrowski): _haproxy_service_configs_simplified should be replaced 116 | # with haproxy_service_configs in 2024.1. 117 | with_subelements: 118 | - "{{ _haproxy_service_configs_simplified | selectattr('haproxy_map_entries', 'defined') }}" 119 | - haproxy_map_entries 120 | notify: Regenerate maps 121 | register: map_delete 122 | -------------------------------------------------------------------------------- /tasks/haproxy_service_config_external.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2023, Cleura AB 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - name: Gather variables for each operating system 17 | ansible.builtin.include_vars: "{{ lookup('first_found', params) }}" 18 | vars: 19 | params: 20 | files: 21 | - "{{ ansible_facts['distribution'] | lower }}-{{ ansible_facts['distribution_version'] | lower }}.yml" 22 | - "{{ ansible_facts['distribution'] | lower }}-{{ ansible_facts['distribution_major_version'] | lower }}.yml" 23 | - "{{ ansible_facts['os_family'] | lower }}-{{ ansible_facts['distribution_major_version'] | lower }}.yml" 24 | - "{{ ansible_facts['distribution'] | lower }}.yml" 25 | - "{{ ansible_facts['os_family'] | lower }}.yml" 26 | paths: 27 | - "{{ role_path }}/vars" 28 | 29 | - name: Including haproxy_service_config tasks 30 | ansible.builtin.include_tasks: haproxy_service_config.yml 31 | args: 32 | apply: 33 | tags: 34 | - haproxy_server-config 35 | tags: 36 | - always 37 | -------------------------------------------------------------------------------- /tasks/haproxy_ssl_letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | - name: Install certbot from distro package 15 | ansible.builtin.package: 16 | name: "{{ haproxy_distro_certbot_packages }}" 17 | state: present 18 | 19 | - name: Create first time ssl cert with certbot 20 | throttle: 1 21 | ansible.builtin.shell: > 22 | {% if haproxy_ssl_letsencrypt_certbot_challenge == 'http-01' %} 23 | timeout {{ haproxy_ssl_letsencrypt_pre_hook_timeout }} 24 | python3 -m http.server {{ haproxy_ssl_letsencrypt_certbot_backend_port }} 25 | --bind {{ haproxy_ssl_letsencrypt_certbot_bind_address }} || true && 26 | {% endif %} 27 | {{ haproxy_ssl_letsencrypt_certbot_binary }} certonly 28 | --agree-tos 29 | --non-interactive 30 | --text 31 | --rsa-key-size 4096 32 | --email {{ haproxy_ssl_letsencrypt_email }} 33 | --domains {{ haproxy_ssl_letsencrypt_domains | join(',') }} 34 | {% if haproxy_ssl_letsencrypt_certbot_server is defined %} 35 | --server {{ haproxy_ssl_letsencrypt_certbot_server }} 36 | {% endif %} 37 | {% if haproxy_ssl_letsencrypt_certbot_challenge == 'http-01' %} 38 | --standalone 39 | --http-01-port {{ haproxy_ssl_letsencrypt_certbot_backend_port }} 40 | --http-01-address {{ haproxy_ssl_letsencrypt_certbot_bind_address }} 41 | {% endif %} 42 | {{ haproxy_ssl_letsencrypt_setup_extra_params }} 43 | args: 44 | creates: "{{ haproxy_ssl_letsencrypt_config_path }}/{{ haproxy_ssl_letsencrypt_domains | first }}/fullchain.pem" 45 | 46 | # Certbot automatically installs its systemd timer responsible for renewals 47 | - name: Create certbot pre hook 48 | ansible.builtin.template: 49 | src: letsencrypt_pre_hook_certbot_distro.j2 50 | dest: /etc/letsencrypt/renewal-hooks/pre/haproxy-pre 51 | mode: "0755" 52 | when: 53 | - haproxy_ssl_letsencrypt_certbot_challenge == 'http-01' 54 | 55 | - name: Create certbot post renewal hook 56 | ansible.builtin.template: 57 | src: letsencrypt_renew_certbot_distro.j2 58 | dest: /etc/letsencrypt/renewal-hooks/post/haproxy-renew 59 | mode: "0755" 60 | 61 | - name: Create new pem file for haproxy 62 | ansible.builtin.assemble: 63 | src: "{{ haproxy_ssl_letsencrypt_config_path }}/{{ haproxy_ssl_letsencrypt_domains | first }}" 64 | dest: >- 65 | {{ 66 | haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (item.get('interface')) | ternary( 67 | item.get('address') ~ '-' ~ item.get('interface'), item['address']) ~ '.pem' 68 | }} 69 | regexp: "(privkey|fullchain).pem$" 70 | owner: haproxy 71 | group: haproxy 72 | mode: "0640" 73 | with_items: 74 | - "{{ haproxy_vip_binds | selectattr('type', 'defined') | selectattr('type', 'eq', 'external') }}" 75 | notify: 76 | - Reload haproxy 77 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2014, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - name: Gather variables for each operating system 17 | ansible.builtin.include_vars: "{{ lookup('first_found', params) }}" 18 | vars: 19 | params: 20 | files: 21 | - "{{ ansible_facts['distribution'] | lower }}-{{ ansible_facts['distribution_version'] | lower }}.yml" 22 | - "{{ ansible_facts['distribution'] | lower }}-{{ ansible_facts['distribution_major_version'] | lower }}.yml" 23 | - "{{ ansible_facts['os_family'] | lower }}-{{ ansible_facts['distribution_major_version'] | lower }}.yml" 24 | - "{{ ansible_facts['distribution'] | lower }}.yml" 25 | - "{{ ansible_facts['os_family'] | lower }}.yml" 26 | paths: 27 | - "{{ role_path }}/vars" 28 | tags: 29 | - always 30 | 31 | - name: Importing haproxy_pre_install tasks 32 | ansible.builtin.import_tasks: haproxy_pre_install.yml 33 | tags: 34 | - haproxy_server-install 35 | 36 | - name: Importing haproxy_install tasks 37 | ansible.builtin.import_tasks: haproxy_install.yml 38 | tags: 39 | - haproxy_server-install 40 | 41 | # NOTE (jrosser) the self signed certificate is also needed for bootstrapping 42 | # letsencrypt, as haproxy will not start with ssl config but a missing certificate 43 | - name: Create and install SSL certificates 44 | ansible.builtin.include_role: 45 | name: pki 46 | apply: 47 | tags: 48 | - haproxy_server-config 49 | - pki 50 | vars: 51 | pki_setup_host: "{{ haproxy_pki_setup_host }}" 52 | pki_dir: "{{ haproxy_pki_dir }}" 53 | pki_create_ca: "{{ haproxy_pki_create_ca }}" 54 | pki_authorities: "{{ haproxy_pki_authorities }}" 55 | pki_install_ca: "{{ haproxy_pki_install_ca }}" 56 | pki_regen_ca: "{{ haproxy_pki_regen_ca }}" 57 | pki_create_certificates: "{{ haproxy_pki_create_certificates }}" 58 | pki_regen_cert: "{{ haproxy_pki_regen_cert }}" 59 | pki_certificates: "{{ haproxy_pki_certificates }}" 60 | pki_install_certificates: "{{ haproxy_pki_install_certificates }}" 61 | pki_handler_cert_installed: "haproxy cert installed" 62 | when: 63 | - haproxy_ssl | bool 64 | tags: 65 | - haproxy_server-config 66 | - pki 67 | 68 | - name: Importing haproxy_post_install tasks 69 | ansible.builtin.import_tasks: haproxy_post_install.yml 70 | tags: 71 | - haproxy_server-config 72 | 73 | # NOTE(jrosser) we must reload the haproxy config before doing the first time certbot setup to ensure the letsencypt backend is configured 74 | - name: Flush handlers 75 | ansible.builtin.meta: flush_handlers 76 | 77 | - name: Including haproxy_ssl_letsencrypt tasks 78 | ansible.builtin.include_tasks: haproxy_ssl_letsencrypt.yml 79 | when: 80 | - haproxy_ssl | bool 81 | - haproxy_ssl_letsencrypt_enable | bool 82 | - haproxy_user_ssl_cert is not defined or haproxy_user_ssl_key is not defined 83 | args: 84 | apply: 85 | tags: 86 | - haproxy_server-config 87 | - letsencrypt 88 | tags: 89 | - haproxy_server-config 90 | - letsencrypt 91 | -------------------------------------------------------------------------------- /templates/haproxy.cfg.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | global 4 | log /dev/log local0 5 | chroot /var/lib/haproxy 6 | user haproxy 7 | group haproxy 8 | daemon 9 | maxconn {{ haproxy_maxconn }} 10 | {% for key, value in ((_haproxy_default_tuning_params | default({})) | combine(haproxy_tuning_params)).items() %} 11 | {{ key }} {{ value }} 12 | {% endfor %} 13 | stats socket /var/run/haproxy.stat level admin mode 600 14 | {% if haproxy_ssl | bool %} 15 | ssl-default-bind-options {{ haproxy_ssl_bind_options }} 16 | ssl-default-server-options {{ haproxy_ssl_server_options }} 17 | {% if haproxy_ssl_cipher_suite_tls13 != "" -%} 18 | ssl-default-bind-ciphersuites {{ haproxy_ssl_cipher_suite_tls13 }} 19 | ssl-default-server-ciphersuites {{ haproxy_ssl_cipher_suite_tls13 }} 20 | {% endif -%} 21 | {% if haproxy_ssl_cipher_suite_tls12 != "" -%} 22 | ssl-default-bind-ciphers {{ haproxy_ssl_cipher_suite_tls12 }} 23 | ssl-default-server-ciphers {{ haproxy_ssl_cipher_suite_tls12 }} 24 | {% endif -%} 25 | tune.ssl.default-dh-param {{ haproxy_ssl_dh_param }} 26 | {% endif %} 27 | 28 | defaults 29 | log global 30 | option dontlognull 31 | option redispatch 32 | option {{ haproxy_keepalive_mode }} 33 | retries {{ haproxy_retries }} 34 | timeout client {{ haproxy_client_timeout }} 35 | timeout connect {{ haproxy_connect_timeout }} 36 | timeout http-request {{ haproxy_http_request_timeout }} 37 | timeout server {{ haproxy_server_timeout }} 38 | maxconn {{ haproxy_maxconn }} 39 | {% for value in haproxy_errorfiles %} 40 | errorfile {{ value.code }} {{ value.path }} 41 | {% endfor %} 42 | {% if haproxy_stats_enabled | bool %} 43 | {% set haproxy_ssl_path = haproxy_ssl_cert_path + "/haproxy_" + (haproxy_host | default(ansible_facts['hostname'])) + "-" + ((haproxy_bind_internal_lb_vip_interface is truthy) | ternary(haproxy_bind_internal_lb_vip_address ~ '-' ~ haproxy_bind_internal_lb_vip_interface, haproxy_bind_internal_lb_vip_address)) + ".pem" %} 44 | listen stats 45 | bind {{ haproxy_stats_bind_address }}:{{ haproxy_stats_port }} {% if haproxy_stats_ssl | bool %}ssl crt {{ haproxy_stats_ssl_cert_path | default(haproxy_ssl_path) }} {% if haproxy_stats_ssl_client_cert_ca is defined %}verify required ca-file {{ haproxy_stats_ssl_client_cert_ca }}{% endif %}{% endif %} 46 | 47 | mode http 48 | {% if haproxy_stats_prometheus_enabled | bool %} 49 | http-request use-service prometheus-exporter if { path /metrics } 50 | {% endif %} 51 | stats enable 52 | stats hide-version 53 | stats realm Haproxy\ Statistics 54 | stats uri / 55 | stats show-node 56 | stats show-legends 57 | stats auth {{ haproxy_username }}:{{ haproxy_stats_password }} 58 | stats admin if TRUE 59 | stats refresh {{ haproxy_stats_refresh_interval }}s 60 | {% endif %} 61 | -------------------------------------------------------------------------------- /templates/letsencrypt_pre_hook_certbot_distro.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # swing load balancer over to this node by starting temporary http server for {{ haproxy_ssl_letsencrypt_pre_hook_timeout }} seconds 3 | 4 | timeout {{ haproxy_ssl_letsencrypt_pre_hook_timeout }} python3 -m http.server {{ haproxy_ssl_letsencrypt_certbot_backend_port }} --bind {{ haproxy_ssl_letsencrypt_certbot_bind_address }} 5 | -------------------------------------------------------------------------------- /templates/letsencrypt_renew_certbot_distro.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # renew cert if required and copy to haproxy destination 3 | 4 | {% for vip in haproxy_vip_binds | selectattr('type', 'defined') | selectattr('type', 'eq', 'external') %} 5 | cat /etc/letsencrypt/live/{{ haproxy_ssl_letsencrypt_domains | first }}/{fullchain,privkey}.pem \ 6 | > {{ haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (vip.get('interface')) | ternary(vip['address'] ~ '-' ~ vip.get('interface'), vip['address']) ~ '.pem' }} 7 | {% endfor %} 8 | 9 | systemctl reload haproxy 10 | -------------------------------------------------------------------------------- /templates/map.j2: -------------------------------------------------------------------------------- 1 | {% for m in item.1.entries %} 2 | {{ m }} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /templates/service-redirect.j2: -------------------------------------------------------------------------------- 1 | {% set haproxy_http_front_port = (haproxy_backend_port | int) + 10000 %} 2 | {% set haproxy_https_front_port = (haproxy_backend_port | int) + 20000 %} 3 | 4 | # Redirect request to HTTP or HTTPS frontend based on used protocol 5 | frontend {{ service.haproxy_service_name }}-tcp-redirect-front-{{ loop.index }} 6 | mode tcp 7 | bind {{ vip_address }}:{{ service.haproxy_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} 8 | tcp-request inspect-delay 2s 9 | tcp-request content accept if HTTP 10 | tcp-request content accept if { req.ssl_hello_type 1 } 11 | use_backend {{ value.backend_name | default(service.haproxy_service_name) }}-redirect-http-back-{{ loop.index }} if HTTP 12 | default_backend {{ value.backend_name | default(service.haproxy_service_name) }}-redirect-https-back-{{ loop.index }} 13 | 14 | backend {{ value.backend_name | default(service.haproxy_service_name) }}-redirect-http-back-{{ loop.index }} 15 | mode tcp 16 | server {{ value.backend_name | default(service.haproxy_service_name) }}-http {{ vip_address }}:{{ haproxy_http_front_port }} 17 | 18 | backend {{ value.backend_name | default(service.haproxy_service_name) }}-redirect-https-back-{{ loop.index }} 19 | mode tcp 20 | server {{ value.backend_name | default(service.haproxy_service_name) }}-https {{ vip_address }}:{{ haproxy_https_front_port }} 21 | 22 | frontend {{ service.haproxy_service_name }}-http-front-{{ loop.index }} 23 | bind {{ vip_address }}:{{ haproxy_http_front_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} 24 | {% if request_option == "http" %} 25 | option httplog 26 | option forwardfor except 127.0.0.0/8 27 | {% if service.haproxy_http_keepalive_mode is defined %} 28 | option {{ service.haproxy_http_keepalive_mode }} 29 | {% endif %} 30 | {% elif request_option == "tcp" %} 31 | option tcplog 32 | {% endif %} 33 | {% if service.haproxy_timeout_client is defined %} 34 | timeout client {{ service.haproxy_timeout_client }} 35 | {% endif %} 36 | {% if service.haproxy_allowlist_networks is defined %} 37 | acl allow_list src 127.0.0.1/8 {{ service.haproxy_allowlist_networks | join(' ') }} 38 | tcp-request content accept if allow_list 39 | tcp-request content reject 40 | {% endif %} 41 | {% if service.haproxy_acls is defined %} 42 | {% for key, value in service.haproxy_acls.items() %} 43 | acl {{ key }} {{ value.rule }} 44 | {% if not service.haproxy_frontend_only | default(false) %} 45 | use_backend {{ value.backend_name | default(service.haproxy_service_name) }}-back if {{ key }} 46 | {% endif %} 47 | {% endfor %} 48 | {% endif %} 49 | {% for entry in service.haproxy_maps | default([]) %} 50 | {{ entry }} 51 | {% endfor %} 52 | mode {{ service.haproxy_balance_type }} 53 | {% if (not service.haproxy_frontend_only | default(false)) or ((service.haproxy_default_backend is defined) and (service.haproxy_default_backend | length > 0)) %} 54 | default_backend {{ service.haproxy_default_backend | default(service.haproxy_service_name) }}-back 55 | {% endif %} 56 | {% for entry in (service.haproxy_frontend_raw|default([])) + haproxy_frontend_extra_raw %} 57 | {{ entry }} 58 | {% endfor %} 59 | 60 | frontend {{ service.haproxy_service_name }}-https-front-{{ loop.index }} 61 | bind {{ vip_address }}:{{ haproxy_https_front_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} ssl crt {{ haproxy_ssl_cert_path }}/haproxy_{{ ansible_facts['hostname'] }}-{{ vip_address }}.pem 62 | {% if request_option == "http" %} 63 | option httplog 64 | option forwardfor except 127.0.0.0/8 65 | {% if service.haproxy_http_keepalive_mode is defined %} 66 | option {{ service.haproxy_http_keepalive_mode }} 67 | {% endif %} 68 | {% elif request_option == "tcp" %} 69 | option tcplog 70 | {% endif %} 71 | {% if service.haproxy_timeout_client is defined %} 72 | timeout client {{ service.haproxy_timeout_client }} 73 | {% endif %} 74 | {% if service.haproxy_allowlist_networks is defined %} 75 | acl allow_list src 127.0.0.1/8 {{ service.haproxy_allowlist_networks | join(' ') }} 76 | tcp-request content accept if allow_list 77 | tcp-request content reject 78 | {% endif %} 79 | {% if service.haproxy_acls is defined %} 80 | {% for key, value in service.haproxy_acls.items() %} 81 | acl {{ key }} {{ value.rule }} 82 | {% if not service.haproxy_frontend_only | default(false) %} 83 | use_backend {{ value.backend_name | default(service.haproxy_service_name) }}-back if {{ key }} 84 | {% endif %} 85 | {% endfor %} 86 | {% endif %} 87 | {% for entry in service.haproxy_maps | default([]) %} 88 | {{ entry }} 89 | {% endfor %} 90 | {% if (service.haproxy_ssl | default(false) | bool) and request_option == 'http' and (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses or (service.haproxy_ssl_all_vips | default(false) | bool and vip_address not in extra_lb_vip_addresses)) %} 91 | http-request add-header X-Forwarded-Proto https 92 | {% endif %} 93 | mode {{ service.haproxy_balance_type }} 94 | {% if (not service.haproxy_frontend_only | default(false)) or ((service.haproxy_default_backend is defined) and (service.haproxy_default_backend | length > 0)) %} 95 | default_backend {{ service.haproxy_default_backend | default(service.haproxy_service_name) }}-back 96 | {% endif %} 97 | {% for entry in (service.haproxy_frontend_raw|default([])) + haproxy_frontend_extra_raw %} 98 | {{ entry }} 99 | {% endfor %} 100 | -------------------------------------------------------------------------------- /templates/service.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | {% set request_option = service.haproxy_balance_type | default("http") -%} 4 | {% if service.haproxy_backend_port is not defined %} 5 | {% set haproxy_backend_port = service.haproxy_port %} 6 | {% else %} 7 | {% set haproxy_backend_port = service.haproxy_backend_port %} 8 | {% endif -%} 9 | {% if service.haproxy_check_port is not defined %} 10 | {% set haproxy_check_port = haproxy_backend_port %} 11 | {% else %} 12 | {% set haproxy_check_port = service.haproxy_check_port %} 13 | {% endif -%} 14 | 15 | {% if service.haproxy_bind is defined %} 16 | {% set vip_binds = service.haproxy_bind %} 17 | {% else %} 18 | {% set vip_binds = haproxy_vip_binds + extra_lb_vip_addresses %} 19 | {% endif %} 20 | 21 | {% if not service.haproxy_backend_only | default(false) %} 22 | {% for vip_bind in vip_binds %} 23 | {% if vip_bind is not string and vip_bind is mapping %} 24 | {% set vip_address = vip_bind['address'] %} 25 | {% set vip_interface = vip_bind['interface'] | default('') %} 26 | {% else %} 27 | {% set vip_address = vip_bind %} 28 | {% set vip_interface = '' %} 29 | {% endif %} 30 | {% if service.haproxy_redirect_http_port is defined and service.haproxy_ssl %} 31 | {% if (loop.index == 1 or service.haproxy_ssl_all_vips | default(false) | bool) %} 32 | 33 | frontend {{ service.haproxy_service_name }}-redirect-front-{{ loop.index }} 34 | bind {{ vip_address }}:{{ service.haproxy_redirect_http_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} 35 | mode http 36 | redirect scheme {{ service.haproxy_redirect_scheme | default('https if !{ ssl_fc }') }} 37 | {% if service.haproxy_frontend_acls is defined %} 38 | {% for key, value in service.haproxy_frontend_acls.items() %} 39 | acl {{ key }} {{ value.rule }} 40 | use_backend {{ value.backend_name | default(service.haproxy_service_name) }}-back if {{ key }} 41 | {% endfor %} 42 | {% for entry in haproxy_frontend_redirect_extra_raw %} 43 | {{ entry }} 44 | {% endfor %} 45 | {% endif %} 46 | {% endif %} 47 | {% endif %} 48 | 49 | {# service-redirect.j2 allows frontend to handle both HTTP and HTTPS connections. #} 50 | {# This is especially useful during HTTP->HTTPS service endpoint transition. #} 51 | {% if service.haproxy_accept_both_protocols | default(false) %} 52 | {% include 'service-redirect.j2' %} 53 | {% else %} 54 | {% set haproxy_ssl_path=haproxy_ssl_cert_path + "/haproxy_" + (haproxy_host | default(ansible_facts['hostname'])) + "-" + ((vip_interface is truthy) | ternary(vip_address ~ '-' ~ vip_interface, vip_address)) + ".pem" %} 55 | frontend {{ service.haproxy_service_name }}-front-{{ loop.index }} 56 | bind {{ vip_address }}:{{ service.haproxy_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} {% if (service.haproxy_ssl | default(false) | bool) and (request_option == "http") and (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses or (service.haproxy_ssl_all_vips | default(false) | bool and vip_address not in extra_lb_vip_addresses)) %}ssl crt {{ service.haproxy_ssl_path | default(haproxy_ssl_path) }}{% if service.haproxy_frontend_h2 | default(haproxy_frontend_h2) %} alpn h2,http/1.1{% endif %}{% endif %} 57 | 58 | {% if request_option == "http" %} 59 | option httplog 60 | option forwardfor except 127.0.0.0/8 61 | {% if service.haproxy_http_keepalive_mode is defined %} 62 | option {{ service.haproxy_http_keepalive_mode }} 63 | {% endif %} 64 | {% elif request_option == "tcp" %} 65 | option tcplog 66 | {% endif %} 67 | {% if service.haproxy_timeout_client is defined %} 68 | timeout client {{ service.haproxy_timeout_client }} 69 | {% endif %} 70 | {% if service.haproxy_allowlist_networks is defined %} 71 | acl allow_list src 127.0.0.1/8 {{ service.haproxy_allowlist_networks | join(' ') }} 72 | tcp-request content accept if allow_list 73 | tcp-request content reject 74 | {% endif %} 75 | {% if service.haproxy_acls is defined %} 76 | {% for key, value in service.haproxy_acls.items() %} 77 | acl {{ key }} {{ value.rule }} 78 | {% if not service.haproxy_frontend_only | default(false) %} 79 | use_backend {{ value.backend_name | default(service.haproxy_service_name) }}-back if {{ key }} 80 | {% endif %} 81 | {% endfor %} 82 | {% endif %} 83 | {% for entry in service.haproxy_maps | default([]) %} 84 | {{ entry }} 85 | {% endfor %} 86 | {% if (service.haproxy_ssl | default(false) | bool) and request_option == 'http' and (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses or (service.haproxy_ssl_all_vips | default(false) | bool and vip_address not in extra_lb_vip_addresses)) %} 87 | http-request add-header X-Forwarded-Proto https 88 | {% endif %} 89 | mode {{ request_option }} 90 | {% if (not service.haproxy_frontend_only | default(false)) or ((service.haproxy_default_backend is defined) and (service.haproxy_default_backend | length > 0)) %} 91 | default_backend {{ service.haproxy_default_backend | default(service.haproxy_service_name) }}-back 92 | {% endif %} 93 | {% for entry in (service.haproxy_frontend_raw|default([])) + haproxy_frontend_extra_raw %} 94 | {{ entry }} 95 | {% endfor %} 96 | {% endif %} 97 | {% endfor %} 98 | {% endif %} 99 | 100 | {% if not service.haproxy_frontend_only | default(false) %} 101 | {% set backend_options = service.haproxy_backend_options|default([]) %} 102 | {% set backend_arguments = service.haproxy_backend_arguments|default([]) %} 103 | 104 | backend {{ service.haproxy_service_name }}-back 105 | mode {{ request_option }} 106 | balance {{ service.haproxy_balance_alg|default("leastconn") }} 107 | {% if service.haproxy_timeout_server is defined %} 108 | timeout server {{ service.haproxy_timeout_server }} 109 | {% endif %} 110 | {% if (service.haproxy_stick_table_enabled | default(true) | bool) %} 111 | {% set stick_table = service.haproxy_stick_table|default( haproxy_stick_table | default([])) %} 112 | {% for entry in stick_table %} 113 | {{ entry }} 114 | {% endfor %} 115 | {% endif %} 116 | {% if request_option == "http" %} 117 | option forwardfor 118 | {% endif %} 119 | {% for option in backend_options %} 120 | option {{ option }} 121 | {% endfor %} 122 | {% for argument in backend_arguments %} 123 | {{ argument }} 124 | {% endfor %} 125 | {% set backend_httpcheck_options = service.haproxy_backend_httpcheck_options|default([]) %} 126 | {% if backend_httpcheck_options %} 127 | option httpchk 128 | {% for option in backend_httpcheck_options %} 129 | http-check {{ option }} 130 | {% endfor %} 131 | {% endif %} 132 | 133 | 134 | {% for host_name in service.haproxy_backend_nodes %} 135 | {% set __ip_addr = host_name.ip_addr | default(hostvars[host_name]['ansible_host']) %} 136 | {% set __host_name = host_name.name | default(host_name) | string %} 137 | {% set __backend_port = host_name.backend_port | default(haproxy_backend_port) | string %} 138 | {% set __check_port = host_name.check_port | default(haproxy_check_port) | string %} 139 | {% set entry = [] %} 140 | {% set _ = entry.append("server") %} 141 | {% set _ = entry.append(__host_name) %} 142 | {% set _ = entry.append(__ip_addr + ":" + __backend_port) %} 143 | {% set _ = entry.append("check") %} 144 | {% set _ = entry.append("port") %} 145 | {% set _ = entry.append(__check_port) %} 146 | {% set _ = entry.append("inter") %} 147 | {% set _ = entry.append(service.interval | default(haproxy_interval) | string) %} 148 | {% set _ = entry.append("rise") %} 149 | {% set _ = entry.append(service.backend_rise | default(haproxy_rise) | string) %} 150 | {% set _ = entry.append("fall") %} 151 | {% set _ = entry.append(service.backend_fall | default(haproxy_fall) | string) %} 152 | {% if service.haproxy_backend_ssl | default(False) %} 153 | {% set _ = entry.append("ssl") %} 154 | {% if service.haproxy_backend_ssl_check | default(service.haproxy_backend_ssl) %} 155 | {% set _ = entry.append("check-ssl") %} 156 | {% endif %} 157 | {% if service.haproxy_backend_ca | default(False) %} 158 | {% set _ = entry.append("ca-file") %} 159 | {% set _ = entry.append(service.haproxy_backend_ca is string | ternary(service.haproxy_backend_ca, haproxy_system_ca)) %} 160 | {% else %} 161 | {% set _ = entry.append("verify none") %} 162 | {% endif %} 163 | {% if service.haproxy_backend_h2 | default(haproxy_backend_h2) and request_option == "http" %} 164 | {% set _ = entry.append("alpn h2,http/1.1") %} 165 | {% endif %} 166 | {% else %} 167 | {% if service.haproxy_backend_h2 | default(haproxy_backend_h2) and request_option == "http" %} 168 | {% set _ = entry.append("proto h2") %} 169 | {% endif %} 170 | {% endif %} 171 | {% set backend_server_options = service.haproxy_backend_server_options|default([]) %} 172 | {% for option in backend_server_options %} 173 | {% set _ = entry.append(option) %} 174 | {% endfor %} 175 | {% set backend_per_server_options = host_name.backend_server_options|default([]) %} 176 | {% for option in backend_per_server_options %} 177 | {% set _ = entry.append(option) %} 178 | {% endfor %} 179 | {{ entry | join(' ') }} 180 | {% endfor %} 181 | 182 | {% for host_name in service.haproxy_backup_nodes | default([]) %} 183 | {% set __ip_addr = host_name.ip_addr | default(hostvars[host_name]['ansible_host']) %} 184 | {% set __host_name = host_name.name | default(host_name) | string %} 185 | {% set __backend_port = host_name.backend_port | default(haproxy_backend_port) | string %} 186 | {% set __check_port = host_name.check_port | default(haproxy_check_port) | string %} 187 | {% set entry = [] %} 188 | {% set _ = entry.append("server") %} 189 | {% set _ = entry.append(__host_name) %} 190 | {% set _ = entry.append(__ip_addr + ":" + __backend_port) %} 191 | {% set _ = entry.append("check") %} 192 | {% set _ = entry.append("port") %} 193 | {% set _ = entry.append(__check_port) %} 194 | {% set _ = entry.append("inter") %} 195 | {% set _ = entry.append(service.interval | default(haproxy_interval) | string) %} 196 | {% set _ = entry.append("rise") %} 197 | {% set _ = entry.append(service.backup_rise | default(haproxy_rise) | string) %} 198 | {% set _ = entry.append("fall") %} 199 | {% set _ = entry.append(service.backup_fall | default(haproxy_fall) | string) %} 200 | {% set _ = entry.append("backup") %} 201 | {% if service.haproxy_backend_ssl | default(False) %} 202 | {% set _ = entry.append("ssl") %} 203 | {% if service.haproxy_backend_ssl_check | default(service.haproxy_backend_ssl) %} 204 | {% set _ = entry.append("check-ssl") %} 205 | {% endif %} 206 | {% if service.haproxy_backend_ca | default(False) %} 207 | {% set _ = entry.append("ca-file") %} 208 | {% set _ = entry.append(service.haproxy_backend_ca is string | ternary(service.haproxy_backend_ca, haproxy_system_ca)) %} 209 | {% else %} 210 | {% set _ = entry.append("verify none") %} 211 | {% endif %} 212 | {% endif %} 213 | {% set backend_server_options = service.haproxy_backend_server_options|default([]) %} 214 | {% for option in backend_server_options %} 215 | {% set _ = entry.append(option) %} 216 | {% endfor %} 217 | {% set backend_per_server_options = host_name.backend_server_options|default([]) %} 218 | {% for option in backend_per_server_options %} 219 | {% set _ = entry.append(option) %} 220 | {% endfor %} 221 | {{ entry | join(' ') }} 222 | {% endfor %} 223 | {% endif %} 224 | -------------------------------------------------------------------------------- /tests/ansible-role-requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: apt_package_pinning 3 | src: https://opendev.org/openstack/openstack-ansible-apt_package_pinning 4 | scm: git 5 | version: master 6 | -------------------------------------------------------------------------------- /tests/host_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2017, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ansible_host: 127.0.0.1 16 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | localhost 3 | 4 | [haproxy_all] 5 | localhost 6 | -------------------------------------------------------------------------------- /tests/test-vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | external_lb_vip_address: 127.0.0.1 3 | internal_lb_vip_address: 127.0.0.1 4 | haproxy_service_configs: 5 | - service: 6 | haproxy_service_name: test_group 7 | haproxy_backend_nodes: "{{ groups['haproxy_all'] | default([]) }}" 8 | haproxy_port: 8180 9 | haproxy_backend_port: 22 10 | haproxy_ssl: False 11 | haproxy_balance_type: tcp 12 | haproxy_backend_options: 13 | - tcp-check 14 | - service: 15 | haproxy_service_name: test_list 16 | haproxy_backend_nodes: 17 | - name: "localhost" 18 | ip_addr: "127.0.0.1" 19 | haproxy_port: 8181 20 | haproxy_backend_port: 22 21 | haproxy_ssl: False 22 | haproxy_balance_type: tcp 23 | haproxy_backend_options: 24 | - tcp-check 25 | - service: 26 | haproxy_service_name: test_backend_ssl 27 | haproxy_backend_nodes: "{{ groups['haproxy_all'] | default([]) }}" 28 | haproxy_port: 8180 29 | haproxy_backend_port: 443 30 | haproxy_backend_ssl: True 31 | haproxy_backend_ca: False 32 | haproxy_ssl: False 33 | haproxy_balance_type: http 34 | - service: 35 | haproxy_service_name: test_absent_service 36 | haproxy_backend_nodes: 37 | - name: "localhost" 38 | ip_addr: "127.0.0.1" 39 | haproxy_port: 65535 40 | haproxy_balance_type: tcp 41 | state: "{{ absent_service_state }}" 42 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2016, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - name: Playbook for role testing 17 | hosts: localhost 18 | connection: local 19 | user: root 20 | become: true 21 | vars_files: 22 | - test-vars.yml 23 | tasks: 24 | - name: Create marker file for idempotence 25 | copy: 26 | content: mark 27 | dest: /tmp/haproxy_pass1 28 | register: haproxy_pass1 29 | 30 | - name: Set fact for idempotence test 31 | set_fact: 32 | idempotence_pass_1: "{{ haproxy_pass1 is changed }}" 33 | 34 | - name: Set fact for absent service state 35 | set_fact: 36 | absent_service_state: "{{ (haproxy_pass1 is changed) | ternary('present', 'absent') }}" 37 | 38 | - name: Run the haproxy_server role 39 | include_role: 40 | name: "haproxy_server" 41 | 42 | - name: Run role again on first pass 43 | when: 44 | - "idempotence_pass_1 | bool" 45 | block: 46 | - name: Ensure the absent service is present 47 | stat: 48 | path: "/etc/haproxy/conf.d/test_absent_service" 49 | register: absent_services 50 | failed_when: not absent_services.stat.exists 51 | 52 | - name: Set fact for absent service state 53 | set_fact: 54 | absent_service_state: "absent" 55 | 56 | - name: Run the haproxy_server role (again) 57 | include_role: 58 | name: "haproxy_server" 59 | 60 | - name: Ensure the absent service is missing 61 | stat: 62 | path: "/etc/haproxy/conf.d/test_absent_service" 63 | register: absent_services 64 | when: 65 | - "not (idempotence_pass_1 | bool)" 66 | failed_when: absent_services.stat.exists 67 | 68 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.1 3 | skipsdist = True 4 | envlist = docs,linters,functional 5 | ignore_basepython_conflict = True 6 | 7 | [testenv] 8 | basepython = python3 9 | usedevelop = False 10 | install_command = 11 | pip install -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} {opts} {packages} 12 | commands = 13 | /usr/bin/find . -type f -name "*.pyc" -delete 14 | passenv = 15 | COMMON_TESTS_PATH 16 | HOME 17 | http_proxy 18 | HTTP_PROXY 19 | https_proxy 20 | HTTPS_PROXY 21 | no_proxy 22 | NO_PROXY 23 | TESTING_BRANCH 24 | TESTING_HOME 25 | USER 26 | allowlist_externals = 27 | bash 28 | setenv = 29 | PYTHONUNBUFFERED=1 30 | ROLE_NAME=haproxy_server 31 | TEST_IDEMPOTENCE=true 32 | VIRTUAL_ENV={envdir} 33 | WORKING_DIR={toxinidir} 34 | 35 | [testenv:docs] 36 | deps = -r{toxinidir}/doc/requirements.txt 37 | commands = 38 | bash -c "rm -rf doc/build" 39 | doc8 doc 40 | sphinx-build -W --keep-going -b html doc/source doc/build/html 41 | 42 | [testenv:pdf-docs] 43 | deps = {[testenv:docs]deps} 44 | allowlist_externals = 45 | make 46 | commands = 47 | sphinx-build -W --keep-going -b latex doc/source doc/build/pdf 48 | make -C doc/build/pdf 49 | 50 | [doc8] 51 | # Settings for doc8: 52 | extensions = .rst 53 | 54 | [testenv:releasenotes] 55 | deps = -r{toxinidir}/doc/requirements.txt 56 | commands = 57 | sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html 58 | 59 | # environment used by the -infra templated docs job 60 | [testenv:venv] 61 | commands = 62 | {posargs} 63 | 64 | [testenv:pep8] 65 | commands = 66 | bash -c "{toxinidir}/tests/common/test-pep8.sh" 67 | 68 | [flake8] 69 | # Ignores the following rules due to how ansible modules work in general 70 | # F403 'from ansible.module_utils.basic import *' used; 71 | # unable to detect undefined names 72 | ignore=F403 73 | 74 | [testenv:bashate] 75 | commands = 76 | bash -c "{toxinidir}/tests/common/test-bashate.sh" 77 | 78 | [testenv:ansible-syntax] 79 | commands = 80 | bash -c "{toxinidir}/tests/common/test-ansible-syntax.sh" 81 | 82 | [testenv:ansible-lint] 83 | commands = 84 | bash -c "{toxinidir}/tests/common/test-ansible-lint.sh" 85 | 86 | [testenv:functional] 87 | commands = 88 | bash -c "{toxinidir}/tests/common/test-ansible-functional.sh" 89 | 90 | [testenv:linters] 91 | commands = 92 | bash -c "{toxinidir}/tests/common/test-ansible-env-prep.sh" 93 | {[testenv:pep8]commands} 94 | {[testenv:bashate]commands} 95 | {[testenv:ansible-lint]commands} 96 | {[testenv:ansible-syntax]commands} 97 | -------------------------------------------------------------------------------- /vars/debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2016, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ## APT Cache options 17 | cache_timeout: 600 18 | 19 | haproxy_distro_packages: 20 | - haproxy 21 | - "{{ (ansible_facts['distribution_release'] == 'bookworm' or 22 | ansible_facts['distribution_release'] == 'noble') 23 | | ternary('netcat-openbsd', 'netcat') }}" # Used for the Ansible haproxy module 24 | - psmisc 25 | - vim-haproxy 26 | 27 | haproxy_distro_certbot_packages: 28 | - certbot 29 | 30 | # Set system CA store which can be used to verify backends 31 | haproxy_system_ca: /etc/ssl/certs/ca-certificates.crt 32 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2021, City Network International AB 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | _haproxy_vip_binds: | 17 | {% set vip_binds = [{'address': haproxy_bind_external_lb_vip_address, 'interface': haproxy_bind_external_lb_vip_interface, 'type': 'external'}] %} 18 | {% if haproxy_bind_internal_lb_vip_address != haproxy_bind_external_lb_vip_address or 19 | haproxy_bind_external_lb_vip_interface != haproxy_bind_internal_lb_vip_interface %} 20 | {% set _ = vip_binds.append({'address': haproxy_bind_internal_lb_vip_address, 'interface': haproxy_bind_internal_lb_vip_interface, 'type': 'internal'}) %} 21 | {% endif %} 22 | {% for vip_address in extra_lb_tls_vip_addresses %} 23 | {% set _ = vip_binds.append({'address': vip_address, 'type': 'external'}) %} 24 | {% endfor %} 25 | {{ vip_binds }} 26 | 27 | _haproxy_pki_certificates: | 28 | {% set _pki_certs = [] %} 29 | {% for vip in haproxy_vip_binds %} 30 | {% set _vip_interface = vip['interface'] | default('') %} 31 | {% set san = ['DNS:' ~ ansible_facts['hostname'], 'DNS:' ~ ansible_facts['fqdn']] %} 32 | {% if vip['address'] != '*' %} 33 | {% set _ = san.append((vip['address'] | ansible.utils.ipaddr) | ternary('IP:', 'DNS:') ~ vip['address']) %} 34 | {% endif %} 35 | {% if vip['address'] == haproxy_bind_internal_lb_vip_address and not (internal_lb_vip_address | ansible.utils.ipaddr) %} 36 | {% set _ = san.append('DNS:' ~ internal_lb_vip_address) %} 37 | {% endif %} 38 | {% if vip['address'] == haproxy_bind_external_lb_vip_address and not (external_lb_vip_address | ansible.utils.ipaddr) %} 39 | {% set _ = san.append('DNS:' ~ external_lb_vip_address) %} 40 | {% endif %} 41 | {% for record in vip.get('pki_san_records', []) %} 42 | {% set _ = san.append((record | ansible.utils.ipaddr) | ternary('IP:', 'DNS:') ~ record) %} 43 | {% endfor %} 44 | {% set _ = _pki_certs.append( 45 | { 46 | 'name': 'haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (_vip_interface is truthy) | ternary(vip['address'] ~ '-' ~ _vip_interface, vip['address']), 47 | 'provider': 'ownca', 48 | 'cn': ansible_facts['hostname'], 49 | 'san': san | join(','), 50 | 'signed_by': haproxy_pki_intermediate_cert_name, 51 | } 52 | ) %} 53 | {% endfor %} 54 | {{ _pki_certs }} 55 | 56 | _haproxy_pki_install_certificates: | 57 | {% set _pki_install = [] %} 58 | {% for vip in haproxy_vip_binds %} 59 | {% set _vip_interface = vip['interface'] | default('') %} 60 | {% set _cert_basename = '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (_vip_interface is truthy) | ternary( 61 | vip['address'] ~ '-' ~ _vip_interface, vip['address']) 62 | %} 63 | {% set _ = _pki_install.append( 64 | { 65 | 'src': haproxy_user_ssl_cert | default(haproxy_pki_certs_path ~ _cert_basename ~ '.crt'), 66 | 'dest': haproxy_ssl_temp_path ~ _cert_basename ~ '.crt', 67 | 'owner': 'root', 68 | 'group': 'root', 69 | 'mode': '0644' 70 | } 71 | ) 72 | %} 73 | {% set _ = _pki_install.append( 74 | { 75 | 'src': haproxy_user_ssl_key | default(haproxy_pki_keys_path ~ _cert_basename ~ '.key.pem'), 76 | 'dest': haproxy_ssl_temp_path ~ _cert_basename ~ '.key', 77 | 'owner': 'root', 78 | 'group': 'root', 79 | 'mode': '0644' 80 | } 81 | ) 82 | %} 83 | {# We need to put CA only when it's provided by user or internal CA is used and user certs are not provided #} 84 | {% if (haproxy_user_ssl_cert is not defined and haproxy_user_ssl_key is not defined) or haproxy_user_ssl_ca_cert is defined %} 85 | {% set _ = _pki_install.append( 86 | { 87 | 'src': haproxy_user_ssl_ca_cert | default(haproxy_pki_intermediate_cert_path), 88 | 'dest': haproxy_ssl_temp_path ~ _cert_basename ~ '-ca.crt', 89 | 'owner': 'root', 90 | 'group': 'root', 91 | 'mode': '0644' 92 | }) 93 | %} 94 | {% endif %} 95 | {% endfor %} 96 | {{ _pki_install }} 97 | 98 | # In case CSP is enabled, on newer haproxy versions, header size 99 | # fill more than bufsize-maxrewrite, which results in 500 100 | # See: https://github.com/haproxy/haproxy/issues/1597 101 | _haproxy_default_tuning_params: 102 | tune.maxrewrite: 1280 103 | -------------------------------------------------------------------------------- /vars/redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2016, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | haproxy_distro_packages: 17 | - haproxy 18 | - nc # Used for the Ansible haproxy module 19 | 20 | # Set system CA store which can be used to verify backends 21 | haproxy_system_ca: /etc/pki/tls/certs/ca-bundle.crt 22 | -------------------------------------------------------------------------------- /zuul.d/project.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright 2017, Rackspace US, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - project: 17 | templates: 18 | - openstack-ansible-linters-jobs 19 | - openstack-ansible-deploy-infra_lxc-jobs 20 | - openstack-ansible-deploy-aio_metal-jobs 21 | - openstack-ansible-upgrade-infra_lxc-jobs 22 | - check-requirements 23 | - publish-openstack-docs-pti 24 | - build-release-notes-jobs-python3 25 | --------------------------------------------------------------------------------