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.
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
GET http://example/hello would be assigned the name
(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.
io.l5d.methodAndHost identifier would assign
the logical name of that request.
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
/#. (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
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
/# 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.
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.