mirror of https://github.com/linkerd/website.git
196 lines
8.3 KiB
Markdown
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!
|