haproxy #41

  • By haproxy-team
  • Latest version (#41)
  • xenial, trusty
  • Stable

Description

HAProxy is a TCP/HTTP reverse proxy which is particularly suited for high availability environments. It features connection persistence through HTTP cookies, load balancing, header addition, modification, deletion both ways. It has request blocking capabilities and provides interface to display server status.

Overview

This charm deploys a reverse proxy in front of other servies. You can use this to load balance existing deployments.

Usage

juju deploy haproxy
juju deploy my-web-app
juju add-relation my-web-app:website haproxy:reverseproxy
juju add-unit my-web-app
...

Reverse Proxy Relation

The reverse proxy relation is used to distribute connections from one frontend
port to many backend services (typically different Juju units). You can use
haproxy just like this, but typically in a production service you would
frontend this service with apache2 to handle the SSL negotiation, etc. See
the "Website Relation" section for more information about that.

When your charm hooks into reverseproxy you have two general approaches
which can be used to notify haproxy about what services you are running.
1) Single-service proxying or 2) Multi-service or relation-driven proxying.

  1. Single-Service Proxying

In this case, your website relation will join underneath a single listen
stanza in haproxy. This stanza will have one service entry for each unit
connecting. By convention, this is typically called "website". The
following is an example of a relation-joined or changed hook:

#!/bin/bash
# hooks/website-relation-joined

relation-set "hostname=$(unit-get private-address)"
relation-set "port=80"

# Set an optional service name, allowing more config-based
# customization
relation-set "service_name=my_web_app"

If you set the service_name relation setting, the configuration services
yaml mapping will be consulted to lookup 3 other options based on service
name.

  • {service_name}_servers - sets the server line in the listen stanza
    explicitly.
  • {service_name}_server_options - Will append to the charm-generated
    server line for for each joining unit in the reverseproxy relation.
  • {service_name}_service_options - expected to be a list of strings. Will
    set each item as an option under the listen stanza.
  1. Relation-Driven Proxying

In this relation style, your charm should specify these relation settings
directly as relation variables when joining reverseproxy. Your charm's
website-relation-changed hook would look something like this:

#!/bin/bash
# hooks/website-relation-changed

host=$(unit-get private-address)
port=80

relation-set "services=
- { service_name: my_web_app,
    service_host: 0.0.0.0,
    service_port: 80,
    service_options: [mode http, balance leastconn],
    servers: [[my_web_app_1, $host, $port, option httpchk GET / HTTP/1.0],
              [... optionally more servers here ...]]}
- { ... optionally more services here ... }
"

Once set, haproxy will union multiple servers stanzas from any units
joining with the same service_name under one backend stanza, which will be
the default backend for the service (requests against the given service_port on
the haproxy unit will be forwarded to that backend). Note that service-options
and server_options will be overwritten, so ensure they are set uniformly on
all services with the same name.

If you need additional backends, possibly handling ACL-filtered requests, you
can add a 'backends' entry to a service stanza. For example in order to redirect
to a different backend all requests to URLs starting with '/foo', you could have:

relation-set "services=
- { service_name: my_web_app,
    service_host: 0.0.0.0,
    service_port: 80,
    service_options: [mode http, acl foo path_beg -i /foo, use_backend foo if foo],
    servers: [[my_web_app_1, $host, $port, option httpchk GET / HTTP/1.0],
              [... optionally more servers here ...]]
    backends:
    - { backend_name: foo,
        servers: [[my_web_app2, $host, $port2, option httpchk GET / HTTP/1.0],
                  [... optionally more servers here ...]]}}

In all cases if your service needs to know the public IP(s) of the haproxy unit(s)
it relates to, or the value of the default SSL certificate set on or generated by
the haproxy service, you can look for the 'public-address' and 'ssl_cert' keys
on your relation, which are set by the haproxy service as soon as it joins the
reverseproxy relation.

Website Relation

The website relation is the other side of haproxy. It can communicate with
charms written like apache2 that can act as a front-end for haproxy to take of
things like ssl encryption. When joining a service like apache2 on its
reverseproxy relation, haproxy's website relation will set an all_services
varaible that conforms to the spec layed out in the apache2 charm.

These settings can then be used when crafting your vhost template to make sure
traffic goes to the correct haproxy listener which will in turn forward the
traffic to the correct backend server/port

SSL Termination

You can turn on SSL termination by using the ssl_cert/ssl_key service configuration
options and then using the crts key in the services yaml, e.g.:

#!/bin/bash
# hooks/website-relation-changed

host=$(unit-get private-address)
port=80

relation-set "services=
- { service_name: my_web_app,
    service_options: [mode http, balance leastconn],
    crts: [DEFAULT]
    servers: [[my_web_app_1, $host, $port, option httpchk GET / HTTP/1.0],
              [... optionally more servers here ...]]}
- { ... optionally more services here ... }
"

where the DEFAULT keyword means use the certificate set with ssl_cert/ssl_key (or
alternatively you can inline different base64-encode certificates).

Note that in order to use SSL termination you need haproxy 1.5 or later, which
is not available in stock trusty, but you can get it from trusty-backports setting
the source configuration option to backports or to whatever PPA/archive you
wish to use.

Development

The following steps are needed for testing and development of the charm,
but not for deployment:

sudo apt-get install python-software-properties
sudo add-apt-repository ppa:cjohnston/flake8
sudo apt-get update
sudo apt-get install python-mock python-flake8 python-nose python-nosexcover python-testtools charm-tools

To run the tests:

make build

... will run the unit tests, run flake8 over the source to warn about
formatting issues and output a code coverage summary of the 'hooks.py' module.

Known Limitations and Issues

  • Expand Single-Service section as I have not tested that mode fully.
  • Trigger website-relation-changed when the reverse-proxy relation changes

Configuration

Many of the haproxy settings can be altered via the standard juju configuration
settings. Please see the config.yaml file as each is fairly clearly documented.

statsd

This charm supports sending metrics to statsd.

This is done by setting config values (metrics_target being the primary one)
to a host/port of a (UDP) statsd server.

This could instead be done using a relation, but it is common to have
one statsd server that serves multiple environments. Once juju supports
cross-environment relations then that will be the best way to handle
this configuration, as it will work in either scenario.

peering_mode and the indirection layer

If you are going to spawn multiple haproxy units, you should pay special
attention to the peering_mode configuration option.

active-passive mode

The peering_mode option defaults to "active-passive" and in this mode, all
haproxy units ("peers") will proxy traffic to the first working peer (i.e. that
passes a basic layer4 check). What this means is that extra peers are working
as "hot spares", and so adding units doesn't add global bandwidth to the
haproxy layer.

In order to achieve this, the charm configures a new service in haproxy that
will simply forward the traffic to the first working peer. The haproxy service
that actually load-balances between the backends is renamed, and its port
number is increased by one.

For example, if you have 3 working haproxy units haproxy/0, haproxy/1 and
haproxy/2 configured to listen on port 80, in active-passive mode, and
haproxy/2 gets a request, the request is routed through the following path :

haproxy/2:80 ==> haproxy/0:81 ==> [backends]

In the same fashion, if haproxy/1 receives a request, it's routed in the following way :

haproxy/1:80 ==> haproxy/0:81 ==> [backends]

If haproxy/0 was to go down, then all the requests would be forwarded to the
next working peer, i.e. haproxy/1. In this case, a request received by
haproxy/2 would be routed as follows :

haproxy/2:80 ==> haproxy/1:81 ==> [backends]

This mode allows a strict control of the maximum number of connections the
backends will receive, and guarantees you'll have enough bandwidth to the
backends should an haproxy unit die, at the cost of having less overall
bandwidth to the backends.

active-active mode

If the peering_mode option is set to "active-active", then any haproxy unit
will be independant from each other and will simply load-balance the traffic to
the backends. In this case, the indirection layer described above is not
created in this case.

This mode allows increasing the bandwidth to the backends by adding additional
units, at the cost of having less control over the number of connections that
they will receive.

HAProxy Project Information

Configuration

ssl_key
(string) base64 encoded private key for the default SSL certificate. If ssl_cert is specified as SELFSIGNED or the installed haproxy package has no SSL support, this will be ignored.
global_default_bind_options
(string) Sets the default string describing the list of global SSL bind options. Use this to force or disable certain protocols like TLS 1.0 or SSL 3.0.
sysctl
(string) YAML-formatted list of sysctl values, e.g.: '{ net.ipv4.tcp_max_syn_backlog : 65536 }'
default_retries
(int) Set the number of retries to perform on a server after a connection failure. It is important to understand that this value applies to the number of connection attempts, not full requests. When a connection has effectively been established to a server, there will be no more retry. In order to avoid immediate reconnections to a server which is restarting, a turn-around timer of 1 second is applied before a retry occurs.
3
global_stats_socket
(boolean) Whether to enable the stats UNIX socket.
global_group
(string) Group
haproxy
monitoring_stats_refresh
(int) Monitoring interface refresh interval (in seconds)
3
default_options
(string) Default options
httplog, dontlognull
global_user
(string) User
haproxy
source
(string) Optional configuration to support use of additional sources such as: . - ppa:myteam/ppa - cloud:precise-proposed/folsom - http://my.archive.com/ubuntu main . The last option should be used in conjunction with the key configuration option.
nagios_context
(string) Used by the nrpe-external-master subordinate charm. A string that will be prepended to instance name to set the host name in nagios. So for instance the hostname would be something like: juju-postgresql-0 If you're running multiple environments with the same services in them this allows you to differentiate between them.
juju
monitoring_password
(string) Password to the monitoring interface ( if "changeme", a new password will be generated and displayed in juju-log )
changeme
default_mode
(string) Default mode
http
peering_mode
(string) Possible values : "active-passive", "active-active". This is only used if several units are spawned. In "active-passive" mode, all the units will forward traffic to the first working haproxy unit, which will then forward it to configured backends. In "active-active" mode, each unit will proxy the traffic directly to the backends. The "active-passive" mode gives a better control of the maximum connection that will be opened to a backend server.
active-passive
metrics_prefix
(string) Prefix for metrics. Special value $UNIT can be used to include the name of the unit in the prefix.
dev.$UNIT.haproxy
nagios_servicegroups
(string) A comma-separated list of nagios servicegroups. If left empty, the nagios_context will be used as the servicegroup.
default_timeouts
(string) Default timeouts
queue 20000, client 50000, connect 5000, server 50000
global_default_dh_param
(int) Sets the maximum size of the Diffie-Hellman parameters used for generating the ephemeral/temporary Diffie-Hellman key in case of DHE key exchange. Default value if 1024, higher values will increase the CPU load, and values greater than 1024 bits are not supported by Java 7 and earlier clients. This config key will be ignored if the installed haproxy package has no SSL support.
1024
global_log
(string) Global log line ( multiples ... comma separated list )
/dev/log local0, /dev/log local1 notice
global_spread_checks
(int) Sometimes it is desirable to avoid sending health checks to servers at exact intervals, for instance when many logical servers are located on the same physical server. With the help of this parameter, it becomes possible to add some randomness in the check interval between 0 and +/- 50%. A value between 2 and 5 seems to show good results.
monitoring_allowed_cidr
(string) CIDR allowed ( multiple CIDRs separated by space ) access to the monitoring interface.
127.0.0.1/32
monitoring_username
(string) Monitoring username
haproxy
default_log
(string) Default log
global
package_status
(string) The status of service-affecting packages will be set to this value in the dpkg database. Useful valid values are "install" and "hold".
install
key
(string) Key ID to import to the apt keyring to support use with arbitary source configuration from outside of Launchpad archives or PPA's.
services
(string) Services definition(s). Although the variable type is a string, this is interpreted in the charm as yaml. To use multiple services within the same haproxy instance, specify all of the variables (service_name, service_host, service_port, service_options, server_options) with a "-" before the first variable, service_name, as above. Service options is a comma separated list, server options will be appended as a string to the individual server lines for a given listen stanza. If your web application serves dynamic content based on users' login sessions, a visitor will experience unexpected behaviour if each request is proxied to a different backend web server. Session stickiness ensures that a visitor 'sticks' to the backend web server which served their first request. This is made possible by tagging each backend server with a cookie. Session are sticky by default. To turn off sticky sessions, remove the 'cookie SRVNAME insert' and 'cookie S{i}' stanzas from `service_options` and `server_options`.
- service_name: haproxy_service service_host: "0.0.0.0" service_port: 80 service_options: [balance leastconn, cookie SRVNAME insert] server_options: maxconn 100 cookie S{i} check
global_maxconn
(int) Sets the maximum per-process number of concurrent connections to <number>.
4096
metrics_target
(string) Destination for statsd-format metrics, format "host:port". If not present and valid, metrics disabled. Requires "enable_monitoring" to be set to true to work.
ssl_cert
(string) base64 encoded default SSL certificate. If the keyword 'SELFSIGNED' is used, the certificate and key will be autogenerated as self-signed. This is the certificate used by services configured using keyword 'DEFAULT' as SSL certificate. This config key will be ignored if the installed haproxy package has no SSL support.
global_default_bind_ciphers
(string) Sets the default string describing the list of cipher algorithms ("cipher suite") that are negotiated during the SSL/TLS handshake for all "bind" lines which do not explicitly define theirs. The format of the string is defined in "man 1 ciphers" from OpenSSL man pages, and can be for instance a string such as "AES:ALL:!aNULL:!eNULL:+RC4:@STRENGTH" (without quotes). Please check the "bind" keyword for more information. This config key will be ignored if the installed haproxy package has no SSL support.
ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:!DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:!DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:!CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
monitoring_port
(int) Default monitoring port
10000
global_debug
(boolean) Debug or not
metrics_sample_interval
(int) Period for metrics cron job to run in minutes
5
global_quiet
(boolean) Quiet
enable_monitoring
(boolean) Enable monitoring