Deploy Lua Custom Plugins
Load a constrained Lua plugin into APISIX or API7 Gateway on Kubernetes, apply it with the Ingress Controller, and verify its response header.
Applies to:API7 Ingress Controller (drives API7 Gateway)APISIX Ingress Controller (drives Apache APISIX)
Goal
Load a custom Lua plugin named guide-response-header into the gateway and apply it to a route. The plugin adds a configured response header while the request still reaches a local upstream.
The example deliberately exposes no filesystem path, command, or arbitrary Lua expression through Route configuration. Treat every custom plugin as gateway code: review it, test it outside production, and restrict who can deploy or configure it.
Before you begin
- Complete Set Up Ingress Controller and Gateway.
- You have
kubectlconfigured against the cluster used in the setup guide. - Install Helm if you use the Apache APISIX path.
- Choose a free local port for the gateway port-forward. The examples use
9080.
Set the variables this guide uses:
export GATEWAY_SERVICE_NAME="apisix-gateway" # your gateway's Service name in namespace aic
export GATEWAY_LOCAL_PORT="9080"
export GATEWAY_URL="http://127.0.0.1:${GATEWAY_LOCAL_PORT}"Confirm that this guide will not overwrite existing Kubernetes resources:
for resource in configmap/guide-response-header deployment/lua-plugin-guide-upstream service/lua-plugin-guide-upstream; do
! kubectl get --namespace aic "${resource}" >/dev/null 2>&1 || { echo "Stop: ${resource} already exists"; exit 1; }
doneSteps
Step 1: Write the Lua plugin
Save the following constrained plugin as guide-response-header.lua:
local core = require("apisix.core")
local ngx = ngx
local plugin_name = "guide-response-header"
local schema = {
type = "object",
properties = {
value = {
type = "string",
minLength = 1,
maxLength = 64,
},
},
required = {"value"},
}
local _M = {
version = 0.1,
priority = 0,
name = plugin_name,
schema = schema,
}
function _M.check_schema(conf)
return core.schema.check(schema, conf)
end
function _M.header_filter(conf, ctx)
ngx.header["X-Guide-Plugin"] = conf.value
end
return _Mcheck_schema rejects missing, empty, or oversized values. The plugin cannot read files or execute user-supplied code.
Step 2: Load the plugin into the gateway
Every gateway pod that can serve the route must load the same reviewed plugin code. Use the workflow for your gateway product.
Create a ConfigMap in the gateway namespace:
kubectl create configmap guide-response-header \
--namespace aic \
--from-file=guide-response-header.luaSave the exact effective Helm values with restricted permissions, then make a working copy. These files can contain secrets; do not commit or retain them after the test.
umask 077
helm get values --namespace aic apisix --all -o yaml > apisix-values.before-lua-guide.yaml
cp apisix-values.before-lua-guide.yaml apisix-values.lua-guide.yamlIn apisix-values.lua-guide.yaml, enable the custom-plugin mount and append guide-response-header to the existing apisix.plugins list. Preserve every existing list entry and every unrelated value:
apisix:
customPlugins:
enabled: true
plugins:
- name: guide-response-header
configMap:
name: guide-response-header
mounts:
- key: guide-response-header.lua
path: /opts/custom_plugins/apisix/plugins/guide-response-header.lua
plugins:
# Keep every plugin already present in this list.
- ...
- guide-response-headerIf customPlugins.plugins already contains entries, append this item instead of replacing them. If the plugin name is already present, stop and use the deployment's source-controlled configuration rather than taking ownership of it in this guide.
Apply the working values and wait for the gateway rollout:
helm upgrade apisix apisix/apisix \
--namespace aic \
-f apisix-values.lua-guide.yaml
kubectl rollout status deployment/apisix --namespace aic --timeout=5mIf the upgrade or rollout fails, restore immediately:
helm upgrade apisix apisix/apisix \
--namespace aic \
-f apisix-values.before-lua-guide.yaml
kubectl rollout status deployment/apisix --namespace aic --timeout=5mUpload the reviewed plugin to the gateway group used by the Ingress Controller. Follow Register the Plugin in API7 Gateway (opens in API7 Gateway docs) and keep the plugin name guide-response-header.
Step 3: Create a local upstream
Save the following manifest as lua-plugin-upstream.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: lua-plugin-guide-upstream
spec:
replicas: 1
selector:
matchLabels:
app: lua-plugin-guide-upstream
template:
metadata:
labels:
app: lua-plugin-guide-upstream
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0.0
args: ["-listen=:5678", "-text=lua-plugin-upstream"]
ports:
- name: http
containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: lua-plugin-guide-upstream
spec:
selector:
app: lua-plugin-guide-upstream
ports:
- name: http
port: 80
targetPort: 5678Apply it and require the Deployment to become available:
kubectl apply -f lua-plugin-upstream.yaml
kubectl rollout status deployment/lua-plugin-guide-upstream --namespace aic --timeout=2mStep 4: Create a route with the custom plugin
Create lua-plugin-route.yaml for the API used by your Ingress Controller:
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: guide-response-header
spec:
plugins:
- name: guide-response-header
config:
value: enabled
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: lua-plugin-guide
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /lua-plugin-guide
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: guide-response-header
backendRefs:
- name: lua-plugin-guide-upstream
port: 80apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: lua-plugin-guide
spec:
ingressClassName: apisix
http:
- name: lua-plugin-guide
match:
paths:
- /lua-plugin-guide
plugins:
- name: guide-response-header
enable: true
config:
value: enabled
backends:
- serviceName: lua-plugin-guide-upstream
servicePort: 80Apply the route:
kubectl apply -f lua-plugin-route.yamlValidate
Expose the gateway Service on your local machine:
kubectl port-forward --namespace aic "svc/${GATEWAY_SERVICE_NAME}" "${GATEWAY_LOCAL_PORT}:80" &
PORT_FORWARD_PID=$!Capture the response and require both the plugin header and the exact upstream body:
curl --fail-with-body -sS --connect-timeout 3 --max-time 15 \
-D lua-guide-headers.txt -o lua-guide-body.txt \
"${GATEWAY_URL}/lua-plugin-guide"
tr -d '\r' < lua-guide-headers.txt | grep -Fqx 'X-Guide-Plugin: enabled'
test "$(cat lua-guide-body.txt)" = "lua-plugin-upstream"Both assertions must succeed. The header proves that guide-response-header ran; the exact body proves that the request still reached the controlled upstream.
Roll back / Clean up
Delete the route and upstream first:
kubectl delete -f lua-plugin-route.yaml
kubectl delete -f lua-plugin-upstream.yamlFor Apache APISIX, restore the exact values captured before the guide, wait for the rollout, and then remove the ConfigMap:
helm upgrade apisix apisix/apisix \
--namespace aic \
-f apisix-values.before-lua-guide.yaml
kubectl rollout status deployment/apisix --namespace aic --timeout=5m
kubectl delete configmap guide-response-header --namespace aicFor API7 Gateway, remove the custom plugin from the gateway group only if this guide created it and no other route uses it.
Stop the port-forward and remove local files, including the sensitive Helm values:
kill "${PORT_FORWARD_PID}" 2>/dev/null || true
rm -f guide-response-header.lua lua-plugin-route.yaml lua-plugin-upstream.yaml \
lua-guide-headers.txt lua-guide-body.txt \
apisix-values.before-lua-guide.yaml apisix-values.lua-guide.yaml
unset GATEWAY_SERVICE_NAME GATEWAY_LOCAL_PORT GATEWAY_URL PORT_FORWARD_PIDTroubleshoot
| Symptom | Cause | Fix |
|---|---|---|
| The gateway rollout fails. | The values file replaced an existing plugin entry, or the module path and plugin name differ. | Restore the pre-guide values immediately, then compare the custom-plugin mount and the complete plugin list. |
| The Route is rejected. | The gateway has not loaded guide-response-header, or value violates its schema. | Confirm that every gateway pod completed its rollout and use a non-empty string no longer than 64 characters. |
The response has no X-Guide-Plugin header. | The plugin is not attached, another Route matched, or the API7 gateway group does not contain the plugin. | Inspect the applied Route and PluginConfig, confirm the exact path, and verify the plugin registration for the gateway group. |
| The body assertion fails. | The route points to another backend or the sample Deployment is not ready. | Check the backend Service name and require the sample Deployment rollout to succeed before retrying. |
| Cleanup rollout fails. | The backup is missing or belongs to another Helm release. | Keep the ConfigMap in place until the correct pre-guide values are recovered and the gateway rolls out successfully. |