You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by to...@apache.org on 2020/11/28 09:12:29 UTC
[apisix] branch feat/status created (now 34276cb)
This is an automated email from the ASF dual-hosted git repository.
tokers pushed a change to branch feat/status
in repository https://gitbox.apache.org/repos/asf/apisix.git.
at 34276cb feat: server info
This branch includes the following new commits:
new 34276cb feat: server info
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[apisix] 01/01: feat: server info
Posted by to...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
tokers pushed a commit to branch feat/status
in repository https://gitbox.apache.org/repos/asf/apisix.git
commit 34276cbaab12b11fc6c626975800786f4a18052c
Author: Alex Zhang <zc...@gmail.com>
AuthorDate: Sat Nov 28 17:12:01 2020 +0800
feat: server info
---
apisix/admin/init.lua | 18 ++++++
apisix/cli/ngx_tpl.lua | 1 +
apisix/core/utils.lua | 46 +++++++++++---
apisix/init.lua | 5 ++
apisix/server_info.lua | 164 +++++++++++++++++++++++++++++++++++++++++++++++++
doc/status.md | 51 +++++++++++++++
doc/zh-cn/status.md | 56 +++++++++++++++++
t/APISIX.pm | 1 +
t/status/server_info.t | 104 +++++++++++++++++++++++++++++++
9 files changed, 437 insertions(+), 9 deletions(-)
diff --git a/apisix/admin/init.lua b/apisix/admin/init.lua
index 7ac2b85..08bce9e 100644
--- a/apisix/admin/init.lua
+++ b/apisix/admin/init.lua
@@ -18,6 +18,8 @@ local require = require
local core = require("apisix.core")
local route = require("resty.radixtree")
local plugin = require("apisix.plugin")
+local server_info = require("apisix.server_info")
+
local ngx = ngx
local get_method = ngx.req.get_method
local ngx_time = ngx.time
@@ -258,6 +260,17 @@ local function post_reload_plugins()
end
+local function get_server_info()
+ local server_info, err = server_info.get()
+ if not server_info then
+ core.log.error("failed to get server_info: ", err)
+ core.response.exit(500, err)
+ end
+
+ core.response.exit(200, server_info)
+end
+
+
local function sync_local_conf_to_etcd()
core.log.warn("sync local conf to etcd")
@@ -316,6 +329,11 @@ local uri_route = {
methods = {"PUT"},
handler = post_reload_plugins,
},
+ {
+ paths = [[/apisix/admin/server_info]],
+ methods = {"GET"},
+ handler = get_server_info,
+ }
}
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index 9e17f4c..ee8cbdd 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -111,6 +111,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 a7cad37..7a49c1c 100644
--- a/apisix/core/utils.lua
+++ b/apisix/core/utils.lua
@@ -14,12 +14,14 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-local table = require("apisix.core.table")
-local ngx_re = require("ngx.re")
-local resolver = require("resty.dns.resolver")
-local ipmatcher= require("resty.ipmatcher")
-local ffi = require("ffi")
-local base = require("resty.core.base")
+local table = require("apisix.core.table")
+local log = require("apisix.core.log")
+local ngx_re = require("ngx.re")
+local resolver = require("resty.dns.resolver")
+local ipmatcher = require("resty.ipmatcher")
+local ffi = require("ffi")
+local base = require("resty.core.base")
+
local open = io.open
local math = math
local sub_str = string.sub
@@ -27,15 +29,21 @@ local str_byte = string.byte
local tonumber = tonumber
local type = type
local C = ffi.C
-local ffi_string = ffi.string
+
+local ffi_string = ffi.string
local get_string_buf = base.get_string_buf
-local exiting = ngx.worker.exiting
-local ngx_sleep = ngx.sleep
+local exiting = ngx.worker.exiting
+local ngx_sleep = ngx.sleep
+
+local hostname
local max_sleep_interval = 1
+local max_hostname_len = 256
ffi.cdef[[
int ngx_escape_uri(char *dst, const char *src,
size_t size, int type);
+ int gethostname(char *name, size_t len);
+ int strlen(const char *s);
]]
@@ -203,6 +211,26 @@ function _M.validate_header_value(value)
end
+function _M.gethostname()
+ if hostname then
+ return hostname
+ end
+
+ local buf = get_string_buf(max_hostname_len)
+
+ if C.gethostname(buf, max_hostname_len) == 0 then
+ buf[max_hostname_len - 1] = str_byte('\0')
+ hostname = ffi_string(buf, ffi.C.strlen(buf))
+
+ else
+ log.alert("ffi.C.gethostname() failed")
+ hostname = "unknown"
+ end
+
+ return hostname
+end
+
+
local function sleep(sec)
if sec <= max_sleep_interval then
return ngx_sleep(sec)
diff --git a/apisix/init.lua b/apisix/init.lua
index 629e750..361b55a 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -23,6 +23,7 @@ local service_fetch = require("apisix.http.service").get
local admin_init = require("apisix.admin.init")
local get_var = require("resty.ngxvar").fetch
local router = require("apisix.router")
+local server_info = require("apisix.server_info")
local set_upstream = require("apisix.upstream").set_by_route
local ipmatcher = require("resty.ipmatcher")
local ngx = ngx
@@ -112,6 +113,8 @@ function _M.http_init_worker()
require("apisix.debug").init_worker()
require("apisix.upstream").init_worker()
+ server_info.init_worker()
+
local_conf = core.config.local_conf()
local dns_resolver_valid = local_conf and local_conf.apisix and
local_conf.apisix.dns_resolver_valid
@@ -805,6 +808,8 @@ function _M.stream_init_worker()
core.config.init_worker()
end
+ server_info.init_worker()
+
load_balancer = require("apisix.balancer").run
local_conf = core.config.local_conf()
diff --git a/apisix/server_info.lua b/apisix/server_info.lua
new file mode 100644
index 0000000..e2df9cd
--- /dev/null
+++ b/apisix/server_info.lua
@@ -0,0 +1,164 @@
+--
+-- 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 process = require("ngx.process")
+local core = require("apisix.core")
+
+local type = type
+local ngx_time = ngx.time
+
+local boot_time = os.time()
+local internal_status = ngx.shared.internal_status
+
+local _M = {}
+
+if not internal_status then
+ error("lua_shared_dict \"internal_status\" not configured")
+end
+
+
+local function is_privileged()
+ return process.type() == "privileged agent" or process.type() == "single"
+end
+
+-- server information will be saved into shared memory only if the key
+-- "server_info" not exist if excl is true.
+local function save(data, excl)
+ local handler = excl and internal_status.add or internal_status.set
+
+ local ok, err = handler(internal_status, "server_info", data)
+ if not ok then
+ return nil, err
+ end
+
+ return true
+end
+
+
+local function encode_and_save(server_info, excl)
+ local data, err = core.json.encode(server_info)
+ if not data then
+ return nil, err
+ end
+
+ return save(data, excl)
+end
+
+
+local function report()
+ local server_info, err = _M.get()
+ if not server_info then
+ core.log.error("failed to get server_info: ", err)
+ return nil, err
+ 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 nil, err
+
+ elseif type(res.body) ~= "table" then
+ core.log.error("failed to fetch etcd version: bad version info")
+ return nil, "bad etcd version info"
+ 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 nil, err
+ end
+
+ local key = "/data_plane/server_info/" .. server_info.id
+ local ok, err = core.etcd.set(key, data, 180)
+ if not ok then
+ core.log.error("failed to report server info to etcd: ", err)
+ return nil, err
+ end
+
+ local ok, err = save(data, false)
+ if not ok then
+ core.log.error("failed to encode and save server info: ", err)
+ return nil, err
+ end
+end
+
+
+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,
+ last_report_time = -1,
+ }
+end
+
+
+function _M.init_worker()
+ if not is_privileged() then
+ return
+ end
+
+ local ok, err = encode_and_save(uninitialized_server_info(), true)
+ if not ok then
+ core.log.error("failed to encode and save server info: ", err)
+ end
+
+ local opts = {
+ check_interval = 5, -- in seconds
+ }
+
+ if core.config ~= require("apisix.core.config_etcd") then
+ return
+ end
+
+ -- only launch timer to report server info when config cener is etcd.
+ local timer, err = core.timer.new("server info", report, opts)
+ if not timer then
+ core.log.error("failed to create timer to report server info ", err)
+ end
+end
+
+
+function _M.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() - boot_time
+ return server_info
+end
+
+
+return _M
diff --git a/doc/status.md b/doc/status.md
new file mode 100644
index 0000000..832b290
--- /dev/null
+++ b/doc/status.md
@@ -0,0 +1,51 @@
+<!--
+#
+# 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.
+#
+-->
+
+## Internal status
+
+The Apache APISIX exposes a series of internal data at runtime, which enhances the observability, and is useful for debugging, monitoring.
+
+## Server information
+
+This is the basic information about APISIX instance, users can get it by asking the admin api `/apisix/admin/server_info` (be sure `allow_admin` is `true` in your config.yaml).
+
+```bash
+curl http://127.0.0.1:9080/apisix/admin/server_info -s | jq
+{
+ "up_time": 5,
+ "last_report_time": 1606551536,
+ "id": "71cb4999-4349-475d-aa39-c703944a63d3",
+ "etcd_version": "3.5.0",
+ "version": "2.0",
+ "hostname": "gentoo"
+}
+```
+
+The meaning of each item in server information is following:
+
+| Name | Type | Description |
+|---------|------|-------------|
+| up_time | integer | Elapsed time since APISIX instance was launched, 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 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. |
+
+Note the server information is also reported to etcd periodically (for now the interval is `5s`) and collected by APISIX Dashboard, so you can also access it from APISIX Dashboard.
diff --git a/doc/zh-cn/status.md b/doc/zh-cn/status.md
new file mode 100644
index 0000000..2ecee9a
--- /dev/null
+++ b/doc/zh-cn/status.md
@@ -0,0 +1,56 @@
+<!--
+#
+# 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](../status.md)
+
+## 内部状态
+
+Apache APISIX 暴露了一系列运行时的内部状态数据,这提高了服务的可观测性,对于调试和监控是十分有帮助的。
+
+## 服务信息
+
+这是关于 APISIX 实例最基本的一些信息,你可以通过请求 admin api
+`/apisix/admin/server_info` 来获取它们(请确保 config.yaml 文件中的 `allow_admin` 选项是被启用的)。
+
+```bash
+curl http://127.0.0.1:9080/apisix/admin/server_info -s | jq
+{
+ "up_time": 5,
+ "last_report_time": 1606551536,
+ "id": "71cb4999-4349-475d-aa39-c703944a63d3",
+ "etcd_version": "3.5.0",
+ "version": "2.0",
+ "hostname": "gentoo"
+}
+```
+
+服务信息中每一项的含义如下:
+
+| Name | Type | Description |
+|---------|------|-------------|
+| up_time | 整数 | APISIX 服务实例当前的运行时间, 如果对 APSIX
+进行热更新操作,该值将被重置;普通的 reload 操作不会影响该值。|
+| last_report_time | 整数 | 最近一次服务信息上报的时间戳 。|
+| id | 字符串 | APISIX 服务实例 id 。 |
+| etcd_version | 字符串 | etcd 集群的版本信息,如果 APISIX 和 etcd 集群之间存在网络分区,该值将设置为 `"unknown"`。 |
+| version | 字符串 | APISIX 版本信息。 |
+| hostname | 字符串 | APISIX 所部署的机器或 pod 的主机名信息。 |
+
+注意服务信息将被周期性地上报到 etcd(目前的上报间隔是 5
+秒)并被 APISIX Dashboard 所收集,所以你也可以通过 APISIX Dashboard 来访问这些数据。
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 957a8aa..8b78141 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -237,6 +237,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/status/server_info.t b/t/status/server_info.t
new file mode 100644
index 0000000..dd40fdd
--- /dev/null
+++ b/t/status/server_info.t
@@ -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.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+log_level('info');
+worker_connections(256);
+no_root_location();
+no_shuffle();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity check
+--- config
+location /t {
+ content_by_lua_block {
+ -- let the server info to be reported
+ ngx.sleep(5.1)
+ local json_decode = require("cjson.safe").decode
+ local t = require("lib.test_admin").test
+ local code, _, body = t('/apisix/admin/server_info', ngx.HTTP_GET, nil, nil)
+ if code >= 300 then
+ ngx.status = code
+ end
+
+ local keys = {}
+ body = json_decode(body)
+ 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{^etcd_version: [\d\.]+
+hostname: \w+
+id: [a-zA-Z\-0-9]+
+last_report_time: \d+
+up_time: \d+
+version: [\d\.]+
+$}
+--- timeout: 6
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: uninitialized server info
+--- config
+location /t {
+ content_by_lua_block {
+ local json_decode = require("cjson.safe").decode
+ local t = require("lib.test_admin").test
+ local code, _, body = t('/apisix/admin/server_info', ngx.HTTP_GET, nil, nil)
+ if code >= 300 then
+ ngx.status = code
+ end
+
+ local keys = {}
+ body = json_decode(body)
+ 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{^etcd_version: unknown
+hostname: \w+
+id: [a-zA-Z\-0-9]+
+last_report_time: -1
+up_time: \d+
+version: [\d\.]+
+$}
+--- no_error_log
+[error]