Kong Custom Plugin
Introduction
Kong is a powerful, open-source API gateway and microservices management layer that sits in front of your APIs. One of Kong's most valuable features is its extensibility through plugins. While Kong offers many built-in plugins for authentication, security, traffic control, and more, you can also create custom plugins to implement specific functionality that meets your unique requirements.
In this tutorial, we'll explore how to create custom plugins for Kong, enabling you to extend Kong's capabilities and tailor it to your specific use cases. By the end, you'll understand how to build, deploy, and manage your own Kong plugins.
Prerequisites
Before we dive in, make sure you have:
- Basic understanding of API gateways and Kong
- Kong Gateway installed (version 2.0 or later recommended)
- Basic knowledge of Lua programming language (Kong plugins are written in Lua)
- Development environment with text editor and terminal
Understanding Kong Plugins
What is a Kong Plugin?
A Kong plugin is a piece of Lua code that gets executed during the Kong request/response lifecycle. Plugins can perform various operations, such as:
- Authentication and authorization
- Request/response transformation
- Logging and monitoring
- Rate limiting and traffic control
- Custom business logic
Kong Plugin Architecture
Kong follows a modular architecture with a well-defined request/response lifecycle:
Plugins can hook into different phases of this lifecycle, allowing you to modify or analyze the request/response at various stages.
Creating Your First Custom Plugin
Let's create a simple custom plugin that adds a custom header to each request. We'll call it custom-header
.
Step 1: Set Up the Plugin Structure
Kong plugins follow a specific directory structure. Create the following directory and files:
kong-custom-plugin/
├── kong-plugin-custom-header-0.1.0-1.rockspec
└── kong
└── plugins
└── custom-header
├── handler.lua
└── schema.lua
Step 2: Define the Plugin Schema
The schema.lua
file defines the configuration parameters for your plugin:
local typedefs = require "kong.db.schema.typedefs"
return {
name = "custom-header",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ header_name = { type = "string", default = "X-Custom-Header" }, },
{ header_value = { type = "string", default = "Kong-Custom-Plugin" }, },
},
},
},
},
}
This schema defines two configuration parameters:
header_name
: The name of the custom header (default: "X-Custom-Header")header_value
: The value of the custom header (default: "Kong-Custom-Plugin")
Step 3: Implement the Plugin Handler
The handler.lua
file contains the actual logic of your plugin:
local CustomHandler = {
VERSION = "0.1.0",
PRIORITY = 1000,
}
function CustomHandler:access(conf)
kong.service.request.set_header(conf.header_name, conf.header_value)
end
return CustomHandler
This handler:
- Sets a version and priority for your plugin
- Implements the
access
phase function, which adds the custom header defined in the configuration
Step 4: Create the Rockspec File
The .rockspec
file is used to package and distribute your plugin:
package = "kong-plugin-custom-header"
version = "0.1.0-1"
source = {
url = "git://github.com/yourusername/kong-plugin-custom-header",
tag = "0.1.0"
}
description = {
summary = "Kong plugin that adds a custom header to requests",
homepage = "https://github.com/yourusername/kong-plugin-custom-header",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["kong.plugins.custom-header.handler"] = "kong/plugins/custom-header/handler.lua",
["kong.plugins.custom-header.schema"] = "kong/plugins/custom-header/schema.lua",
}
}
Installing Your Custom Plugin
Installing Locally for Development
-
Navigate to your plugin directory:
bashcd kong-custom-plugin
-
Install the plugin:
bashluarocks make
-
Enable the plugin in Kong's configuration (
kong.conf
):plugins = bundled,custom-header
-
Restart Kong:
bashkong restart
Deploying in Production
For production environments, you have several options:
-
Package your plugin as a LuaRock:
bashluarocks pack kong-plugin-custom-header
-
Add it to your Kong container/VM image
-
Use Kong's Plugin Development Kit (PDK) for advanced distribution
Using Your Custom Plugin
Enabling the Plugin Globally
To enable your plugin for all services:
curl -X POST http://localhost:8001/plugins/ \
--data "name=custom-header" \
--data "config.header_name=X-Custom-Header" \
--data "config.header_value=Hello-World"
Enabling for a Specific Service
To enable your plugin for a specific service:
curl -X POST http://localhost:8001/services/{service_id}/plugins \
--data "name=custom-header" \
--data "config.header_name=X-Service-Header" \
--data "config.header_value=Service-Specific"
Enabling for a Specific Route
To enable your plugin for a specific route:
curl -X POST http://localhost:8001/routes/{route_id}/plugins \
--data "name=custom-header" \
--data "config.header_name=X-Route-Header" \
--data "config.header_value=Route-Specific"
Verifying the Plugin is Working
Once your plugin is enabled, you can verify it's working by sending a request to your API through Kong and checking the headers:
curl -i http://localhost:8000/your-api-endpoint
You should see your custom header in the upstream request.
Advanced Plugin Development
Let's explore more advanced plugin development topics:
Plugin Lifecycle and Phases
Kong plugins can hook into multiple phases of the request/response lifecycle:
init_worker
: Called when Kong initializes the Lua workercertificate
: Called during TLS handshakerewrite
: Early-stage request processingaccess
: Authentication and authorizationheader_filter
: Processing response headersbody_filter
: Processing response bodylog
: Final phase, for logging purposes
Here's an example of a plugin that hooks into multiple phases:
local AdvancedPlugin = {
VERSION = "0.1.0",
PRIORITY = 1000,
}
function AdvancedPlugin:init_worker()
kong.log.debug("Plugin initialized")
end
function AdvancedPlugin:rewrite(conf)
kong.log.debug("Rewrite phase")
end
function AdvancedPlugin:access(conf)
kong.log.debug("Access phase")
kong.service.request.set_header("X-Custom-Header", "Custom Value")
end
function AdvancedPlugin:header_filter(conf)
kong.log.debug("Header filter phase")
kong.response.set_header("X-Response-Header", "Response Value")
end
function AdvancedPlugin:log(conf)
kong.log.debug("Logging request details")
end
return AdvancedPlugin
Using Kong PDK
Kong provides a Plugin Development Kit (PDK) that offers a stable interface to Kong's functionality. Here are some common PDK functions:
Request/Response Manipulation
-- Get request information
local path = kong.request.get_path()
local method = kong.request.get_method()
local headers = kong.request.get_headers()
local query_params = kong.request.get_query()
-- Modify request
kong.service.request.set_header("X-Modified", "true")
kong.service.request.set_path("/modified" .. path)
-- Modify response
kong.response.set_header("X-Response", "Modified")
kong.response.set_status(201)
Logging and Debugging
kong.log.debug("Debug message")
kong.log.info("Info message")
kong.log.err("Error message")
Cache Access
local value, err = kong.cache:get("my_key", nil, function()
return "cached_value"
end)
Real-World Plugin Example: Rate Limiting by IP Address
Let's create a more practical plugin that implements rate limiting based on client IP addresses:
-- schema.lua
local typedefs = require "kong.db.schema.typedefs"
return {
name = "ip-rate-limiter",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ limit = { type = "number", default = 100 }, },
{ window_size = { type = "number", default = 60 }, },
},
},
},
},
}
-- handler.lua
local IPRateLimiter = {
VERSION = "0.1.0",
PRIORITY = 901, -- Run after authentication plugins
}
local redis = require "resty.redis"
function IPRateLimiter:access(conf)
-- Get client IP
local client_ip = kong.client.get_ip()
-- Connect to Redis
local red = redis:new()
red:set_timeout(2000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
kong.log.err("Failed to connect to Redis: ", err)
return
end
-- Create Redis key
local key = "rate_limit:" .. client_ip
-- Increment counter
local current, err = red:incr(key)
if not current then
kong.log.err("Failed to increment counter: ", err)
return
end
-- Set expiry on key for first request in window
if current == 1 then
red:expire(key, conf.window_size)
end
-- Check if rate limit exceeded
if current > conf.limit then
return kong.response.exit(429, { message = "Rate limit exceeded" })
end
-- Set headers
kong.response.set_header("X-RateLimit-Limit", conf.limit)
kong.response.set_header("X-RateLimit-Remaining", conf.limit - current)
-- Close Redis connection
local ok, err = red:set_keepalive(10000, 100)
if not ok then
kong.log.err("Failed to set Redis keepalive: ", err)
end
end
return IPRateLimiter
Testing Your Plugins
Unit Testing with Busted
Kong uses Busted for unit testing. Here's a simple example for our custom header plugin:
-- spec/custom-header/01-unit_spec.lua
local PLUGIN_NAME = "custom-header"
describe(PLUGIN_NAME .. ": (unit)", function()
local plugin, conf
setup(function()
-- Load the plugin
plugin = require("kong.plugins." .. PLUGIN_NAME .. ".handler")
-- Mock Kong PDK
_G.kong = {
service = {
request = {
set_header = function() end
}
}
}
-- Spy on the set_header function
spy.on(_G.kong.service.request, "set_header")
conf = {
header_name = "X-Test-Header",
header_value = "Test-Value"
}
end)
describe("access", function()
it("adds the configured header", function()
plugin:access(conf)
assert.spy(_G.kong.service.request.set_header).was_called_with("X-Test-Header", "Test-Value")
end)
end)
end)
Integration Testing
To run integration tests, you'll need a running Kong instance:
-- spec/custom-header/02-integration_spec.lua
local helpers = require "spec.helpers"
local PLUGIN_NAME = "custom-header"
describe(PLUGIN_NAME .. ": (integration)", function()
local client
setup(function()
local bp = helpers.get_db_utils()
-- Create a test route and service
local service = bp.services:insert {
name = "test-service",
url = "http://mockbin.org"
}
local route = bp.routes:insert {
hosts = { "test.com" },
service = service
}
-- Add the plugin to the route
bp.plugins:insert {
name = PLUGIN_NAME,
route = { id = route.id },
config = {
header_name = "X-Test-Header",
header_value = "Test-Value"
}
}
-- Start Kong
assert(helpers.start_kong({
plugins = "bundled," .. PLUGIN_NAME,
}))
end)
teardown(function()
helpers.stop_kong()
end)
before_each(function()
client = helpers.proxy_client()
end)
after_each(function()
if client then client:close() end
end)
describe("request", function()
it("adds the configured header", function()
local r = client:get("/request", {
headers = { host = "test.com" }
})
assert.response(r).has.status(200)
local body = assert.response(r).has.jsonbody()
assert.equal("Test-Value", body.headers["X-Test-Header"])
end)
end)
end)
Debugging Plugins
When developing plugins, you might encounter issues. Here are some tips for debugging:
-
Enable debug logging in Kong:
log_level = debug
-
Use
kong.log.debug()
statements in your plugin code -
Check Kong error logs:
bashtail -f /usr/local/kong/logs/error.log
-
Test your plugins with curl and examine the responses:
bashcurl -i http://localhost:8000/your-api-endpoint
Best Practices for Kong Plugin Development
-
Follow the Kong Plugin Design Pattern
- Create separate schema.lua and handler.lua files
- Use appropriate plugin priority
-
Use Kong PDK
- Leverage Kong's Plugin Development Kit instead of direct Nginx APIs
- This ensures compatibility with future Kong versions
-
Error Handling
- Implement proper error handling in your plugins
- Use kong.log for debugging and error reporting
-
Testing
- Write unit and integration tests for your plugins
- Test all configuration options
-
Performance
- Keep plugins lightweight and efficient
- Minimize external dependencies
- Use caching when appropriate
-
Documentation
- Document your plugin's purpose, configuration, and usage
- Include examples
Common Use Cases for Custom Plugins
-
Custom Authentication
- Implementing proprietary or legacy authentication schemes
-
Request/Response Transformation
- Adding/removing headers
- Modifying request/response bodies
- Format conversion (XML to JSON, etc.)
-
Business Logic
- Feature flags
- A/B testing
- User-specific processing
-
Advanced Logging and Monitoring
- Custom analytics
- Audit logging
- Performance metrics
-
Traffic Control
- Custom rate limiting
- Request routing based on content
- Canary deployments
Summary
In this tutorial, we've explored how to create, deploy, and test custom plugins for Kong API Gateway:
- We learned about Kong's plugin architecture and lifecycle
- We created a simple custom header plugin
- We explored more advanced plugin development techniques
- We implemented a practical IP-based rate limiting plugin
- We covered testing and debugging strategies
- We discussed best practices and common use cases
Kong's plugin system provides a powerful way to extend the gateway's functionality to meet your specific requirements. By creating custom plugins, you can implement unique business logic, integrate with legacy systems, and build a truly tailored API management solution.
Further Resources
To continue learning about Kong plugins:
-
Official Documentation
-
Books and Courses
- "API Gateway Pattern & Kong" by Marco Palladino
- "Microservices API Design with Kong" - Kong Academy
-
Practice Exercises
- Modify the custom header plugin to support multiple headers
- Create a plugin that modifies request/response bodies
- Implement a plugin that integrates with a third-party service
By mastering Kong custom plugin development, you'll be able to extend Kong's capabilities to support virtually any API management requirement your organization might have.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)