Consul + Fabio + Your Application - monitoring and Load Balance

+ Your Application

July 2018

Sysinfo: Centos 7, Consul 0.9.2, hypothetical Pong application

Background

Need a good way to rout users to a custom application (lets call it Pong, running on Port 8300 (via Apache on port 80). There are 3 nodes running Pong and these will be monitored by Consul

Fabio comes in as an add-on to Consul and does the actual Routing and maintains its own Routing tables based on Consul data.

Fabio is great because you dont need to update Routing table, its completely automated. It reads whatever Service and host is on Consul and works off that.

Note: This article is not a good solution for apps that need persistent sessions. Consul and Fabio do not support session persistence. This setup is best used for small applications and microservices. For large applications like Splunk or anything Java-based, use HAProxy to balance nodes

Basic structure:

1. Users go to a shared (virtual) IP, lets say "pong.company.com" -> this is resolved by DNS to 10.155.20.5

2. Virtual IP then passes the request to one of the 2 Consul/Fabio servers

3. Fabio reads healthcheck data from Consul, determines there are 3 active Pong

Configure Consul server cluster

Lets create a 2 node Consul cluster. Consul will monitor application service health, and Fabio will run on top of each Consul and get its routing tables from Consul data.

1. on each Consul server, install Consul

yum install consul

2. generate an Encryption key & Master Token

consul keygen

UAvkAzdjGfQ7J2NlgkrJMA==

geneate a Master Token

uuidgen

dbef8b5a-6110-4575-bf61-dda1c21ca339

3. create Consul dirs

mkdir -p /etc/consul.d/server

mkdir /var/consul

4. add Consul user + group

groupadd consul

useradd consul -g consul

5. change permissions

chown -R consul:consul /var/consul

Consul Config

6. on the 1st Consul node, create new Boostrap config,

{

"bind_addr": "10.185.20.180",

"client_addr": "0.0.0.0",

"data_dir": "/var/consul",

"server": true,

"ui": true,

"bootstrap": true,

"retry_join": ["10.185.20.179","10.185.20.180"],

"datacenter": "mrx",

"enable_script_checks": true,

"encrypt": "UAvkAzdjGfQ7J2NlgkrJMA==",

"enable_syslog": true,

"addresses": {

"http": "10.185.20.180",

"dns": "10.185.20.180"

},

"dns_config": {

"allow_stale": true,

"max_stale": "30s",

"node_ttl": "30s",

"enable_truncate": true,

"only_passing": true

},

"acl_datacenter": "mrx",

"acl_down_policy": "extend-cache",

"acl_default_policy": "allow",

"acl_master_token": "dbef8b5a-6110-4575-bf61-dda1c21ca339"

}

validate the syntax

consul validate /etc/consul.d/*

fix any validation errors

Startup Service

7. create Startup Service

vim /usr/lib/systemd/system/consul.service

[Unit]

Description=Consul service discovery agent

Requires=network-online.target

After=network.target

[Service]

User=consul

Group=consul

Restart=on-failure

ExecStartPre=[ -f "/var/consul/consul.pid" ] && /usr/bin/rm -f /var/consul/consul.pid

ExecStart=/usr/bin/consul agent -config-dir=/etc/consul.d -config-file=/etc/consul.d/server/consul.json

ExecReload=/bin/kill -s HUP $MAINPID

KillSignal=SIGINT

TimeoutStopSec=5

[Install]

WantedBy=multi-user.target

systemctl enable consul.service

systemctl daemon-reload

systemctl start consul.service

8. Do the same for 2 remaining Consul servers, change "bootstrap": false

9. Start service, Web console should be up at <IP>:8500

UI should be available

Create ACL on Consul cluster

Login to Web UI, click on config gear, enter Master ACL token, click Close, this will save the Master token access between your browser and Consul cluster (this is better than using username/password)

Now click ACL button, update Anonymous Token ACL to let Read-Only access

Now create a new ACL for Pong service,

service "" { policy = "write" }

key "pong/" { policy = "write" }

node "" { policy = "write" }

session "" { policy = "write" }

get the Token ID of this Pong ACL policy

go back to each Consul node, change the consul.json setting, change to "Deny"

"acl_default_policy": "deny"

restart Consul service on all server nodes

Configure Consul agent on each Pong instance

on each Pong node, install Consul

Configure Consul agent on each Pong instance, for ACL token add the Pong ACL token from above

pong01> vim /etc/consul.d/client/consul.json

{ "bind_addr": "10.185.20.173", "data_dir": "/var/consul", "ui": false, "bootstrap": false, "server": false, "start_join": ["10.185.20.179","10.185.20.180"], "datacenter": "mrx", "encrypt": "UAvkAzdjGfQ7J2NlgkrJMA==", "enable_syslog": false, "enable_script_checks": true, "pid_file": "/var/consul/consul.pid", "acl_token": "548bb56f-33c9-622a-4351-1a04851ebb1a" }

Service Health Check

pong01> vim /etc/consul.d/pong.json

{

"service": {

"name": "pong",

"status": "critical",

"check": {

"service_id": "pong",

"interval": "10s",

"script": "/usr/bin/netstat -an | grep 8300 | grep LISTEN"

},

"port": 8300,

"tags": ["pong"]

}

}

This will check the Pong app running on Port 8300

To start Consul, add the same start up scripts as for Consul Server

start Consul

Client should register itself and its Pong service with the cluster

Try stopping Pong and watch for Consul to show the service as Orange (failing)

Fabio config

To rout users to any of the 3 operating Pong instances, you will need Fabio to read the health status of each instance (from Consul)

on each Consul sever, create Fabio user + group

useradd -M -d /opt/fabio -s /sbin/nologin fabio

mkdir -p /opt/fabio/bin

get the Binary

wget https://github.com/fabiolb/fabio/releases/download/v1.5.9/fabio-1.5.9-go1.10.2-linux_amd64 -O /opt/fabio/bin/fabio && chmod +x /opt/fabio/bin/fabio

On each Consul server, Add Fabio properties file, update ui.addr and regitstry.consul.addr, by default Fabio will listen on port 9999

Fabio Properties

vim /opt/fabio/fabio.properties

# These two lines are example of running fabio with HTTPS certificates

#proxy.cs = cs=lb;type=file;cert=/opt/fabio/certs.d/mydomain_com.ca-bundle.crt;key=/opt/fabio/certs.d/mydomain_com.key

#proxy.addr = :443;cs=lb;tlsmin=tls11;tlsmax=tls12;tlsciphers="0xc02f,0x9f,0xc030,0xc028,0xc014,0x6b,0x39,0x009d,0x0035",# :80

proxy.addr = :9999

proxy.header.tls = Strict-Transport-Security

proxy.header.tls.value = "max-age=63072000; includeSubDomains"

ui.addr = 10.185.20.180:9998

ui.access = ro

runtime.gogc = 800

log.access.target = stdout

log.access.format = - - [] "" ".Referer" ".User-Agent" "" "" "" ""

log.access.level = INFO

registry.consul.addr = 10.185.20.180:8500

proxy.maxconn = 20000

Create a Fabio startup script

Fabio startup

cat <<EOF > /etc/systemd/system/fabio.service

[Unit]

Description=Fabio Proxy

After=syslog.target

After=network.target


[Service]

LimitMEMLOCK=infinity

LimitNOFILE=65535


Type=simple

WorkingDirectory=/opt/fabio

Restart=always

ExecStart=/opt/fabio/bin/fabio -cfg fabio.properties


# Log to syslog with identifier for syslog to process

StandardOutput=syslog

StandardError=syslog

SyslogIdentifier=fabio


# No need that fabio messes with /dev

PrivateDevices=yes


# Dedicated /tmp

PrivateTmp=yes


# Make /usr, /boot, /etc read only

ProtectSystem=full


# /home is not accessible at all

ProtectHome=yes


# You will have to run “setcap ‘cap_net_bind_service=+ep’ /opt/fabio/bin/fabio”

# to be able to bind ports under 1024. This directive allows it to happen:

AmbientCapabilities=CAP_NET_BIND_SERVICE


# Only ipv4, ipv6, unix socket and netlink networking is possible

# Netlink is necessary so that fabio can list available IPs on startup

RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK


# Unprivileged user

User=fabio

Group=fabio


[Install]

WantedBy=multi-user.target

EOF

set permissions

chown -R fabio:fabio /opt/fabio

systemctl enable fabio.service

systemctl daemon-reload

systemctl start fabio.service

check Fabio service

journalctl -u fabio --no-pager -n100

You should now see Fabio service listed on Consul

Fabio should be listening on port 9998, open up browser to <IP of Consul Server>:9998

But there should be no routes yet

Fabio Routes

We will create 1 Route for our Apache thats running on each Pong server.

Fabio will route all requests coming to Fabio hostname, port 9999, it will route to <IP of Pong server>:80

We now have a total of 2 Health Checks, 1. Pong 2. Apache

Consul monitors both, but Fabio will create a route only for Apache,

Apache service check

lets add the Apache healthcheck, we will want to rout users to our Apache (which will use Reverse Proxy using certs to proxy users to Pong Web via HTTPS). Note: Fabio also supports TLS authentication but that is outside the scope of this article

add a new Apache health check, the Tag is using the "urlprefix-" to tell Fabio to create a Route, followed by a slash,

Fabio will route any requests coming to Fabio host (port 9999)

{

"service": {

"name": "apache-svc",

"status": "critical",

"check": {

"service_id": "apache-svc",

"interval": "10s",

"script": "/usr/bin/systemctl status httpd.service"

},

"port": 80,

"tags": ["urlprefix-/"]

}

}

restart Consul client on the Pong node (make sure the Pong healthcheck passes, otherwise Fabio wont show the route)

you should now see Fabio display a proper route

Check the available routes using API

curl -s http://10.185.20.180:9998/api/routes

[{"service":"apache-svc","host":"","path":"/","src":"/","dst":"http://10.185.20.173:80/","opts":"","weight":1,"cmd":"route add","rate1":0,"pct99":0}]

Test Routes

on the Consul/Fabio host, tail Fabio output,

journalctl -u fabio -f

Turn off either Apache or Pong service, watch the routing table get updated automatically

Fabio adds 2 routes, Pong and Apache

I turned off Apache service, Fabio instantly removes Apache route (minus sign)

In the browser, try going to <IP or Hostname of Consul/Fabio host>:9999

It should redirect you to <Pong host>:80, and from there, Apache will take over and reverse proxy you to Pong service running on its own different port