ansible-role-docker-swarm/templates/haproxy.cfg.j2

340 lines
15 KiB
Django/Jinja

#
# https://www.haproxy.com/blog/haproxy-on-docker-swarm-load-balancing-and-dns-service-discovery/
#
global
log fd@2 local2 {{ docker_swarm_haproxy_loglevel }}
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn {{ haproxy_maxconns }}
user haproxy
group haproxy
stats socket {{ haproxy_admin_socket }} expose-fd listeners level admin
master-worker
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# https://ssl-config.mozilla.org/#server=haproxy&version=2.2&config=intermediate&openssl=1.1.1d&guideline=5.6
tune.ssl.default-dh-param 2048
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
resolvers docker
nameserver dns1 127.0.0.11:53
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 10s
hold refused 10s
hold nx 10s
hold timeout 10s
hold valid 10s
hold obsolete 10s
defaults
timeout connect 10s
timeout client 30s
timeout server 30s
log global
monitor-uri /_haproxy_health_check
timeout http-keep-alive {{ haproxy_global_keepalive_timeout }}
timeout connect {{ haproxy_connect_timeout }}
timeout client {{ haproxy_client_timeout }}
timeout server {{ haproxy_server_timeout }}
timeout check {{ haproxy_check_timeout }}
timeout http-request 10s # slowloris protection
default-server inter 3s fall 2 rise 2 slowstart 60s
option dontlognull
option log-health-checks
# Needed to preserve the stick tables
peers mypeers
peer local_haproxy 127.0.0.1:1024
listen stats
{% if docker_swarm_haproxy_ipv4_only %}
bind 0.0.0.0:{{ haproxy_admin_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1
{% elif docker_swarm_haproxy_ipv6_only %}
bind :::{{ haproxy_admin_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1
{% else %}
bind *:{{ haproxy_admin_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1
{% endif %}
mode http
http-request use-service prometheus-exporter if { path /metrics }
option httplog
stats enable
stats uri /
stats realm HAProxy\ Statistics
stats auth admin:{{ haproxy_admin_pwd }}
stats refresh 15s
stats show-legends
stats show-node
listen local_stats
bind 127.0.0.1:8881
mode http
option httplog
stats enable
stats uri /
stats realm HAProxy\ Statistics
{% if docker_swarm_haproxy_plain_http_listener %}
frontend http_{{ docker_swarm_haproxy_plain_http_port }}
{% if docker_swarm_haproxy_ipv4_only %}
bind 0.0.0.0:{{ docker_swarm_haproxy_plain_http_port }} {% if docker_swarm_haproxy_accept_proxy %}accept-proxy{% endif %}
{% elif docker_swarm_haproxy_ipv6_only %}
bind :::{{ docker_swarm_haproxy_plain_http_port }} {% if docker_swarm_haproxy_accept_proxy %}accept-proxy{% endif %}
{% else %}
bind *:{{ docker_swarm_haproxy_plain_http_port }} {% if docker_swarm_haproxy_accept_proxy %}accept-proxy{% endif %}
{% endif %}
mode http
option http-keep-alive
option httplog
option forwardfor
{% for srv in docker_swarm_haproxy_plain_http_services %}
acl {{ srv.acl_name }} {{ srv.acl_rule }}
{%if srv.acl_path_rule is defined %}acl {{ srv.acl_name }}_path {{ srv.acl_path_rule }}{% endif %}
{% if docker_swarm_haproxy_plain_http_global_acl_rules is defined %}
{% for rule in docker_swarm_haproxy_plain_http_global_acl_rules %}
acl {{rule.acl_name }} {{ rule.acl_args }}
{{ rule.http_action }}
{% endfor %}
{% endif %}
{% if srv.allowed_networks is defined %}
acl {{ srv.acl_name }}_nets src {% for net in srv.allowed_networks %} {{ net }}{% endfor %}
http-request deny if {{ srv.acl_name }} !{{ srv.acl_name }}_nets
{% endif %}
use_backend {{ srv.acl_name }}_bck if {{ srv.acl_name }} {%if srv.acl_path_rule is defined %}{{ srv.acl_name }}_path {% endif %}
{% endfor %}
{% endif %}
frontend http
{% if docker_swarm_haproxy_ipv4_only %}
bind 0.0.0.0:{{ https_port }} ssl crt {{ haproxy_cert_dir }}{% if docker_swarm_haproxy_http2_enabled %} alpn h2,http/1.1{% endif %}{% if docker_swarm_haproxy_accept_proxy %} accept-proxy{% endif %}
bind 0.0.0.0:{{ haproxy_default_port }} {% if docker_swarm_haproxy_accept_proxy %}accept-proxy{% endif %}
{% elif docker_swarm_haproxy_ipv6_only %}
bind :::{{ https_port }} ssl crt {{ haproxy_cert_dir }}{% if docker_swarm_haproxy_http2_enabled %} alpn h2,http/1.1{% endif %}{% if docker_swarm_haproxy_accept_proxy %} accept-proxy{% endif %}
bind :::{{ haproxy_default_port }} {% if docker_swarm_haproxy_accept_proxy %}accept-proxy{% endif %}
{% else %}
bind *:{{ https_port }} ssl crt {{ haproxy_cert_dir }}{% if docker_swarm_haproxy_http2_enabled %} alpn h2,http/1.1{% endif %}{% if docker_swarm_haproxy_accept_proxy %} accept-proxy{% endif %}
bind *:{{ haproxy_default_port }} {% if docker_swarm_haproxy_accept_proxy %}accept-proxy{% endif %}
{% endif %}
mode http
option http-keep-alive
option httplog
option forwardfor
# HSTS (63072000 seconds)
http-response set-header Strict-Transport-Security max-age=63072000
{% if docker_swarm_cluster_portainer_install %}
acl portainer_srv hdr(host) -i {{ docker_swarm_portainer_hostname }}
{% endif %}
{% for srv in docker_swarm_haproxy_additional_services %}
{% if srv.mode is defined and srv.mode == 'tcp' %}
{% else %}
acl {{ srv.acl_name }} {{ srv.acl_rule }}
{%if srv.acl_path_rule is defined %}acl {{ srv.acl_name }}_path {{ srv.acl_path_rule }}{% endif %}
{% if docker_swarm_haproxy_global_acl_rules is defined %}
{% for rule in docker_swarm_haproxy_global_acl_rules %}
acl {{rule.acl_name }} {{ rule.acl_args }}
{{ rule.http_action }}
{% endfor %}
{% endif %}
{% if srv.allowed_networks is defined %}
acl {{ srv.acl_name }}_nets src {% for net in srv.allowed_networks %} {{ net }}{% endfor %}
http-request deny if {{ srv.acl_name }} !{{ srv.acl_name }}_nets
{% endif %}
{% endif %}
{% endfor %}
{% if docker_swarm_cluster_portainer_install %}
use_backend portainer_bck if portainer_srv
{% endif %}
{% for srv in docker_swarm_haproxy_additional_services %}
{% if srv.mode is defined and srv.mode == 'tcp' %}
{% else %}
use_backend {{ srv.acl_name }}_bck if {{ srv.acl_name }} {%if srv.acl_path_rule is defined %}{{ srv.acl_name }}_path{% endif %}
{% endif %}
{% endfor %}
{% if docker_swarm_expose_api_via_haproxy %}
frontend docker_ft
{% if docker_swarm_haproxy_ipv4_only %}
{% if docker_swarm_haproxy_plain_http_api %}
bind 0.0.0.0:{{ docker_swarm_haproxy_swarm_port }} {% if docker_swarm_haproxy_accept_proxy and docker_swarm_api_accept_proxy %}accept-proxy{% endif %}
{% else %}
bind 0.0.0.0:{{ docker_swarm_haproxy_swarm_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1 {% if docker_swarm_haproxy_accept_proxy and docker_swarm_api_accept_proxy %}accept-proxy{% endif %}
{% endif %}
{% elif docker_swarm_haproxy_ipv6_only %}
{% if docker_swarm_haproxy_plain_http_api %}
bind :::{{ docker_swarm_haproxy_swarm_port }} {% if docker_swarm_haproxy_accept_proxy and docker_swarm_api_accept_proxy %}accept-proxy{% endif %}
{% else %}
bind :::{{ docker_swarm_haproxy_swarm_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1 {% if docker_swarm_haproxy_accept_proxy and docker_swarm_api_accept_proxy %}accept-proxy{% endif %}
{% endif %}
{% else %}
{% if docker_swarm_haproxy_plain_http_api %}
bind *:{{ docker_swarm_haproxy_swarm_port }} {% if docker_swarm_haproxy_accept_proxy and docker_swarm_api_accept_proxy %}accept-proxy{% endif %}
{% else %}
bind *:{{ docker_swarm_haproxy_swarm_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1 {% if docker_swarm_haproxy_accept_proxy and docker_swarm_api_accept_proxy %}accept-proxy{% endif %}
{% endif %}
{% endif %}
mode {{ docker_swarm_api_haproxy_mode }}
acl swarm_api hdr_dom(host) -i {{ docker_swarm_expose_api_hostname }}
acl swarm_api_allowed_nets src {% for net in docker_swarm_api_networks_acl %} {{ net }}{% endfor %}
http-request deny if swarm_api !swarm_api_allowed_nets
# The following variables must be set in the haproxy docker file
# http-request deny unless METH_GET || { env(POST) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/((stop)|(restart)|(kill)) } { env(ALLOW_RESTARTS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/post } { env(POST) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool }
# http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool }
# http-request deny
default_backend swarm_api_bck
{% endif %}
{% for srv in docker_swarm_haproxy_additional_services %}
{% if srv.mode is defined and srv.mode == 'tcp' %}
frontend {{ srv.acl_name }}
bind :{{ srv.service_port }}
mode {{ srv.mode }}
{% if srv.allowed_networks is defined %}
acl {{ srv.acl_name }}_nets src {% for net in srv.allowed_networks %} {{ net }}{% endfor %}
tcp-request connection reject if {{ srv.acl_name }} !{{ srv.acl_name }}_nets
{% endif %}
use_backend {{ srv.acl_name }}_bck
{% endif %}
{% endfor %}
#
# Backends
#
{% if docker_swarm_expose_api_via_haproxy %}
# swarm API
backend swarm_api_bck
mode http
{% if docker_swarm_api_check_availability %}
option httpchk
http-check send meth HEAD uri /version ver HTTP/1.1 hdr Host localhost
http-check expect rstatus (2|3)[0-9][0-9]
{% endif %}
server {{ docker_swarm_api_backend }}
{% endif %}
{% if docker_swarm_cluster_portainer_install %}
backend portainer_bck
mode http
# No need to check until portainer can be replicated
# option httpchk
# http-check send meth HEAD uri / ver HTTP/1.1 hdr Host localhost
# http-check expect rstatus (2|3)[0-9][0-9]
# balance roundrobin
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request redirect scheme https code 301 unless { ssl_fc }
server-template portainer- 1 portainer_portainer:{{ docker_swarm_portainer_http_port }} check resolvers docker init-addr libc,none
{% endif %}
{% for srv in docker_swarm_haproxy_additional_services %}
backend {{ srv.acl_name }}_bck
{% if srv.mode is defined and srv.mode == 'tcp' %}
mode tcp
balance {{ srv.balance_type | default('roundrobin') }}
server-template {{ srv.service_name }}- {{ srv.service_replica_num }} {{ srv.stack_name }}_{{ srv.service_name }}:{{ srv.service_port }} resolvers docker init-addr libc,none
{% else %}
mode http
option httpchk
balance {{ srv.balance_type | default('roundrobin') }}
{% if srv.http_check_enabled is defined and srv.http_check_enabled %}
http-check send {{ srv.http_check }}
http-check expect {{ srv.http_check_expect }}
{% endif %}
{% if srv.stick_sessions %}
{% if srv.stick_on_cookie %}
dynamic-cookie-key {{ srv.acl_name }}
cookie {{ srv.stick_cookie }} dynamic
{% else %}
stick on src
stick-table {{ srv.stick_table }} peers mypeers
{% endif %}
{% endif %}
{% if srv.http_redirect_to_https %}
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request redirect scheme https code 301 unless { ssl_fc }
{% endif %}
server-template {{ srv.service_name }}- {{ srv.service_replica_num }} {{ srv.stack_name }}_{{ srv.service_name }}:{{ srv.service_port }} {{ srv.backend_options | default('') }} {% if srv.http_check_enabled is defined and srv.http_check_enabled %}check {{ srv.check_options | default('') }}{% endif %} resolvers docker init-addr libc,none
{% endif %}
{% endfor %}
{% if docker_swarm_haproxy_plain_http_listener %}
{% for srv in docker_swarm_haproxy_plain_http_services %}
backend {{ srv.acl_name }}_bck
mode http
option httpchk
balance {{ srv.balance_type | default('roundrobin') }}
{% if srv.http_check_enabled is defined and srv.http_check_enabled %}
http-check send {{ srv.http_check }}
http-check expect {{ srv.http_check_expect }}
{% endif %}
{% if srv.stick_sessions %}
{% if srv.stick_on_cookie %}
dynamic-cookie-key {{ srv.acl_name }}
cookie {{ srv.stick_cookie }} dynamic
{% else %}
stick on src
stick-table {{ srv.stick_table }} peers mypeers
{% endif %}
{% endif %}
server-template {{ srv.service_name }}- {{ srv.service_replica_num }} {{ srv.stack_name }}_{{ srv.service_name }}:{{ srv.service_port }} {{ srv.backend_options | default('') }} {% if srv.http_check_enabled is defined and srv.http_check_enabled %}check {{ srv.check_options | default('') }}{% endif %} resolvers docker init-addr libc,none
{% endfor %}
{% endif %}