Kong Plugins as Microservices: Writing a Single-Plugin Server for Kong in Go
Writing Kong plugins? In this post, learn parts of the request/response lifecycle that you can tap into and see an example of how to put it all together.
Join the DZone community and get the full member experience.
Join For FreeMany developers and DevOps engineers have been deploying Kong Gateway in front of their microservices-based applications. While the extensive library of built-in plugins can add a lot of flexibility and power to your deployments, you might encounter the occasional use case where you need a custom plugin that’s just not found in the library.
Fortunately, you can use Go to create and run plugins for Kong Gateway. This post will cover how to get set up for writing Kong plugins. We’ll look at the parts of the request/response lifecycle that you can tap into, and we’ll walk through an example of how to put it all together.
Custom plugins for Kong are achievable using a Go-specific plugin server that uses the sidecar model to load and run a plugin written in Go dynamically. Communication between the plugin and the plugin server is done through a Unix domain socket. Luckily, all of this is made simple through Kong’s Go Plugin Development Kit (PDK).
Why Would You Write Your Own Plugin?
Ultimately, the ability to write your own Go plugin and integrate it with Kong Gateway provides nearly limitless flexibility during the request and response lifecycle. A classic example would be simple request and response enrichment: Imagine adding or removing headers to influence request routing or incorporating customized request validation or logging requests for auditing. Harnessing Kong Gateway’s power while building in custom gateway logic for your unique business case gives you the best of both worlds.
Enough talk. Let’s get to it.
Development Setup
For this example, we’ll assume that you have already done the following:
- Installed Go
- Installed Kong locally on your machine.
- Installed decK to manage the service configuration, though you can also configure Kong using the Admin API.
The first step for environment setup is to create a directory and our Go mod file:
$ mkdir kong-go-plugin
$ cd kong-go-plugin/
~/kong-go-plugin$ go mod init kong-go-plugin
go: creating new go.mod: module kong-go-plugin
Next, we run go get
and go build
to build the plugin server:
~/kong-go-plugin$ go get -d -v github.com/Kong/go-pluginserver
go: downloading github.com/Kong/go-pluginserver v0.6.1
go: downloading github.com/Kong/go-pdk v0.6.0
go: downloading github.com/ugorji/go v1.2.1
go: downloading github.com/ugorji/go/codec v1.2.1
go get: added github.com/Kong/go-pluginserver v0.6.1
~/kong-go-plugin$ go build github.com/Kong/go-pluginserver
By default, Kong expectsgo-pluginserver
to be in /usr/local/bin
, but you can modify this behavior by changing the value for go_pluginserver_exe
in your Kong configuration. For the sake of consistency, let’s move the newly built binary to the default location.
~/kong-go-plugin$ sudo mv go-pluginserver /usr/local/bin/
One other default that we’ll want to modify is go_plugins_dir
which defaults to off
. This is where Kong looks for our Go plugins. For this example, we’ll use $HOME/goplugins/
, so if you’re following along, you’ll need to update your configuration to match.
Before diving into the code, one last thing is that consistency issues can occur. They normally manifest as error messages like the following:
failed to open plugin kong: plugin.Open("/path/go-plugins/myplugin"): plugin was built with a different version of package github.com/Kong/go-pdk/bridge
If you do run into that message, check out the documentation here for more info on possible root causes. Now that we set up our development environment, we can start coding our plugin!
Coding Time!
Our next step is to write the plugin and verify it works as expected. We’ll start with a simple bit of code to add a header to a response. If you’re following along, you can copy this directly into a file called goplug.go
:
package main
import (
"github.com/Kong/go-pdk"
)
type Config struct {
Attach bool
}
func New() interface{} {
return &Config{}
}
func (c *Config) Access(kong *pdk.PDK) {
if c.Attach {
kong.ServiceRequest.SetHeader("x-goplug", "Set by custom go plugin")
}
}
If you’ve worked with Go before, this should be pretty straightforward. That said, you can find more details in the Kong Go Plugin Documentation. The documentation explains the structs and functions, and it provides a full list of the phases you can tap into. We’re using the Access
phase at this point, so we can attach the header before it gets proxied to the upstream service. This is useful in cases where you want to add data to the response based on some incoming criteria that the upstream service can use.
Next, we need to build our plugin and “install” it with go build -buildmode plugin goplug.go && mv goplug.so ~/goplugins/
After that, we restart Kong to ensure it has picked up our new plugin.
~/kong-go-plugin$ sudo kong restart
Now, our plugin is loaded and ready to use, but we still need a service to use it on. I’ve used decK and added the following to a kong.yaml
file to test out the plugin. All you need to do is run deck sync
once you’ve created the kong.yaml
file.
_format_version: "1.1"
services:
- host: mockbin.org
name: example_service
port: 80
protocol: http
plugins:
- name: goplug
config:
attach: true
routes:
- name: mocking
paths:
- /mock
strip_path: true
With our plugin loaded and service created, we’re ready for a test. Simply make a cURL request to the service and we’ll see the plugin in action!
~/kong-go-plugin$ curl -s -i -X GET http://localhost:8000/mock/request \
| grep "x-goplug"
x-goplug: Set by custom go plugin
Success! For good measure, let’s tie into one more phase of the request and response lifecycle. In this next function, we’ll tap into the Response
phase to add a debugging header in case our plugin runs into issues.
func (c *Config) Response(kong *pdk.PDK) {
v := runtime.Version()
kong.Response.AddHeader("x-goplug-go-version", v)
}
Add the above function to goplug.go
, then repeat the process to rebuild the plugin. Next, restart Kong. When we rerun our cURL command, we see that x-goplug
is included with the request to the upstream service, but it is removed before sending a response back to the client.
$ curl -s -i -X GET http://localhost:8000/mock/request \
| grep "x-goplug-go-version"
x-goplug-go-version: go1.16.5
Let’s draw some parallels to real-world applications. We’ve shown here how you can take data from a request and turn that into actionable information, ready to be processed by an upstream service fronted by Kong Gateway. What’s even better is that all of this background work is transparent to the client. We’ve also shown how you can modify the response to the client. With this ability, you can ease troubleshooting or even pass back rate-limiting data (such as the number of remaining API requests per day or per hour) to keep your customers informed of their utilization.
Wrap Up
In this post we’ve covered a lot, so let’s wrap it up with a brief review. Here’s what we’ve covered:
- What you need to develop Go plugins for use with Kong Gateway
- Why you might build a custom plugin rather than use a plugin that’s built-in with Kong Gateway
- Set up a local development environment that supports creating Go-based Kong plugins
- Used that local development environment to tap into the request/response lifecycle to modify the data on the fly
Now that the process is down, it’s easily repeatable for whatever use case you may have for Go-based Kong plugin development. You can find the Kong documentation for Go support on Kong’s website along with some other examples and walkthroughs.
Published at DZone with permission of Michael Bogan. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments