You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by me...@apache.org on 2020/12/09 02:08:05 UTC
[apisix] branch master updated: feat: implement new plugin
`server-info` (#2926)
This is an automated email from the ASF dual-hosted git repository.
membphis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 7855d9e feat: implement new plugin `server-info` (#2926)
7855d9e is described below
commit 7855d9eed252f579ad43027912cb1a513ed7ef7e
Author: Alex Zhang <zc...@gmail.com>
AuthorDate: Wed Dec 9 10:07:57 2020 +0800
feat: implement new plugin `server-info` (#2926)
close #2821
---
apisix/cli/ngx_tpl.lua | 1 +
apisix/core/utils.lua | 30 ++++++
apisix/plugins/server-info.lua | 210 +++++++++++++++++++++++++++++++++++++++
apisix/timers.lua | 11 +-
conf/config-default.yaml | 4 +
doc/plugins/server-info.md | 104 +++++++++++++++++++
doc/zh-cn/plugins/server-info.md | 108 ++++++++++++++++++++
t/APISIX.pm | 1 +
t/plugin/server-info.t | 137 +++++++++++++++++++++++++
9 files changed, 605 insertions(+), 1 deletion(-)
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index a962784..ed693d0 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -123,6 +123,7 @@ http {
.. [=[$prefix/deps/lib/lua/5.1/?.so;;]=]
.. [=[{*lua_cpath*};";
+ lua_shared_dict internal_status 10m;
lua_shared_dict plugin-limit-req 10m;
lua_shared_dict plugin-limit-count 10m;
lua_shared_dict prometheus-metrics 10m;
diff --git a/apisix/core/utils.lua b/apisix/core/utils.lua
index 041bd7e..ab9ba0a 100644
--- a/apisix/core/utils.lua
+++ b/apisix/core/utils.lua
@@ -16,6 +16,8 @@
--
local core_str = require("apisix.core.string")
local table = require("apisix.core.table")
+local log = require("apisix.core.log")
+local string = require("apisix.core.string")
local ngx_re = require("ngx.re")
local resolver = require("resty.dns.resolver")
local ipmatcher= require("resty.ipmatcher")
@@ -29,11 +31,14 @@ local tonumber = tonumber
local tostring = tostring
local re_gsub = ngx.re.gsub
local type = type
+local io_popen = io.popen
local C = ffi.C
local ffi_string = ffi.string
local get_string_buf = base.get_string_buf
local exiting = ngx.worker.exiting
local ngx_sleep = ngx.sleep
+
+local hostname
local max_sleep_interval = 1
ffi.cdef[[
@@ -206,6 +211,31 @@ function _M.validate_header_value(value)
end
+-- only use this method in init/init_worker phase.
+function _M.gethostname()
+ if hostname then
+ return hostname
+ end
+
+ local hd = io_popen("/bin/hostname")
+ local data, err = hd:read("*a")
+ if err == nil then
+ hostname = data
+ if string.has_suffix(hostname, "\r\n") then
+ hostname = sub_str(hostname, 1, -3)
+ elseif string.has_suffix(hostname, "\n") then
+ hostname = sub_str(hostname, 1, -2)
+ end
+
+ else
+ hostname = "unknown"
+ log.error("failed to read output of \"/bin/hostname\": ", err)
+ end
+
+ return hostname
+end
+
+
local function sleep(sec)
if sec <= max_sleep_interval then
return ngx_sleep(sec)
diff --git a/apisix/plugins/server-info.lua b/apisix/plugins/server-info.lua
new file mode 100644
index 0000000..f5dccb8
--- /dev/null
+++ b/apisix/plugins/server-info.lua
@@ -0,0 +1,210 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local require = require
+local core = require("apisix.core")
+local timers = require("apisix.timers")
+
+local ngx_time = ngx.time
+local ngx_timer_at = ngx.timer.at
+local ngx_worker_id = ngx.worker.id
+local type = type
+
+local boot_time = os.time()
+local plugin_name = "server-info"
+local default_report_interval = 60
+local default_report_ttl = 7200
+
+local schema = {
+ type = "object",
+ additionalProperties = false,
+}
+local attr_schema = {
+ type = "object",
+ properties = {
+ report_interval = {
+ type = "integer",
+ description = "server info reporting interval (unit: second)",
+ default = default_report_interval,
+ minimum = 60,
+ maximum = 3600,
+ },
+ report_ttl = {
+ type = "integer",
+ description = "live time for server info in etcd",
+ default = default_report_ttl,
+ minimum = 3600,
+ maximum = 86400,
+ }
+ }
+}
+
+local internal_status = ngx.shared.internal_status
+if not internal_status then
+ error("lua_shared_dict \"internal_status\" not configured")
+end
+
+
+local _M = {
+ version = 0.1,
+ priority = 990,
+ name = plugin_name,
+ schema = schema,
+}
+
+
+local function uninitialized_server_info()
+ return {
+ etcd_version = "unknown",
+ hostname = core.utils.gethostname(),
+ id = core.id.get(),
+ version = core.version.VERSION,
+ up_time = ngx_time() - boot_time,
+ boot_time = boot_time,
+ last_report_time = -1,
+ }
+end
+
+
+local function get()
+ local data, err = internal_status:get("server_info")
+ if err ~= nil then
+ return nil, err
+ end
+
+ if not data then
+ return uninitialized_server_info()
+ end
+
+ local server_info, err = core.json.decode(data)
+ if not server_info then
+ return nil, err
+ end
+
+ server_info.up_time = ngx_time() - server_info.boot_time
+ return server_info
+end
+
+
+local function report(premature, report_ttl)
+ if premature then
+ return
+ end
+
+ local server_info, err = get()
+ if not server_info then
+ core.log.error("failed to get server_info: ", err)
+ return
+ end
+
+ if server_info.etcd_version == "unknown" then
+ local res, err = core.etcd.server_version()
+ if not res then
+ core.log.error("failed to fetch etcd version: ", err)
+ return
+
+ elseif type(res.body) ~= "table" then
+ core.log.error("failed to fetch etcd version: bad version info")
+ return
+
+ else
+ server_info.etcd_version = res.body.etcdcluster
+ end
+ end
+
+ server_info.last_report_time = ngx_time()
+
+ local data, err = core.json.encode(server_info)
+ if not data then
+ core.log.error("failed to encode server_info: ", err)
+ return
+ end
+
+ local key = "/data_plane/server_info/" .. server_info.id
+ local ok, err = core.etcd.set(key, data, report_ttl)
+ if not ok then
+ core.log.error("failed to report server info to etcd: ", err)
+ return
+ end
+
+ local ok, err = internal_status:set("server_info", data)
+ if not ok then
+ core.log.error("failed to encode and save server info: ", err)
+ return
+ end
+end
+
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ return true
+end
+
+
+function _M.init()
+ core.log.info("server info: ", core.json.delay_encode(get()))
+
+ if core.config ~= require("apisix.core.config_etcd") then
+ -- we don't need to report server info if etcd is not in use.
+ return
+ end
+
+ local local_conf = core.config.local_conf()
+ local attr = core.table.try_read_attr(local_conf, "plugin_attr",
+ plugin_name)
+ local ok, err = core.schema.check(attr_schema, attr)
+ if not ok then
+ core.log.error("failed to check plugin_attr: ", err)
+ return
+ end
+
+ local report_ttl = attr and attr.report_ttl or default_report_ttl
+ local report_interval = attr and attr.report_interval or default_report_interval
+ local start_at = ngx_time()
+
+ local fn = function()
+ local now = ngx_time()
+ if now - start_at >= report_interval then
+ start_at = now
+ report(nil, report_ttl)
+ end
+ end
+
+ if ngx_worker_id() == 0 then
+ local ok, err = ngx_timer_at(0, report, report_ttl)
+ if not ok then
+ core.log.error("failed to create initial timer to report server info: ", err)
+ return
+ end
+ end
+
+ timers.register_timer("plugin#server-info", fn, true)
+
+ core.log.info("timer created to report server info, interval: ",
+ report_interval)
+end
+
+
+function _M.destory()
+ timers.unregister_timer("plugin#server-info", true)
+end
+
+
+return _M
diff --git a/apisix/timers.lua b/apisix/timers.lua
index 51b0271..a08f651 100644
--- a/apisix/timers.lua
+++ b/apisix/timers.lua
@@ -21,6 +21,7 @@ local unpack = unpack
local thread_spawn = ngx.thread.spawn
local thread_wait = ngx.thread.wait
+local check_interval = 0.5
local timers = {}
@@ -57,7 +58,10 @@ end
function _M.init_worker()
- local timer, err = core.timer.new("background", background_timer, {check_interval = 0.5})
+ local opts = {
+ check_interval = check_interval,
+ }
+ local timer, err = core.timer.new("background", background_timer, opts)
if not timer then
core.log.error("failed to create background timer: ", err)
return
@@ -85,4 +89,9 @@ function _M.unregister_timer(name, privileged)
end
+function _M.check_interval()
+ return check_interval
+end
+
+
return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index fed43f4..91c08a3 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -239,6 +239,7 @@ plugins: # plugin list (sorted in alphabetical order)
- uri-blocker
- wolf-rbac
- zipkin
+ #- server-info
stream_plugins:
- mqtt-proxy
@@ -253,3 +254,6 @@ plugin_attr:
endpoint_addr: http://127.0.0.1:12800
prometheus:
export_uri: /apisix/prometheus/metrics
+ server-info:
+ report_interval: 60 # server info report interval (unit: second)
+ report_ttl: 3600 # live time for server info in etcd (unit: second)
diff --git a/doc/plugins/server-info.md b/doc/plugins/server-info.md
new file mode 100644
index 0000000..f9744d3
--- /dev/null
+++ b/doc/plugins/server-info.md
@@ -0,0 +1,104 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+- [中文](../zh-cn/plugins/server-info.md)
+
+# Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [API](#api)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+`server-info` is a plugin that reports basic server information to etcd periodically.
+
+The meaning of each item in server information is following:
+
+| Name | Type | Description |
+|---------|------|-------------|
+| up_time | integer | Elapsed time (in seconds) since APISIX instance was launched, value will be reset when you hot updating APISIX but is kept for intact if you just reloading APISIX. |
+| boot_time | integer | Bootstrap time (UNIX timestamp) of the APISIX instance, value will be reset when you hot updating APISIX but is kept for intact if you just reloading APISIX. |
+| last_report_time | integer | Last reporting time (UNIX timestamp). |
+| id | string | APISIX instance id. |
+| etcd_version | string | The etcd cluster version that APISIX is using, value will be `"unknown"` if the network (to etcd) is partitioned. |
+| version | string | APISIX version. |
+| hostname | string | Hostname of the machine/pod that APISIX is deployed. |
+
+## Attributes
+
+None
+
+## API
+
+None
+
+## How to Enable
+
+Just configure `server-info` in the plugin list of the configuration file `conf/config.yaml`.
+
+```
+plugins: # plugin list
+ - example-plugin
+ - limit-req
+ - node-status
+ - server-info
+ - jwt-auth
+ - zipkin
+ ......
+```
+
+## How to customize the server info report configurations
+
+We can change the report configurations in the `plugin_attr` section of `conf/config.yaml`.
+
+| Name | Type | Default | Description |
+| ------------ | ------ | -------- | -------------------------------------------------------------------- |
+| report_interval | integer | 60 | the interval to report server info to etcd (unit: second, maximum: 3600, minimum: 60). |
+| report_ttl | integer | 7200 | the live time for server info in etcd (unit: second, maximum: 86400, minimum: 3600). |
+
+Here is an example, which modifies the `report_interval` to 10 minutes and sets the `report_ttl` to one hour.
+
+```yaml
+plugin_attr:
+ server-info:
+ report_interval: 600,
+ report_ttl: 3600
+```
+
+## Test Plugin
+
+The APISIX Dashboard will collects server info in etcd, after enabling this plugin, you may try to check them through Dashboard.
+
+## Disable Plugin
+
+Remove `server-info` in the plugin list of configure file `conf/config.yaml`.
+
+```
+plugins: # plugin list
+ - example-plugin
+ - limit-req
+ - node-status
+ - jwt-auth
+ - zipkin
+ ......
+```
diff --git a/doc/zh-cn/plugins/server-info.md b/doc/zh-cn/plugins/server-info.md
new file mode 100644
index 0000000..9642da5
--- /dev/null
+++ b/doc/zh-cn/plugins/server-info.md
@@ -0,0 +1,108 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+- [English](../../plugins/server-info.md)
+
+# Summary
+
+- [插件简介](#插件简介)
+- [插件属性](#插件属性)
+- [插件接口](#插件接口)
+- [启用插件](#启用插件)
+- [如何自定义服务信息上报间隔](#如何自定义服务信息上报间隔)
+- [测试插件](#测试插件)
+- [禁用插件](#禁用插件)
+
+## 插件简介
+
+`server-info` 是一款能够定期将服务基本信息上报至 etcd 的插件。
+
+服务信息中每一项的含义如下:
+
+| 名称 | 类型 | 描述 |
+|---------|------|-------------|
+| up_time | integer | APISIX 服务实例当前的运行时间(单位:秒), 如果对 APISIX 进行热更新操作,该值将被重置;普通的 reload 操作不会影响该值。 |
+| boot_time | integer | APISIX 服务实例的启动时间(UNIX 时间戳),如果对 APIISIX 进行热更新操作,该值将被重置;普通的 reload 操作不会影响该值。|
+| last_report_time | integer | 最近一次服务信息上报的时间 (UNIX 时间戳)。|
+| id | string | APISIX 服务实例 id 。|
+| etcd_version | string | etcd 集群的版本信息,如果 APISIX 和 etcd 集群之间存在网络分区,该值将设置为 `"unknown"`。|
+| version | string | APISIX 版本信息。 |
+| hostname | string | APISIX 所部署的机器或 pod 的主机名信息。|
+
+## 插件属性
+
+无
+
+## 插件接口
+
+无
+
+## 启用插件
+
+在配置文件 `apisix/conf/config.yaml` 的插件列表中添加 `server-info`, 即可启用该插件。
+
+```
+plugins: # plugin list
+ - example-plugin
+ - limit-req
+ - node-status
+ - server-info
+ - jwt-auth
+ - zipkin
+ ......
+```
+
+## 如何自定义服务信息上报配置
+
+我们可以在 `conf/config.yaml` 文件的 `plugin_attr` 一节中修改上报配置。
+
+| 名称 | 类型 | 默认值 | 描述 |
+| ------------ | ------ | -------- | -------------------------------------------------------------------- |
+| report_interval | integer | 60 | 上报服务信息至 etcd 的间隔(单位:秒,最大值:3600,最小值:60)|
+| report_ttl | integer | 7200 | etcd 中服务信息保存的 TTL(单位:秒,最大值:86400,最小值:3600)|
+
+下面的例子将 `report_interval` 修改成了 10 秒,并将 `report_ttl` 修改成了 1
+小时:
+
+```yaml
+plugin_attr:
+ server-info:
+ report_interval: 10
+ report_ttl: 3600
+```
+
+
+## 测试插件
+
+Apache APISIX Dashboard 会收集上报到 etcd 中的服务信息,在启用这个插件后,你可以通过 APISIX Dashboard 来查看这些数据。
+
+
+## 禁用插件
+
+通过移除配置文件 `apisix/conf/config.yaml` 插件列表中的 `server-info`,即可方便地禁用该插件。
+
+```
+plugins: # plugin list
+ - example-plugin
+ - limit-req
+ - node-status
+ - jwt-auth
+ - zipkin
+ ......
+```
diff --git a/t/APISIX.pm b/t/APISIX.pm
index b3b7e60..f9d5e4c 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -239,6 +239,7 @@ _EOC_
lua_shared_dict plugin-limit-count 10m;
lua_shared_dict plugin-limit-conn 10m;
lua_shared_dict prometheus-metrics 10m;
+ lua_shared_dict internal_status 10m;
lua_shared_dict upstream-healthcheck 32m;
lua_shared_dict worker-events 10m;
lua_shared_dict lrucache-lock 10m;
diff --git a/t/plugin/server-info.t b/t/plugin/server-info.t
new file mode 100644
index 0000000..c61a4eb
--- /dev/null
+++ b/t/plugin/server-info.t
@@ -0,0 +1,137 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+our $SkipReason;
+
+BEGIN {
+ if ($ENV{TEST_NGINX_CHECK_LEAK}) {
+ $SkipReason = "unavailable for the hup tests";
+
+ } else {
+ $ENV{TEST_NGINX_USE_HUP} = 1;
+ undef $ENV{TEST_NGINX_USE_STAP};
+ }
+}
+use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : ();
+use t::APISIX 'no_plan';
+
+master_on();
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity check
+--- yaml_config
+apisix:
+ id: 123456
+plugins:
+ - server-info
+plugin_attr:
+ server-info:
+ report_interval: 60
+--- config
+location /t {
+ content_by_lua_block {
+ ngx.sleep(2)
+ local json_decode = require("cjson.safe").decode
+ local core = require("apisix.core")
+ local key = "/data_plane/server_info/" .. core.id.get()
+ local res, err = core.etcd.get(key)
+ if err ~= nil then
+ ngx.status = 500
+ ngx.say(err)
+ return
+ end
+
+ local keys = {}
+ local body = json_decode(res.body.node.value)
+ for k in pairs(body) do
+ keys[#keys + 1] = k
+ end
+
+ table.sort(keys)
+ for i = 1, #keys do
+ ngx.say(keys[i], ": ", body[keys[i]])
+ end
+ }
+}
+--- request
+GET /t
+--- response_body eval
+qr{^boot_time: \d+
+etcd_version: [\d\.]+
+hostname: [a-zA-Z\-0-9\.]+
+id: [a-zA-Z\-0-9]+
+last_report_time: \d+
+up_time: \d+
+version: [\d\.]+
+$}
+--- no_error_log
+[error]
+--- error_log
+timer created to report server info, interval: 60
+
+
+
+=== TEST 2: verify the data integrity after reloading
+--- yaml_config
+apisix:
+ id: 123456
+plugins:
+ - server-info
+plugin_attr:
+ server-info:
+ report_interval: 60
+--- config
+location /t {
+ content_by_lua_block {
+ local json_decode = require("cjson.safe").decode
+ local core = require("apisix.core")
+ local key = "/data_plane/server_info/" .. core.id.get()
+ local res, err = core.etcd.get(key)
+ if err ~= nil then
+ ngx.status = 500
+ ngx.say(err)
+ return
+ end
+
+ local keys = {}
+ local body = json_decode(res.body.node.value)
+ for k in pairs(body) do
+ keys[#keys + 1] = k
+ end
+
+ if body.up_time >= 2 then
+ ngx.say("integral")
+ else
+ ngx.say("reset")
+ end
+ }
+}
+--- request
+GET /t
+--- response_body
+integral
+--- no_error_log
+[error]
+--- error_log
+timer created to report server info, interval: 60