Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: supported zipkin plugin. #304

Merged
merged 9 commits into from
Jul 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions COPYRIGHT
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,44 @@ Redistributions in binary form must reproduce the above copyright notice, this l

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

%%%%%%%%%

opentracing-openresty

https://github.com/iresty/opentracing-openresty

Apache License 2

Licensed 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.

%%%%%%%%%

kong-plugin-zipkin

https://github.com/Kong/kong-plugin-zipkin

Copyright 2018-2019 Kong Inc.

Licensed 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.

%%%%%%%%%
3 changes: 2 additions & 1 deletion conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ plugins: # plugin list
- example-plugin
- limit-req
- limit-count
- limit-conn
- key-auth
- prometheus
- limit-conn
- node-status
- jwt-auth
- zipkin
Binary file added doc/images/plugin/zipkin-1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/plugin/zipkin-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions doc/plugins/zipkin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Summary
- [**Name**](#name)
- [**Attributes**](#attributes)
- [**How To Enable**](#how-to-enable)
- [**Test Plugin**](#test-plugin)
- [**Disable Plugin**](#disable-plugin)


## Name

[Zipkin](https://github.com/openzipkin/zipkin) is a OpenTracing plugin.

It's also works with `Apache SkyWalking`, which is support Zipkin v1/v2 format.

## Attributes

* `endpoint`: the http endpoint of Ziplin, for example: `http://127.0.0.1:9411/api/v2/spans`.

* `sample_ratio`: the ratio of sample, the minimum is 0.00001, the maximum is 1.

## How To Enable

Here's an example, enable the zipkin plugin on the specified route:

```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d '
{
"methods": ["GET"],
"uri": "/index.html",
"plugins": {
"zipkin": {
"endpoint": "http://127.0.0.1:9411/api/v2/spans",
"sample_ratio": 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"39.97.63.215:80": 1
}
}
}'
```

## Test Plugin

### run the Zipkin instance

e.g. using docker:

```
sudo docker run -d -p 9411:9411 openzipkin/zipkin
```

Here is a test example:

```shell
$ curl http://127.0.0.1:9080/index.html
HTTP/1.1 200 OK
...
```

Then you can use a browser to access the webUI of Zipkin:
moonming marked this conversation as resolved.
Show resolved Hide resolved

```
http://127.0.0.1:9411/zipkin
```

![](../../doc/images/plugin/zipkin-1.png)

![](../../doc/images/plugin/zipkin-2.png)

## Disable Plugin

When you want to disable the zipkin plugin, it is very simple,
you can delete the corresponding json configuration in the plugin configuration,
no need to restart the service, it will take effect immediately:

```shell
$ curl http://127.0.0.1:2379/v2/keys/apisix/routes/1 -X PUT -d value='
{
"methods": ["GET"],
"uri": "/index.html",
"plugins": {
},
"upstream": {
"type": "roundrobin",
"nodes": {
"39.97.63.215:80": 1
}
}
}'
```

The zipkin plugin has been disabled now. It works for other plugins.
22 changes: 21 additions & 1 deletion lua/apisix/core/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

local ngx = ngx
local get_headers = ngx.req.get_headers

local tonumber = tonumber

local _M = {version = 0.1}


local function _headers(ctx)
if not ctx then
ctx = ngx.ctx.api_ctx
end
local headers = ctx.headers
if not headers then
headers = get_headers()
Expand All @@ -20,6 +23,9 @@ _M.headers = _headers


function _M.header(ctx, name)
if not ctx then
ctx = ngx.ctx.api_ctx
end
return _headers(ctx)[name]
end

Expand All @@ -28,15 +34,29 @@ end
-- so if there is a load balancer between downstream client and APISIX,
-- this function will return the ip of load balancer.
function _M.get_ip(ctx)
if not ctx then
ctx = ngx.ctx.api_ctx
end
return ctx.var.realip_remote_addr or ctx.var.remote_addr or ''
end


-- get remote address of downstream client,
-- in cases there is a load balancer between downstream client and APISIX.
function _M.get_remote_client_ip(ctx)
if not ctx then
ctx = ngx.ctx.api_ctx
end
return ctx.var.remote_addr or ''
end


function _M.get_remote_client_port(ctx)
if not ctx then
ctx = ngx.ctx.api_ctx
end
return tonumber(ctx.var.remote_port)
end


return _M
8 changes: 7 additions & 1 deletion lua/apisix/core/response.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ local type = type
local ngx_exit = ngx.exit
local insert_tab = table.insert
local concat_tab = table.concat

local str_sub = string.sub
local tonumber = tonumber

local _M = {version = 0.1}

Expand Down Expand Up @@ -78,4 +79,9 @@ function _M.set_header(...)
end


function _M.get_upstream_status(ctx)
-- $upstream_status maybe including mutiple status, only need the last one
return tonumber(str_sub(ctx.var.upstream_status or "", -3))
moonming marked this conversation as resolved.
Show resolved Hide resolved
end

return _M
165 changes: 165 additions & 0 deletions lua/apisix/plugins/zipkin.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
local core = require("apisix.core")
local new_tracer = require("opentracing.tracer").new
local zipkin_codec = require("apisix.plugins.zipkin.codec")
local new_random_sampler = require("apisix.plugins.zipkin.random_sampler").new
local new_reporter = require("apisix.plugins.zipkin.reporter").new
local ngx = ngx
local pairs = pairs

local plugin_name = "zipkin"


-- You can follow this document to write schema:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about we remove this comment?

-- https://github.com/Tencent/rapidjson/blob/master/bin/draft-04/schema
-- rapidjson not supported `format` in draft-04 yet
local schema = {
type = "object",
properties = {
endpoint = {type = "string"},
sample_ratio = {type = "number", minimum = 0.00001, maximum = 1}
},
required = {"endpoint", "sample_ratio"}
}


local _M = {
version = 0.1,
priority = -1000, -- last running plugin
name = plugin_name,
schema = schema,
}


function _M.check_schema(conf)
return core.schema.check(schema, conf)
end


local function create_tracer(conf)
local tracer = new_tracer(new_reporter(conf), new_random_sampler(conf))
tracer:register_injector("http_headers", zipkin_codec.new_injector())
tracer:register_extractor("http_headers", zipkin_codec.new_extractor())
return tracer
end

local function report2endpoint(premature, reporter)
if premature then
return
end

local ok, err = reporter:flush()
if not ok then
core.log.error("reporter flush ", err)
return
end

core.log.info("report2endpoint ok")
end


function _M.rewrite(conf, ctx)
local tracer = core.lrucache.plugin_ctx(plugin_name, ctx,
create_tracer, conf)

ctx.opentracing_sample = tracer.sampler:sample()
if not ctx.opentracing_sample then
return
end

local wire_context = tracer:extract("http_headers",
core.request.headers(ctx))

local start_timestamp = ngx.req.start_time()
local request_span = tracer:start_span("apisix.request", {
child_of = wire_context,
start_timestamp = start_timestamp,
tags = {
component = "apisix",
["span.kind"] = "server",
["http.method"] = ctx.var.method,
["http.url"] = ctx.var.request_uri,
-- TODO: support ipv6
["peer.ipv4"] = core.request.get_remote_client_ip(ctx),
["peer.port"] = core.request.get_remote_client_port(ctx),
}
})

ctx.opentracing = {
tracer = tracer,
wire_context = wire_context,
request_span = request_span,
rewrite_span = nil,
access_span = nil,
proxy_span = nil,
}

local request_span = ctx.opentracing.request_span
ctx.opentracing.rewrite_span = request_span:start_child_span(
"apisix.rewrite", start_timestamp)
ctx.REWRITE_END_TIME = tracer:time()
ctx.opentracing.rewrite_span:finish(ctx.REWRITE_END_TIME)
end

function _M.access(conf, ctx)
if not ctx.opentracing_sample then
return
end

local opentracing = ctx.opentracing

opentracing.access_span = opentracing.request_span:start_child_span(
"apisix.access", ctx.REWRITE_END_TIME)

local tracer = opentracing.tracer

ctx.ACCESS_END_TIME = tracer:time()
opentracing.access_span:finish(ctx.ACCESS_END_TIME)

opentracing.proxy_span = opentracing.request_span:start_child_span(
"apisix.proxy", ctx.ACCESS_END_TIME)

-- send headers to upstream
local outgoing_headers = {}
tracer:inject(opentracing.proxy_span, "http_headers", outgoing_headers)
for k, v in pairs(outgoing_headers) do
core.response.set_header(k, v)
end
end


function _M.header_filter(conf, ctx)
if not ctx.opentracing_sample then
return
end

local opentracing = ctx.opentracing

ctx.HEADER_FILTER_END_TIME = opentracing.tracer:time()
opentracing.body_filter_span = opentracing.proxy_span:start_child_span(
"apisix.body_filter", ctx.HEADER_FILTER_END_TIME)
end


function _M.log(conf, ctx)
if not ctx.opentracing_sample then
return
end

local opentracing = ctx.opentracing

local log_end_time = opentracing.tracer:time()
opentracing.body_filter_span:finish(log_end_time)

local upstream_status = core.response.get_upstream_status(ctx)
opentracing.request_span:set_tag("http.status_code", upstream_status)
opentracing.proxy_span:finish(log_end_time)
opentracing.request_span:finish(log_end_time)

local reporter = opentracing.tracer.reporter
local ok, err = ngx.timer.at(0, report2endpoint, reporter)
if not ok then
core.log.error("failed to create timer: ", err)
end
end

return _M
Loading