fix(cli): stop gating nginx.conf on the config file plugin list - #13878
Open
AlinsRan wants to merge 6 commits into
Open
fix(cli): stop gating nginx.conf on the config file plugin list#13878AlinsRan wants to merge 6 commits into
AlinsRan wants to merge 6 commits into
Conversation
`plugins` / `stream_plugins` in config.yaml is only the boot-time default:
`/apisix/plugins` in etcd replaces the list while APISIX runs, long after
`apisix init` has rendered nginx.conf. Any directive the template made
conditional on that list is therefore missing for a plugin enabled the etcd
way, and nginx cannot grow a shared memory zone -- or a location, or a mirror
-- without a reload, so such a plugin silently does nothing.
`apisix_stream_metrics_zone` is the case that surfaced this: the zone was
rendered only when `stream_plugins` named `prometheus`, so on a deployment
whose plugin list lives in etcd the stream prometheus plugin loaded and ran
but `apisix_stream_active_connections` and `apisix_stream_bandwidth` were
never published, while the two metrics that do not read the zone kept working.
Every shared dict, `lua_capture_error_log`, the proxy-cache, proxy-mirror and
proxy-buffering directives are now rendered unconditionally, and the
prometheus export server follows `plugin_attr.prometheus.enable_export_server`
alone. Two gates move to a condition that is actually about the environment
rather than the plugin list: the dubbo directives need `mod_dubbo` and
`ngx_multi_upstream_module`, so they follow APISIX-Runtime, and
`prometheus-metrics` stays in the `lua{}` block whenever the stream subsystem
runs so that both subsystems share it.
The cost is memory that a trimmed plugin list used to save. With the default
list only `tracing_buffer`, `ocsp-stapling` and `lua_capture_error_log` are
new; a deployment that lists only a handful of plugins now allocates the full
set of dicts. A stream-only deployment also gets the http{} block that hosts
the export server, which `enable_export_server: false` still removes.
…list The template half of this change renders `apisix_stream_metrics_zone` whatever config.yaml lists, but the exporter still decided whether to define the L4 metrics at all from `stream_plugins` in that same file: the prometheus plugin's `init` hook passed `array_find(local_conf.stream_plugins, "prometheus")` into `exporter.http_init`. That hook runs whenever the plugin is loaded -- including when `/apisix/plugins` in etcd loads it -- so on a data plane whose plugin list comes from etcd `metrics` was rebuilt without `stream_active_connections`, and `collect_stream_zone_metrics()` stopped at its first guard for the rest of the process's life. The zone was there and filling; nothing read it. It now follows whether the stream subsystem runs, which is a property of the deployment rather than of a list that changes under APISIX. This is what `t/cli/test_prometheus_stream.sh` caught: the block added in the previous commit renders the zone, but exported no `apisix_stream_bandwidth` until this.
The zipkin variables and the stream only export server both stopped
depending on the config file plugin list without a case that would notice.
test_zipkin_set_ngx_var.sh now leaves zipkin out of `plugins` (it fails
against the old gate), and test_stream_config.sh asserts the export server's
http{} block goes away again with `enable_export_server: false`.
shreemaan-abhishek
previously approved these changes
Aug 27, 2026
There was a problem hiding this comment.
Pull request overview
Decouples generated NGINX configuration from boot-time plugin lists so plugins enabled through etcd retain required resources.
Changes:
- Renders plugin infrastructure independently of local plugin lists.
- Bases Prometheus stream setup on proxy mode.
- Updates documentation and CLI/integration tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
apisix/cli/ngx_tpl.lua |
Removes plugin-list template gates. |
apisix/cli/ops.lua |
Stops passing plugin lists to the template. |
apisix/plugins/prometheus.lua |
Uses stream subsystem state for initialization. |
docs/en/latest/plugins/prometheus.md |
Documents etcd-enabled stream metrics. |
docs/zh/latest/plugins/prometheus.md |
Updates Chinese documentation. |
t/cli/test_http_config.sh |
Verifies unconditional HTTP shared dictionaries. |
t/cli/test_stream_config.sh |
Updates stream rendering assertions. |
t/cli/test_prometheus_stream.sh |
Adds an etcd plugin-list metrics scenario. |
t/cli/test_zipkin_set_ngx_var.sh |
Tests Zipkin attributes without local enablement. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
nic-6443
previously approved these changes
Aug 27, 2026
The http{} block hosting the export server is rendered inside the template's
`use_apisix_base` guard, so stock OpenResty still renders the stream block
alone. test_stream_config.sh has no exit_if_not_customed_nginx -- the rest of
it is runtime independent -- so the count is what has to be conditional.
AlinsRan
dismissed stale reviews from nic-6443, shreemaan-abhishek, and membphis
via
August 27, 2026 08:32
e81e3bf
`exporter.http_init()` returns early outside the init phases -- the `todo:
support hot reload` above that guard -- so it can restore an exporter that
init_worker built but cannot build one. Driving it from the plugin's own
`init` hook therefore only ever worked when config.yaml already listed
prometheus: a data plane that learns its plugin list from `/apisix/plugins`
kept `prometheus` nil for the life of the process and `/apisix/prometheus/
metrics` answered `{}`. `plugin.init_prometheus()` had the same problem for
the exporter timer.
This is wired the way the API7 gateway has been wiring it: the exporter is
built in `plugin.init_worker()` whatever the config file lists, and `load()`
drops it again when prometheus is not in the effective list and restores it
when it is. A deployment that never enables prometheus is left with a
destroyed exporter, so `exporter_timer` returns at its own `if not prometheus`
and costs nothing. The plugin `init` hooks that used to do this are gone,
which also stops the stream plugin's `destroy` hook from reaching across into
the HTTP exporter when `load_stream()` runs in the HTTP subsystem.
t/cli/test_prometheus.sh gains the case: no prometheus in config.yaml, Admin
API off, enabled through etcd once APISIX is already running. It answers `{}`
without this and exports metrics with it, and disabling it again through etcd
returns it to `{}`.
Whether the xRPC protocol metrics were recorded used to depend on nothing but `init_stream_metrics()` having run, which is to say on `stream_plugins` in config.yaml naming prometheus. Now that the exporter is built whatever the config file lists, that side effect is gone and every xRPC session would be counted, including on a data plane whose plugin list never enables the stream prometheus plugin. sdk.get_metrics() asks the effective plugin list instead, which is the list that actually decides, and keeps working when /apisix/plugins turns the plugin on or off later. t/xrpc/prometheus.t TEST 4 covers it: prometheus out of `stream_plugins`, a redis command through xRPC, and the counter must not move.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
plugins/stream_pluginsinconfig.yamlis only the boot-time default for the plugin list./apisix/pluginsin etcd replaces it while APISIX runs — through the Admin API, or written directly by whatever owns the configuration — and that happens long afterapisix inithas renderednginx.conf. So any directiveapisix/cli/ngx_tpl.luamade conditional on that list is missing for a plugin enabled the etcd way, and nginx cannot grow a shared memory zone (or a location, or amirror) without a reload. The plugin loads, runs, and silently does nothing.apisix_stream_metrics_zoneis the case that surfaced this. The zone was rendered only whenstream_pluginsnamedprometheus, so where the plugin list lives in etcd the stream prometheus plugin ran butapisix_stream_active_connectionsandapisix_stream_bandwidthwere never published, whileapisix_stream_connection_totalandapisix_stream_status, which do not read the zone, kept working. Fixing only that one gate would leave the same trap forlimit-conn,limit-count,api-breaker,openid-connect,ocsp-stapling,ext-plugin-*,mcp-bridge,skywalking,error-log-logger,proxy-cache,proxy-mirrorandproxy-buffering, so this removes the coupling everywhere.What changes in the template
lua_shared_dictthat was gated on the plugin list is now rendered unconditionally, as arelua_capture_error_log, the proxy-cache directives, the proxy-mirror locations andmirror, and the@disable_proxy_bufferinglocation.plugin_attr.prometheus.enable_export_serveralone (viaprometheus_server_addr), which is the knob that was always meant to govern it. The same applies to thehttp{}block that hosts it in stream-only mode.mod_dubboandngx_multi_upstream_module, so they follow APISIX-Runtime; andprometheus-metricsis declared in thelua{}block whenever the stream subsystem runs, so both subsystems share one dict.enabled_plugins/enabled_stream_pluginsare no longer passed to the template.What changes in the exporter
The same coupling existed in Lua, in two places, and both are fixed here:
inithook decided whether to define the L4 metrics fromstream_pluginsinconfig.yaml, so where the plugin list comes from etcd,metricswas built withoutstream_active_connectionsandcollect_stream_zone_metrics()stopped at its first guard for the rest of the process's life: the zone was rendered and filling, and nothing read it.exporter.http_init()returns early outside the init phases (thetodo: support hot reloadabove that guard), so driving it from the plugin'sinithook only ever worked whenconfig.yamlalready listed prometheus. Enabling the plugin through/apisix/pluginsafter startup leftprometheusnil for the life of the process and/apisix/prometheus/metricsanswering{}.plugin.init_prometheus()had the same problem for the exporter timer.Both are now wired the way the API7 gateway wires them, since its data plane always learns the plugin list from a control plane: the exporter is built in
plugin.init_worker()whatever the config file lists, andload()drops it withexporter.destroy()when prometheus is not in the effective list and restores it when it is. A deployment that never enables prometheus is left with a destroyed exporter, soexporter_timerreturns at its ownif not prometheusand costs nothing. The plugininithooks are gone, which also stops the stream plugin'sdestroyhook from reaching across into the HTTP exporter whenload_stream()runs in the HTTP subsystem.Cost
Memory that a trimmed plugin list used to save. With the default plugin list only
tracing_buffer,ocsp-staplingandlua_capture_error_logare new; a deployment that lists just a handful of plugins now allocates the full set of dicts. A stream-only deployment also gets thehttp{}block hosting the export server, whichenable_export_server: falsestill removes.Tests
t/cli/test_http_config.shandt/cli/test_stream_config.sh: the two "enable shdict on demand" checks asserted exactly the behaviour being removed; they now assert the dict is present whatever the config file lists.t/cli/test_stream_config.sh: stream-only mode renders a secondlua_package_pathon APISIX-Runtime (the export server'shttp{}), so the expected count follows the runtime and the case additionally asserts the http proxy is still absent, which is what it was really testing. A second case withenable_export_server: falseasserts the block goes away again.t/cli/test_prometheus_stream.sh: a new block seeds the plugin list into etcd with nostream_pluginsinconfig.yamland the Admin API off, then asserts both that the zone is rendered and thatapisix_stream_bandwidthis exported for proxied traffic. Both assertions fail onmaster.t/cli/test_prometheus.sh: a new block with prometheus in no local list at all and the Admin API off, enabled through etcd once APISIX is already running. It answers{}without the exporter change and exports metrics with it.t/cli/test_zipkin_set_ngx_var.sh: zipkin is now left out ofplugins, so the case fails against the oldenabled_plugins["zipkin"]gate.Which issue(s) this PR fixes:
N/A
Checklist