Routing under the hood

At its core, linkerd’s main job is routing: accepting a request (HTTP, Thrift, Mux, or other protocol) and sending that request to the correct destination. This guide will explain exactly how linkerd determines where requests should be be sent. This process consists of 4 steps: identification, delegation, binding, and load balancing.

linkerd routing

Identification

Identification is the act of assigning a name (also called a path) to the request. A name is a slash-delimited string representing the destination of the request. By default, linkerd uses an identifier called io.l5d.methodAndHost which assigns names to requests based on the HTTP method and Host header like so: /http/1.1/<METHOD>/<HOST>. This means that an HTTP request to GET http://example/hello would be assigned the name /http/1.1/GET/example.

(Note that the path of this URL, /hello, is dropped in the name. It will still be proxied as part of the request–the name only determines how the request is routed, not what is sent to the destination service.)

Of course, the identifier is a pluggable module and can be replaced with a custom identifier which assigns names to requests based on any logic you desire. Learn more about linkerd’s built in identifiers and how to configure them in the linkerd identifier docs.

The name that the identifier assigns to the request is called the logical name because it should encode the destination as specified by the application. It typically does not encode information about clusters, zones, environments, or hosts because your application shouldn’t need to worry about these concerns.

For example, if your application wants to make a request to the “users” service, it could issue an HTTP GET request to linkerd with “users” as the Host header. The io.l5d.methodAndHost identifier would assign /http/1.1/GET/users as the logical name of that request.

Delegation

Once a logical name has been assigned to a request, that name undergoes transformations by the dtab (short for delegation table). This is called delegation. Detailed documentation on how dtab transformations work can be found in on the Dtabs page. Dtabs encode the routing rules that describe how a logical name is transformed into a concrete name. A concrete name is the name of a set of addresses, typically the name of a service discovery entry. Unlike logical names, concrete names often contain details like cluster, zone, and/or environment.

Concrete names always begin with /$ or /#. (See below for the distinction between these two prefixes.)

Continuing the example, suppose we had the following dtab:

/srv => /#/io.l5d.serversets/discovery
/host => /srv/prod
/http/1.1/* => /host

The logical name /http/1.1/GET/users would get delegated like this:

/http/1.1/GET/users
/host/users
/srv/prod/users
/#/io.l5d.serversets/discovery/prod/users

and result in /#/io.l5d.serversets/discovery/prod/users as the concrete name.

Binding

Binding is the act of resolving a concrete name into a set of physical addresses (ip address + port). Binding is done by something called a namer which typically does a lookup into some service discovery backend. linkerd comes with namers for most major service discovery implementations built in; learn more about how to configure them in the linkerd namer docs.

Concrete names that start with /$ indicate that a namer from the classpath should be loaded to bind that name, whereas concrete names that start with /# indicate that a namer from the linkerd config file should be loaded to bind that name.

For example, suppose we have /#/io.l5d.serversets/discovery/prod/users as a concrete name. This means that the io.l5d.serversets namer from the linkerd config should look up the /discovery/prod/users serverset (the result of this lookup is a set of physical addresses).

Similarly, the concrete name /$/inet/users/8888 means to search the classpath for the inet namer. This namer gets the set of addresses by doing a DNS lookup on “users” and using port 8888.

Load balancing

Once linkerd has a set of addresses, it uses a load balancing algorithm to determine to where to send the request. Because linkerd does load balancing at the request layer instead of at the connection layer, the load balancing algorithm can take advantage of request latency information to de-weight slow nodes and avoid overloading struggling hosts.