You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by we...@apache.org on 2020/05/27 09:55:55 UTC

[incubator-apisix] branch master updated: feature: add skywalking plugin. (#1241)

This is an automated email from the ASF dual-hosted git repository.

wenming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 398941b  feature: add skywalking plugin. (#1241)
398941b is described below

commit 398941b72f0e35c671b7046f5dae2af97dbf9c74
Author: Wen Ming <mo...@gmail.com>
AuthorDate: Wed May 27 17:55:47 2020 +0800

    feature: add skywalking plugin. (#1241)
---
 Makefile                             |   3 +
 README.md                            |   2 +-
 README_CN.md                         |   2 +-
 apisix/admin/routes.lua              |  12 +-
 apisix/plugins/skywalking.lua        |  80 ++++++++++++
 apisix/plugins/skywalking/client.lua | 226 ++++++++++++++++++++++++++++++++
 apisix/plugins/skywalking/tracer.lua | 101 +++++++++++++++
 apisix/plugins/zipkin.lua            |   2 +-
 bin/apisix                           |   1 +
 conf/config.yaml                     |   2 +
 doc/plugin-develop-cn.md             |   6 +
 doc/plugin-develop.md                |   6 +
 rockspec/apisix-master-0.rockspec    |   1 +
 t/APISIX.pm                          |   1 +
 t/admin/plugins.t                    |   2 +-
 t/debug/debug-mode.t                 |   1 +
 t/lib/server.lua                     |  19 ++-
 t/plugin/skywalking.t                | 242 +++++++++++++++++++++++++++++++++++
 18 files changed, 698 insertions(+), 11 deletions(-)

diff --git a/Makefile b/Makefile
index 16ded88..760c692 100644
--- a/Makefile
+++ b/Makefile
@@ -151,6 +151,9 @@ install: default
 	$(INSTALL) -d $(INST_LUADIR)/apisix/plugins/zipkin
 	$(INSTALL) apisix/plugins/zipkin/*.lua $(INST_LUADIR)/apisix/plugins/zipkin/
 
+	$(INSTALL) -d $(INST_LUADIR)/apisix/plugins/skywalking
+	$(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalking/
+
 	$(INSTALL) -d $(INST_LUADIR)/apisix/stream/plugins
 	$(INSTALL) apisix/stream/plugins/*.lua $(INST_LUADIR)/apisix/stream/plugins/
 
diff --git a/README.md b/README.md
index 73a4ee2..f6b4a4f 100644
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against
     - [CORS](doc/plugins/cors.md)
 
 - **OPS friendly**
-    - OpenTracing: [support Apache Skywalking and Zipkin](doc/plugins/zipkin.md)
+    - OpenTracing: support [Apache Skywalking](doc/plugins/skywalking.md) and [Zipkin](doc/plugins/zipkin.md)
     - Monitoring And Metrics: [Prometheus](doc/plugins/prometheus.md)
     - Clustering: APISIX nodes are stateless, creates clustering of the configuration center, please refer to [etcd Clustering Guide](https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/clustering.md).
     - High availability: support to configure multiple etcd addresses in the same cluster.
diff --git a/README_CN.md b/README_CN.md
index 2b85609..b914326 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -94,7 +94,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵
     - [CORS](doc/plugins/cors-cn.md)
 
 - **运维友好**
-    - OpenTracing 可观测性: [支持 Apache Skywalking 和 Zipkin](doc/plugins/zipkin-cn.md)。
+    - OpenTracing 可观测性: 支持 [Apache Skywalking](doc/plugins/skywalking-cn.md) 和 [Zipkin](doc/plugins/zipkin-cn.md)。
     - 监控和指标: [Prometheus](doc/plugins/prometheus-cn.md)
     - 集群:APISIX 节点是无状态的,创建配置中心集群请参考 [etcd Clustering Guide](https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/clustering.md)。
     - 高可用:支持配置同一个集群内的多个 etcd 地址。
diff --git a/apisix/admin/routes.lua b/apisix/admin/routes.lua
index 3303e8d..bb7092f 100644
--- a/apisix/admin/routes.lua
+++ b/apisix/admin/routes.lua
@@ -135,7 +135,7 @@ function _M.put(id, conf, sub_path, args)
     local key = "/routes/" .. id
     local res, err = core.etcd.set(key, conf, args.ttl)
     if not res then
-        core.log.error("failed to put route[", key, "]: ", err)
+        core.log.error("failed to put route[", key, "] to etcd: ", err)
         return 500, {error_msg = err}
     end
 
@@ -151,7 +151,7 @@ function _M.get(id)
 
     local res, err = core.etcd.get(key)
     if not res then
-        core.log.error("failed to get route[", key, "]: ", err)
+        core.log.error("failed to get route[", key, "] from etcd: ", err)
         return 500, {error_msg = err}
     end
 
@@ -169,7 +169,7 @@ function _M.post(id, conf, sub_path, args)
     -- core.log.info("key: ", key)
     local res, err = core.etcd.push("/routes", conf, args.ttl)
     if not res then
-        core.log.error("failed to post route[", key, "]: ", err)
+        core.log.error("failed to post route[", key, "] to etcd: ", err)
         return 500, {error_msg = err}
     end
 
@@ -186,7 +186,7 @@ function _M.delete(id)
     -- core.log.info("key: ", key)
     local res, err = core.etcd.delete(key)
     if not res then
-        core.log.error("failed to delete route[", key, "]: ", err)
+        core.log.error("failed to delete route[", key, "] in etcd: ", err)
         return 500, {error_msg = err}
     end
 
@@ -214,7 +214,7 @@ function _M.patch(id, conf, sub_path, args)
 
     local res_old, err = core.etcd.get(key)
     if not res_old then
-        core.log.error("failed to get route [", key, "]: ", err)
+        core.log.error("failed to get route [", key, "] in etcd: ", err)
         return 500, {error_msg = err}
     end
 
@@ -261,7 +261,7 @@ function _M.patch(id, conf, sub_path, args)
     -- TODO: this is not safe, we need to use compare-set
     local res, err = core.etcd.set(key, node_value, args.ttl)
     if not res then
-        core.log.error("failed to set new route[", key, "]: ", err)
+        core.log.error("failed to set new route[", key, "] to etcd: ", err)
         return 500, {error_msg = err}
     end
 
diff --git a/apisix/plugins/skywalking.lua b/apisix/plugins/skywalking.lua
new file mode 100644
index 0000000..f95286b
--- /dev/null
+++ b/apisix/plugins/skywalking.lua
@@ -0,0 +1,80 @@
+--
+-- 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 core = require("apisix.core")
+local ngx = ngx
+local math = math
+
+local sw_client = require("apisix.plugins.skywalking.client")
+local sw_tracer = require("apisix.plugins.skywalking.tracer")
+
+local plugin_name = "skywalking"
+
+
+local schema = {
+    type = "object",
+    properties = {
+        endpoint = {type = "string"},
+        sample_ratio = {type = "number", minimum = 0.00001, maximum = 1, default = 1}
+    },
+    service_name = {
+        type = "string",
+        description = "service name for skywalking",
+        default = "APISIX",
+    },
+    required = {"endpoint"}
+}
+
+
+local _M = {
+    version = 0.1,
+    priority = -1100, -- last running plugin, but before serverless post func
+    name = plugin_name,
+    schema = schema,
+}
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.rewrite(conf, ctx)
+    core.log.debug("rewrite phase of skywalking plugin")
+    ctx.skywalking_sample = false
+    if conf.sample_ratio == 1 or math.random() < conf.sample_ratio then
+        ctx.skywalking_sample = true
+        sw_client.heartbeat(conf)
+        -- Currently, we can not have the upstream real network address
+        sw_tracer.start(ctx, conf.endpoint, "upstream service")
+    end
+end
+
+
+function _M.body_filter(conf, ctx)
+    if ctx.skywalking_sample and ngx.arg[2] then
+        sw_tracer.finish(ctx)
+    end
+end
+
+
+function _M.log(conf, ctx)
+    if ctx.skywalking_sample then
+        sw_tracer.prepareForReport(ctx, conf.endpoint)
+    end
+end
+
+return _M
diff --git a/apisix/plugins/skywalking/client.lua b/apisix/plugins/skywalking/client.lua
new file mode 100644
index 0000000..676735c
--- /dev/null
+++ b/apisix/plugins/skywalking/client.lua
@@ -0,0 +1,226 @@
+--
+-- 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 core = require("apisix.core")
+local http = require("resty.http")
+local cjson = require('cjson')
+local ngx = ngx
+local ipairs = ipairs
+
+local register = require("skywalking.register")
+
+local _M = {}
+
+local function register_service(conf)
+    local endpoint = conf.endpoint
+
+    local tracing_buffer = ngx.shared['skywalking-tracing-buffer']
+    local service_id = tracing_buffer:get(endpoint .. '_service_id')
+    if service_id then
+        return service_id
+    end
+
+    local service_name = conf.service_name
+    local service = register.newServiceRegister(service_name)
+
+    local httpc = http.new()
+    local res, err = httpc:request_uri(endpoint .. '/v2/service/register',
+                        {
+                            method = "POST",
+                            body = core.json.encode(service),
+                            headers = {
+                                ["Content-Type"] = "application/json",
+                            },
+                        })
+    if not res then
+        core.log.error("skywalking service register failed, request uri: ",
+                       endpoint .. '/v2/service/register', ", err: ", err)
+
+    elseif res.status == 200 then
+        core.log.debug("skywalking service register response: ", res.body)
+        local register_results = cjson.decode(res.body)
+
+        for _, result in ipairs(register_results) do
+            if result.key == service_name then
+                service_id = result.value
+                core.log.debug("skywalking service registered, service id:"
+                                .. service_id)
+            end
+        end
+
+    else
+        core.log.error("skywalking service register failed, request uri:",
+                        endpoint .. "/v2/service/register",
+                        ", response code:", res.status)
+    end
+
+    if service_id then
+        tracing_buffer:set(endpoint .. '_service_id', service_id)
+    end
+
+    return service_id
+end
+
+local function register_service_instance(conf, service_id)
+    local endpoint = conf.endpoint
+
+    local tracing_buffer = ngx.shared['skywalking-tracing-buffer']
+    local instance_id = tracing_buffer:get(endpoint .. '_instance_id')
+    if instance_id then
+        return instance_id
+    end
+
+    local service_instance_name = core.id.get()
+    local service_instance = register.newServiceInstanceRegister(
+                                        service_id,
+                                        service_instance_name,
+                                        ngx.now() * 1000)
+
+    local httpc = http.new()
+    local res, err = httpc:request_uri(endpoint .. '/v2/instance/register',
+                        {
+                            method = "POST",
+                            body = core.json.encode(service_instance),
+                            headers = {
+                                ["Content-Type"] = "application/json",
+                            },
+                        })
+
+    if not res then
+        core.log.error("skywalking service Instance register failed",
+                        ", request uri: ", conf.endpoint .. '/v2/instance/register',
+                        ", err: ", err)
+
+    elseif res.status == 200 then
+        core.log.debug("skywalking service instance register response: ", res.body)
+        local register_results = cjson.decode(res.body)
+
+        for _, result in ipairs(register_results) do
+            if result.key == service_instance_name then
+                instance_id = result.value
+                core.log.debug("skywalking service Instance registered, ",
+                                "service instance id: ", instance_id)
+            end
+        end
+
+    else
+        core.log.error("skywalking service instance register failed, ",
+                        "response code:", res.status)
+    end
+
+    if instance_id then
+        tracing_buffer:set(endpoint .. '_instance_id', instance_id)
+    end
+
+    return instance_id
+end
+
+local function ping(endpoint)
+    local tracing_buffer = ngx.shared['skywalking-tracing-buffer']
+    local ping_pkg = register.newServiceInstancePingPkg(
+        tracing_buffer:get(endpoint .. '_instance_id'),
+        core.id.get(),
+        ngx.now() * 1000)
+
+    local httpc = http.new()
+    local _, err = httpc:request_uri(endpoint .. '/v2/instance/heartbeat', {
+        method = "POST",
+        body = core.json.encode(ping_pkg),
+        headers = {
+            ["Content-Type"] = "application/json",
+        },
+    })
+
+    if err then
+        core.log.error("skywalking agent ping failed, err: ", err)
+    end
+end
+
+-- report trace segments to the backend
+local function report_traces(endpoint)
+    local tracing_buffer = ngx.shared['skywalking-tracing-buffer']
+    local segment = tracing_buffer:rpop(endpoint .. '_segment')
+
+    local count = 0
+
+    local httpc = http.new()
+
+    while segment ~= nil do
+        local res, err = httpc:request_uri(endpoint .. '/v2/segments', {
+            method = "POST",
+            body = segment,
+            headers = {
+                ["Content-Type"] = "application/json",
+            },
+        })
+
+        if err == nil then
+            if res.status ~= 200 then
+                core.log.error("skywalking segment report failed, response code ", res.status)
+                break
+            else
+                count = count + 1
+            end
+        else
+            core.log.error("skywalking segment report failed, err: ", err)
+            break
+        end
+
+        segment = tracing_buffer:rpop('segment')
+    end
+
+    if count > 0 then
+        core.log.debug(count, " skywalking segments reported")
+    end
+end
+
+do
+    local heartbeat_timer
+
+function _M.heartbeat(conf)
+    local sw_heartbeat = function()
+        local service_id = register_service(conf)
+        if not service_id then
+            return
+        end
+
+        local service_instance_id = register_service_instance(conf, service_id)
+        if not service_instance_id then
+            return
+        end
+
+        report_traces(conf.endpoint)
+        ping(conf.endpoint)
+    end
+
+    local err
+    if ngx.worker.id() == 0 and not heartbeat_timer then
+        heartbeat_timer, err = core.timer.new("skywalking_heartbeat",
+                                            sw_heartbeat,
+                                            {check_interval = 3}
+                                            )
+        if not heartbeat_timer then
+            core.log.error("failed to create skywalking_heartbeat timer: ", err)
+        else
+            core.log.info("succeed to create timer: skywalking heartbeat")
+        end
+    end
+end
+
+end -- do
+
+
+return _M
diff --git a/apisix/plugins/skywalking/tracer.lua b/apisix/plugins/skywalking/tracer.lua
new file mode 100644
index 0000000..edc4bfe
--- /dev/null
+++ b/apisix/plugins/skywalking/tracer.lua
@@ -0,0 +1,101 @@
+--
+-- 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 core = require("apisix.core")
+local span = require("skywalking.span")
+local tracing_context = require("skywalking.tracing_context")
+local span_layer = require("skywalking.span_layer")
+local sw_segment = require('skywalking.segment')
+
+local pairs = pairs
+local ngx = ngx
+
+-- Constant pre-defined in SkyWalking main repo
+-- 84 represents Nginx
+local NGINX_COMPONENT_ID = 6000
+
+local _M = {}
+
+function _M.start(ctx, endpoint, upstream_name)
+    local context
+    -- TODO: use lrucache for better performance
+    local tracing_buffer = ngx.shared['skywalking-tracing-buffer']
+    local instance_id = tracing_buffer:get(endpoint .. '_instance_id')
+    local service_id = tracing_buffer:get(endpoint .. '_service_id')
+
+    if service_id and service_id then
+        context = tracing_context.new(service_id, instance_id)
+    else
+        context = tracing_context.newNoOP()
+    end
+
+    local context_carrier = {}
+    context_carrier["sw6"] = ngx.req.get_headers()["sw6"]
+    local entry_span = tracing_context.createEntrySpan(context, ctx.var.uri, nil, context_carrier)
+    span.start(entry_span, ngx.now() * 1000)
+    span.setComponentId(entry_span, NGINX_COMPONENT_ID)
+    span.setLayer(entry_span, span_layer.HTTP)
+
+    span.tag(entry_span, 'http.method', ngx.req.get_method())
+    span.tag(entry_span, 'http.params', ctx.var.scheme .. '://'
+                                        .. ctx.var.host .. ctx.var.request_uri)
+
+    context_carrier = {}
+    local exit_span = tracing_context.createExitSpan(context,
+                                                    ctx.var.upstream_uri,
+                                                    entry_span,
+                                                    upstream_name,
+                                                    context_carrier)
+    span.start(exit_span, ngx.now() * 1000)
+    span.setComponentId(exit_span, NGINX_COMPONENT_ID)
+    span.setLayer(exit_span, span_layer.HTTP)
+
+    for name, value in pairs(context_carrier) do
+        ngx.req.set_header(name, value)
+    end
+
+    -- Push the data in the context
+    ctx.sw_tracing_context = context
+    ctx.sw_entry_span = entry_span
+    ctx.sw_exit_span = exit_span
+
+    core.log.debug("push data into skywalking context")
+end
+
+function _M.finish(ctx)
+    -- Finish the exit span when received the first response package from upstream
+    if ctx.sw_exit_span then
+        span.finish(ctx.sw_exit_span, ngx.now() * 1000)
+        ctx.sw_exit_span = nil
+    end
+end
+
+function _M.prepareForReport(ctx, endpoint)
+    if ctx.sw_entry_span then
+        span.finish(ctx.sw_entry_span, ngx.now() * 1000)
+        local status, segment = tracing_context.drainAfterFinished(ctx.sw_tracing_context)
+        if status then
+            local segment_json = core.json.encode(sw_segment.transform(segment))
+            core.log.debug('segment = ', segment_json)
+
+            local tracing_buffer = ngx.shared['skywalking-tracing-buffer']
+            local length = tracing_buffer:lpush(endpoint .. '_segment', segment_json)
+            core.log.debug('segment buffer size = ', length)
+        end
+    end
+end
+
+return _M
diff --git a/apisix/plugins/zipkin.lua b/apisix/plugins/zipkin.lua
index 5641239..934d883 100644
--- a/apisix/plugins/zipkin.lua
+++ b/apisix/plugins/zipkin.lua
@@ -48,7 +48,7 @@ local schema = {
 
 local _M = {
     version = 0.1,
-    priority = -1000, -- last running plugin, but before serverless post func
+    priority = -1000,
     name = plugin_name,
     schema = schema,
 }
diff --git a/bin/apisix b/bin/apisix
index f066c8a..98039a2 100755
--- a/bin/apisix
+++ b/bin/apisix
@@ -179,6 +179,7 @@ http {
     lua_shared_dict upstream-healthcheck 10m;
     lua_shared_dict worker-events        10m;
     lua_shared_dict lrucache-lock        10m;
+    lua_shared_dict skywalking-tracing-buffer    100m;
 
     # for openid-connect plugin
     lua_shared_dict discovery             1m; # cache for discovery metadata documents
diff --git a/conf/config.yaml b/conf/config.yaml
index 11d0d76..7475bae 100644
--- a/conf/config.yaml
+++ b/conf/config.yaml
@@ -159,5 +159,7 @@ plugins:                          # plugin list
   - syslog
   - batch-requests
   - http-logger
+  - skywalking
+
 stream_plugins:
   - mqtt-proxy
diff --git a/doc/plugin-develop-cn.md b/doc/plugin-develop-cn.md
index c8a7663..1494d52 100644
--- a/doc/plugin-develop-cn.md
+++ b/doc/plugin-develop-cn.md
@@ -95,6 +95,12 @@ plugins:                          # plugin list
 
 注:先后顺序与执行顺序无关。
 
+特别需要注意的是,如果你的插件有新建自己的代码目录,那么就需要修改 Makefile 文件,新增创建文件夹的操作,比如:
+```
+$(INSTALL) -d $(INST_LUADIR)/apisix/plugins/skywalking
+$(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalking/
+```
+
 ## 配置描述与校验
 
 定义插件的配置项,以及对应的 [Json Schema](https://json-schema.org) 描述,并完成对 json 的校验,这样方便对配置的数据规
diff --git a/doc/plugin-develop.md b/doc/plugin-develop.md
index f6a6cb6..a2c8b8b 100644
--- a/doc/plugin-develop.md
+++ b/doc/plugin-develop.md
@@ -98,6 +98,12 @@ plugins:                          # plugin list
 
 Note : the order of the plugins is not related to the order of execution.
 
+If your plugin has a new code directory of its own, you will need to modify the `Makefile` to create directory, such as:
+```
+$(INSTALL) -d $(INST_LUADIR)/apisix/plugins/skywalking
+$(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalking/
+```
+
 ## schema and check
 
 Write [Json Schema](https://json-schema.org) descriptions and check functions. similarly, take the key-auth plugin as an example to see its
diff --git a/rockspec/apisix-master-0.rockspec b/rockspec/apisix-master-0.rockspec
index aefe8cf..17014c5 100644
--- a/rockspec/apisix-master-0.rockspec
+++ b/rockspec/apisix-master-0.rockspec
@@ -51,6 +51,7 @@ dependencies = {
     "lua-resty-ipmatcher = 0.6",
     "lua-resty-kafka = 0.07",
     "lua-resty-logger-socket = 2.0-0",
+    "skywalking-nginx-lua-plugin = 1.0",
 }
 
 build = {
diff --git a/t/APISIX.pm b/t/APISIX.pm
index b8aa664..173aa5c 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -199,6 +199,7 @@ _EOC_
     lua_shared_dict upstream-healthcheck 32m;
     lua_shared_dict worker-events        10m;
     lua_shared_dict lrucache-lock        10m;
+    lua_shared_dict skywalking-tracing-buffer    100m;
 
     resolver $dns_addrs_str;
     resolver_timeout 5;
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 0c838e6..0cb4c66 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -30,7 +30,7 @@ __DATA__
 --- request
 GET /apisix/admin/plugins/list
 --- response_body_like eval
-qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache","tcp-logger","proxy-mirror","kafka-logger","cors","syslog","batch-requests","http-logger"\]/
+qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache","tcp-logger","proxy-mirror","kafka-logger","cors","syslog","batch-requests","http-logger","skywalking"\]/
 --- no_error_log
 [error]
 
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index 1799f50..4efadfe 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -83,6 +83,7 @@ loaded plugin and sort by priority: 401 name: syslog
 loaded plugin and sort by priority: 400 name: udp-logger
 loaded plugin and sort by priority: 0 name: example-plugin
 loaded plugin and sort by priority: -1000 name: zipkin
+loaded plugin and sort by priority: -1100 name: skywalking
 loaded plugin and sort by priority: -2000 name: serverless-post-function
 
 
diff --git a/t/lib/server.lua b/t/lib/server.lua
index 0f8fbe3..32a419c 100644
--- a/t/lib/server.lua
+++ b/t/lib/server.lua
@@ -90,7 +90,6 @@ function _M.opentracing()
     ngx.say("opentracing")
 end
 
-
 function _M.with_header()
     ngx.header['Content-Type'] = 'application/xml'
     ngx.header['X-Server-id'] = 100
@@ -100,6 +99,24 @@ function _M.with_header()
     ngx.say("!")
 end
 
+function _M.mock_skywalking_v2_service_register()
+    ngx.say('[{"key":"APISIX","value":1}]')
+end
+
+function _M.mock_skywalking_v2_instance_register()
+    ngx.req.read_body()
+    local data = ngx.req.get_body_data()
+    data = json_decode(data)
+    local key = data['instances'][1]['instanceUUID']
+    local ret = {}
+    ret[1] = {key = key, value = 1}
+    ngx.say(json_encode(ret))
+end
+
+function _M.mock_skywalking_v2_instance_heartbeat()
+    ngx.say('ok')
+end
+
 function _M.mock_zipkin()
     ngx.req.read_body()
     local data = ngx.req.get_body_data()
diff --git a/t/plugin/skywalking.t b/t/plugin/skywalking.t
new file mode 100644
index 0000000..7e448be
--- /dev/null
+++ b/t/plugin/skywalking.t
@@ -0,0 +1,242 @@
+#
+# 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.
+#
+
+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 t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+log_level("debug");
+run_tests;
+
+__DATA__
+
+=== TEST 1: add plugin
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "skywalking": {
+                                "endpoint": "http://127.0.0.1:1982/mock_skywalking",
+                                "sample_ratio": 1,
+                                "service_name": "APISIX"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "skywalking": {
+                                    "endpoint": "http://127.0.0.1:1982/mock_skywalking",
+                                    "sample_ratio": 1,
+                                    "service_name":"APISIX"
+                                }
+                            },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1980": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: tiger skywalking
+--- request
+GET /opentracing
+--- response_body
+opentracing
+--- no_error_log
+[error]
+--- grep_error_log eval
+qr/skywalking service Instance registered, service instance id: \d+/
+--- grep_error_log_out eval
+qr/skywalking service Instance registered, service instance id: 1/
+
+
+
+=== TEST 3: change sample ratio
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "skywalking": {
+                                "endpoint": "http://127.0.0.1:1982/mock_skywalking",
+                                "sample_ratio": 0.00001
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "skywalking": {
+                                    "endpoint": "http://127.0.0.1:1982/mock_skywalking",
+                                    "sample_ratio": 0.00001
+                                }
+                            },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1980": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: not tiger skywalking
+--- request
+GET /opentracing
+--- response_body
+opentracing
+--- no_error_log
+push data into skywalking context
+
+
+
+=== TEST 5: disabled
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                            },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1980": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: not tiger skywalking
+--- request
+GET /opentracing
+--- response_body
+opentracing
+--- no_error_log
+rewrite phase of skywalking plugin