OpenStack-Ansible HAProxy server

This Ansible role installs the HAProxy Load Balancer service.

To clone or view the source code for this repository, visit the role repository for haproxy_server.

Default variables

# Validate Certificates when downloading hatop. May be set to "no" when proxy server
# is intercepting the certificates.
haproxy_hatop_download_validate_certs: yes

# Set the package install state for distribution packages
# Options are 'present' and 'latest'
haproxy_package_state: "latest"

## Haproxy Configuration
haproxy_rise: 3
haproxy_fall: 3
haproxy_interval: 12000

## Haproxy Stats
haproxy_stats_enabled: False
haproxy_stats_bind_address: 127.0.0.1
haproxy_stats_port: 1936
haproxy_stats_ssl: "{{ haproxy_ssl }}"
# haproxy_stats_ssl_cert_path: "{{ haproxy_ssl_cert_path }}/somecustomstatscert.pem"
# haproxy_stats_ssl_client_cert_ca: "{{ haproxy_ssl_cert_path }}/somecustomrootca.pem"
haproxy_username: admin
haproxy_stats_password: secrete
haproxy_stats_refresh_interval: 60
# Prometheus stats are supported from HAProxy v2
# Stats must be enabled above before this can be used
haproxy_stats_prometheus_enabled: False
# Pin stats gathering to one or more processes when using 'nbproc' tuning
# For permitted options see https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#3.1-stats%20bind-process
# haproxy_stats_process: all

# Default haproxy backup nodes to empty list so this doesn't have to be
# defined for each service.
haproxy_backup_nodes: []

# Configuration lines to write directly into all frontends
haproxy_frontend_extra_raw: []
haproxy_frontend_redirect_extra_raw: "{{ haproxy_frontend_extra_raw }}"

# Default values for enabling HTTP/2 support
# Note, that while HTTP/2 will be enabled on frontends that are covered with TLS,
# backends can be configured to use HTTP/2 regardless of TLS.
haproxy_frontend_h2: True
haproxy_backend_h2: False

haproxy_service_configs: []
# Example:
# haproxy_service_configs:
#   - haproxy_service_name: haproxy_all
#     haproxy_backend_nodes: "{{ groups['haproxy_all'][0] }}"
#     # haproxy_backup_nodes: "{{ groups['haproxy_all'][1:] }}"
#     haproxy_port: 80
#     haproxy_balance_type: http
#     haproxy_backend_options:
#       - "forwardfor"
#       - "httpchk"
#       - "httplog"
#     haproxy_backend_server_options:
#       - "inter 3000"                # a contrived example, there are many server config options possible
#     haproxy_acls:
#       allow_list:
#         rule: "src 127.0.0.1/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8"
#         backend_name: "mybackend"
#     haproxy_frontend_h2: True
#     haproxy_backend_h2: False
#     haproxy_frontend_acls:
#       letsencrypt-acl:
#         rule: "path_beg /.well-known/acme-challenge/"
#         backend_name: letsencrypt
#     haproxy_stick_table:
#       - "stick-table  type ipv6  size 256k  expire 10s  store http_err_rate(10s)"
#       - "http-request track-sc0 src"
#       - "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 }"
#       # https://www.haproxy.com/blog/haproxy-exposes-a-prometheus-metrics-endpoint/
#   - haproxy_service_name: prometheus-metrics
#     haproxy_port: 8404
#     haproxy_bind:
#       - '127.0.0.1'
#     haproxy_allowlist_networks: "{{ haproxy_allowlist_networks }}"
#     haproxy_frontend_only: True
#     haproxy_balance_type: "http"
#     haproxy_frontend_raw:
#       - 'http-request use-service prometheus-exporter if { path /metrics }'
#     haproxy_service_enabled: True

# HAProxy maps (unrelated keys are omitted but are required as the previous service example)
# Example:
# haproxy_service_configs:
#   - state: present                         # state 'absent' will remove map entries defined in this service
#     haproxy_service_enabled: true          # haproxy_service_enabled 'false' will remove map entries defined in this service
#     haproxy_service_name: "one"
#     haproxy_maps:
#       - 'use_backend %[req.hdr(host),lower,map(/etc/haproxy/route.map)]'
#     haproxy_map_entries:
#       - name: 'route'                      # this service contributes entries to the map called 'route'
#         order: 10                         # prefix the name of the map fragment wih this string to control ordering of the assembled map
#         entries:
#           - compute.example.com nova-api
#           - dashboard.example.com horizon
#   - haproxy_service_name: "two"
#   - haproxy_service_name: "three"
#     haproxy_map_entries:
#       - name: 'route'                     # this service contributes to the map called 'route'
#         entries:
#           - s3.example.com radosgw
#           - sso.example.com keycloak
#       - name: 'rate'                      # and also to the map called 'rate'
#         state: present                    # individual map entries can be removed with state 'absent'
#         entries:
#           - /api/foo 20
#           - /api/bar 40
#
# Results:
#
# /etc/haproxy/route.map
#    s3.example.com radosgw
#    sso.example.com keycloak
#    compute.example.com nova-api
#    dashboard.example.com horizon
#
# /etc/haproxy/rate.map
#    /api/foo 20
#    /api/bar 40

galera_monitoring_user: monitoring
haproxy_bind_on_non_local: False

## haproxy SSL
haproxy_ssl: true
haproxy_ssl_all_vips: false
haproxy_ssl_dh_param: 2048
haproxy_ssl_cert_path: /etc/haproxy/ssl
haproxy_ssl_bind_options: "ssl-min-ver TLSv1.2 prefer-client-ciphers"
haproxy_ssl_server_options: "ssl-min-ver TLSv1.2"
# TLS v1.2 and below
haproxy_ssl_cipher_suite_tls12: >-
  {{ haproxy_ssl_cipher_suite | default(ssl_cipher_suite_tls12 | default('ECDH+AESGCM:ECDH+CHACHA20:ECDH+AES256:ECDH+AES128:!aNULL:!SHA1:!AESCCM')) }}
# TLS v1.3
haproxy_ssl_cipher_suite_tls13: "{{ ssl_cipher_suite_tls13 | default('TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256') }}"

# haproxy self signed certificate

# Storage location for SSL certificate authority
haproxy_pki_dir: "{{ openstack_pki_dir | default('/etc/pki/haproxy-ca') }}"

# Delegated host for operating the certificate authority
haproxy_pki_setup_host: "{{ openstack_pki_setup_host | default('localhost') }}"

# Create a certificate authority if one does not already exist
haproxy_pki_create_ca: "{{ openstack_pki_authorities is not defined | bool }}"
haproxy_pki_regen_ca: ''
haproxy_pki_authorities:
  - name: "HAProxyRoot"
    country: "GB"
    state_or_province_name: "England"
    organization_name: "Example Corporation"
    organizational_unit_name: "IT Security"
    cn: "HAProxy Root CA"
    provider: selfsigned
    basic_constraints: "CA:TRUE"
    key_usage:
      - digitalSignature
      - cRLSign
      - keyCertSign
    not_after: "+3650d"
  - name: "HAProxyIntermediate"
    country: "GB"
    state_or_province_name: "England"
    organization_name: "Example Corporation"
    organizational_unit_name: "IT Security"
    cn: "HAProxy Intermediate CA"
    provider: ownca
    basic_constraints: "CA:TRUE,pathlen:0"
    key_usage:
      - digitalSignature
      - cRLSign
      - keyCertSign
    not_after: "+3650d"
    signed_by: "HAProxyRoot"

# Installation details for certificate authorities
haproxy_pki_install_ca:
  - name: "HAProxyRoot"
    condition: "{{ haproxy_pki_create_ca }}"

# HAProxy server certificate
haproxy_pki_keys_path: "{{ haproxy_pki_dir ~ '/certs/private/' }}"
haproxy_pki_certs_path: "{{ haproxy_pki_dir ~ '/certs/certs/' }}"
haproxy_pki_intermediate_cert_name: "{{ openstack_pki_service_intermediate_cert_name | default('HAProxyIntermediate') }}"
haproxy_pki_intermediate_cert_path: >-
  {{ haproxy_pki_dir ~ '/roots/' ~ haproxy_pki_intermediate_cert_name ~ '/certs/' ~ haproxy_pki_intermediate_cert_name ~ '.crt' }}
haproxy_pki_regen_cert: ''
haproxy_pki_certificates: "{{ _haproxy_pki_certificates }}"

# Installation details for SSL certificates
haproxy_pki_install_certificates: "{{ _haproxy_pki_install_certificates }}"

# activate letsencrypt option
haproxy_ssl_letsencrypt_enable: false
haproxy_ssl_letsencrypt_certbot_binary: 'certbot'
haproxy_ssl_letsencrypt_certbot_backend_port: 8888
haproxy_ssl_letsencrypt_pre_hook_timeout: 5
haproxy_ssl_letsencrypt_certbot_bind_address: "{{ ansible_host }}"
haproxy_ssl_letsencrypt_certbot_challenge: "http-01"
haproxy_ssl_letsencrypt_email: "example@example.com"
haproxy_ssl_letsencrypt_config_path: "/etc/letsencrypt/live"
haproxy_ssl_letsencrypt_setup_extra_params: ""
haproxy_ssl_letsencrypt_acl:
  letsencrypt-acl:
    rule: "path_beg /.well-known/acme-challenge/"
    backend_name: letsencrypt
# Use alternative CA that supports ACME, can be a public or private CA
# haproxy_ssl_letsencrypt_certbot_server: "https://acme-staging-v02.api.letsencrypt.org/directory"
haproxy_ssl_letsencrypt_domains:
  - "{{ external_lb_vip_address }}"

# hatop extra package URL and checksum
haproxy_hatop_download_url: "https://github.com/jhunt/hatop/archive/refs/tags/v0.8.2.tar.gz"
haproxy_hatop_download_checksum: "sha256:7fac1f593f92b939cfce34175b593e43862eee8e25db251d03a910b37721fc5d"

# Install hatop
haproxy_hatop_install: true

# The location where the extra packages are downloaded to
haproxy_hatop_download_path: "/opt/cache/files"

## haproxy default
# Set the number of retries to perform on a server after a connection failure
haproxy_retries: "3"
# Set the maximum inactivity time on the client side
haproxy_client_timeout: "50s"
# Set the maximum time to wait for a connection attempt to a server to succeed
haproxy_connect_timeout: "10s"
# Set the maximum allowed time to wait for a complete HTTP request
haproxy_http_request_timeout: "5s"
# Set the maximum inactivity time on the server side
haproxy_server_timeout: "50s"
# Set the HTTP keepalive mode to use
# Disable persistent connections by default because they can cause issues when the server side closes the connection
# at the same time a request is sent.
haproxy_keepalive_mode: 'httpclose'


## haproxy tuning params
haproxy_maxconn: 4096

# Parameters below should only be specified if necessary, defaults are programmed in the template
# haproxy_tuning_params:
#   nbproc: 1
#   tune.bufsize: 384000
#   tune.chksize: 16384
#   tune.comp_maxlevel: 1
#   tune.http_maxhdr: 101
#   tune.maxaccept: 64
#   tune.ssl_cachesize: 20000
#   tune.ssl_lifetime: 300
haproxy_tuning_params: {}

# Add extra VIPs to all services
extra_lb_vip_addresses: []

# Add extra TLS VIPs to all services
extra_lb_tls_vip_addresses: []

# Option to override which address haproxy binds to for external vip.
haproxy_bind_external_lb_vip_address: "{{ external_lb_vip_address }}"

# Option to override which address haproxy binds to for internal vip.
haproxy_bind_internal_lb_vip_address: "{{ internal_lb_vip_address }}"

# Option to define if you need haproxy to bind on specific interface.
haproxy_bind_external_lb_vip_interface:
haproxy_bind_internal_lb_vip_interface:

# Option to override haproxy frontend binds
# Example:
# haproxy_tls_vip_binds:
#   - address: '*'
#     interface: bond0
#   - address: '192.168.0.10'

haproxy_tls_vip_binds: "{{ _haproxy_tls_vip_binds }}"

# Make the log socket available to the chrooted filesystem
haproxy_log_socket: "/dev/log"
haproxy_log_mount_point: "/var/lib/haproxy/dev/log"

# Ansible group name which should be used for distrtibuting self signed SSL Certificates
haproxy_ansible_group_name: haproxy_all

## security.txt
# When security risks in web services are discovered by independent security
# researchers who understand the severity of the risk, they often lack the
# channels to disclose them properly. As a result, security issues may be
# left unreported. security.txt defines a standard to help organizations
# define the process for security researchers to disclose security
# vulnerabilities securely. For more information see https://securitytxt.org/
# This content will be hosted at /security.txt and /.well-known/security.txt
haproxy_security_txt_dir: "/etc/haproxy"
haproxy_security_txt_headers: |
  HTTP/1.0 200 OK
  Cache-Control: no-cache
  Connection: close
  Content-Type: text/plain; charset=utf-8

haproxy_security_txt_content: ''
# haproxy_security_txt_content: |
#   # Please see https://securitytxt.org/ for details of the specification of this file

# Allows to copy any static file to the destination hosts
haproxy_static_files_default:
  - dest: "{{ haproxy_security_txt_dir }}/security.txt"
    content: "{{ haproxy_security_txt_headers + '\n' + haproxy_security_txt_content }}"
    condition: "{{ haproxy_security_txt_content is truthy }}"
haproxy_static_files_extra: []
haproxy_static_files: "{{ haproxy_static_files_default + haproxy_static_files_extra }}"

Required variables

None.

Dependencies

None.

Example playbook

---
- name: Install haproxy
  hosts: haproxy
  user: root
  roles:
    - role: haproxy_server
      tags:
        - haproxy-server
  vars:
    haproxy_service_configs:
      - haproxy_service_name: group_name
        haproxy_backend_nodes: "{{ groups['group_name'][0] }}"
        haproxy_backup_nodes: "{{ groups['group_name'][1:] }}"
        haproxy_port: 80
        haproxy_balance_type: http
        haproxy_backend_options:
          - "forwardfor"
          - "httpchk"
          - "httplog"
        haproxy_backend_arguments:
          - 'http-check expect string OK'