Code Monkey home page Code Monkey logo

haproxy-consul's Introduction

haproxy-consul

Dynamic haproxy configuration using consul packed into a Docker container that weighs 18MB.

Table of Contents

Overview

This project combines Alpine Linux, consul template, and haproxy to create a proxy that forwards traffic to apps registered in Marathon and forwarded with marathon-consul.

How it works

First, you must set up a wildcard dns (using something like CloudFlare or xip.io). This means that if your domain is example.com, any request to a <name>.example.com will resolve to the IP of your haproxy container.

Inside the haproxy container, a header match is used to map <application>.example.com to the service registered in consul under application.

Building

docker build -t haproxy .

Running

Modes

haproxy-consul can run in two different modes: forwarding either consul services (the default) or Marathon apps. This behavior is controlled by the HAPROXY_MODE variable, which should be set to consul or marathon.

Reload configuration

It's possible to reload the HA proxy configuration without restarting the container itself. docker exec -it <container_id> bash reload.sh

consul Configuration

When HAPROXY_MODE is set to consul, haproxy-consul uses consul service names to set subdomains. No other configuration is required.

Marathon Configuration

When HAPROXY_MODE is set to marathon, haproxy-consul assumes that there will be app information in the marathon prefix of the Consul KV store. It was written to work with the information provided by marathon-consul.

By default, haproxy will forward all Marathon-assigned ports. So if you specify that your application should own port 10000 in the "ports" member of the app JSON, haproxy will open port 10000 to direct traffic to your app. This works with auto-assigned ports (ports set to 0), as well. This is all automatic, you don't need to think about it other than to pull the ports from Marathon.

However, if you want HTTP load balancing using the host header, you need a specify the following labels on your app:

{
    "id": "hello-rails",
    "cmd": "cd hello && bundle install && bundle exec unicorn -p $PORT",
    "mem": 100,
    "cpus": 1.0,
    "instances": 1,
    "uris": [
        "http://downloads.mesosphere.com/tutorials/RailsHello.tgz"
    ],
    "env": {
        "RAILS_ENV": "production"
    },
    "ports": [10000],
    "labels": {
        "HAPROXY_HTTP": "true",
        "HTTP_PORT_IDX_0_NAME": "hello_rails",
    }
}

In this example (available at examples/rails.json), the hello-rails application is assigned port 10000. This is different from the service or host port of the app; it is a global value that Marathon tracks. This means that haproxy-consul will forward all TCP traffic to port 10000 to the app workers.

When HAPROXY_HTTP is set to true and HTTP_PORT_IDX_0_NAME is set to a DNS-valid name Haproxy will forward all HTTP traffic with the host header (the name specified plus HAPROXY_DOMAIN) to the app workers. This extends to as many ports as you'd care to give it in the form HTTP_PORT_IDX_{port_number}_NAME.

This particular app results in something like the following haproxy configuration:

global
    maxconn 256
    debug

defaults
    mode tcp
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

# HTTP services
frontend www
    mode http
    bind *:80

    # files ACLs
    acl host_hello_rails hdr(host) -i hello_rails.haproxy.service.consul
    use_backend hello_rails_backend if host_hello_rails

# files backends
backend hello_rails_backend
    mode http
    server 1.2.3.4:49165 # TASK_RUNNING

# TCP services
listen hello-rails_10000
    mode tcp
    bind *:10000
    server task_id 1.2.3.4:41965 # TASK_RUNNING

Usage

If you don't want to configure wildcard dns, you can use xip.io. In this example, we are going to assume that the IP of your server is 180.19.20.21, then all domains in 180.19.20.21.xip.io will forward to your host.

Start the container as follows:

docker run --net=host --name=haproxy -d -e HAPROXY_DOMAIN=180.19.20.21.xip.io asteris/haproxy-consul

If you have wildcard DNS set up for your company (say at *.mycompany.com) use the following:

docker run --net=host --name=haproxy -d -e HAPROXY_DOMAIN=mycompany.com asteris/haproxy-consul

Now that it is set up, connect to an app:

curl -L http://myapp.mycompany.com

Or if you do not have a wildcard DNS:

curl -L http://myapp.180.19.20.21.xip.io

Options

If you want to override the config and template files, mount a volume and set the CONSUL_CONFIG environment variable before launch. In docker this can be accomplished with the -e option:

docker run -v /host/config:/my_config -e CONSUL_CONFIG=/my_config -net=host --name=haproxy -d -e HAPROXY_DOMAIN=mycompany.com asteris/haproxy-consul

If you need to have a root CA added so you can connect to Consul over SSL, mount a directory containing your root CA at /usr/local/share/ca-certificates/.

Configure using the following environment variables:

Variable Description Default
HAPROXY_DOMAIN The domain to match against haproxy.service.consul (for app.haproxy.service.consul).
HAPROXY_MODE forward consul service or Marathon apps consul (marathon also available, as described above)
HAPROXY_USESSL Enable the SSL frontend (see below) false
HAPROXY_STATS Enable Statistics UI on port 1936 (see below) false
HAPROXY_STATS_TITLE Change Statistics Title (see below) false
HAPROXY_STATS_URI Change Statistics URI (see below) false

consul-template variables:

Variable Description Default
CONSUL_TEMPLATE Location of consul-template bin /usr/local/bin/consul-template
CONSUL_CONNECT The consul connection consul.service.consul:8500
CONSUL_CONFIG File/directory for consul-template config /consul-template/config.d
CONSUL_LOGLEVEL Valid values are "debug", "info", "warn", and "err". debug
CONSUL_TOKEN The Consul API token

consul KV variables:

Variable Description Default
service/haproxy/maxconn maximum connections 256
service/haproxy/timeouts/connect connect timeout 5000ms
service/haproxy/timeouts/client client timeout 50000ms
service/haproxy/timeouts/server server timeout 50000ms

SSL Termination

If you wish to configure HAproxy to terminate incoming SSL connections, you must set the environment variable HAPROXY_USESSL=true, and mount your SSL certificate at /haproxy/ssl.crt - this file should contain both the SSL certificate and the private key to use (with no passphrase), in PEM format. You should also include any intermediate certificates in this bundle.

If you do not provide an SSL certificate at container runtime, a self-signed certificate will be generated for the value of *.HAPROXY_DOMAIN.

For example:

docker run -v /etc/ssl/wildcard.example.com.pem:/haproxy/ssl.crt -e HAPROXY_USESSL=true -e HAPROXY_DOMAIN=example.com --net=host --name=haproxy haproxy-consul

You can also force that all incoming connections are redirected to HTTPS, by setting HAPROXY_USESSL=force.

SSL termination is currently only available in 'consul' mode.

License

Released under an Apache 2.0 License. See LICENSE

haproxy-consul's People

Contributors

brianhicks avatar chazsconi avatar chrisaubuchon avatar corebug avatar jfautley avatar lalarsson avatar peterlamar avatar stevendborrelli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

haproxy-consul's Issues

Old instance stays alive

I had random issues of my load balancer returning 503 errors on available services. The configuration was ok, the server seemed fine. I couldn't grasp the cause of the issue until I noticed that several instances of haproxy were running in the container.

Here is a ps from within a failing container:

PID   USER     TIME   COMMAND
    1 root       0:00 {launch.sh} /bin/bash /launch.sh
    7 root       0:06 /usr/local/bin/consul-template -config /consul-template/config.d -log-level info -wait 2s:10s -consul 127.0.0.1:8500
   17 root       0:22 /usr/sbin/haproxy -D -p /var/run/haproxy.pid -f /haproxy/haproxy.cfg -sf
   21 root       0:00 /usr/sbin/haproxy -D -p /var/run/haproxy.pid -f /haproxy/haproxy.cfg -sf 17
   25 root       0:00 /usr/sbin/haproxy -D -p /var/run/haproxy.pid -f /haproxy/haproxy.cfg -sf 21
   26 root       0:00 sh
   31 root       0:00 ps

So the new instance, bearing the correct configuration, is living amoung the old instances. All of them are listening and answering to requests on 0.0.0.0:80. This gives random error because the correct instance could be the one to answer the call. But as time goes by, more zombie instances are living, making the correct answer more and more improbable.

I will try to fix this issue, so far I had to kill and start new containers for it to work...

Is this project still alive by the way ? I see PR waiting and the last commit is 4 months old...

Why HAPROXY_DOMAIN ?

I've been using haproxy-consul for some weeks now and I still don't get the requirement of specifying the HAPROXY_DOMAIN environment variable for consul use.

Wouldn't it be easier to just define the acl in the tmpl file like this:

acl host_{{ .Name }} hdr(host) -m beg {{ .Name }}.
use_backend {{ .Name }}_backend if host_{{ .Name }}

Notice the dot following the second occurrence of {{ .Name }}. It is here to ensure that services don't get confused (for example "mail" and "mailinglist").

I'm not sure though how this would work on the marathon counterpart but I don't believe HAPROXY_DOMAIN should be a requirement for at least consul.

I went even further and extended the tmpl file for multiple domains use, using consul's tags for storing the domains under the form <domain>_<tld>.

Here are the relevant parts:

# Generated automatically by consul-template

#tagged services, tag is use for domain information
{{ range $tag, $services := services | byTag }}
{{ range $services}}
    acl host_{{ .Name }}_{{ $tag}} hdr(host) -i {{ .Name }}.{{ $tag |replaceAll "_" "." }} 
    use_backend {{ .Name }}_{{$tag}}_backend if host_{{ .Name }}_{{ $tag }}
{{ end }}
{{ end }}

#not tagged services, the url starting part is used
{{range services}}{{if eq (.Tags |len) 0 }}
    acl host_{{ .Name }} hdr(host) -m beg {{ .Name }}.
    use_backend {{ .Name }}_backend if host_{{ .Name }}
{{ end }}{{ end }}

#backend definition for tagged services
{{ range $tag, $services := services | byTag }}{{range $services}}
backend {{ .Name }}_{{$tag}}_backend{{ range service (print $tag "." .Name) }}
   server {{ .Node }} {{ .Address }}:{{ .Port }}{{ end }}
{{ end }}{{ end }}

#backend definition for untagged services
{{ range services }}{{ if eq (.Tags |len) 0 }}
backend {{ .Name }}_backend{{ range service .Name }}
   server {{ .Node }} {{ .Address }}:{{ .Port }}{{ end }}
{{ end }}{{ end }}

There is a lot of room for improvement ;-)

Constant log messages

I am receiving the same messages all the time, but i am not sure what the problem is:

2016/02/11 07:27:11 [WARN] ("key(service/haproxy/timeouts/client)") Consul returned no data (does the path exist?)
2016/02/11 07:27:11 [WARN] ("key(service/haproxy/timeouts/connect)") Consul returned no data (does the path exist?)
2016/02/11 07:27:12 [WARN] ("key(service/haproxy/maxconn)") Consul returned no data (does the path exist?)
2016/02/11 07:27:14 [WARN] ("key(service/haproxy/timeouts/server)") Consul returned no data (does the path exist?)
2016/02/11 07:28:11 [WARN] ("key(service/haproxy/timeouts/client)") Consul returned no data (does the path exist?)
2016/02/11 07:28:13 [WARN] ("key(service/haproxy/timeouts/connect)") Consul returned no data (does the path exist?)
2016/02/11 07:28:15 [WARN] ("key(service/haproxy/timeouts/server)") Consul returned no data (does the path exist?)
2016/02/11 07:28:16 [WARN] ("key(service/haproxy/maxconn)") Consul returned no data (does the path exist?)

Any ideas?

Using FQNDs in service catalog to identify service names breaks configuration generation

Using the default HAPROXY_MODE of 'consul', I believe I'm seeing what appears to be breakage when parsing for .Name to configure backend servers.

I'm using marathon-consul to get Marathon data in to consul. It works great and I can curl GET query consul for this Marathon data all day long.

An excerpt from haproxy.cfg:

backend api.184.integration.project.platform.domain_backend


backend database.184.integration.project.platform.domain_backend


backend frontend.184.integration.project.platform.domain_backend


backend project-api_backend

   server mesos-master01.infrastructure.domain 172.32.x.x:31132

backend project-database_backend

   server mesos-master01.infrastructure.domain 172.32.x.x:32861

backend project-frontend_backend

   server mesos-master01.infrastructure.domain 172.32.x.x:32072

I'm using SERVICE_NAME to name the service (http://gliderlabs.com/registrator/latest/user/services/) and had been wondering if this is what broke the configuration, until I did a test using SERVICE_NAME but using a more simple name, one which did not represent a FQDN.

If I don't use SERVICE_NAME, or if I don't name the service to contain periods, my backends are then assigned a server as expected.

Incompatibility with lastest version of marathon-consul

Hi,

I've several problems with the generated configuration of HAproxy.

Problem

Here is an extract of the current configuration :

listen kibana_30070
    mode tcp
    bind *:30070

    server <no value> dc1-docker-dev-mesos-master1.dev.dc1.kelkoo.net.node.consul:31306 # TASK_RUNNING

This <no value> should be the taskId, it also break make the configuration invalid.

In consulKV (marathon/kibana/tasks/<kibana.ID> ) I got :

{
  "timestamp": "2015-07-16T12:41:21.370Z",
  "slaveId": "20150716-074236-1046891530-15050-30616-S0",
  "id": "kibana.f2a929ba-2bb7-11e5-a9ad-ae227afcbacd",
  "taskStatus": "TASK_RUNNING",
  "appId": "/kibana",
  "host": "dc1-docker-dev-mesos-master1.dev.dc1.kelkoo.net.node.consul",
  "ports": [
    31306
  ],
  "version": "2015-07-16T12:41:12.875Z"
}

With this id key which used to be taskId.

It's a recent modification of marathon-consul :

Incriminated (no offence, You are doing a great jog @BrianHicks ) commit :
CiscoCloud/marathon-consul@c7b968b#diff-a3520fb65206c9e232f10baaff310c7a

Solutions:

Change the HAproxy template and use this id key instead of taskId. It's easy and simple but I'm against this id key which is a too generic name (we already got slaveId so taskId is not a long name in comparison ).

OR

Adapt the incriminated commit (poke @BrianHicks ) and stick with taskId .

OR

go talk with marathon people and make them use consistent name for every taskId. Slowest solution since it has to be discuss and approve on marathon side and to be release in marathon.

Check conf before restart

I just had a nasty crash of haproxy-consul because the data coming from consul led to a bad haproxy.cfg file. This caused haproxy to crash on restart, stopping the container.

I would be nice if haproxy-consul could check the config file (using haproxy -f /haproxy/haproxy.cfg -c) and if it's clean, only then restart haproxy. I would make the overall more robust and more trustworthy ;-)

Container does not restart properly after running "docker stop"

Runnung docker stop haproxy-consul followed by docker start haproxy-consul results in the following error in the logs:

ln: /consul-template/template.d/haproxy.tmpl: File exists

It looks like this line in launch.sh needs to remove the link before creating it or needs to test for the existence of the link:

ln -s /consul-template/template.d/${HAPROXY_MODE}.tmpl /consul-template/template.d/haproxy.tmpl

The same problem happens if the docker daemon or the VM it runs on are restarted.

I have an application in Marathon that doesn't have any ports assigned to it

I have an application specified in Marathon that shouldn't have any ports assigned to it. In fact, I don't assign any ports to it, period; I omit the portMappings and network directives in my application definition.

Even, still, port information still makes it in to the haproxy configuration. The haproxy-consul container fails to work properly because it creates a configuration that tries to make haproxy listen on interface *:0, which is not valid.

The documentation implies that by setting HAPROXY_HTTP to 'false' will skip support for haproxy in the application, but this does not appear to be the case.

Some old PRs?

Hi again! :)

I was reading up on existing PRs after creating my own and saw some issues being fixed that I'd very much like to use in my environment.

I could help out with reviewing these changes if that could speed up merges?

Cheers!

Support suspended services from marathon

An issue we have seen is where a service exists, but is not running any tasks, as when a Marathon app is suspended. This causes haproxy to have an invalid configuration.

ALERT] 253/161656 (8102) : Unable to use proxy 'xxx_backend' with wrong mode, required: http, has: tcp.
[ALERT] 253/161656 (8102) : You may want to use 'mode http'.
[ALERT] 253/161656 (8102) : Proxy 'www': unable to find required use_backend: 'xxx_backend'.
[ALERT] 253/161656 (8102) : Fatal errors found in configuration.

taskId -> id in Marathon 0.11

It looks like marathon 0.11 uses Id instead of taskId. This causes <no value> to be set in the haproxy.cfg file when marathon mode is enabled.

openssl package missing

/launch.sh: line 68: openssl: command not found

When letting launch.sh generate pem file, the openssl binary is not found. Should be added.

What is the purpose of '/reload.sh'

Hi,

I just tried your image. I was taking a look around and was trying out the /reload.sh which fails to run. If I call with debug enabled I get the following.

/ $ DEBUG=true sh reload.sh 
+ HAPROXY_MODE=consul
+ HAPROXY_DOMAIN=haproxy.service.consul
+ CONSUL_TEMPLATE=/usr/local/bin/consul-template
+ CONSUL_CONFIG=/consul-template/config.d
+ CONSUL_CONNECT=consul.service.consul:8500
+ CONSUL_MINWAIT=2s
+ CONSUL_MAXWAIT=10s
+ CONSUL_LOGLEVEL=info
+ function update_configuration {
reload.sh: line 1: function: not found

It seems as something is not working as expected in the script, but I haven't figured out what exactly.

After my first try I was very interested in the solution of almost zero package drop on reloading the configuration. So I took a look into the lines beginning at #48.
There you make use of nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --buffer which is also recommended by some other people dealing with haproxy and low package drop while reloading.

But the command nl-qdisc-add could not be executed because the Dockerfile is missing to install the right package (libnl3-cli). After installing it is still not possible to make use of it, because the shell is complaining about missing permissions. As you can see below.

Error: Unable to add qdisc: Operation not permitted
Adding qdisc plug dev lo id 40: parent 1:4
  refcnt 0no options
Error: Unknown qdisc "plug--release-indefinite"

Do you have an idea, recommendations, tips how I can get this to work? Any help would be very appreciated.

Cheers,
Volker

HA Statistics

Hi,

I love this project - it works perfectly together with my Consul environment! Thank you so much for your work!

One thing I'd like in this project is the ability to enable/disable HA statistics. I can add it myself in a PR but I'd like to know if there is any reason for it not already being implemented?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.