Skip to content

Commit

Permalink
more
Browse files Browse the repository at this point in the history
Signed-off-by: spacewander <spacewanderlzx@gmail.com>
  • Loading branch information
spacewander committed Jan 20, 2021
1 parent d1be543 commit b66f814
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 33 deletions.
114 changes: 87 additions & 27 deletions apisix/control/v1.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ local get_services = require("apisix.http.service").services
local upstream_mod = require("apisix.upstream")
local get_upstreams = upstream_mod.upstreams
local ipairs = ipairs
local str_format = string.format
local ngx_var = ngx.var


local _M = {}
Expand Down Expand Up @@ -54,51 +56,103 @@ function _M.schema()
end


local function iter_and_add_checker(infos, values, src)
local function extra_checker_info(value, src_type)
local checker = value.checker
local upstream = value.checker_upstream
local host = upstream.checks and upstream.checks.active and upstream.checks.active.host
local port = upstream.checks and upstream.checks.active and upstream.checks.active.port
local nodes = upstream.nodes
local healthy_nodes = core.table.new(#nodes, 0)
for _, node in ipairs(nodes) do
local ok = checker:get_target_status(node.host, port or node.port, host)
if ok then
core.table.insert(healthy_nodes, node)
end
end

local conf = value.value
return {
name = upstream_mod.get_healthchecker_name(value),
src_id = conf.id,
src_type = src_type,
nodes = nodes,
healthy_nodes = healthy_nodes,
}
end


local function iter_and_add_healthcheck_info(infos, values, src_type)
if not values then
return
end

for _, value in core.config_util.iterate_values(values) do
if value.checker then
local checker = value.checker
local upstream = value.checker_upstream
local host = upstream.checks and upstream.checks.active and upstream.checks.active.host
local port = upstream.checks and upstream.checks.active and upstream.checks.active.port
local nodes = upstream.nodes
local health_nodes = core.table.new(#nodes, 0)
for _, node in ipairs(nodes) do
local ok = checker:get_target_status(node.host, port or node.port, host)
if ok then
core.table.insert(health_nodes, node)
end
end

local conf = value.value
core.table.insert(infos, {
name = upstream_mod.get_healthchecker_name(value),
src_id = conf.id,
src_type = src,
nodes = nodes,
health_nodes = health_nodes,
})
core.table.insert(infos, extra_checker_info(value, src_type))
end
end
end


function _M.healthcheck()
function _M.get_health_checkers()
local infos = {}
local routes = get_routes()
iter_and_add_checker(infos, routes, "routes")
iter_and_add_healthcheck_info(infos, routes, "routes")
local services = get_services()
iter_and_add_checker(infos, services, "services")
iter_and_add_healthcheck_info(infos, services, "services")
local upstreams = get_upstreams()
iter_and_add_checker(infos, upstreams, "upstreams")
iter_and_add_healthcheck_info(infos, upstreams, "upstreams")
return 200, infos
end


local function iter_and_find_healthcheck_info(values, src_type, src_id)
if not values then
return nil, str_format("%s[%s] not found", src_type, src_id)
end

for _, value in core.config_util.iterate_values(values) do
if value.value.id == src_id then
if not value.checker then
return nil, str_format("no checker for %s[%s]", src_type, src_id)
end

return extra_checker_info(value, src_type)
end
end

return nil, str_format("%s[%s] not found", src_type, src_id)
end


function _M.get_health_checker()
local uri_segs = core.utils.split_uri(ngx_var.uri)
core.log.info("healthcheck uri: ", core.json.delay_encode(uri_segs))

local src_type, src_id = uri_segs[4], uri_segs[5]
if not src_id then
return 404, {error_msg = str_format("missing src id for src type %s", src_type)}
end

local values
if src_type == "routes" then
values = get_routes()
elseif src_type == "services" then
values = get_services()
elseif src_type == "upstreams" then
values = get_upstreams()
else
return 400, {error_msg = str_format("invalid src type %s", src_type)}
end

local info, err = iter_and_find_healthcheck_info(values, src_type, src_id)
if not info then
return 404, {error_msg = err}
end
return 200, info
end


return {
-- /v1/schema
{
Expand All @@ -110,6 +164,12 @@ return {
{
methods = {"GET"},
uris = {"/healthcheck"},
handler = _M.healthcheck,
handler = _M.get_health_checkers,
},
-- /v1/healthcheck/{src_type}/{src_id}
{
methods = {"GET"},
uris = {"/healthcheck/*"},
handler = _M.get_health_checker,
}
}
37 changes: 34 additions & 3 deletions doc/control-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Return current [health check](health-check.md) status in the format below:
```json
[
{
"health_nodes": [
"healthy_nodes": [
{
"host": "127.0.0.1",
"port": 1980,
Expand All @@ -113,7 +113,7 @@ Return current [health check](health-check.md) status in the format below:
"src_type": "upstreams"
},
{
"health_nodes": [
"healthy_nodes": [
{
"host": "127.0.0.1",
"port": 1980,
Expand Down Expand Up @@ -146,4 +146,35 @@ Each entry contains fields below:
object with id 1 creates a health checker, the `src_type` is `upstreams` and the `src_id` is `1`.
* name: the name of the health checker.
* nodes: the target nodes of the health checker.
* health_nodes: the health node known by the health checker.
* healthy_nodes: the healthy node known by the health checker.

User can also use `/v1/healthcheck/$src_type/$src_id` can get the status of a health checker.

For example, `GET /v1/healthcheck/upstreams/1` returns:

```json
{
"healthy_nodes": [
{
"host": "127.0.0.1",
"port": 1980,
"weight": 1
}
],
"name": "upstream#/upstreams/1",
"nodes": [
{
"host": "127.0.0.1",
"port": 1980,
"weight": 1
},
{
"host": "127.0.0.2",
"port": 1988,
"weight": 1
}
],
"src_id": "1",
"src_type": "upstreams"
}
```
5 changes: 5 additions & 0 deletions doc/health-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
Health Check of APISIX is based on [lua-resty-healthcheck](https://github.com/Kong/lua-resty-healthcheck),
you can use it for upstream.

Note that we only start the health check when the upstream is hit by a request.
There won't be any health check if an upstream is configured but isn't in used.

The following is an example of health check:

```shell
Expand Down Expand Up @@ -105,3 +108,5 @@ contains: `active` or `passive`.
* `passive.unhealthy.tcp_failures`: Number of TCP failures in proxied traffic to consider a target unhealthy, as observed by passive health checks.
* `passive.unhealthy.timeouts`: Number of timeouts in proxied traffic to consider a target unhealthy, as observed by passive health checks.
* `passive.unhealthy.http_failures`: Number of HTTP failures in proxied traffic (as defined by `passive.unhealthy.http_statuses`) to consider a target unhealthy, as observed by passive health checks.

The health check status can be fetched via `GET /v1/healthcheck` in [control API](./control-api.md).
5 changes: 5 additions & 0 deletions doc/zh-cn/health-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

APISIX的健康检查使用[lua-resty-healthcheck](https://github.com/Kong/lua-resty-healthcheck)实现,你可以在upstream中使用它。

注意只有在 upstream 被请求时才会开始健康检查。
如果一个 upstream 被配置但没有被请求,那么就不会有健康检查。

下面是一个检查检查的例子:

```shell
Expand Down Expand Up @@ -105,3 +108,5 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
* `passive.unhealthy.tcp_failures`: 如果TCP通讯失败次数超过 `tcp_failures` 次,则将upstream节点设置为 `unhealthy` 状态。
* `passive.unhealthy.timeouts`: 如果被动健康检查超时次数超过 `timeouts` 次,则将upstream节点设置为 `unhealthy` 状态。
* `passive.unhealthy.http_failures`: 如果被动健康检查的HTTP请求失败(由 `passive.unhealthy.http_statuses` 定义)的次数超过 `http_failures`次,则将upstream节点设置为 `unhealthy` 状态。

健康检查信息可以通过 [控制接口](./control_api.md) 中的 `GET /v1/healthcheck` 接口得到。
51 changes: 48 additions & 3 deletions t/control/healthcheck.t
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ upstreams:
return a.host < b.host
end)
ngx.say(json.encode(res))
local code, body, res = t.test('/v1/healthcheck/upstreams/1',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res.nodes, function(a, b)
return a.host < b.host
end)
ngx.say(json.encode(res))
}
}
--- grep_error_log eval
Expand All @@ -95,7 +103,8 @@ qr/unhealthy TCP increment \(.+\) for '[^']+'/
unhealthy TCP increment (1/2) for '(127.0.0.2:1988)'
unhealthy TCP increment (2/2) for '(127.0.0.2:1988)'
--- response_body
[{"health_nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"name":"upstream#/upstreams/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.2","port":1988,"weight":1}],"src_id":"1","src_type":"upstreams"}]
[{"healthy_nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"name":"upstream#/upstreams/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.2","port":1988,"weight":1}],"src_id":"1","src_type":"upstreams"}]
{"healthy_nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"name":"upstream#/upstreams/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.2","port":1988,"weight":1}],"src_id":"1","src_type":"upstreams"}
Expand Down Expand Up @@ -141,6 +150,14 @@ routes:
return a.port < b.port
end)
ngx.say(json.encode(res))
local code, body, res = t.test('/v1/healthcheck/routes/1',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res.nodes, function(a, b)
return a.port < b.port
end)
ngx.say(json.encode(res))
}
}
--- grep_error_log eval
Expand All @@ -149,7 +166,8 @@ qr/unhealthy TCP increment \(.+\) for '[^']+'/
unhealthy TCP increment (1/2) for '127.0.0.1(127.0.0.1:1988)'
unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1988)'
--- response_body
[{"health_nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"name":"upstream#/routes/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.1","port":1988,"weight":1}],"src_id":"1","src_type":"routes"}]
[{"healthy_nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"name":"upstream#/routes/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.1","port":1988,"weight":1}],"src_id":"1","src_type":"routes"}]
{"healthy_nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"name":"upstream#/routes/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.1","port":1988,"weight":1}],"src_id":"1","src_type":"routes"}
Expand Down Expand Up @@ -200,6 +218,14 @@ services:
return a.port < b.port
end)
ngx.say(json.encode(res))
local code, body, res = t.test('/v1/healthcheck/services/1',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res.nodes, function(a, b)
return a.port < b.port
end)
ngx.say(json.encode(res))
}
}
--- grep_error_log eval
Expand All @@ -208,7 +234,8 @@ qr/unhealthy TCP increment \(.+\) for '[^']+'/
unhealthy TCP increment (1/2) for '127.0.0.1(127.0.0.1:1988)'
unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1988)'
--- response_body
[{"health_nodes":{},"name":"upstream#/services/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.1","port":1988,"weight":1}],"src_id":"1","src_type":"services"}]
[{"healthy_nodes":{},"name":"upstream#/services/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.1","port":1988,"weight":1}],"src_id":"1","src_type":"services"}]
{"healthy_nodes":{},"name":"upstream#/services/1","nodes":[{"host":"127.0.0.1","port":1980,"weight":1},{"host":"127.0.0.1","port":1988,"weight":1}],"src_id":"1","src_type":"services"}
Expand All @@ -224,3 +251,21 @@ unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1988)'
}
--- response_body
{}
=== TEST 5: no checker
--- request
GET /v1/healthcheck/routes/1
--- error_code: 404
--- response_body
{"error_msg":"routes[1] not found"}
=== TEST 6: invalid src type
--- request
GET /v1/healthcheck/route/1
--- error_code: 400
--- response_body
{"error_msg":"invalid src type route"}

0 comments on commit b66f814

Please sign in to comment.