Система плагинов Kong: встроенные и кастомные плагины, жизненный цикл запроса
Плагины — это расширяемая система Kong, которая позволяет добавлять кросс-сервисные функции (аутентификация, логирование, rate limiting) на разных этапах обработки запроса.
Проблема: Нужно понимать, когда и какие плагины выполняются, чтобы правильно реализовать кастомную логику.
Решение: Kong выполняет плагины на определённых этапах жизненного цикла запроса.
┌─────────────────────────────────────────────────────────────┐
│ Жизненный цикл запроса │
└─────────────────────────────────────────────────────────────┘
1. DNS Resolution
└─ Выбор upstream, разрешение имён
2. Rewrite
└─ Преобразование URL, SSL handshake
3. Access ←─── Большинство плагинов (аутентификация, rate limiting)
└─ Проверка credentials, принятие решений
4. Proxy
└─ Отправка запроса на upstream
5. Response
└─ Получение ответа от upstream
6. Body Filter
└─ Трансформация тела ответа (чанки)
7. Header Filter
└─ Трансформация заголовков ответа
8. Log ←─── Асинхронное логирование, метрики
└─ После отправки ответа клиенту
| Этап | Назначение | Примеры плагинов |
|---|---|---|
| dns | Разрешение имён upstream | Внутренние механизмы Kong |
| rewrite | Преобразование URL, SSL | ssl-handshake |
| access | Аутентификация, авторизация, rate limiting | key-auth, jwt, rate-limiting, acl |
| proxy | Проксирование на upstream | Внутренние механизмы Kong |
| response | Обработка ответа от upstream | response-transformer |
| body_filter | Построчная обработка тела | cors (добавление заголовков) |
| header_filter | Обработка заголовков | cors, response-transformer |
| log | Асинхронное логирование | http-log, tcp-log, prometheus |
Проблема: Нужно быстро добавить стандартные функции без написания кода.
Решение: Kong предоставляет множество встроенных плагинов.
Проблема: Нужно применить плагин к разным уровням (глобально, Service, Route, Consumer).
Решение: Плагины можно применять на четырёх уровнях.
# Применяется ко всем запросам в Kong
curl -X POST http://localhost:8001/plugins \
--data "name=prometheus"# Применяется ко всем запросам к users-api
curl -X POST http://localhost:8001/services/users-api/plugins \
--data "name=rate-limiting" \
--data "config.minute=100"# Применяется только к запросам на /api/admin
curl -X POST http://localhost:8001/routes/admin-route/plugins \
--data "name=ip-restriction" \
--data "config.allow=10.0.0.0/8"# Индивидуальный rate limit для premium пользователя
curl -X POST http://localhost:8001/consumers/premium-user/plugins \
--data "name=rate-limiting" \
--data "config.hour=10000"Проблема: Плагин применён на нескольких уровнях. В каком порядке они выполняются?
Решение: Kong применяет плагины в определённом порядке.
1. Global plugins
↓
2. Route plugins
↓
3. Service plugins
↓
4. Consumer plugins
Пример:
# Глобальный prometheus (все запросы)
curl -X POST http://localhost:8001/plugins --data "name=prometheus"
# Rate limiting на Route
curl -X POST http://localhost:8001/routes/api-route/plugins \
--data "name=rate-limiting" --data "config.minute=60"
# Индивидуальный лимит для Consumer
curl -X POST http://localhost:8001/consumers/premium/plugins \
--data "name=rate-limiting" --data "config.minute=600"Для запроса от premium Consumer к api-route:
prometheus (log-этап)rate-limiting с лимитом 600 (Consumer переопределяет Route)# Rate limiting с параметрами
curl -X POST http://localhost:8001/routes/api-route/plugins \
--data "name=rate-limiting" \
--data "config.minute=100" \
--data "config.hour=1000" \
--data "config.policy=redis" \
--data "config.redis_host=redis" \
--data "config.fault_tolerant=true"# CORS с несколькими разрешёнными origin
curl -X POST http://localhost:8001/services/api/plugins \
--data "name=cors" \
--data "config.origins[]=https://app.example.com" \
--data "config.origins[]=https://admin.example.com" \
--data "config.methods[]=GET" \
--data "config.methods[]=POST" \
--data "config.headers[]=Accept" \
--data "config.headers[]=Content-Type" \
--data "config.exposed_headers[]=X-Total-Count" \
--data "config.credentials=true" \
--data "config.max_age=3600"# Временное отключение плагина
curl -X POST http://localhost:8001/routes/api-route/plugins \
--data "name=rate-limiting" \
--data "config.enabled=false"
# Включение обратно
curl -X PATCH http://localhost:8001/routes/api-route/plugins/{plugin-id} \
--data "config.enabled=true"Проблема: Встроенных плагинов недостаточно для специфических требований.
Решение: Написание кастомного плагина на Lua.
my-plugin/
├── kong/
│ └── plugins/
│ └── my-plugin/
│ ├── handler.lua ← логика плагина
│ └── schema.lua ← схема конфигурации
Проблема: Нужно определить конфигурацию плагина с валидацией.
Решение: Файл schema.lua определяет поля конфигурации.
-- kong/plugins/my-plugin/schema.lua
local typedefs = require "kong.db.schema.typedefs"
return {
name = "my-plugin",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ header_name = {
type = "string",
required = true,
default = "X-Custom-Header"
}},
{ header_value = {
type = "string",
required = true
}},
{ skip_for_paths = {
type = "array",
elements = { type = "string" },
required = false,
default = {}
}},
},
},
},
},
}Проблема: Нужно реализовать логику плагина на разных этапах.
Решение: Файл handler.lua с методами для каждого этапа.
-- kong/plugins/my-plugin/handler.lua
local Plugin = require "kong.plugins.base_plugin"
local MyPlugin = Plugin:extend()
function MyPlugin:new()
MyPlugin.super.new(self, "my-plugin")
end
-- Этап access: проверка и модификация запроса
function MyPlugin:access(config)
MyPlugin.super.access(self)
-- Пропуск для определённых paths
local path = kong.request.get_path()
for _, skip_path in ipairs(config.skip_for_paths) do
if string.find(path, skip_path, 1, true) then
return
end
end
-- Добавление кастомного заголовка
kong.request.set_header(config.header_name, config.header_value)
-- Логирование
kong.log.info("Added header ", config.header_name, ": ", config.header_value)
end
-- Этап response: обработка ответа
function MyPlugin:response(config)
MyPlugin.super.response(self)
-- Добавление заголовка к ответу
kong.response.set_header("X-Processed-By", "my-plugin")
end
-- Этап log: асинхронное логирование
function MyPlugin:log(config)
MyPlugin.super.log(self)
-- Отправка метрик
local status = kong.response.get_status()
kong.log.info("Response status: ", status)
end
return MyPluginПроблема: Kong должен знать о кастомном плагине.
Решение: Добавление плагина в конфигурацию.
# docker-compose.yml
services:
kong:
image: kong:3.5
environment:
KONG_PLUGINS: bundled,my-plugin
volumes:
- ./my-plugin:/usr/local/share/lua/5.1/kong/plugins/my-plugin# /etc/kong/kong.conf
plugins = bundled,my-plugin
lua_package_path = /path/to/plugins/?.lua;;# Проверка конфигурации
kong check /etc/kong/kong.conf
# Перезапуск
kong restartПроблема: Нужно взаимодействовать с запросом, ответом, конфигурацией.
Решение: PDK предоставляет API для работы с Kong.
-- Работа с запросом
local path = kong.request.get_path() -- /api/users
local method = kong.request.get_method() -- GET
local headers = kong.request.get_headers() -- таблица заголовков
local query = kong.request.get_query() -- таблица query-параметров
local body = kong.request.get_raw_body() -- тело запроса
-- Работа с ответом
local status = kong.response.get_status() -- 200
local headers = kong.response.get_headers() -- таблица заголовков
-- Отправка ответа (прекращает обработку)
kong.response.exit(401, "Unauthorized", {["WWW-Authenticate"] = "Bearer"})
-- Работа с Consumer
local consumer = kong.client.get_consumer() -- таблица Consumer
local credential = kong.client.get_credential() -- данные аутентификации
-- Сетевые операции
local ip = kong.client.get_ip() -- IP клиента
local forward_ip = kong.client.get_forwarded_ip()
-- Логирование
kong.log.info("Info message")
kong.log.warn("Warning message")
kong.log.err("Error message")
kong.log.debug("Debug message (если включён debug log)")
-- Кэширование
local cache = kong.cache
local data, err = cache:get("key", function()
-- Функция для получения данных (вызывается при cache miss)
return fetch_from_db()
end)
-- Shared dict (между worker'ами)
local shared = kong.shared
shared:set("counter", 42)
local value = shared:get("counter")Проблема: Нужно протестировать плагин перед production.
Решение: Использование kong-plugin-tester или интеграционные тесты.
-- spec/my-plugin_spec.lua (используя busted)
local helper = require "kong.spec.helper"
describe("my-plugin", function()
local client
setup(function()
client = helper.test_client()
end)
it("adds custom header", function()
local response = client:GET("/api/users", {
headers = {
["Host"] = "example.com"
}
})
assert.equals(200, response.status)
assert.equals("my-value", response.headers["x-custom-header"])
end)
end)Запуск тестов:
busted spec/my-plugin_spec.luaПроблема: Плагин может замедлить обработку запросов.
Решение:
-- ✅ Хорошо: кэширование результатов
local data, err = kong.cache:get("key", function()
return expensive_operation()
end)
-- ❌ Плохо: вызов на каждый запрос
local data = expensive_operation()
-- ✅ Хорошо: ранний выход
if not should_process() then
return
end
-- ❌ Плохо: лишние проверки
if should_process() then
-- обработка
endПроблема: Ошибка в плагине не должна ломать весь запрос.
Решение:
function MyPlugin:access(config)
MyPlugin.super.access(self)
local ok, err = pcall(function()
-- рискованная операция
process_request()
end)
if not ok then
kong.log.err("Plugin error: ", err)
-- Не прерываем запрос, продолжаем обработку
end
endПроблема: Хардкод значений в плагине.
Решение:
-- ✅ Хорошо: значения из конфигурации
local timeout = config.timeout or 5000
-- ❌ Плохо: хардкод
local timeout = 5000# /etc/kong/kong.conf
log_level = debug# Docker
docker logs kong
# Bare-metal
tail -f /var/log/kong/error.logcurl http://localhost:8001/plugins/enabledВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.