Skip to main content

Hashicorp Consul Refresher - Loadbalancing with Fabio

Tsim Sha Tsui, Hongkong

Load Balancing with Fabio

The main use case for Fabio in this scenario is to distribute incoming HTTP(S) and TCP requests from the Internet to front-end services that can handle these requests. Fabio can natively integrate with Consul using the Consul Catalog Provider and can use tags to route traffic.

Create the Web Service

I am going to modify the HTTP Echo service I created earlier to be load balanced by Fabio:

job "http-echo-gui" {
  datacenters = ["instaryun"]

  group "echo" {
  
    network {
        port "heartbeat" {
            static = 8080
            }
    }

    count = 1
    task "server" {
      driver = "docker"  
      config {
        image = "hashicorp/http-echo:latest"
        ports = ["heartbeat"]
        args  = [
          "-listen", ":${NOMAD_PORT_heartbeat}",
          "-text", "${attr.os.name}: server running on ${NOMAD_IP_heartbeat} with port ${NOMAD_PORT_heartbeat}",
        ]
      }
      service {
        name = "http-echo"
        port = "heartbeat"
        
        tags = [
          "heartbeat",
          "urlprefix-/http-echo",
        ]

        check {
          type     = "http"
          path     = "/health"
          interval = "2s"
          timeout  = "2s"
        }
      }
    }
  }
}

Note that this job only deploys 1 instance of the Echo web application which I will "load balance" with Fabio in the next few steps. Obviously, you should increase this count to have this make sense... but I only have two LINUX PCs running at the moment and so my "Datacenter" consists of a single minion server.

Create and run a Fabio job

Create a job named:

nano ~/nomad_jobs/fabio.nomad

This job starts an instance of Fabio and configures it to discover its configuration from Consul. This Fabio instance provides routing and load balancing to the sample web application.

job "fabio" {
  datacenters = ["instaryun"]
  type = "system"

  group "fabio" {
    network {
      port "lb" {
        static = 9999
      }
      port "ui" {
        static = 9998
      }
    }
    task "fabio" {
      driver = "docker"
      config {
        image = "fabiolb/fabio"
        network_mode = "host"
        ports = ["lb","ui"]
        args    = ["-proxy.strategy=rr"]
      }

      resources {
        cpu    = 200
        memory = 128
      }
    }
  }
}

Fabio can be run without any additional parameters and will use a pseudo-random strategy for load balancing between multiple instances of the same application. In our job file, an additional argument of -proxy.strategy=rr is passed to the Fabio command to use a round-robin strategy instead.

nomad plan ~/nomad_jobs/fabio.nomad
+ Job: "fabio"
+ Task Group: "fabio" (1 create)
  + Task: "fabio" (forces create)

Scheduler dry-run:
- All tasks successfully allocated.

Run Fabio and it will automatically use the tags that were provided to Consul when the job was registered, to automatically configure itself with the instances running on the various dynamic ports.

nomad job run -check-index 0 ~/nomad_jobs/fabio.nomad

==> 2021-09-04T17:48:20+08:00: Evaluation "4ccd671f" finished with status "complete"

Hashicorp Consul Fabio

The tag urlprefix-/http-echo tells Fabio to add the instance to the route /http-echo. The instances of the application are then available by visiting /http-echo on port 9999:

curl 192.168.2.111:9999/http-echo

debian: server running on 192.168.2.111 with port 8080

The response shows that the service is still running on port 8080 but is now proxied through Fabio and - in theory - it would be loadbalanced. Refreshing the page would show you that you will reach all your instances of http-echo. You can visit the Fabio UI on port 9998 to verify that all your nodes were picked up successfully:

Hashicorp Consul Fabio