The explosive growth of APIs, driven by the rise of AI and microservices, demands robust and adaptable API management solutions.
Because there is "No AI without APIs", we should prioritize their security and performance. Therefore, we can rely on Kong, an API Gateway with a large set of plugins to simplify development and deployment of APIs.
Their catalog of official plugins is available at their Plugin Hub and provides solutions for common API management needs. Beyond these, Kong supports community plugins available on platforms like GitLab and GitHub. For very specific requirements, they also enable custom plugin development via the Lua PDK or other languages they support.
In this post, we will dive deep into the Kong plugin ecosystem, exploring its architecture and configuration. By the end, we should be equipped with the knowledge to optimize our gateway and build effective custom solutions.
There are 4 main factors that influence when plugins are triggered:
- Scope
- Protocols
- Lifecycle phases
- Plugin priority
🎯 Scope
Kong plugins can be applied at various scopes, allowing for granular control. There are 5 scopes a plugin can be applied (sorted by specificity):
- Global - applies to all routes
- Service - applies to all routes that are assigned to the service
- Route - applies to a single route
- ConsumerGroup - applies to a consumer group
- Consumer - applies to a single consumer
It's important to note that when a plugin is configured at multiple scopes simultaneously, the most specific one will take precedence.
To illustrate this, I have prepared an example here. The deployment instructions are provided within the repository.
The detailed configuration is available in the README of the repository. But basically we have 4 routes: 2 have a plugin configured at the route level, 1 has a plugin configured at the service level and the other one relies on the global configuration. Additionally, we have 2 consumers, one with the plugin set at Consumer level and another without it. Consumers authenticate by providing their name as the value for the apikey
header.
Here is a table with some responses given by a set of the routes:
Route | Plugin Scope | Consumer | Response |
---|---|---|---|
/free-service |
Global | - | Hello from Global |
/free-service |
Consumer | user1 | Hello from Consumer |
/free-service-override |
Route | - | Hello from Route |
/free-service-override |
Consumer | user1 | Hello from Consumer |
/service |
Service | user2 | Hello from Service |
As we can see, the most specific scope takes precedence over the others, depending on the request. Feel free to play around with all the consumers and routes to see all the possible outcomes!
📜 Protocols
Each plugin can be set to be used only for some protocols or to use all possible. There are plugins that only support some protocols, like the gRPC Web plugin which only supports gRPC (both secure and insecure).
The plugin protocols allows us to have a route being served for multiple protocols at the same time but having different configurations depending on them.
For example, I can have in the same route 🔑 Key Authentication for HTTP and ↔️🔒 Mutual TLS Authentication for HTTPS, allowing different authentication mechanisms for the same route.
NOTE: I think I found a problem with the protocols field in the plugins and I am in clarification with the Kong Support team to confirm it. As soon as I have a clear answer, I will post an example for this section.
♻️ Lifecycle
The Kong gateway has defined a set of phases that a request/response goes through when reaching it. These phases are based on nginx directives and are documented here.
Even though there are more phases, we will only cover the ones accessible to plugins:
-
🌐 HTTP connections
-
init_worker
- executed on every nginx worker process startup -
configure
- executed every time there is a configuration change in the gateway -
certificate
- executed during serving of SSL certificate (only available for secure protocols) -
rewrite
- executed for every request, before determining the consumer and service (only executed at Global scope) -
access
- executed for every request before sending request upstream -
header_filter
- executed after receiving all response header bytes -
body_filter
- executed for each chunk of the response body received -
response
- executed after the whole response has been received but not forwarded to the client (cannot be used withheader_filter
orbody_filter
) -
ws_handshake
- executed for every WebSocket request before the handshake -
ws_client_frame
- executed for every WebSocket message sent by the client -
ws_upstream_frame
- executed for every WebSocket message sent by the upstream service -
log
- executed after sending the response to the client -
ws_close
- executed after WebSocket connection is closed
-
-
📡 Streaming connections
init_worker
configure
certificate
-
preread
- executed once for every connection after service and consumer have been identified (similar toaccess
) log
For the details on which protocols each phase supports, check the official documentation.
All plugins, including the official ones, have to define 2 things:
- Schema - provides the configuration parameters' types to be used by the plugin
- Handler - priority (next section) and actual code of the plugin
In the handler, each plugin defines at least a function with the name of the phase they want their code to run.
For example, for the 🔑 Key Authentication plugin will run its code in the access
phase (for curiosity, it's source is in GitHub) while the ⏪ Pre-Function and ⏩ Post-Function plugins override multiple phases and execute the code provided by the user.
Most plugins have the main code running in the access
phase as it is the latest before the request is sent to the upstream service, so it contains the most information.
⬆️ Plugin priority
Priority is probably the simplest concept of the 4 to understand.
In their definition, each plugin needs to set a static value, defining their priority, which is just a number. Plugins with a bigger number have higher priority, thus are executed first.
Kong provides a list of each their plugin's priority in the Kong Plugin Ordering page. For custom plugins or others not provided by Kong, we have to check their priority in the source code (or documentation) usually in the plugin's main code file (e.g., handler.lua
for Lua-based plugins).
However, since this isn't a very flexible approach, Kong also provides Dynamic Plugin Ordering. This means that we can have plugins with lower priority running before others which have higher priority.
For example, the 🚦 Rate Limiting plugin executes after the 🌳 LDAP Authentication one, meaning it only rate limits the authenticated requests. But let's say we want to rate limit any request by IP and do this before checking the authentication as this plugin needs to contact an external server.
To do that, we have 2 options:
- Add in the
ordering.after
field of LDAP plugin the valuerate-limiting
- Add in the
ordering.before
field of Rate Limiting plugin the valueldap-auth
Dynamic ordering can be very useful when we have some very specific cases, however it comes with some limitations like only being available for the access
phase and some performance. These limitations are described in the Kong documentation.
💭 Final Thoughts
We’ve covered a significant amount of ground in exploring the intricacies of Kong plugins, from understanding their scope and protocol limitations to the critical aspects of their lifecycle phases and execution priority. These four factors are the foundations of the Kong plugins and something we should have in mind both when using plugins and also when writing them.
The Kong Plugin Hub and the wider community offer a vast array of pre-built solutions for the most common API management challenges. However, the true potential lies in our ability to combine the existing plugins effectively and craft custom solutions tailored to unique use cases. That's why we should be aware of all these factors when dealing with Kong plugins.
The API landscape is constantly evolving, new plugins are being released and requirements may change over time. Therefore, continuous learning and experimentation are the keys to staying ahead. And Kong seems to be a good option as they are constantly evolving their gateway to support new usecases.