An nginx module for setting backends from Consul services.
https://github.com/hashicorp/ngx_http_consul_backend_module.git
This repository contains an nginx module extension for dynamically choosing a healthy backend by communicating directly with HashiCorp Consul's API.
**This code is for example purposes only. It demonstrates both the ability to call Go code from C and the ability to link nginx directly to Consul! It is not production ready and should be considered inspiration only.**
This module installs a consul directive inside the location block, and sets
the resulting $backend variable to one of the healthy IP:PORT by the given
service.
http {
server {
listen 80;
server_name example.com;
location /my-service {
consul $backend service-name;
proxy_pass http://$backend;
}
}
}
In this example, when a request to "http://example.com/my-service" comes to
nginx, nginx invokes the consul directive and looks for a healthy service
named "service-name", returning a random entry from the list. Then it utilized
nginx's built-in proxy_pass to send traffic to that IP:PORT.
To put it another way, requests to "http://example.com/my-service" are load-balanced among the instances registered in a Consul service, using Consul's health checks to remove unhealthy backends automatically.
Instead of re-inventing the wheel, this module uses Consul's existing Golang API client. The majority of the code is written in Go, with small glue pieces to wire it back into the required C code for nginx. This makes use of golang shared C libraries.
Positively, this gains all the benefits of using the official API client library, including configuration via familiar environment variables, connection pooling and multiplexing, and time-tested stability. This saves the need to write and maintain a new client library written in pure C, and allows us to showcase a really cool feature of Go - shared C libraries. Each request goes through Consul, meaning the probability of routing traffic to an unhealthy host is significantly lower than other solutions.
Negatively, this requires Golang to compile the dynamic library .so file. In
theory, this could be compiled in advance by a CI/CD system. There is no need
for the Golang runtime, since the runtime is compiled into the dynamic library.
Additionally, each request goes through Consul. Thus using a local agent is
required for performance and latency reasons.
The general flow is as follows:
location block with aconsul directive.
ngx_http_consul_backend function with two arguments.$backend).
my-service).
ngx_http_consul_backend calls dlopen on the shared C library (the.so file mentioned above), and executes the Go function by calling its symbol.
ngx_http_consul_backend function, which then$backend).
proxy_pass directive to sendThis installation guide uses ubuntu/debian. Adapt as-needed for other platforms.
$ apt-get -yqq install build-essential curl git libpcre3 libpcre3-dev libssl-dev zlib1g-dev
$ cd /tmp
$ curl -sLo nginx.tgz https://nginx.org/download/nginx-1.12.2.tar.gz
$ tar -xzvf nginx.tgz
$ cd /tmp
$ curl -sLo ngx_devel_kit-0.3.0.tgz https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
$ tar -xzvf ngx_devel_kit-0.3.0.tgz
$ git clone https://github.com/hashicorp/ngx_http_consul_backend_module.git /go/src/github.com/hashicorp/ngx_http_consul_backend_module
$ cd /tmp/ngx_http_consul_backend_module/src
$ mkdir -p /usr/local/nginx/ext
$ CGO_CFLAGS="-I /tmp/ngx_devel_kit-0.3.0/src" \
go build \
-buildmode=c-shared \
-o /usr/local/nginx/ext/ngx_http_consul_backend_module.so \
src/ngx_http_consul_backend_module.go
This will compile the object file with symbols to
/usr/local/nginx/ext/nginx_http_consul_backend_module.so. Note that the
name and location of this file is important - it will be dlopened at
runtime by nginx.
$ cd /tmp/nginx-1.12.2
$ CFLAGS="-g -O0" \
./configure \
--with-debug \
--add-module=/tmp/ngx_devel_kit-0.3.0 \
--add-module=/go/src/github.com/hashicorp/ngx_http_consul_backend_module
$ make
$ make install
http {
server {
listen 80;
server_name example.com;
location /my-service {
consul $backend service-name;
proxy_pass http://$backend;
}
}
}
Unlike other solutions, you will not have to restart nginx each time a change happens in the Consul services. Instead, because each request delegates to Consul, you will get real-time results, and traffic will never be routed to an unhealthy host!
There is a sample Dockerfile and entrypoint which builds and runs this custom nginx installation with all required modules.