在前面的文章介绍了 Kong 的相关实践,本文将会介绍 Kong 的利器:插件以及自定义插件。

Kong 几种常用插件的应用

请求到达 Kong,在转发给服务端应用之前,我们可以应用 Kong 自带的插件对请求进行处理,如合法认证、限流控制、黑白名单校验和日志采集等等。同时,我们也可以按照 Kong 的教程文档,定制开发属于自己的插件。本小节将会选择其中的两个插件示例应用,其余的插件应用,可以参见:https://docs.konghq.com/hub/。

JWT 认证插件

JWT 是目前最流行的跨域身份验证解决方案。作为一个开放的标准(RFC 7519),定义了一种简洁的、自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的。

关于为什么使用 JWT,不在本小节详细论述,具体可见 统一认证与授权在微服务架构中的设计与实战。Kong 提供了 JWT 认证插件,用以验证包含 HS256 或 RS256 签名的 JWT 的请求(如RFC 7519中所述)。每个消费者都将拥有 JWT 凭证(公钥和密钥),这些凭证必须用于签署其 JWT。JWT 令牌可以通过请求字符串、cookie 或者认证头部传递。Kong 将会验证令牌的签名,通过则转发,否则直接丢弃请求。

我们在前面小节配置的路由基础上,增加 JWT 认证插件。

curl -X POST http://localhost:8001/routes/e33d6aeb-4f35-4219-86c2-a41e879eda36/plugins \
--data "name=jwt"

可以看到,在插件列表增加了相应的记录。

在增加了 JWT 插件之后,就没法直接访问 /api/blog 接口了,接口返回:"message": "Unauthorized"。提示客户端要访问需要提供 JWT 的认证信息。因此,我们需要创建用户:

curl -i -X POST \
--url http://localhost:8001/consumers/  \
--data "username=aoho"

如上创建了一个名为 aoho 的用户。

创建好用户之后,需要获取用户 JWT 凭证,执行如下的调用:

curl -i -X POST \
--url http://localhost:8001/consumers/aoho/jwt \
--header "Content-Type: application/x-www-form-urlencoded"// 响应
{"rsa_public_key": null,"created_at": 1563566125,"consumer": {"id": "8c0e1ab4-8411-42fc-ab80-5eccf472d2fd"},"id": "1d69281d-5083-4db0-b42f-37b74e6d20ad","algorithm": "HS256","secret": "olsIeVjfVSF4RuQuylTMX4x53NDAOQyO","key": "TOjHFM4m1qQuPPReb8BTWAYCdM38xi3C"
}

使用 key 和 secret 在 https://jwt.io 可以生成 JWT 凭证信息。在实际的使用过程中,我们通过编码实现,此处为了演示使用网页工具生成 Token。

将生成的 Token,配置到请求的认证头部,再次执行请求:

可以看到,我们能够正常请求相应的 API 接口。JWT 认证插件应用成功。

Prometheus 可视化监控

Prometheus 是一套开源的系统监控报警框架。它启发于 Google 的 borgmon 监控系统,由工作在 SoundCloud 的 google 前员工在 2012 年创建,作为社区开源项目进行开发,并于 2015 年正式发布。2016 年,Prometheus 正式加入 Cloud Native Computing Foundation,成为受欢迎度仅次于 Kubernetes 的项目。作为新一代的监控框架,Prometheus 适用于记录时间序列数据,具有强大的多维度数据模型、灵活而强大的查询语句、易于管理和伸缩等特点。

Kong 官方提供的 Prometheus 插件,可用的 metric 如下:

  • 状态码:上游服务返回的 HTTP 状态码;

  • 时延柱状图:Kong 中的时延都将被记录,包括如下:

    • 请求:完整请求的时延;

    • Kong:Kong用来路由、验证和运行其他插件所花费的时间;

    • 上游:上游服务所花费时间来响应请求。

  • Bandwidth:流经 Kong 的总带宽(出口/入口);

  • DB 可达性:Kong 节点是否能访问其 DB;

  • Connections:各种 NGINX 连接指标,如 Active、读取、写入、接受连接。

我们在 Service 为 aoho-blog 的服务上安装 Prometheus 插件:

curl -X POST http://localhost:8001/services/aoho-blog/plugins \
--data "name=prometheus"

可以从管理界面看到,我们己经成功将 Prometheus 插件绑定到 aoho-blog 服务上。

通过访问 /metrics 接口返回收集度量数据:

$ curl -i http://localhost:8001/metrics
HTTP/1.1 200 OK
Server: openresty/1.13.6.2
Date: Sun, 21 Jul 2019 09:48:42 GMT
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *kong_bandwidth{type="egress",service="aoho-blog"} 178718
kong_bandwidth{type="ingress",service="aoho-blog"} 1799
kong_datastore_reachable 1
kong_http_status{code="200",service="aoho-blog"} 4
kong_http_status{code="401",service="aoho-blog"} 1kong_latency_bucket{type="kong",service="aoho-blog",le="00005.0"} 1
kong_latency_bucket{type="kong",service="aoho-blog",le="00007.0"} 1
...
kong_latency_bucket{type="upstream",service="aoho-blog",le="00300.0"} 4
kong_latency_bucket{type="upstream",service="aoho-blog",le="00400.0"} 4
...
kong_latency_count{type="kong",service="aoho-blog"} 5
kong_latency_count{type="request",service="aoho-blog"} 5
kong_latency_count{type="upstream",service="aoho-blog"} 4
kong_latency_sum{type="kong",service="aoho-blog"} 409
kong_latency_sum{type="request",service="aoho-blog"} 1497
kong_latency_sum{type="upstream",service="aoho-blog"} 1047kong_nginx_http_current_connections{state="accepted"} 2691
kong_nginx_http_current_connections{state="active"} 2
kong_nginx_http_current_connections{state="handled"} 2691
kong_nginx_http_current_connections{state="reading"} 0
kong_nginx_http_current_connections{state="total"} 2637
kong_nginx_http_current_connections{state="waiting"} 1
kong_nginx_http_current_connections{state="writing"} 1kong_nginx_metric_errors_total 0

返回的响应太长,有省略,从响应可以看到 Prometheus 插件提供的 metric 都有体现。Prometheus 插件导出的度量标准,可以在 Grafana 中绘制,读者可以自行尝试。

链路追踪 Zipkin 插件

Zipkin 是一款开源的分布式实时数据追踪系统。其主要功能是聚集来自各个异构系统的实时监控数据,用来追踪微服务架构下的系统延时问题。应用系统需要向 Zipkin 报告数据。Kong 的 Zipkin 插件作为 zipkin-client 就是组装好 Zipkin 需要的数据包,往 Zipkin-server 发送数据。Zipkin 插件会将请求打上如下标签,并推送到 Zipkin 服务端:

  • span.kind (sent to Zipkin as “kind”)

  • http.method

  • http.status_code

  • http.url

  • peer.ipv4

  • peer.ipv6

  • peer.port

  • peer.hostname

  • peer.service

关于链路追踪和 Zipkin 的具体信息,参见详解微服务架构中的全链路追踪,本次 chat 旨在介绍如何在 Kong 中使用 Zipkin 插件追踪所有请求的链路。

首先开启 Zipkin 插件,将插件绑定到路由上(这里可以绑定为全局的插件)。

curl -X POST http://kong:8001/routes/e33d6aeb-4f35-4219-86c2-a41e879eda36/plugins \--data "name=zipkin"  \--data "config.http_endpoint=http://localhost:9411/api/v2/spans" \--data "config.sample_ratio=1"

如上配置了 Zipkin Collector 的地址和采样率,为了效果明显,设置采样率为 100%,生产环境谨慎使用,采样率对系统吞吐量有影响。

可以看到,Zipkin 插件已经应用到指定的路由上。下面我们将会执行请求 /api/blog 接口,打开 http://localhost:9411 界面如下:

Zipkin 已经将请求记录,我们可以点开查看详细的链路详情:

从链路调用可以知道,请求到达 Kong 之后,经历了哪些服务和 Span,每个 Span 所花费的时间等等信息。

自定义插件的实践

官方虽然提供了很多插件,但是我们在实际的业务场景中还会有业务的需求,定制插件能够帮助我们更好地管理 API Gateway。Kong 提供了插件开发包和示例,自定义插件只需要按照提供的步骤即可。

Kong 安装

在上面小节,笔者介绍了通过镜像的方式安装 Kong,本部分为了方便编写自定义插件,我们使用本地安装的 Kong,笔者的环境是 macOS,安装较为简单:

 $ brew tap kong/kong$ brew install kong

其次安装 Postgres,并下载 kong.conf.default 配置文件(参见 https://raw.githubusercontent.com/Kong/kong/master/kong.conf.default),执行如下的命令:

 $ sudo mkdir -p /etc/kong$ sudo cp kong.conf.default /etc/kong/kong.conf

执行 migration:

kong migrations bootstrap -c /etc/kong/kong.conf

随后即可启动 Kong:

kong start -c /etc/kong/kong.conf

启动之后,通过 8001 管理端口验证是否成功。

curl -i http://localhost:8001/

基于安装好的 Kong,我们介绍一下如何将自定义的插件加入到 Kong 的可选插件中,这里以鉴权的 token-auth 插件为例进行讲解。

Kong 官方提供了有关认证的插件有:JWT、OAuth 2.0 和 Basic Auth 等,我们在实际业务中,也经常会自建认证和授权服务器,这样就需要我们在 API 网关处拦截验证请求的合法性。基于此,我们实现一个类似 Kong 过滤器的插件:token-auth。

Kong 自带的插件在 /usr/local/share/lua/5.1/kong/plugins/ 目录下。每个插件文件夹下有如下两个主要文件:

  • schema.lua:定义的启动插件时的参数检查;

  • handler.lua:文件定义了各阶段执行的函数,插件的核心。

token-auth 是我们定制的插件名。在 /usr/local/share/lua/5.1/kong/plugins 下新建 token-auth 目录。Plugin 的加载和初始化阶段,即 Kong.init() 在加载插件的时候,会将插件目录中的 schema.lua 和 handler.lua 加载,下面我们看下这两个脚本的实现。

插件配置定义:schema.lua

Kong 中每个插件的配置存放在 plugins 表中的 config 字段,是一段 json 文本,token-auth 所需的配置定义如下:

return {no_consumer = true,fields = {auth_server_url = {type = "url", required = true},}
}

从 schema.lua 可以看到,启用 token-auth 插件时,需要检查 auth_server_url 字段为 URL 类型,且不能为空。

插件功能实现:handler.lua

handler.lua 实现了插件认证功能,这个插件中定义的方法,会在处理请求和响应的时候被调用。

llocal http = require "socket.http"
local ltn12 = require "ltn12"
local cjson = require "cjson.safe"local BasePlugin = require "kong.plugins.base_plugin"local TokenAuthHandler = BasePlugin:extend()TokenAuthHandler.PRIORITY = 1000local KEY_PREFIX = "auth_token"
local EXPIRES_ERR = "token expires"--- 提取 JWT 头部信息
-- @param request    ngx request object
-- @return token     JWT
-- @return err
local function extract_token(request)local auth_header = request.get_headers()["authorization"]if auth_header thenlocal iterator, ierr = ngx.re.gmatch(auth_header, "\\s*[Bb]earer\\s+(.+)")if not iterator thenreturn nil, ierrendlocal m, err = iterator()if err thenreturn nil, errendif m and #m > 0 thenreturn m[1]endend
end--- 调用 auth server 验证 token 合法性
-- @param token    Token to be validated
-- @param conf     Plugin configuration
-- @return info    Information associated with token
-- @return err
local function query_and_validate_token(token, conf)ngx.log(ngx.DEBUG, "get token info from: ", conf.auth_server_url)local response_body = {}local res, code, response_headers = http.request{url = conf.auth_server_url,method = "GET",headers = {["Authorization"] = "bearer " .. token},sink = ltn12.sink.table(response_body),}if type(response_body) ~= "table" thenreturn nil, "Unexpected response"endlocal resp = table.concat(response_body)ngx.log(ngx.DEBUG, "response body: ", resp)if code ~= 200 thenreturn nil, respendlocal decoded, err = cjson.decode(resp)if err thenngx.log(ngx.ERR, "failed to decode response body: ", err)return nil, errendif not decoded.expires_in thenreturn nil, decoded.error or respendif decoded.expires_in <= 0 thenreturn nil, EXPIRES_ERRenddecoded.expires_at = decoded.expires_in + os.time()return decoded
endfunction TokenAuthHandler:new()TokenAuthHandler.super.new(self, "token-auth")
end
--- 实现 access 方法
function TokenAuthHandler:access(conf)TokenAuthHandler.super.access(self)local token, err = extract_token(ngx.req)if err thenngx.log(ngx.ERR, "failed to extract token: ", err)return kong.response.exit(500, { message = err })endngx.log(ngx.DEBUG, "extracted token: ", token)local ttype = type(token)if ttype ~= "string" thenif ttype == "nil" thenreturn kong.response.exit(401, { message = "Missing token"})endif ttype == "table" thenreturn kong.response.exit(401, { message = "Multiple tokens"})endreturn kong.response.exit(401, { message = "Unrecognized token" })endlocal infoinfo, err = query_and_validate_token(token, conf)if err thenngx.log(ngx.ERR, "failed to validate token: ", err)if EXPIRES_ERR == err thenreturn kong.response.exit(401, { message = EXPIRES_ERR })endreturn kong.response.exit(500,{ message = EXPIRES_ERR })endif info.expires_at < os.time() thenreturn kong.response.exit(401, { message = EXPIRES_ERR })endngx.log(ngx.DEBUG, "token will expire in ", info.expires_at - os.time(), " seconds")endreturn TokenAuthHandler

token-auth 插件实现了 new() 和 access() 两个方法,只在 access 阶段发挥作用。在 access() 方法中,首先会提取 JWT 头部信息,检查 token 是否存在以及格式是否正确等,随后请求认证服务器验证 token 的合法性。

加载插件

插件开发完成后,首先要在插件目录中新建 token-auth-1.2.1-0.rockspec 文件,填写新开发的插件:

package = "token-auth"
version = "1.2.1-0"supported_platforms = {"linux", "macosx"}local pluginName = "token-auth"
build = {type = "builtin",modules = {["kong.plugins.token-auth.handler"] = "kong/plugins/token-auth/handler.lua",["kong.plugins.token-auth.schema"] = "kong/plugins/token-auth/schema.lua",}
}

然后在 kong.conf 配置文件中添加新开发的插件:

$ vim /etc/kong/kong.conf# 去掉开头的注释并修改如下
plugins = bundled, token-auth

bundled 属性是指官方提供的插件合集,默认开启。这里,我们增加了自定义的 token-auth 插件。验证一下,自定义的插件是否成功加载:

$ curl http://127.0.0.1:8001/plugins/enabled{"enabled_plugins":["correlation-id","pre-function","cors","token-auth","ldap-auth","loggly","hmac-auth","zipkin","request-size-limiting","azure-functions","request-transformer","oauth2","response-transformer","ip-restriction","statsd","jwt","proxy-cache","basic-auth","key-auth","http-log","datadog","tcp-log","post-function","prometheus","acl","kubernetes-sidecar-injector","syslog","file-log","udp-log","response-ratelimiting","aws-lambda","bot-detection","rate-limiting","request-termination"]}%

启用插件

在 Service 上启用 token-auth 插件,同时需要指定 config.auth_server_url 的属性:

$ curl -i -XPOST localhost:8001/services/aoho-blog/plugins \--data 'name=token-auth' \--data 'config.auth_server_url=<URL of verification API>'

如果插件有自己的数据库表,或者对数据库表或表中数据有要求,在插件目录中创建 migrations 目录。根据使用的是 Postgres 还是 Cassandra,创建 migrations/postgres.lua 或者 migrations/cassandra.lua。

如果插件有自己的数据库表,还需要在插件目录中创建 daos.lua,返回数据库表定义,如果没有单独的数据库表,不需要创建这个文件。

这里不做过多演示,读者可以结合笔者之前的 chat:统一认证与授权在微服务架构中的设计与实战,构建认证授权服务器,自行尝试一下。

小结

网关是微服务架构中不可或缺的基础服务,本文介绍了如何使用 Kong 构建微服务网关。相比于其他网关组件,Kong 在易用性和性能方面表现优异,是一款现代的云原生网关。随后介绍了 Kong 的部分插件使用。Kong 官方和社区提供了丰富的 API 网关插件,配置即可使用。最后,笔者在文中实现了一个自定义的 token-auth 的插件,Kong 开放的插件机制,使得开发者可以灵活地实现特殊的业务需求。

推荐阅读

云原生架构下的 API 网关实践

订阅最新文章,欢迎关注我的公众号

微信公众号
给个[在看],是最大的支持!

云原生架构下的 API 网关实践:Kong (三)相关推荐

  1. 云原生架构下的 API 网关实践: Kong (二)

    Kong 是 Mashape 开源的一款云原生架构下的分布式 API 网关,其性能和可扩展性在同类组件中,表现都很优异.Kong 官方提供了很多直接可用的插件,此外,Kong 还可以通过插件扩展已有功 ...

  2. 云原生架构下的持续交付实践

    导读:随着虚拟化技术的成熟和分布式框架的普及,在容器技术.可持续交付.编排系统等开源社区的推动下,以及微服务等开发理念的带动下,应用上云已经是不可逆转的趋势. 云原生带来了标准化.松耦合.易观测.易扩 ...

  3. 云原生架构下微服务最佳实践-如何拆分微服务架构

    转自: https://mp.weixin.qq.com/s?__biz=MzI3MzEzMDI1OQ==&mid=2651821066&idx=1&sn=8475f813a8 ...

  4. 王启军:云原生架构下如何拆分微服务?

    王启军,云原生技术架构专家,曾任当当架构师,主导电商平台架构设计,包括订单.支付.价格.库存.物流等.曾就职于搜狐,负责手机微博的研发.十余年的技术历练,也曾作为技术负责人带领过近百人的团队.公众号& ...

  5. 云原生架构下复杂工作负载混合调度的思考与实践

    作者: 实验室小陈 / 大数据开放实验室 10月25日,第一届中国云计算基础架构开发者大会在长沙召开,星环科技与众多国内外厂商共同就"云原生"."安全与容错"和 ...

  6. 云原生架构下日志服务数据预处理

    简介:本篇实践将以某家国际教育机构为例,为大家详细介绍云原生架构下日志服务数据预处理以及对应的解决方案和最佳实践操作手册,方便用户快速对号入座,解决云原生架构下的常见日志难题. 直达最佳实践:[htt ...

  7. 从重大漏洞应急看云原生架构下的安全建设与安全运营(下)

    前言: 前一篇文章"从重大漏洞应急看云原生架构下的安全建设与安全运营(上)"中,我们简要分析了对于重大安全漏洞,在云原生架构下该如何快速进行应急和修复,以及云原生架构对于这种安全应 ...

  8. 从重大漏洞应急看云原生架构下的安全建设与安全运营(上)

    前言 近年来,云原生架构被广泛的部署和使用,业务容器化部署的比例逐年提高,对于突发重大漏洞等0day安全事件,往往给安全的应急带来重大的挑战.例如前段时间广受影响的重大漏洞的爆发,可以说是云原生架构下 ...

  9. 博时基金云原生架构下的统一云管平台

    博时基金管理有限公司(以下简称为"博时基金")成立于1998年,是中国内地首批成立的五家基金管理公司之一.博时基金总部位于深圳,在北京.上海等地设有分公司,同时拥有博时基金(国际) ...

  10. 云原生架构下的微服务选型和演进

    作者:彦林 本文整理自阿里云智能高级技术专家彦林的线上直播分享<云原生微服务最佳实践>.视频回放地址:https://yqh.aliyun.com/live/detail/28454 随着 ...

最新文章

  1. 从头开始学习深度学习之卷积
  2. 检查Lync SRV记录是否正常
  3. SNAT和DNAT的区别
  4. Java多线程知识小抄集(二)
  5. 控制用户的访问之权限、角色【weber出品必属精品】
  6. node seneca_使用Node.js和Seneca编写国际象棋微服务,第2部分
  7. Oracle的逆向工程generatorConfig
  8. vue控制台报错Duplicate keys detected: 'xxxx'. This may cause an update error.解决方案
  9. 学习一门编程语言的基本步骤
  10. Web前端——HTML
  11. 通俗易懂!视觉slam第六部分——旋转向量,欧拉角
  12. 小tips:JS语法之标签(label)
  13. 金笛JDMail邮件系统从源头上为企业铸造防lj邮件墙--4
  14. 笔记本计算机摄像头怎么打开,笔记本摄像头怎么打开,教您怎么打开笔记本的摄像头...
  15. xpdf工具(PDF转图片工具)
  16. 运营MM又来求我发软文了......
  17. IPv4如何向IPv6过渡?IPv6改造方案有哪些?
  18. 【渝粤题库】陕西师范大学163209 旅游企业战略管理
  19. HTML 文本域textarea
  20. 大数据 -- 数据倾斜

热门文章

  1. 什么品牌蓝牙耳机音质好?2020蓝牙耳机排行榜10强!
  2. Navicat设置mysql时间字段自动获取当前时间
  3. 网站怎么样对接微信公众号,看以下操作
  4. 如何用计算机校验信息,Win10如何校验文件哈希值(系统自带方法)?
  5. Win10 Edge浏览器假死解决方案
  6. 用户体验 | 银行如何优化APP用户体验
  7. 首届“中科杯”全国软件设计大赛获奖名单揭晓
  8. 安卓平板“枯木回春”,vivo能分杯羹吗?
  9. C#删除IE临时文件、缓存、Cookies
  10. 工业物联网:平台架构、关键技术与应用实践