Кастомные плагины Kong на Kubernetes без Docker-образов

Kong Gateway — один из самых популярных API-шлюзов с открытым исходным кодом, на котором строятся микросервисные архитектуры в компаниях любого масштаба. Одна из его ключевых возможностей — система плагинов: модульная архитектура, позволяющая перехватывать и преобразовывать API-трафик на разных этапах жизненного цикла запроса и ответа.

Kong поставляется с богатым набором встроенных плагинов (ограничение частоты запросов, аутентификация, логирование и др.), однако в реальных production-окружениях почти всегда требуются кастомные плагины под конкретную бизнес-логику. Возникает закономерный вопрос: как разворачивать такие плагины, если Kong работает на Kubernetes?

В этой статье я покажу подход, при котором код кастомного плагина упаковывается в Helm-чарт, разворачивается как Kubernetes ConfigMap и монтируется в контейнер Kong во время выполнения — без создания пользовательских Docker-образов.

Весь код из статьи доступен на github.com/shambhand/kong-plugins.

Сквозной поток: исходный код плагина → Helm-чарт → OCI-реестр → ConfigMap → под Kong

Сквозной поток: исходный код плагина → Helm-чарт → OCI-реестр → ConfigMap → под Kong

Архитектура плагинов Kong: краткий обзор

Прежде чем переходить к деплою, разберёмся, как плагины Kong работают под капотом.

Как Kong выполняет плагины

Kong Gateway построен поверх OpenResty (Nginx + LuaJIT). Каждый плагин — это Lua-модуль, встраивающийся в определённые фазы жизненного цикла запроса Nginx. Когда запрос поступает в Kong, он последовательно проходит через несколько фаз, и каждый включённый плагин получает возможность выполнить свою логику в подходящий момент.

Ключевые фазы жизненного цикла плагинов:

  • init_worker — при запуске воркера Kong; используется для инициализации общих ресурсов и настройки таймеров.

  • certificate — во время TLS-рукопожатия; используется для нестандартной обработки SSL-сертификатов.

  • rewrite — до маршрутизации; используется для ранней модификации запроса.

  • access — после маршрутизации, до проксирования; используется для аутентификации, авторизации и трансформации запроса.

  • header_filter — при получении заголовков ответа от upstream; используется для модификации заголовков ответа.

  • body_filter — при получении тела ответа от upstream; используется для модификации тела ответа.

  • log — после отправки ответа клиенту; используется для логирования и сбора метрик.

Фазы жизненного цикла плагинов Kong с указанием точек подключения jwt2headers и circuit-breaker

Фазы жизненного цикла плагинов Kong — точки подключения jwt2headers и circuit-breaker

Структура файлов плагина

Каждый кастомный плагин Kong располагается в директории kong/plugins/<plugin-name>/ и требует как минимум двух файлов:

kong/plugins/my-plugin/
├── handler.lua    -- Логика плагина (реализация фаз жизненного цикла)
└── schema.lua     -- Схема конфигурации (правила валидации)

handler.lua — здесь живёт логика плагина. Файл экспортирует Lua-таблицу с полем PRIORITY (определяет порядок выполнения) и функциями, названными по фазам жизненного цикла, в которые встраивается плагин:

local MyPlugin = {
    PRIORITY = 1000,  -- Чем выше число, тем раньше выполняется
    VERSION = "1.0.0",
}

function MyPlugin:access(conf)
    -- 'conf' содержит конфигурацию плагина
    -- 'kong' — глобальный Plugin Development Kit (PDK)
    kong.log.info("My plugin is running!")
    local header_value = kong.request.get_header("X-Custom")
    kong.service.request.set_header("X-Processed", "true")
end

function MyPlugin:header_filter(conf)
    kong.response.set_header("X-Plugin-Version", self.VERSION)
end
return MyPlugin

schema.lua — определяет, какие параметры конфигурации принимает плагин и как их валидировать:

local typedefs = require "kong.db.schema.typedefs"

return {
    name = "my-plugin",
    fields = {
        { consumer = typedefs.no_consumer },
        {
            config = {
                type = "record",
                fields = {
                    {
                        my_setting = {
                            type = "string",
                            default = "default_value",
                            required = true,
                        },
                    },
                },
            },
        },
    },
}

Plugin Development Kit (PDK)

Kong предоставляет PDK (пространство имён kong.*) — стабильный задокументированный API, через который плагины взаимодействуют с циклом запрос/ответ, не касаясь внутренностей Nginx напрямую:

  • kong.request.* — чтение входящего запроса (заголовки, тело, путь, метод)

  • kong.service.request.* — модификация запроса, идущего к upstream-сервису

  • kong.response.* — чтение/модификация ответа, возвращаемого клиенту

  • kong.log.* — структурированное логирование (debug, info, notice, warn, err, crit)

  • kong.ctx.* — общий контекст между фазами в рамках одного запроса

Приоритет плагинов

Поле PRIORITY в handler.lua определяет порядок выполнения. Чем выше приоритет — тем раньше запускается плагин. Например:

pre-function:                     1000000
jwt2headers (наш кастомный):      1998
jwt:                              1005
circuit-breaker (наш кастомный):  920
rate-limiting:                    910
post-function:                    -1000

Этот порядок критически важен. Например, jwt2headers (1998) выполняется раньше встроенного плагина jwt (1005): он извлекает claims из уже присутствующего JWT в заголовке Authorization и добавляет их как upstream-заголовки, чтобы последующие плагины — rate-limiting или circuit-breaker — могли использовать эти заголовки.

Подходы к установке кастомных плагинов на Kubernetes

Когда Kong работает на Kubernetes (как правило, через официальный Helm-чарт), существует три основных способа деплоя кастомных плагинов:

Сравнение трёх подходов к деплою кастомных плагинов Kong на Kubernetes

Сравнение трёх подходов к деплою кастомных плагинов Kong на Kubernetes

1. Пользовательский Docker-образ

Самый прямолинейный подход — собрать кастомный Docker-образ на основе официального образа Kong с предустановленным кодом плагина. Существуют два варианта в зависимости от способа распространения плагина.

Вариант А: прямое копирование Lua-файлов

Если плагин написан на чистом Lua (без зависимостей от C), можно просто скопировать файлы плагина в директорию плагинов Kong:

FROM kong/kong-gateway:3.6

# Копируем исходный код плагина в директорию плагинов Kong
COPY kong/plugins/jwt2headers /usr/local/share/lua/5.1/kong/plugins/jwt2headers
COPY kong/plugins/circuit-breaker /usr/local/share/lua/5.1/kong/plugins/circuit-breaker

# Для библиотечных зависимостей — копируем по пути Lua-пакетов
COPY kong/plugins/lua-circuit-breaker/lua-circuit-breaker /usr/local/share/lua/5.1/lua-circuit-breaker

# Указываем Kong загружать эти плагины (дополнительно к встроенным)
ENV KONG_PLUGINS="bundled,jwt2headers,circuit-breaker"

Вариант Б: установка через LuaRocks

Если плагин опубликован как LuaRocks-пакет (.rockspec), его можно установить командой luarocks, которая уже есть в образе Kong:

FROM kong/kong-gateway:3.6

USER root

# Устанавливаем плагин из LuaRocks
RUN luarocks install kong-plugin-my-custom-plugin 1.0.0

# Или устанавливаем из локального rockspec
COPY my-plugin-1.0.0-1.rockspec /tmp/
COPY kong/plugins/my-plugin /tmp/kong/plugins/my-plugin
RUN cd /tmp && luarocks make my-plugin-1.0.0-1.rockspec

USER kong
ENV KONG_PLUGINS="bundled,my-custom-plugin"

Затем в values.yaml Helm-чарта Kong:

image:
  repository: ghcr.io/your-org/kong-custom
  tag: "3.6-with-plugins"

Нюансы:

  • При выходе новой версии Kong нужно пересобрать кастомный образ поверх нового базового — и протестировать все плагины на совместимость.

  • Жизненный цикл плагина жёстко привязан к среде выполнения Kong: обновить плагин без передеплоя всего Kong невозможно.

  • Откат означает повторный деплой всего Kong с предыдущим тегом образа.

2. CRD KongPluginInstallation (Kong Ingress Controller)

Если вы используете Kong Ingress Controller (KIC 3.2+), Custom Resource Definition KongPluginInstallation предоставляет нативный для Kubernetes способ установки плагинов из OCI-совместимых реестров контейнеров:

apiVersion: gateway-operator.konghq.com/v1alpha1
kind: KongPluginInstallation
metadata:
  name: jwt2headers
spec:
  image: ghcr.io/your-org/kong-plugins/jwt2headers:1.0.3

Kong Gateway Operator отслеживает такие CRD и берёт на себя загрузку и монтирование кода плагина в контейнер Kong.

Нюансы:

  • Доступно только при использовании Kong Ingress Controller и Kong Gateway Operator — не подходит для standalone-Kong или деплоев Kong Konnect data plane.

  • Требует сборки и публикации плагинов как OCI-образов контейнеров (формат, отличный от Helm-чартов).

  • Добавляет зависимость от цикла согласования KIC: доступность плагина определяется тем, успешно ли оператор обработал CRD.

3. Монтирование через ConfigMap с помощью Helm-чартов (подход этой статьи)

Упакуйте Lua-код плагина в Helm-чарт, создающий Kubernetes ConfigMap. Затем настройте Helm-чарт Kong на монтирование этих ConfigMap в контейнер Kong во время выполнения.

Преимущества:

  • Никаких кастомных Docker-образов — используется официальный образ Kong как есть.

  • Код плагина версионируется независимо в виде Helm-чартов.

  • Стандартные примитивы Kubernetes (ConfigMap, Volume, VolumeMount).

  • Работает с любым деплоем Kong (standalone, KIC, Kong Konnect data planes).

  • Простой откат — достаточно сменить версию Helm-чарта.

  • Обновление плагинов не требует смены образа пода Kong — только обновление ConfigMap и перезапуск пода.

Именно этот подход мы подробно рассмотрим в дальнейшем.

Структура репозитория

Полный исходный код организован в репозитории kong-plugins:

kong-plugins/
├── .github/workflows/
│   ├── jwt2headers-plugin-release.yml
│   ├── circuit-breaker-plugin-release.yml
│   └── lua-circuit-breaker-lib-release.yml
├── kong/plugins/
│   ├── jwt2headers/
│   │   ├── handler.lua
│   │   ├── schema.lua
│   │   └── helm/
│   │       ├── Chart.yaml
│   │       ├── values.yaml
│   │       └── templates/
│   │           └── configmap.yaml
│   ├── circuit-breaker/
│   │   ├── handler.lua
│   │   ├── schema.lua
│   │   ├── helpers.lua
│   │   └── helm/
│   │       └── ...
│   └── lua-circuit-breaker/
│       ├── lua-circuit-breaker/
│       │   └── *.lua
│       └── helm/
│           └── ...
├── spec/                  # Тесты (Pongo/Busted)
├── docs/                  # Документация плагинов
└── README.md

В репозитории два кастомных плагина и одна библиотека:

  • jwt2headers — простой плагин без внешних зависимостей: извлекает claims из JWT и добавляет их как заголовки запроса к upstream.

  • circuit-breaker — плагин, реализующий паттерн «предохранитель» (circuit breaker). Зависит от внешней Lua-библиотеки lua-circuit-breaker.

  • lua-circuit-breaker — чистая Lua-библиотека, используемая плагином circuit-breaker. Разворачивается отдельно как собственный Helm-чарт.

Детальный разбор плагина: jwt2headers

Плагин jwt2headers — хороший пример простого самодостаточного кастомного плагина.

Что он делает

Плагин разбирает JWT-токен из заголовка Authorization, извлекает настроенные claims и добавляет их как HTTP-заголовки в запрос, идущий к upstream-сервису. Таким образом, бэкенд-сервисы могут читать данные о пользователе (email, роли, группы) прямо из заголовков запроса, не разбирая JWT самостоятельно.

handler.lua

Основная логика в handler.lua:

local jwt_parser = require "kong.plugins.jwt.jwt_parser"
local cjson = require "cjson"

local Jwt2Headers = {
    PRIORITY = 1998,
    VERSION = "0.0.1",
}

function Jwt2Headers:access(conf)
    local auth_header = kong.request.get_header("Authorization")
    local token = extract_token(auth_header)
    if not auth_header or not token then
        return kong.response.exit(401, { message = "Missing or Invalid Authorization header" })
    end
    local jwt, err = jwt_parser:new(token)
    if err then
        return kong.response.exit(401, { message = "Invalid JWT token" })
    end

   for claim_name, claim_value in pairs(jwt.claims) do
        if should_include_claim(claim_name, conf) then
            local header_name = get_header_name(claim_name, conf)
            kong.service.request.set_header(header_name, stringify_value(claim_value))
        end
    end
end

return Jwt2Headers

Ключевые моменты:

  • Приоритет 1998 — выполняется после стандартных плагинов аутентификации, но до ограничения частоты запросов и трансформаций.

  • Использует kong.plugins.jwt.jwt_parser (встроен в Kong) — внешних библиотек не требуется.

  • Фаза access выбрана потому, что на этом этапе запрос уже маршрутизирован, но ещё не проксирован.

  • Используются методы PDK: kong.request.get_header(), kong.service.request.set_header(), kong.response.exit().

schema.lua

Файл schema.lua определяет параметры конфигурации:

config = {
    type = "record",
    fields = {
        { claims_to_include = { type = "array", elements = { type = "string" }, default = { "*" } } },
        { claims_to_exclude = { type = "array", elements = { type = "string" }, default = { "iat", "exp", "jti", "ver", "auth_time" } } },
        { custom_headers = { type = "map", keys = { type = "string" }, values = { type = "string" }, default = {} } },
        { header_prefix = { type = "string", default = "x-", len_min = 1 } },
    },
}

Детальный разбор плагина: circuit-breaker (с библиотечной зависимостью)

Установка circuit-breaker plugin сложнее, поскольку он зависит от библиотеки lua-circuit-breaker.

Что он делает

Плагин оборачивает вызовы к upstream в машину состояний предохранителя (Closed → Open → Half-Open). Когда доля ошибок для upstream превышает порог в рамках скользящего окна, предохранитель «открывается» и последующие запросы немедленно отклоняются без обращения к upstream — защищая систему от каскадных сбоев.

Библиотечная зависимость

Посмотрим на первую строку handler.lua:

local circuit_breaker_lib = require "lua-circuit-breaker.factory"

Эта инструкция require заставляет Lua искать файл lua-circuit-breaker/factory.lua где-то в пути поиска Lua-пакетов. В этом и состоит ключевая сложность — код библиотеки должен находиться в директории, доступной Lua-среде выполнения Kong.

По умолчанию package.path Kong включает /usr/local/share/lua/5.1/. Поэтому если смонтировать файлы библиотеки по следующим путям:

/usr/local/share/lua/5.1/lua-circuit-breaker/factory.lua
/usr/local/share/lua/5.1/lua-circuit-breaker/circuit_breaker.lua
...

Вызов require "lua-circuit-breaker.factory" разрешится корректно без каких-либо изменений package.path.

Упаковка плагинов в Helm-чарты

Ключевая идея подхода: версионировать код плагина как Helm-чарт. Каждый плагин (и каждая библиотека) получает собственный Helm-чарт, создающий Kubernetes ConfigMap с Lua-исходниками.

Структура Helm-чарта

Директория helm/ каждого плагина содержит:

helm/
├── Chart.yaml              -- Метаданные и версия чарта
├── values.yaml             -- Значения по умолчанию
├── .helmignore             -- Файлы, исключаемые при упаковке
└── templates/
    └── configmap.yaml      -- Шаблон, создающий ConfigMap

Chart.yaml

Файл Chart.yaml определяет метаданные чарта:

apiVersion: v1
appVersion: "1.0"
description: A Helm chart for jwt2headers Kong custom plugin
name: jwt2headers
version: 0.1.0
sources:
  - https://github.com/shambhand/kong-plugins.git

Шаблон ConfigMap

Шаблон configmap.yaml динамически включает все .lua-файлы:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  {{ $currentScope := .}}
  {{ range $path, $_ :=  .Files.Glob  "**.lua" }}
  {{ $path }}: |-
  {{- with $currentScope}}
{{ .Files.Get $path | indent 4}}
  {{- end }}
  {{ end }}

Здесь происходит самое интересное. Шаблон использует .Files.Glob Helm для поиска всех .lua-файлов в директории чарта и включает их содержимое в ConfigMap. Это значит, что в процессе CI/CD мы сначала копируем Lua-исходники в директорию helm/, а затем упаковываем чарт — и они оказываются внутри него.

CI/CD: workflows GitHub Actions

Для каждого плагина создан отдельный workflow GitHub Actions, автоматизирующий версионирование, упаковку и публикацию.

CI/CD-пайплайн: автоматическое версионирование

CI/CD-пайплайн: автоматическое версионирование, упаковка и публикация в GHCR

Обзор workflow

Рассмотрим workflow для jwt2headers:

name: jwt2headers-plugin
on:
  push:
    branches: [main]
    paths:
      - 'kong/plugins/jwt2headers/**'
      - '.github/workflows/jwt2headers-plugin-release.yml'
  workflow_dispatch:
    inputs:
      Branch:
        description: 'Branch to Build'
        required: true
        default: 'main'

Workflow запускается автоматически при пуше изменений в ветку main или вручную через workflow_dispatch с возможностью выбрать ветку.

Семантическое версионирование по git-тегам

Workflow вычисляет версии из git-тегов с префиксом, специфичным для каждого плагина:

- name: Compute Semantic Version
  id: compute-version
  run: |
    TAG_PREFIX="jwt2headers-"
    LATEST_TAG=$(git tag --list "${TAG_PREFIX}*" --sort=-v:refname | head -n 1)
    if [ -z "$LATEST_TAG" ]; then
      NEW_VERSION="1.0.0"
    else
      VERSION="${LATEST_TAG#$TAG_PREFIX}"
      MAJOR=$(echo "$VERSION" | cut -d. -f1)
      MINOR=$(echo "$VERSION" | cut -d. -f2)
      PATCH=$(echo "$VERSION" | cut -d. -f3)
      NEW_PATCH=$((PATCH + 1))
      NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
    fi

Каждый плагин использует свой префикс тега (jwt2headers-, circuit-breaker-, lua-circuit-breaker-), поэтому несколько плагинов в одном репозитории можно версионировать независимо.

Сборка и публикация

- name: Copy Lua Files to Helm Chart Directory
  run: |
    find ./kong/plugins/jwt2headers -name '*.lua' -exec cp -prv '{}' "${{ env.helm_chart_dir }}" ";"

- name: Helm Package
  run: helm package ${{ env.helm_chart_dir }} --version ${{ steps.compute-version.outputs.VERSION }}

- name: Helm Push to GHCR
  run: helm push jwt2headers-${{ steps.compute-version.outputs.VERSION }}.tgz oci://ghcr.io/${{ github.repository_owner }}/helm-charts

Ключевые шаги:

  1. Копирование Lua-файлов в директорию Helm-чарта (чтобы .Files.Glob их нашёл).

  2. Упаковка чарта с вычисленной версией.

  3. Публикация в GitHub Container Registry (GHCR) как OCI-артефакт.

После успешной сборки из ветки main workflow тегирует коммит:

- name: Push Release Tag
  if: ${{ github.event.inputs.Branch == 'main' }}
  run: |
    git tag -a "${{ steps.compute-version.outputs.TAG_VERSION }}" \
      -m "Release ${{ steps.compute-version.outputs.TAG_VERSION }}"
    git push origin "${{ steps.compute-version.outputs.TAG_VERSION }}"

Деплой в Kubernetes: пошаговое руководство

Теперь перейдём к главному — развёртыванию плагинов в Kong Gateway на Kubernetes с помощью официального Helm-чарта Kong.

Шаг 1: Установка Helm-чартов плагинов

Сначала разверните ConfigMap плагинов в namespace Kong. Порядок важен: библиотечные зависимости устанавливаются раньше плагинов, которые их используют.

# Добавляем репозиторий Helm Kong (для самого чарта Kong)
helm repo add kong https://charts.konghq.com
helm repo update

# Создаём namespace
kubectl create namespace kong

# Устанавливаем чарт плагина jwt2headers (простой плагин, без зависимостей)
helm upgrade --install jwt2headers oci://ghcr.io/shambhand/helm-charts/jwt2headers \
  --version 1.0.3 \
  -n kong

# Сначала устанавливаем чарт библиотеки lua-circuit-breaker (зависимость)
helm upgrade --install lua-circuit-breaker oci://ghcr.io/shambhand/helm-charts/lua-circuit-breaker \
  --version 1.0.2 \
  -n kong

# Затем устанавливаем чарт плагина circuit-breaker
helm upgrade --install circuit-breaker oci://ghcr.io/shambhand/helm-charts/circuit-breaker \
  --version 1.0.2 \
  -n kong

После этого шага в namespace kong появятся три ConfigMap:

$ kubectl get configmaps -n kong
NAME                                DATA
jwt2headers-configmap               2    # handler.lua, schema.lua
circuit-breaker-configmap           3    # handler.lua, schema.lua, helpers.lua
lua-circuit-breaker-configmap       6    # factory.lua, circuit_breaker.lua, ...

Шаг 2: Настройка values.yaml Helm-чарта Kong

Теперь настроим Helm-чарт Kong на использование этих ConfigMap. Есть два варианта конфигурации в зависимости от наличия у плагина библиотечных зависимостей.

Простой плагин (jwt2headers) — только ConfigMap

Для плагинов без внешних Lua-зависимостей секция plugins.configMaps Helm-чарта Kong справляется сама:

# values.yaml для Helm-чарта Kong
plugins:
  configMaps:
    - pluginName: jwt2headers
      name: jwt2headers-configmap

Плагин с библиотечной зависимостью (circuit-breaker) — ConfigMap + монтирование тома

Для circuit-breaker нужно решить две задачи:

  1. Подключить код самого плагина (через plugins.configMaps — как выше).

  2. Подключить библиотеку lua-circuit-breaker (через deployment.userDefinedVolumes и deployment.userDefinedVolumeMounts).

Полный values.yaml для Helm-чарта Kong с обоими плагинами:

# values.yaml для Helm-чарта Kong
plugins:
  configMaps:
    - pluginName: jwt2headers
      name: jwt2headers-configmap
    - pluginName: circuit-breaker
      name: circuit-breaker-configmap

deployment:
  userDefinedVolumes:
    - name: lua-circuit-breaker-lib
      configMap:
        name: lua-circuit-breaker-configmap
  userDefinedVolumeMounts:
    - name: lua-circuit-breaker-lib
      mountPath: /usr/local/share/lua/5.1/lua-circuit-breaker

Критически важная деталь — mountPath: /usr/local/share/lua/5.1/lua-circuit-breaker. Этот путь входит в стандартный package.path Kong, поэтому когда плагин circuit-breaker выполняет:

local circuit_breaker_lib = require "lua-circuit-breaker.factory"

Lua разрешает это в /usr/local/share/lua/5.1/lua-circuit-breaker/factory.lua — ровно туда, куда мы смонтировали ConfigMap.

Альтернатива: пользовательский путь поиска Lua-пакетов

Если библиотека монтируется в нестандартное место (например, /opt/kong/lua-libs/lua-circuit-breaker/), нужно добавить его в package.path Kong:

env:
  lua_package_path: "/opt/kong/lua-libs/?.lua;/opt/kong/lua-libs/?/init.lua;;"

Замыкающие ;; добавляют путь поиска по умолчанию. Однако использовать стандартный путь /usr/local/share/lua/5.1/ проще — это позволяет обойтись без такой настройки.

Шаг 3: Установка Kong

$ helm upgrade --install kong kong/kong \
  -f values.yaml \
  -n kong \
  --create-namespace

$ kubectl get pods -n kong
NAME                         READY   STATUS    RESTARTS   AGE
kong-kong-86c7cd455f-wdpct   2/2     Running   0          44m

$ helm list -n kong
NAME                NAMESPACE REVISION UPDATED                              STATUS   CHART                     APP VERSION
circuit-breaker     kong      1        2026-03-17 23:37:26.853931 +0530 IST deployed circuit-breaker-1.0.2     1.0
jwt2headers         kong      1        2026-03-17 23:36:26.858752 +0530 IST deployed jwt2headers-1.0.3         1.0
kong                kong      1        2026-03-17 23:41:41.161226 +0530 IST deployed kong-3.1.0                3.9
lua-circuit-breaker kong      1        2026-03-17 23:37:13.268607 +0530 IST deployed lua-circuit-breaker-1.0.2 1.0

Kong запустится с загруженными кастомными плагинами, готовыми к подключению к сервисам и маршрутам.

Что происходит под капотом

Стоит разобраться, что именно Helm-чарт Kong делает с конфигурацией plugins.configMaps и deployment.userDefined*. При рендеринге деплоя Kong Helm транслирует эти значения в стандартные примитивы Kubernetes в спецификации пода.

Как values.yaml транслируется в тома Kubernetes

Как values.yaml транслируется в тома Kubernetes, монтирования и переменные окружения

1. Переменная окружения KONG_PLUGINS

Чарт Kong собирает все pluginName из plugins.configMaps и формирует переменную окружения KONG_PLUGINS. Она сообщает Kong, какие плагины загружать при старте — встроенные плюс кастомные:

env:
  - name: KONG_PLUGINS
    value: "bundled,jwt2headers,circuit-breaker"

Без этой переменной Kong не будет пытаться загружать кастомные плагины, даже если их код присутствует на диске. Ключевое слово bundled гарантирует, что стандартные плагины Kong остаются доступными наряду с кастомными.

2. Volume и VolumeMount для ConfigMap плагинов

Для каждой записи в plugins.configMaps чарт создаёт Volume, ссылающийся на ConfigMap, и VolumeMount, размещающий Lua-файлы плагина по пути /opt/kong/plugins/<pluginName>/:

containers:
  - name: proxy
    volumeMounts:
      - name: kong-plugin-jwt2headers
        mountPath: /opt/kong/plugins/jwt2headers
        readOnly: true
      - name: kong-plugin-circuit-breaker
        mountPath: /opt/kong/plugins/circuit-breaker
        readOnly: true
      # Из deployment.userDefinedVolumeMounts
      - name: lua-circuit-breaker-lib
        mountPath: /usr/local/share/lua/5.1/lua-circuit-breaker
        readOnly: true

# Рендерится в спецификации пода деплоя Kong
volumes:
  - name: kong-plugin-jwt2headers
    configMap:
      name: jwt2headers-configmap
  - name: kong-plugin-circuit-breaker
    configMap:
      name: circuit-breaker-configmap
  # Из deployment.userDefinedVolumes
  - name: lua-circuit-breaker-lib
    configMap:
      name: lua-circuit-breaker-configmap

Обратите внимание на разницу между двумя типами монтирования:

  • Код плагина (plugins.configMaps) → монтируется в /opt/kong/plugins/<pluginName>/. Стандартный KONG_LUA_PACKAGE_PATH уже включает /opt/kong/plugins/?.lua и /opt/kong/plugins/?/init.lua, поэтому Kong автоматически находит handler.lua и schema.lua.

  • Код библиотеки (deployment.userDefinedVolumeMounts) → монтируется в /usr/local/share/lua/5.1/lua-circuit-breaker/. Это путь в стандартном package.path Lua, поэтому require "lua-circuit-breaker.factory" разрешается без переопределения путей.

3. Переменная окружения KONG_LUA_PACKAGE_PATH

Чарт Kong также задаёт KONG_LUA_PACKAGE_PATH с включением директории /opt/kong/plugins/, куда монтируются ConfigMap плагинов:

env:
  - name: KONG_LUA_PACKAGE_PATH
    value: "/opt/kong/plugins/?.lua;/opt/kong/plugins/?/init.lua;;"

Замыкающие ;; добавляют стандартный путь поиска Lua (включающий /usr/local/share/lua/5.1/). Именно так Lua-среда выполнения Kong находит как код плагина, так и код библиотеки.

Итого, поток выглядит так:

Установка Helm-чарта плагина
 → Создаётся ConfigMap (Lua-исходники как ключи данных)
   → Helm-чарт Kong ссылается на ConfigMap в values.yaml
     → Чарт Kong рендерит: Volume + VolumeMount + переменная KONG_PLUGINS
       → Контейнер Kong стартует с файлами плагина на диске и зарегистрированными плагинами

Шаг 4: Включение плагинов на сервисах и маршрутах

Когда Kong запущен с загруженными плагинами, нужно включить их на сервисах и маршрутах. Есть два способа: декларативная конфигурация через decK или императивные вызовы Admin API.

Вариант А: Декларативная конфигурация через decK

decK — инструмент декларативного управления конфигурацией Kong, синхронизирующий настройки из YAML-файлов с Admin API Kong. Команда deck gateway sync с флагом --select-tag позволяет применять конфигурацию выборочно по тегам — идеальное решение для распределённого управления конфигурацией в многокомандных или многосредовых окружениях.

# Декларативная конфигурация Kong (kong.yaml)
_format_version: "3.0"
services:
  - name: "sample-httpbin"
    url: "https://httpbin.org"
    retries: 5
    connect_timeout: 60000
    write_timeout: 60000
    read_timeout: 60000
    tags: ["sample-httpbin"]
    routes:
      - name: uuid
        protocols: ["http"]
        methods: ["GET"]
        paths: ["/uuid"]
        strip_path: false
        tags: ["sample-httpbin"]
      - name: anything
        protocols: ["http"]
        methods: ["GET"]
        paths: ["/anything"]
        strip_path: false
        tags: ["sample-httpbin"]
    plugins:
      - name: jwt2headers
        tags: ["sample-httpbin"]
        config:
          claims_to_include: ["sub", "role", "groups"]
          claims_to_exclude: ["iat", "exp", "auth_time"]
          custom_headers:
            sub: "X-User-Email"
            role: "X-User-Role"
          header_prefix: "x-"
      - name: circuit-breaker
        tags: ["sample-httpbin"]
        config:
          window_time: 10
          min_calls_in_window: 2
          failure_percent_threshold: 51
          wait_duration_in_half_open_state: 5
          wait_duration_in_open_state: 5
          half_open_max_calls_in_window: 10
          half_open_min_calls_in_window: 5
          api_call_timeout_ms: 100
          error_status_code: 599
          error_msg_override: "{ \"message\": \"Circuit breaker did not allow the request\" }"

Применяем с помощью decK и флага --select-tag:

# Синхронизируем только ресурсы с тегом "sample-httpbin"
deck gateway sync kong.yaml --select-tag sample-httpbin

Флаг --select-tag особенно удобен при распределённом управлении конфигурацией: каждая команда владеет своими YAML-файлами с тегами и синхронизирует их независимо, не затрагивая конфигурацию других.

Вариант Б: Admin API

Альтернативно — настроить всё императивно через Admin API Kong с помощью curl:

# Создаём сервис
curl -s -X POST http://localhost:8001/services \
  -d "name=sample-httpbin" \
  -d "url=https://httpbin.org" \
  -d "retries=5" \
  -d "connect_timeout=60000" \
  -d "write_timeout=60000" \
  -d "read_timeout=60000" \
  -d "tags[]=sample-httpbin"
# Создаём маршруты для сервиса
curl -s -X POST http://localhost:8001/services/sample-httpbin/routes \
  -d "name=uuid" \
  -d "protocols[]=http" \
  -d "methods[]=GET" \
  -d "paths[]=/uuid" \
  -d "strip_path=false" \
  -d "tags[]=sample-httpbin"
curl -s -X POST http://localhost:8001/services/sample-httpbin/routes \
  -d "name=anything" \
  -d "protocols[]=http" \
  -d "methods[]=GET" \
  -d "paths[]=/anything" \
  -d "strip_path=false" \
  -d "tags[]=sample-httpbin"
# Включаем плагин jwt2headers для сервиса
curl -s -X POST http://localhost:8001/services/sample-httpbin/plugins \
  -d "name=jwt2headers" \
  -d "config.claims_to_include[]=sub" \
  -d "config.claims_to_include[]=role" \
  -d "config.claims_to_include[]=groups" \
  -d "config.claims_to_exclude[]=iat" \
  -d "config.claims_to_exclude[]=exp" \
  -d "config.claims_to_exclude[]=auth_time" \
  -d "config.custom_headers.sub=X-User-Email" \
  -d "config.custom_headers.role=X-User-Role" \
  -d "config.header_prefix=x-" \
  -d "tags[]=sample-httpbin"
# Включаем плагин circuit-breaker для сервиса
curl -s -X POST http://localhost:8001/services/sample-httpbin/plugins \
  -d "name=circuit-breaker" \
  -d "config.window_time=10" \
  -d "config.min_calls_in_window=2" \
  -d "config.failure_percent_threshold=51" \
  -d "config.wait_duration_in_half_open_state=5" \
  -d "config.wait_duration_in_open_state=5" \
  -d "config.half_open_max_calls_in_window=10" \
  -d "config.half_open_min_calls_in_window=5" \
  -d "config.api_call_timeout_ms=100" \
  -d "config.error_status_code=599" \
  -d "tags[]=sample-httpbin"

Декларативный подход с decK предпочтителен для production-окружений: конфигурация хранится в системе контроля версий, воспроизводима и поддерживает обнаружение расхождений (deck gateway diff). Admin API удобен для быстрого тестирования или скриптовой автоматизации.

Kong Konnect: особенности гибридного режима

Если вы используете Kong Konnect в гибридном режиме — где Control Plane размещается у Kong (SaaS), а Data Plane разворачивается самостоятельно в вашем Kubernetes-кластере — для кастомных плагинов потребуется дополнительный шаг.

Проблема

В гибридном режиме Control Plane должна валидировать конфигурации плагинов перед их отправкой на Data Plane. Для встроенных плагинов схемы уже есть на Control Plane. Для кастомных плагинов схему нужно загрузить вручную, чтобы Control Plane могла проверять конфигурации.

Загрузка схем плагинов в Konnect

Загрузите schema.lua вашего плагина на Control Plane Konnect через Admin API или интерфейс Gateway Manager:

  1. Откройте Gateway Manager в дашборде Konnect.

  2. Выберите свой Control Plane.

  3. Перейдите в Plugins → Custom Plugins → Add Custom Plugin Schema.

  4. Загрузите файл schema.lua для каждого кастомного плагина.

Либо воспользуйтесь Konnect Admin API:

curl -X POST https://{KONNECT_REGION}.api.konghq.com/v2/control-planes/{CP_ID}/core-entities/plugin-schemas \
  -H "Authorization: Bearer $KONNECT_TOKEN" \
  -F "name=jwt2headers" \
  -F "lua_schema=@kong/plugins/jwt2headers/schema.lua"

Деплой Data Plane для Konnect с плагинами

Деплой Data Plane остаётся точно таким же, как описано выше: устанавливаем Helm-чарты плагинов и прописываем ссылки на ConfigMap в values.yaml Helm-чарта Kong. Data Plane нужен Lua-код для выполнения плагинов, тогда как Control Plane требуется только схема для валидации.

Стратегия версионирования

Одно из главных преимуществ этого подхода — независимое версионирование. Каждый плагин — это отдельный Helm-чарт со своим жизненным циклом версий:

jwt2headers         → 1.0.0, 1.0.1, 1.1.0, ...
circuit-breaker     → 1.0.0, 1.0.1, 2.0.0, ...
lua-circuit-breaker → 1.0.0, 1.0.1, ...

Это означает:

  • Исправление бага в jwt2headers? Увеличиваем только его версию. Чарт circuit-breaker не меняется.

  • Обновление Kong? Версии чартов плагинов не затрагиваются — достаточно обновить версию чарта Kong.

  • Откат плагина? helm rollback jwt2headers 1 -n kong — и готово.

CI/CD-workflow автоматически вычисляет версии по git-тегам:

git tag jwt2headers-1.0.0     → публикует чарт jwt2headers v1.0.0
git tag jwt2headers-1.0.1     → публикует чарт jwt2headers v1.0.1
git tag circuit-breaker-2.0.0 → публикует чарт circuit-breaker v2.0.0

Все чарты публикуются в GHCR как OCI-артефакты и доступны по адресам:

oci://ghcr.io/shambhand/helm-charts/jwt2headers
oci://ghcr.io/shambhand/helm-charts/circuit-breaker
oci://ghcr.io/shambhand/helm-charts/lua-circuit-breaker

Тестирование с Pongo

В репозитории используется Kong Pongo — фреймворк для тестирования, поднимающий полноценное окружение Kong с PostgreSQL для интеграционных тестов.

# Клонируем репозиторий
git clone https://github.com/shambhand/kong-plugins.git
cd kong-plugins
# Запускаем оболочку Pongo
pongo shell
# Запускаем все тесты
pongo run
# Запускаем тесты конкретного плагина
pongo run spec/plugins/jwt2headers/
pongo run spec/plugins/circuit-breaker/

Тесты охватывают:

  • Валидацию схемы — проверяет корректность конфигурационной валидации.

  • Логику фазы access — тестирует реальную обработку запросов через Kong.

  • Сценарии применения — проверяет, что плагин можно подключить глобально, на уровне сервиса и на уровне маршрута.

Итоги

Деплой кастомных плагинов Kong на Kubernetes вовсе не обязательно означает сборку пользовательских Docker-образов или управление пакетами LuaRocks. Упаковывая код плагинов в Helm-чарты, создающие ConfigMap, вы получаете:

  • Чёткое разделение — код плагина версионируется и разворачивается независимо от среды выполнения Kong.

  • Стандартный инструментарий — Helm, OCI-реестры и Kubernetes ConfigMap — хорошо понятные примитивы.

  • Простой CI/CD — GitHub Actions и GHCR обеспечивают прямолинейный пайплайн публикации.

  • Лёгкие обновления — версии Kong и плагинов обновляются независимо друг от друга.

  • Поддержка библиотек — даже плагины с зависимостями от Lua-библиотек подключаются через userDefinedVolumes.

Полный исходный код, Helm-чарты, CI/CD-workflows и тесты доступны на github.com/shambhand/kong-plugins.

© 2026 meganuke