website/linkerd.io/content/1/advanced/plugin.md

196 lines
8.3 KiB
Markdown

+++
aliases = ["/doc/0.7.0/plugin", "/doc/0.7.1/plugin", "/doc/0.7.2/plugin", "/doc/0.7.3/plugin", "/doc/0.7.4/plugin", "/doc/0.7.5/plugin", "/doc/0.8.0/plugin", "/doc/head/plugin", "/doc/latest/plugin", "/doc/plugin", "/in-depth/plugin", "/advanced/plugin"]
description = "Sheds light on Linkerd's modular plugin system, and provides a detailed walkthrough for writing your own plugins."
title = "Plugins"
weight = 50
[menu.docs]
parent = "advanced"
weight = 29
+++
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]({{%
linkerdconfig "http-response-classifiers" %}}). However, the ideas in this
guide apply just as well for writing custom [identifiers]({{% linkerdconfig
"http-1-1-identifiers" %}}), [namers]({{% linkerdconfig "namers" %}}), [name
interpreters]({{% linkerdconfig "interpreter" %}}), [protocols]({{%
linkerdconfig "routers" %}}), or any other Linkerd plugin.
All the code for the plugin in this guide is available on
[GitHub](https://github.com/linkerd/linkerd-examples/tree/master/plugins/header-classifier).
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](https://github.com/linkerd/linkerd-examples/blob/master/plugins/header-classifier/src/main/java/io/buoyant/http/classifiers/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](https://github.com/twitter/finagle/blob/master/finagle-core/src/main/scala/com/twitter/finagle/Namer.scala#L16)
and identifier plugins must implement
[Identifier](https://github.com/linkerd/linkerd/blob/master/router/core/src/main/scala/io/buoyant/router/RoutingFactory.scala#L21).
### 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](https://github.com/linkerd/linkerd-examples/blob/master/plugins/header-classifier/src/main/java/io/buoyant/http/classifiers/HeaderClassifierConfig.java).
Notice that `HeaderClassifierConfig` must implement
[`ResponseClassifierConfig`](https://github.com/linkerd/linkerd/blob/master/linkerd/core/src/main/scala/io/buoyant/linkerd/ResponseClassifierInitializer.scala#L14).
ResponseClassifierConfigs are deserialized from the response classifier section
of the Linkerd config by [Jackson](https://github.com/FasterXML/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](https://github.com/linkerd/linkerd-examples/blob/master/plugins/header-classifier/src/main/java/io/buoyant/http/classifiers/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`](https://github.com/linkerd/linkerd-examples/blob/master/plugins/header-classifier/src/main/resources/META-INF/services/io.buoyant.linkerd.ResponseClassifierInitializer)
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](https://github.com/linkerd/linkerd-examples/blob/master/plugins/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:
```bash
./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:
```yaml
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:
```bash
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`.
```yaml
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:
```bash
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:
```bash
curl -v localhost:4140
```
By checking Linkerd's metrics, we can see that this request was classified as
a success:
```bash
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":
```bash
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:
```bash
curl -v localhost:4140
```
Now when we check Linkerd's metrics, we'll see that this request was classified
as a failure:
```bash
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](
http://slack.linkerd.io). We hope to see you soon!