Plugins in Other Languages

Introduction

External plugins are those that run on a process separate from Kong Gateway itself, enabling the use of any programming language for which an appropriate plugin server is available.

Each plugin server hosts one or more plugins and communicates with the main Kong Gateway process through Unix sockets. If so configured, Kong Gateway can manage those processes, starting, restarting and stopping as necessary.

Kong Gateway currently maintains a Go language plugin server, go-pluginserver, the corresponding PDK library package go-pdk, the JavaScript language support kong-js-pdk and Python language support kong-python-pdk.

Kong Gateway plugin server configuration

The pluginserver_names property is a comma-separated list of names, one for each plugin server process. These names are used to group each process’ properties and to annotate log entries.

For each name, other properties can be defined:

PropertyDescriptionDefault
pluginserver<NAME>_socketUnix socket path/usr/local/kong/<NAME>.socket
pluginserver<NAME>start_cmdCommand to start the plugin server process/usr/local/bin/<NAME>
pluginserver<NAME>query_cmdCommand to dump available plugins’ info/usr/local/bin/query<NAME>

For example, you could set Go and Python plugins like this (assuming an hypothetical Python plugin server called pypluginserver.py):

  1. pluginserver_names = go,python
  2. pluginserver_go_socket = /usr/local/kong/go_pluginserver.sock
  3. pluginserver_go_start_cmd = /usr/local/bin/go-pluginserver -kong-prefix /usr/local/kong/ -plugins-directory /usr/local/kong/go-plugins
  4. pluginserver_go_query_cmd = /usr/local/bin/go-pluginserver -dump-all-plugins -plugins-directory /usr/local/kong/go-plugins
  5. pluginserver_python_socket = /usr/local/kong/python_pluginserver.sock
  6. pluginserver_python_start_cmd = /usr/local/bin/kong-python-pluginserver
  7. pluginserver_python_query_cmd = /usr/local/bin/kong-python-pluginserver --dump-all-plugins

To enable those plugins, add the each plugin name to the plugins config. Assume we have those hello plugins in each language:

  1. plugins = bundled, go-hello, js-hello, py-hello

Note: The pluginserver_XXX_start_cmd and pluginserver_XXX_query_cmd commands use a limited default PATH variable. In most cases, you have to specify the full executable path instead.

Legacy configuration

Kong Gateway versions 2.0.x to 2.2.x supported only Go external plugins and a single plugin server using a different configuration style. Starting with Kong Gateway version 2.3, the old style is recognized and internally transformed to the new style.

If property pluginserver_names isn’t defined, the legacy properties go_plugins_dir and go_pluginserver_exe are tried:

PropertyDescriptionDefault
go_plugins_dirDirectory with Go pluginsoff, meaning to disable Go plugins
go_pluginserver_exePath to the go-pluginserver executable/usr/local/bin/go-pluginserver

Notes:

  • The old style doesn’t allow multiple plugin servers.
  • Version 0.5.0 of go-pluginserver requires the old style configuration.
  • The new style configuration requires v0.6.0 of go-pluginserver

Developing Go plugins

Kong Gateway support for the Go language consist of two parts:

  • go-pdk as a library, provides Go functions to access Kong Gateway features of the PDK.
  • go-pluginserver an executable to dynamically load plugins written in Go.

Notes:

The Kong Gateway version 2.3 allows multiple plugin servers; in particular it’s now possible to write single-plugin servers, in effect plugins as microservices. To help with this, version v0.6.0 of the go-pdk package includes an optional plugin server. See Embedded Server for more information.

The go-pluginserver process is still supported. Its main advantage is that it’s a single process for any number of plugins, but the dynamic loading of plugins has proven challenging under the Go language (unlike the microservice architecture, which is well supported by the language and tools).

Development

To write a Kong Gateway plugin in Go, you need to:

  1. Define a structure type to hold configuration.
  2. Write a New() function to create instances of your structure.
  3. Add methods on that structure to handle phases.

    If you want a dynamically-loaded plugin to be used with go-pluginserver:

  4. Compile your Go plugin with go build -buildmode plugin.

  5. Put the resulting library (the .so file) into the go_plugins_dir directory.

    If you want a standalone plugin microservice:

  6. Include the go-pdk/server sub-library.

  7. Add a main() function that calls server.StartServer(New, Version, Priority).
  8. Compile as an executable with go build.

Note: Check out this repository for example Go plugins.

1. Configuration Structure

Plugins written in Lua define a schema to specify how to read and validate configuration data coming from the datastore or the Admin API. Since Go is a statically-typed language, all that specification is handled by defining a configuration structure:

  1. type MyConfig struct {
  2. Path string
  3. Reopen bool
  4. }

Public fields (that is, those starting with a capital letter) will be filled with configuration data. If you want them to have a different name in the datastore, add field tags as defined in the encoding/json package:

  1. type MyConfig struct {
  2. Path string `json:my_file_path`
  3. Reopen bool `json:reopen`
  4. }

2. New() Constructor

Your plugin must define a function called New that creates an instance of this type and returns as an interface{}. In most cases, it’s just this:

  1. func New() interface{} {
  2. return &MyConfig{}
  3. }

You can add more fields to the structure and they’ll be passed around, but there are no guarantees about the lifetime or quantity of configuration instances.

3. Phase Handlers

Similarly to Kong Gateway Lua plugins, you can implement custom logic to be executed at various points of the request processing lifecycle. For example, to execute custom Go code in the access phase, define a function named Access:

  1. func (conf *MyConfig) Access (kong *pdk.PDK) {
  2. ...
  3. }

The phases you can implement custom logic for are as follows, and the expected function signature is the same for all of them:

  • Certificate
  • Rewrite
  • Access
  • Response
  • Preread
  • Log

Similar to Lua plugins, the presence of the Response handler automatically enables the buffered proxy mode.

4. Version and Priority

Similarly to Kong Gateway Lua plugins, you can define the version number and priority of execution by having following lines in plugin code:

  1. const Version = "1.0.0"
  2. const Priority = 1

Kong Gateway executes plugins from highest priority to lowest ones.

Embedded server

Each plugin can be a microservice, compiled as a standalone executable.

To use the embedded server, include github.com/Kong/go-pdk/server in the imports list, and add a main() function:

  1. func main () {
  2. server.StartServer(New, Version, Priority)
  3. }

Note that the main() function must have a package main line at the top of the file.

Then, a standard Go build creates an executable. There are no extra go-pluginserver, no plugin loading, and no compiler/library/environment compatibility issues.

The resulting executable can be placed somewhere in your path (for example, /usr/local/bin). The common -h flag shows a usage help message:

  1. $ my-plugin -h
  2. Usage of my-plugin:
  3. -dump
  4. Dump info about plugins
  5. -help
  6. Show usage info
  7. -kong-prefix string
  8. Kong prefix path (specified by the -p argument commonly used in the Kong CLI) (default "/usr/local/kong")

When run without arguments, it creates a socket file with the kong-prefix and the executable name, appending .socket. For example, if the executable is my-plugin, it would be /usr/local/kong/my-plugin.socket by default.

Example configuration

Two standalone plugins, called my-plugin and other-one:

  1. pluginserver_names = my-plugin,other-one
  2. pluginserver_my_plugin_socket = /usr/local/kong/my-plugin.socket
  3. pluginserver_my_plugin_start_cmd = /usr/local/bin/my-plugin
  4. pluginserver_my_plugin_query_cmd = /usr/local/bin/my-plugin -dump
  5. pluginserver_other_one_socket = /usr/local/kong/other-one.socket
  6. pluginserver_other_one_start_cmd = /usr/local/bin/other-one
  7. pluginserver_other_one_query_cmd = /usr/local/bin/other-one -dump

Note that the socket and start command settings coincide with their defaults, so they can be omitted:

  1. pluginserver_names = my-plugin,other-one
  2. pluginserver_my_plugin_query_cmd = /usr/local/bin/my-plugin -dump
  3. pluginserver_other_one_query_cmd = /usr/local/bin/other-one -dump

Developing JavaScript plugins

Kong Gateway support for the JavaScript language is provided by kong-js-pdk. The library provides a plugin server to provide runtime for JavaScript plugins, and functions to access Kong Gateway features of the PDK.

TypeScript is also supported in the following ways:

  • kong-js-pdk includes type definitions for PDK functions that allow type checking when developing plugins in TypeScript.
  • Plugin written in TypeScript can be loaded directly and transpiled on the fly.

Example configuration

kong-js-pdk can be installed using npm. To install the plugin server binary globally:

  1. npm install kong-pdk -g

Assume the plugins are stored in /usr/local/kong/js-plugins:

  1. pluginserver_names = js
  2. pluginserver_js_socket = /usr/local/kong/js_pluginserver.sock
  3. pluginserver_js_start_cmd = /usr/local/bin/kong-js-pluginserver --plugins-directory /usr/local/kong/js-plugins
  4. pluginserver_js_query_cmd = /usr/local/bin/kong-js-pluginserver --plugins-directory /usr/local/kong/js-plugins --dump-all-plugins

Development

Install kong-js-pdk in your local development directory:

  1. npm install kong-pdk --save

A valid JavaScript plugin implementation should export the following object:

  1. module.exports = {
  2. Plugin: KongPlugin,
  3. Schema: [
  4. { message: { type: "string" } },
  5. ],
  6. Version: '0.1.0',
  7. Priority: 0,
  8. }

Plugin attribute defines the class that implements this plugin. Schema defines the config schema of plugin, it shares the same syntax as it’s a Lua plugin. Version and Priority defines the version number and priority of execution respectively.

Note: Check out this repository for example JavaScript and TypeScript plugins.

1. Phase Handlers

Similarly to Kong Gateway Lua plugins, you can implement custom logic to be executed at various points of the request processing lifecycle. For example, to execute custom JavaScript code in the access phase, define a function named access:

  1. class KongPlugin {
  2. constructor(config) {
  3. this.config = config
  4. }
  5. async access(kong) {
  6. // ...
  7. }
  8. }

The phases you can implement custom logic for are as follows, and the expected function signature is the same for all of them:

  • certificate
  • rewrite
  • access
  • response
  • preread
  • log

Similar to Lua plugins, the presence of the response handler automatically enables the buffered proxy mode.

2. PDK functions

kong-js-pdk invokes PDK functions in Kong through network-based IPC (inter-process communication). So each function returns a Promise instance; it’s convenient to use async/await keywords in phase handlers for better readability.

  1. class KongPlugin {
  2. constructor(config) {
  3. this.config = config
  4. }
  5. async access(kong) {
  6. let host = await kong.request.getHeader("host")
  7. // do something to host
  8. }
  9. }

Or consume Promise in a traditional way:

  1. class KongPlugin {
  2. constructor(config) {
  3. this.config = config
  4. }
  5. async access(kong) {
  6. kong.request.getHeader("host")
  7. .then((host) => {
  8. // do something to host
  9. })
  10. }
  11. }

3. Plugin dependencies

When using the plugin server, plugins are allowed to have extra dependencies, as long as the directory that holds plugin source code also includes a node_modules directory.

Assuming plugins are stored under /usr/local/kong/js-plugins, the extra dependencies are then defined in /usr/local/kong/js-plugins/package.json. Developers also need to run npm install under /usr/local/kong/js-plugins to install those dependencies locally into /usr/local/kong/js-plugins/node_modules.

Note in this case, the node version and architecture that runs the plugin server and the one that runs npm install under plugins directory must match. For example, it may break when you run npm install under macOS and mount the working directory into a Linux container.

Testing

kong-js-pdk provides a mock framework to test plugin code correctness through jest.

Install jest as a development dependency, and add the test script in package.json:

  1. npm install jest --save-dev

The package.json has content similar to the following:

  1. {
  2. "scripts": {
  3. "test": "jest"
  4. },
  5. "devDependencies": {
  6. "jest": "^26.6.3",
  7. "kong-pdk": "^0.3.2"
  8. }
  9. }

Run the test through npm with:

  1. npm test

Note: Check out this repository for examples on how to write test using jest.

Developing Python plugins

Kong Gateway support for the Python language is provided by kong-python-pdk. The library provides a plugin server to provide runtime for Python plugins, and functions to access Kong Gateway features of the PDK.

Example configuration

kong-python-pdk can be installed using pip. To install the plugin server binary and PDK globally, use:

  1. pip3 install kong-pdk

Assume the plugins are stored in /usr/local/kong/python-plugins:

  1. pluginserver_names = python
  2. pluginserver_python_socket = /usr/local/kong/python_pluginserver.sock
  3. pluginserver_python_start_cmd = /usr/local/bin/kong-python-pluginserver --plugins-directory /usr/local/kong/python-plugins
  4. pluginserver_python_query_cmd = /usr/local/bin/kong-python-pluginserver --plugins-directory /usr/local/kong/python-plugins --dump-all-plugins

Development

A valid Python plugin implementation has following attributes:

  1. Schema = (
  2. { "message": { "type": "string" } },
  3. )
  4. version = '0.1.0'
  5. priority = 0
  6. class Plugin(object):
  7. pass

A class named Plugin defines the class that implements this plugin. Schema defines the config schema of plugin, it shares the same syntax as it’s a Lua plugin. version and priority defines the version number and priority of execution respectively.

Note: Check out this repository for example Python plugins and API reference.

1. Phase Handlers

Similarly to Kong Gateway Lua plugins, you can implement custom logic to be executed at various points of the request processing lifecycle. For example, to execute custom Go code in the access phase, define a function named access:

  1. class Plugin(object):
  2. def __init__(self, config):
  3. self.config = config
  4. def access(self, kong):
  5. pass

The phases you can implement custom logic for are as follows, and the expected function signature is the same for all of them:

  • certificate
  • rewrite
  • access
  • response
  • preread
  • log

Similar to Lua plugins, the presence of the response handler automatically enables the buffered proxy mode.

2. Type hints

kong-python-pdk supports type hint in Python 3.x. To use type lint and autocomplete in IDE, user can annotate the kong parameter in phase handler:

  1. import kong_pdk.pdk.kong as kong
  2. class Plugin(object):
  3. def __init__(self, config):
  4. self.config = config
  5. def access(self, kong: kong.kong):
  6. host, err = kong.request.get_header("host")

Embedded server

Each plugin can be a microservice. To use the embedded server, use the following code:

  1. if __name__ == "__main__":
  2. from kong_pdk.cli import start_dedicated_server
  3. start_dedicated_server("py-hello", Plugin, version, priority)

Note the first argument to start_dedicated_server defines the plugin name and must be unique across all languages.

Example configuration

Two standalone plugins, called my-plugin and other-one:

  1. pluginserver_names = my-plugin,other-one
  2. pluginserver_my_plugin_socket = /usr/local/kong/my-plugin.socket
  3. pluginserver_my_plugin_start_cmd = /path/to/my-plugin.py
  4. pluginserver_my_plugin_query_cmd = /path/to/my-plugin.py --dump
  5. pluginserver_other_one_socket = /usr/local/kong/other-one.socket
  6. pluginserver_other_one_start_cmd = /path/to/other-one.py
  7. pluginserver_other_one_query_cmd = /path/to/other-one.py -dump

Note that the socket and start command settings coincide with their defaults, so they can be omitted:

  1. pluginserver_names = my-plugin,other-one
  2. pluginserver_my_plugin_query_cmd = /path/to/my-plugin --dump
  3. pluginserver_other_one_query_cmd = /path/to/other-one --dump

Concurrency model

Python plugin server and embedded server supports multiple concurrency model. By default, the server starts in multi-threading mode.

If your workload is IO intensive, consider the gevent model by adding -g to pluginserver’s start_cmd. If your workload is CPU intensive, consider the multi-processing model by adding -m to pluginserver’s start_cmd.

Performance for external plugins

Depending on implementation details, Go plugins are able to use multiple CPU cores and so perform best on a multi-core system. JavaScript plugins are currently single-core only and there’s no dedicated plugin server support. Python plugins can use a dedicated plugin server to span workload to multiple CPU cores as well.

Unlike Lua plugins where invoking PDK functions are handled in local processes, calling PDK functions in external plugins implies inter-process communications and so is a relatively expensive operation. Because of the expense of calling PDK functions in external plugins, the performance of Kong using external plugins is highly related to the number of IPC (inter-process communication) calls in each request.

The following graph demonstrates the correlation between performance and count of IPC calls per request. Numbers of RPS and latency are removed as they are dependent on hardware and to avoid confusion.

Plugins in Other Languages - 图1

Plugins in Other Languages - 图2

Use external plugins in container and Kubernetes

To use plugins requiring external plugin servers, both the plugin servers and the plugins themselves need to be installed inside the Kong Gateway container.

For plugins written in Golang, build external plugins in embedded server mode in a builder container and copy or mount the binary artifacts into the Kong Gateway container. For plugins written in JavaScript, first install Node and npm, then use npm to install kong-pdk, and finally copy or mount the plugins source code into the Kong Gateway container. For plugins written in Python, install Python and pip. Then use pip to install kong-pdk. Finally, copy or mount the plugin’s source code into the Kong Gateway container.

Refer to previous sections on how to configure Kong Gateway after you build the image or create the container.

Note: Official Kong Gateway images are configured to run as the nobody user. When building a custom image, to copy files into the Kong Gateway image, you must temporarily set the user to root.

  1. FROM kong
  2. USER root
  3. # Example for GO:
  4. COPY your-go-plugin /usr/local/bin/your-go-plugin
  5. # Example for JavaScript:
  6. RUN apk update && apk add nodejs npm && npm install -g kong-pdk
  7. COPY you-js-plugin /path/to/your/js-plugins/you-js-plugin
  8. # Example for Python
  9. # PYTHONWARNINGS=ignore is needed to build gevent on Python 3.9
  10. RUN apk update && \
  11. apk add python3 py3-pip python3-dev musl-dev libffi-dev gcc g++ file make && \
  12. PYTHONWARNINGS=ignore pip3 install kong-pdk
  13. COPY you-py-plugin /path/to/your/py-plugins/you-py-plugin
  14. # reset back the defaults
  15. USER kong
  16. ENTRYPOINT ["/docker-entrypoint.sh"]
  17. EXPOSE 8000 8443 8001 8444
  18. STOPSIGNAL SIGQUIT
  19. HEALTHCHECK --interval=10s --timeout=10s --retries=10 CMD kong health
  20. CMD ["kong", "docker-start"]