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.

Readme

Juju charm haproxy

HAProxy is a free, very fast and reliable solution offering high availability, load balancing, and proxying for TCP and HTTP-based applications. It is particularly suited for web sites crawling under very high loads while needing persistence or Layer7 processing. Supporting tens of thousands of connections is clearly realistic with todays hardware. Its mode of operation makes its integration into existing architectures very easy and riskless, while still offering the possibility not to expose fragile web servers to the Net.

How to deploy the charm

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

Reverseproxy 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.

2) 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_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 listen stanza. service-options and server_options will be overwritten, so ensure they are set uniformly on all services with the same name.

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

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.

Testing

This charm has a simple unit-test program. Please expand it and make sure new changes are covered by simple unit tests. To run the unit tests:

sudo apt-get install python-mocker
sudo apt-get install python-twisted-core
cd hooks; trial test_hooks

TODO:

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

Configuration

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
default_timeouts
(string) Default timeouts
queue 1000, connect 1000, client 1000, server 1000
monitoring_allowed_cidr
(string) CIDR allowed ( multiple CIDRs separated by space ) access to the monitoring interface.
127.0.0.1/32
global_log
(string) Global log line ( multiples ... comma separated list )
127.0.0.1 local0, 127.0.0.1 local1 notice
global_spread_checks
(int)
monitoring_port
(int) Default monitoring port
10000
global_debug
(boolean) Debug or not
default_options
(string) Default options
httplog, dontlognull
global_quiet
(boolean) Quiet
global_user
(string) User
haproxy
monitoring_username
(string) Monitoring username
haproxy
enable_monitoring
(boolean) Enable monitoring
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
global_group
(string) Group
haproxy
monitoring_stats_refresh
(int) Monitoring interface refresh interval (in seconds)
3
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
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.
- service_name: haproxy_service service_host: "0.0.0.0" service_port: 80 service_options: [balance leastconn] server_options: maxconn 100
global_maxconn
(int) Sets the maximum per-process number of concurrent connections to <number>.
4096
default_log
(string) Default log
global