Skip to content

Commit c5fd723

Browse files
outsinredndx
andauthored
feat(pdk) support HTTP/2 on Admin API server by adding new PDK function nginx.get_statistics() for fetching Nginx statistics
Retired the `/nginx_status` location block and use LuaJIT FFI to fetch the counters directly instead. Co-authored-by: Datong Sun <[email protected]>
1 parent 0b000d2 commit c5fd723

File tree

9 files changed

+298
-76
lines changed

9 files changed

+298
-76
lines changed

kong/api/routes/health.lua

+1-24
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
local utils = require "kong.tools.utils"
22
local declarative = require "kong.db.declarative"
33

4-
local find = string.find
5-
local select = select
64
local tonumber = tonumber
75
local kong = kong
86
local knode = (kong and kong.node) and kong.node or
97
require "kong.pdk.node".new()
108

119

12-
local select = select
13-
local tonumber = tonumber
14-
local kong = kong
1510
local dbless = kong.configuration.database == "off"
1611
local data_plane_role = kong.configuration.role == "data_plane"
1712

@@ -41,27 +36,9 @@ return {
4136
end
4237

4338
-- nginx stats
44-
45-
local r = ngx.location.capture "/nginx_status"
46-
if r.status ~= 200 then
47-
kong.log.err(r.body)
48-
return kong.response.exit(500, { message = "An unexpected error happened" })
49-
end
50-
51-
local var = ngx.var
52-
local accepted, handled, total = select(3, find(r.body, "accepts handled requests\n (%d*) (%d*) (%d*)"))
53-
5439
local status_response = {
5540
memory = knode.get_memory_stats(unit, scale),
56-
server = {
57-
connections_active = tonumber(var.connections_active),
58-
connections_reading = tonumber(var.connections_reading),
59-
connections_writing = tonumber(var.connections_writing),
60-
connections_waiting = tonumber(var.connections_waiting),
61-
connections_accepted = tonumber(accepted),
62-
connections_handled = tonumber(handled),
63-
total_requests = tonumber(total)
64-
},
41+
server = kong.nginx.get_statistics(),
6542
database = {
6643
reachable = true,
6744
},

kong/pdk/nginx.lua

+64-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,39 @@
44
-- details and meta information.
55
-- @module kong.nginx
66

7+
local ffi = require "ffi"
78

8-
local ngx = ngx
9+
10+
local C = ffi.C
11+
local arch = ffi.arch
12+
local ngx = ngx
13+
local tonumber = tonumber
14+
15+
if arch == "x64" or arch == "arm64" then
16+
ffi.cdef[[
17+
uint64_t *ngx_stat_active;
18+
uint64_t *ngx_stat_reading;
19+
uint64_t *ngx_stat_writing;
20+
uint64_t *ngx_stat_waiting;
21+
uint64_t *ngx_stat_requests;
22+
uint64_t *ngx_stat_accepted;
23+
uint64_t *ngx_stat_handled;
24+
]]
25+
26+
elseif arch == "x86" or arch == "arm" then
27+
ffi.cdef[[
28+
uint32_t *ngx_stat_active;
29+
uint32_t *ngx_stat_reading;
30+
uint32_t *ngx_stat_writing;
31+
uint32_t *ngx_stat_waiting;
32+
uint32_t *ngx_stat_requests;
33+
uint32_t *ngx_stat_accepted;
34+
uint32_t *ngx_stat_handled;
35+
]]
36+
37+
else
38+
kong.log.err("Unsupported arch: " .. arch)
39+
end
940

1041

1142
local function new(self)
@@ -26,6 +57,38 @@ local function new(self)
2657
end
2758

2859

60+
---
61+
-- Returns various connection and request metrics exposed by
62+
-- Nginx, similar to those reported by the
63+
-- [ngx_http_stub_status_module](https://nginx.org/en/docs/http/ngx_http_stub_status_module.html#data).
64+
--
65+
-- The following fields are included in the returned table:
66+
-- * `connections_active` - the current number of active client connections including `connections_waiting`.
67+
-- * `connections_reading` - the current number of connections where nginx is reading the request header.
68+
-- * `connections_writing` - the current number of connections where nginx is writing the response back to the client.
69+
-- * `connections_waiting` - the current number of idle client connections waiting for a request.
70+
-- * `connections_accepted` - the total number of accepted client connections.
71+
-- * `connections_handled` - the total number of handled connections. Same as `connections_accepted` unless some resource limits have been reached
72+
-- (for example, the [`worker_connections`](https://nginx.org/en/docs/ngx_core_module.html#worker_connections) limit).
73+
-- * `total_requests` - the total number of client requests.
74+
--
75+
-- @function kong.nginx.get_statistics
76+
-- @treturn table Nginx connections and requests statistics
77+
-- @usage
78+
-- local nginx_statistics = kong.nginx.get_statistics()
79+
function _NGINX.get_statistics()
80+
return {
81+
connections_active = tonumber(C.ngx_stat_active[0]),
82+
connections_reading = tonumber(C.ngx_stat_reading[0]),
83+
connections_writing = tonumber(C.ngx_stat_writing[0]),
84+
connections_waiting = tonumber(C.ngx_stat_waiting[0]),
85+
connections_accepted = tonumber(C.ngx_stat_accepted[0]),
86+
connections_handled = tonumber(C.ngx_stat_handled[0]),
87+
total_requests = tonumber(C.ngx_stat_requests[0])
88+
}
89+
end
90+
91+
2992
return _NGINX
3093
end
3194

kong/plugins/prometheus/exporter.lua

+8-22
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
local kong = kong
22
local ngx = ngx
3-
local find = string.find
43
local lower = string.lower
54
local concat = table.concat
6-
local select = select
75
local ngx_timer_pending_count = ngx.timer.pending_count
86
local ngx_timer_running_count = ngx.timer.running_count
97
local balancer = require("kong.runloop.balancer")
@@ -297,26 +295,14 @@ local function metric_data()
297295
return kong.response.exit(500, { message = "An unexpected error occurred" })
298296
end
299297

300-
if ngx.location then
301-
local r = ngx.location.capture "/nginx_status"
302-
303-
if r.status ~= 200 then
304-
kong.log.warn("prometheus: failed to retrieve /nginx_status ",
305-
"while processing /metrics endpoint")
306-
307-
else
308-
local accepted, handled, total = select(3, find(r.body,
309-
"accepts handled requests\n (%d*) (%d*) (%d*)"))
310-
metrics.connections:set(accepted, { "accepted" })
311-
metrics.connections:set(handled, { "handled" })
312-
metrics.connections:set(total, { "total" })
313-
end
314-
end
315-
316-
metrics.connections:set(ngx.var.connections_active or 0, { "active" })
317-
metrics.connections:set(ngx.var.connections_reading or 0, { "reading" })
318-
metrics.connections:set(ngx.var.connections_writing or 0, { "writing" })
319-
metrics.connections:set(ngx.var.connections_waiting or 0, { "waiting" })
298+
local nginx_statistics = kong.nginx.get_statistics()
299+
metrics.connections:set(nginx_statistics['connections_accepted'], { "accepted" })
300+
metrics.connections:set(nginx_statistics['connections_handled'], { "handled" })
301+
metrics.connections:set(nginx_statistics['total_requests'], { "total" })
302+
metrics.connections:set(nginx_statistics['connections_active'], { "active" })
303+
metrics.connections:set(nginx_statistics['connections_reading'], { "reading" })
304+
metrics.connections:set(nginx_statistics['connections_writing'], { "writing" })
305+
metrics.connections:set(nginx_statistics['connections_waiting'], { "waiting" })
320306

321307
metrics.timers:set(ngx_timer_running_count(), {"running"})
322308
metrics.timers:set(ngx_timer_pending_count(), {"pending"})

kong/templates/nginx_kong.lua

-12
Original file line numberDiff line numberDiff line change
@@ -363,12 +363,6 @@ server {
363363
}
364364
}
365365
366-
location /nginx_status {
367-
internal;
368-
access_log off;
369-
stub_status;
370-
}
371-
372366
location /robots.txt {
373367
return 200 'User-agent: *\nDisallow: /';
374368
}
@@ -408,12 +402,6 @@ server {
408402
}
409403
}
410404
411-
location /nginx_status {
412-
internal;
413-
access_log off;
414-
stub_status;
415-
}
416-
417405
location /robots.txt {
418406
return 200 'User-agent: *\nDisallow: /';
419407
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
local helpers = require "spec.helpers" -- hard dependency
2+
3+
4+
local ngx = ngx
5+
6+
local fixtures = {
7+
dns_mock = helpers.dns_mock.new({
8+
mocks_only = true
9+
}),
10+
http_mock = {},
11+
stream_mock = {}
12+
}
13+
14+
fixtures.dns_mock:A{
15+
name = "mock.example.com",
16+
address = "127.0.0.1"
17+
}
18+
19+
fixtures.dns_mock:A{
20+
name = "status.example.com",
21+
address = "127.0.0.1"
22+
}
23+
24+
local status_api_port = helpers.get_available_port()
25+
26+
27+
for _, strategy in helpers.each_strategy() do
28+
describe("Plugin: prometheus (metrics)", function()
29+
local bp
30+
local admin_ssl_client -- admin_ssl_client (lua-resty-http) does not support h2
31+
local proxy_ssl_client -- proxy_ssl_client (lua-resty-http) does not support h2
32+
33+
setup(function()
34+
bp = helpers.get_db_utils(strategy, {"services", "routes", "plugins"})
35+
36+
local mock_ssl_service = bp.services:insert{
37+
name = "mock-ssl-service",
38+
host = helpers.mock_upstream_ssl_host,
39+
port = helpers.mock_upstream_ssl_port,
40+
protocol = helpers.mock_upstream_ssl_protocol
41+
}
42+
bp.routes:insert{
43+
name = "mock-ssl-route",
44+
protocols = {"https"},
45+
hosts = {"mock.example.com"},
46+
paths = {"/"},
47+
service = {
48+
id = mock_ssl_service.id
49+
}
50+
}
51+
52+
local status_api_ssl_service = bp.services:insert{
53+
name = "status-api-ssl-service",
54+
url = "https://127.0.0.1:" .. status_api_port .. "/metrics"
55+
}
56+
bp.routes:insert{
57+
name = "status-api-ssl-route",
58+
protocols = {"https"},
59+
hosts = {"status.example.com"},
60+
paths = {"/metrics"},
61+
service = {
62+
id = status_api_ssl_service.id
63+
}
64+
}
65+
66+
bp.plugins:insert{
67+
name = "prometheus" -- globally enabled
68+
}
69+
70+
assert(helpers.start_kong({
71+
nginx_conf = "spec/fixtures/custom_nginx.template",
72+
plugins = "bundled,prometheus",
73+
status_listen = '127.0.0.1:' .. status_api_port .. ' ssl', -- status api does not support h2
74+
status_access_log = "logs/status_access.log",
75+
status_error_log = "logs/status_error.log"
76+
}, nil, nil, fixtures))
77+
78+
end)
79+
80+
teardown(function()
81+
if admin_ssl_client then
82+
admin_ssl_client:close()
83+
end
84+
if proxy_ssl_client then
85+
proxy_ssl_client:close()
86+
end
87+
88+
helpers.stop_kong()
89+
end)
90+
91+
before_each(function()
92+
admin_ssl_client = helpers.admin_client()
93+
proxy_ssl_client = helpers.proxy_ssl_client()
94+
end)
95+
96+
after_each(function()
97+
if admin_ssl_client then
98+
admin_ssl_client:close()
99+
end
100+
if proxy_ssl_client then
101+
proxy_ssl_client:close()
102+
end
103+
end)
104+
105+
it("expose Nginx connection metrics by admin API #a1.1", function()
106+
local res = assert(admin_ssl_client:send{
107+
method = "GET",
108+
path = "/metrics"
109+
})
110+
local body = assert.res_status(200, res)
111+
112+
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
113+
assert.matches('kong_nginx_' .. ngx.config.subsystem .. '_current_connections{state="%w+"} %d+', body)
114+
end)
115+
116+
it("increments the count of proxied requests #p1.1", function()
117+
local res = assert(proxy_ssl_client:send{
118+
method = "GET",
119+
path = "/status/400",
120+
headers = {
121+
["Host"] = "mock.example.com"
122+
}
123+
})
124+
assert.res_status(400, res)
125+
126+
helpers.wait_until(function()
127+
local res = assert(admin_ssl_client:send{
128+
method = "GET",
129+
path = "/metrics"
130+
})
131+
local body = assert.res_status(200, res)
132+
133+
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
134+
135+
return body:find('kong_http_status{service="mock-ssl-service",route="mock-ssl-route",code="400"} 1',
136+
nil, true)
137+
end)
138+
end)
139+
140+
it("expose Nginx connection metrics by status API #s1.1", function()
141+
local res = assert(proxy_ssl_client:send{
142+
method = "GET",
143+
path = "/metrics",
144+
headers = {
145+
["Host"] = "status.example.com"
146+
}
147+
})
148+
local body = assert.res_status(200, res)
149+
150+
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
151+
assert.matches('kong_nginx_' .. ngx.config.subsystem .. '_current_connections{state="%w+"} %d+', body)
152+
end)
153+
154+
end)
155+
end

spec/fixtures/custom_nginx.template

-12
Original file line numberDiff line numberDiff line change
@@ -385,12 +385,6 @@ http {
385385
}
386386
}
387387

388-
location /nginx_status {
389-
internal;
390-
access_log off;
391-
stub_status;
392-
}
393-
394388
location /robots.txt {
395389
return 200 'User-agent: *\nDisallow: /';
396390
}
@@ -430,12 +424,6 @@ http {
430424
}
431425
}
432426

433-
location /nginx_status {
434-
internal;
435-
access_log off;
436-
stub_status;
437-
}
438-
439427
location /robots.txt {
440428
return 200 'User-agent: *\nDisallow: /';
441429
}

spec/fixtures/prometheus/metrics.conf

-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,4 @@ server {
1010
}
1111
}
1212

13-
location /nginx_status {
14-
internal;
15-
access_log off;
16-
stub_status;
17-
}
1813
}

0 commit comments

Comments
 (0)