Ops Lua Analysis En
Categories:
APISIX CLI ops.lua Source Code Analysis
File Overview
apisix/cli/ops.lua is the CLI entry point module of Apache APISIX, responsible for handling all CLI subcommands (init, start, stop, restart, reload, test, etc.). It converts YAML configuration into nginx.conf and manages the OpenResty process lifecycle.
1. Command Routing Mechanism (action Table + _M.execute)
-- Lines 1104-1115: Command registry
local action = {
help = help,
version = version,
init = init,
init_etcd = etcd.init,
start = start,
stop = stop,
quit = quit,
restart = restart,
reload = reload,
test = test,
}
-- Lines 1118-1130: Command dispatch
function _M.execute(env, arg)
local cmd_action = arg[1]
if not cmd_action then
return help()
end
if not action[cmd_action] then
stderr:write("invalid argument: ", cmd_action, "\n")
return help()
end
action[cmd_action](env, arg[2])
end
Design note: An action table maps commands to functions — simple and extensible. Invalid commands print a hint and display help.
2. init(env) — Initialize nginx.conf (Lines 234-890)
This is the core function, responsible for generating nginx configuration from YAML. The flow:
2.1 Environment Pre-check (Lines 236-248)
- Detect running under
/root(only allowed in dev environments) - Check
ulimit; warn if below 1024
2.2 YAML Loading & Validation (Lines 251-259)
local yaml_conf, err = file.read_yaml_conf(env.apisix_home)
local ok, err = schema.validate(yaml_conf)
2.3 Key Configuration Validation
| Check | Location | Description |
|---|---|---|
| admin_key | Lines 284-331 | Checks if Admin API token is set; supports auto-generation of empty tokens |
| admin_api_mtls | Lines 333-344 | SSL cert must be configured when https_admin is enabled |
| OpenResty version | Lines 346-354 | Requires >= 1.21.4 |
| http_stub_status_module | Lines 357-361 | This module must be compiled in |
| proxy-cache plugin | Lines 397-399 | apisix.proxy_cache must be configured when enabled |
| batch-requests plugin | Lines 401-421 | Checks if real_ip_from includes loopback |
| standalone mode | Lines 262-281 | config_provider must be yaml/json when APISIX_STAND_ALONE=true |
2.4 Proxy Mode Handling (Lines 362-380)
if yaml_conf.apisix.proxy_mode == "http" then
enable_http, enable_stream = true, false
elseif yaml_conf.apisix.proxy_mode == "stream" then
enable_http, enable_stream = false, true
elseif yaml_conf.apisix.proxy_mode == "http&stream" then
enable_http, enable_stream = true, true
end
2.5 Port Conflict Detection (Lines 423-521)
validate_and_get_listen_addr() and listen_table_insert() use a ports_to_check table to prevent conflicts among admin/status/control/prometheus/HTTP/HTTPS ports.
Ports involved:
- Admin: default 9180
- Status: default 7085
- Control: default 9090
- Prometheus: default 9091
2.6 HTTP/HTTPS Listen Normalization (Lines 523-601)
- Normalizes
node_listen(which can be a number, table, or nested table) to a standard format[{ip, port, enable_http3}] - Normalizes
ssl.listensimilarly - Since v3.9,
enable_http2has moved from port-level to the global apisix level; port-level configs now raise errors
2.7 Port Validation (validate_port_or_range, Lines 61-131)
A hand-written port number/range validator supporting:
- Integer port numbers (1-65535)
- Range format (
start-end) - IPv4 with port (
0.0.0.0:8080) - IPv6 with port (
[::1]:8080)
2.8 Template Rendering (Lines 679-889)
Packs all processed configuration into a sys_conf table, renders the nginx template using resty.template, and writes to conf/nginx.conf.
Key sys_conf fields:
worker_rlimit_nofile: Automatically set toworker_connections + 128worker_processes: Forced to 1 in dev_mode, otherwise defaults to “auto”dns_resolver: Auto-read from/etc/resolv.confwhen not configuredextra_lua_path/cpath: Appends custom Lua pathsenvs: Injects environment variables (including those needed for Kubernetes service discovery)
2.9 Cluster Role Handling (Lines 663-671)
if role == "control_plane" and not admin_server_addr then
local listen = node_listen[1]
admin_server_addr = str_format("%s:%s", listen.ip, listen.port)
end
The control_plane role automatically reuses the first node_listen address as the admin address.
2.10 Service Discovery Special Handling (Lines 811-876)
- Kubernetes: Injects shared_dict, resolves environment variable placeholders (
${VAR}) - Consul: Injects shared_dict
3. start(env, ...) — Start APISIX (Lines 923-1019)
Execution flow:
- cleanup(env) — Delete custom yaml index files
- Deny root execution — Hard reject in production
- Create logs directory
- Wait for old process to exit — Up to 3 seconds (30× 0.1s), detected via
signal.kill(pid, 0) - Parse
-cargument — Supports--configto specify a custom YAML path - init(env) — Generate nginx.conf
- init_etcd(env, args) — Initialize etcd for non-data_plane roles
- Execute openresty — The actual launch
4. test(env) — Config Test (Lines 1022-1057)
-- Backup original nginx.conf → generate new → nginx -t → restore original nginx.conf
This is a non-destructive operation that guarantees restoration of the original nginx.conf. Handles macOS/Linux os.execute return value differences.
5. stop(env) / quit(env) — Stop Service (Lines 1060-1073)
local cmd = env.openresty_args .. [[ -s quit]] -- Graceful stop
local cmd = env.openresty_args .. [[ -s stop]] -- Fast stop
Both run cleanup(env) first, then send the corresponding nginx signal.
- quit: Graceful stop; waits for workers to finish current requests
- stop: Fast stop; immediately terminates all workers
6. restart(env) / reload(env) — Restart & Hot Reload (Lines 1076-1100)
restart: test → stop → start (verify first, then stop, then start)
reload: init → nginx -t → nginx -s reload (hot reload without service interruption)
On reload failure, only prints a message without die, avoiding disruption to the running instance.
7. Helper Functions
| Function | Location | Purpose |
|---|---|---|
local_dns_resolver() |
Line 189 | Parse DNS server addresses from /etc/resolv.conf |
version_greater_equal() |
Line 152 | Semantic version comparison |
get_openresty_version() |
Line 173 | Get version via openresty -v |
check_running() |
Line 912 | Check if process is alive via pid file |
get_lua_path() |
Line 215 | Normalize Lua path (ensure trailing ;) |
validate_port_or_range() |
Line 61 | Port/port-range validity check |
validate_and_get_listen_addr() |
Line 425 | Inner function: validate port conflicts and build address string |
listen_table_insert() |
Line 480 | Inner function: normalize listen table insertion (supports IPv6 dual-stack) |
cleanup() |
Line 898 | Delete custom yaml index files |
sleep() |
Line 907 | Wait via os.execute("sleep " .. n) |
8. Security & Design Highlights
- Port conflict checks — Multiple service ports (admin/status/control/prometheus/http/https) unified through
ports_to_checkto prevent conflicts - Pre-emptive config validation — start/reload/restart/test all re-run
init(), ensuring instant validation on config changes - Safety warnings — Root execution, empty admin_key, low ulimit, admin_key_required=false all produce clear warnings
- Process detection — Checks for old processes before starting to avoid port conflicts; verifies the pid corresponds to a real process, not just a stale file
- Backward compatibility —
node_listensupports three historical formats: number, table, and nested table - Non-destructive testing — The
testcommand backs up and restores the original nginx.conf - Graceful degradation — Reload failure does not terminate the running instance
9. Execution Flow Diagram
apisix start
│
├── cleanup()
├── Deny /root execution
├── Create logs/
├── Wait for old process exit (max 3s)
├── Parse -c argument
├── init(env)
│ ├── Read & validate config.yaml
│ ├── Validate admin_key / mtls / plugins
│ ├── Handle proxy_mode (http/stream/http&stream)
│ ├── Port conflict detection
│ ├── Normalize node_listen / ssl.listen
│ ├── Handle service discovery (K8s/Consul)
│ ├── DNS resolver config
│ └── Template rendering → nginx.conf
├── init_etcd() [non data_plane]
└── Launch openresty
apisix reload
├── init(env) # Regenerate nginx.conf
├── nginx -t # Syntax test
└── nginx -s reload # Hot reload signal
apisix restart
├── test(env) # Backup→Generate→Test→Restore
├── stop(env) # nginx -s stop
└── start(env) # Full start flow