Plugins

linkerd is built on a modular plugin system so that individual components may be swapped out without needing to recompile. This also allows anyone to build custom plugins that implement functionality specific to their needs. This guide will show you how to write your own custom linkerd plugin, how to package it, and how to install it in linkerd.

In this guide we will write a custom HTTP response classifier. However, the ideas in this guide apply just as well for writing custom identifiers, namers, name interpreters, protocols, or any other linkerd plugin.

All the code for the plugin in this guide is available on GitHub. I recommend keeping the code open in another window while you read this guide so that you can follow along.

Overview

We will write a custom HTTP response classifier that classifies responses based on a special response header instead of the HTTP response code. If the header’s value is “success” we will treat the response as a success, if it is “retry” we will treat it as a retryable failure, and otherwise we will treat it as a non-retryable failure.

We will describe how to write the plugin, how to build and package it, and how to install it in linkerd.

Writing plugins

While linkerd itself is written in Scala, its plugins can be written in Scala or Java. To demonstrate this, we will write our plugin in Java. For our plugin to be functional, we will need 3 things: the response classifier itself, a config class, and a config initializer. All plugins follow this pattern of having the class that implements the business logic, a config class, and a config initializer.

Response classifier

HeaderClassifier.java is the response classifier itself. Response classifiers must extend PartialFunction[ReqRep, ResponseClass]. Each plugin type has a different interface that it must implement. For example, namer plugins must implement Namer and identifier plugins must implement Identifier.

Config class

Next we need a class that defines the structure of the config block for this plugin and constructs the response classifier. We will call this HeaderClassifierConfig.java. Notice that HeaderClassifierConfig must implement ResponseClassifierConfig. ResponseClassifierConfigs are deserialized from the response classifier section of the linkerd config by Jackson and its public members are populated by the corresponding JSON (or YAML) properties. (In Scala this would be a case class.) In our case we have one public member called headerName, which defines the name of the response header to use.

To satisfy ResponseClassifierConfig we must also implement a method called mk() which constructs the response classifier.

Config initializer

A config initializer is a special class that linkerd loads at startup. It tells linkerd about config classes it can use. We create a config initializer called HeaderClassifierInitializer.java. This class must define a configId and a configClass. When linkerd parses a config block with a kind property, it looks for a config initializer with that configId and attempts to deserialize the block as an instance of the configClass. HeaderClassifierInitializer tells linkerd that when it finds a responseClassifier block with kind: io.buoyant.headerClassifier, it should deserialize that block as a HeaderClassifierConfig.

Finally, in order for linkerd to be able to dynamically load the config initializer at startup, we must register it with the service loader. To do this simply create a resource file called META-INF/services/io.buoyant.linkerd.ResponseClassifierConfig and add the fully qualified class name of the config initializer to that file.

Build & package

We use sbt to build our plugin and the assembly sbt plugin to package it into a jar. Here is the build.sbt file for the project. Note that we can mark any linkerd dependencies as “provided”. This means that those dependencies will be provided by linkerd and do not need to be included in the plugin jar. Similarly, linkerd will provide the Scala standard libraries, so we can exclude those from the jar as well by setting includeScala = false.

Build the plugin jar by running:

./sbt headerClassifier/assembly

Installing

To install this plugin with linkerd, simply move the plugin jar into linkerd’s plugin directory ($L5D_HOME/plugins). Then add a classifier block to the router in your linkerd config:

routers:
- ...
  responseClassifier:
    kind: io.buoyant.headerClassifier
    headerName: status

If you run linkerd with -log.level=DEBUG then you should see a line printed at startup that indicates the HeaderClassifierInitializer has been loaded:

LoadService: loaded instance of class io.buoyant.http.classifiers.HeaderClassifierInitializer for requested service io.buoyant.linkerd.ResponseClassifierInitializer

Trying it out

Now that we have our plugin in the plugins directory, let’s try it out. Start linkerd with this simple config that sends all requests to localhost:8888.

routers:
- protocol: http
  dtab: /svc/* => /$/inet/localhost/8888
  responseClassifier:
    kind: io.buoyant.headerClassifier
    headerName: status
  servers:
  - ip: 0.0.0.0
    port: 4140

Then we’ll start a simple server on port 8888 that responds with the status header indicating success:

while true; do echo -e "HTTP/1.1 200 OK\r\nstatus: success\r\n" | nc -i 1 -l 8888; done

Now let’s issue a request:

curl -v localhost:4140

By checking linkerd’s metrics, we can see that this request was classified as a success:

curl -s localhost:9990/admin/metrics.json?pretty=1 | grep -E 'srv.*(success|failure)'
  "rt/http/srv/0.0.0.0/4140/success" : 1,

Now let’s restart our server and have it set the header to “failure”:

while true; do echo -e "HTTP/1.1 200 OK\r\nstatus: failure\r\n" | nc -i 1 -l 8888; done

And issue another request:

curl -v localhost:4140

Now when we check linkerd’s metrics, we’ll see that this request was classified as a failure:

curl -s localhost:9990/admin/metrics.json?pretty=1 | grep -E 'srv.*(success|failure)'
  "rt/http/srv/0.0.0.0/4140/failures" : 1,
  "rt/http/srv/0.0.0.0/4140/failures/com.twitter.finagle.service.ResponseClassificationSyntheticException" : 1,
  "rt/http/srv/0.0.0.0/4140/success" : 1,

More information

If you have any questions about using or developing linkerd plugins, or would like to share what you’ve created, please drop into the linkerd public Slack. We hope to see you soon!