Kong Gateway 是一个轻量、快速、灵活的基于Nginx开发云原生 API 网关。在云原生领域,Kong Gateway 越来受欢迎。
Kong提供了插件化能力,在对后台业务服务代码无侵入的条件下,可以在接入层方便地引入认证鉴权、安全防护、流量控制都能功能。这也是其受欢迎的原因之一。
Kong Gateway 官方已经提供了一系列常用的插件,但是业务开发中有时需要定制自己的插件。本文将介绍如何编写 Kong 的自定义插件,以及如何将插件集成到 Kong 网关中
docker network create kong-net
docker run -d --name kong-database \
--network=kong-net \
-p 5432:5432 \
-e "POSTGRES_USER=kong" \
-e "POSTGRES_DB=kong" \
-e "POSTGRES_PASSWORD=kongpass" \
-v /data/home/windealli/workspace/kong/postgres/data:/var/lib/postgresql/data \
postgres:13
docker run --rm --network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_PASSWORD=kongpass" \
-e "KONG_PASSWORD=test" \
kong/kong-gateway:3.4.0.0 kong migrations bootstrap
网关容器Kong-Gateway的启动包含三个步骤
第三步是为了方便后面配置我们自定义的插件,第一、二步是服务与第三步的,否则容器无法启动。
1) 不带目录映射启动容器
docker run -d --name kong-gateway \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kongpass" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCE_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
-e "KONG_ADMIN_GUI_URL=http://localhost:8002" \
-e "KONG_PLUGIN_PATH=/kong/plugins/?.lua;;" \
-e "KONG_LUA_PACKAGE_PATH=/kong/plugins/my-first-plugin/?.lua;;" \
-e "KONG_PLUGINS=bundled,my-first-plugin" \
-e "KONG_CUSTOM_PLUGINS=my-first-plugin" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
-e "KONG_LOG_LEVEL=debug" \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
-p 8002:8002 \
-p 8445:8445 \
-p 8003:8003 \
-p 8004:8004 \
kong/kong-gateway:3.4.0.0
2) docker cp 把容器中需要修改的配置文件拷贝到宿主机
mkdir etc lua_kong
docker cp ${container_id}:/etc/kong etc/kong # ${container_id} 换成容器ID,下同
docker cp ${container_id}:/usr/local/share/lua/5.1/kong lua_kong/kong #
3)使用目录映射,重新启动Kong-Gateway容器
先删除旧容器
docker ps -a # 得到容器ID
docker stop ${container_id}
docker rm ${container_id}
启动带目录映射的Kong-gateway新容器
docker run -d --name kong-gateway \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kongpass" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCE_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
-e "KONG_ADMIN_GUI_URL=http://localhost:8002" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
-e "KONG_LOG_LEVEL=debug" \
-v "/data/home/windealli/workspace/kong/my-plugins:/kong/plugins" \
-v "/data/home/windealli/workspace/kong/etc/kong:/etc/kong" \
-v "/data/home/windealli/workspace/kong/lua_kong/kong:/usr/local/share/lua/5.1/kong" \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
-p 8002:8002 \
-p 8445:8445 \
-p 8003:8003 \
-p 8004:8004 \
kong/kong-gateway:3.4.0.0
docker run -p 1337:1337 \
--network=kong-net \
--name=konga \
-e "NODE_ENV=development" \
pantsel/konga
安装后访问http://localhost:1337 即可访问konga
第一次访问需要创建用户。 创建完后登录。
之后需要配置与Kong-Gateway的连接,注意因为konga和kong-gateway在不同容器中,因此配置http://locolhost:8001 是无法连接的,需要使用本机的IP。
如果连接建立成功,可以看到如下控制台。
Kong网关插件由 Lua 模块组成,Kong网关提供了一套用于插件开发的**Plugin Development Kit**(PDK) ,通过PDK可以与请求/响应对象或流进行交互,实现任意逻辑。PDK 是一组 Lua 函数。
Kong插件需要以特定的文件结构组织。
一个最简单的插件必须包含下面两个文件:
[my-first-plugin]$ tree
.
├── handler.lua # 插件的核心,逻辑实现。
└── schema.lua # 定义配置结构和运行规则。
如果需要与Kong进行更深入的交互,实现更加复杂的插件。比如需要与数据库交互,需要公开管理API的endpoint。 这时就需要更加复杂的文件结构。
complete-plugin
├── api.lua # 定义管理 API 中可用的端点列表
├── daos.lua # 定义 DAO(数据库访问对象)列表
├── handler.lua # 要实现的接口。
├── migrations # 数据库迁移(例如创建表)
│ ├── init.lua
│ └── 000_base_complete_plugin.lua
└── schema.lua # 保存插件配置的架构,
本文主要介绍简单插件开发和调试流程,高级插件的开发后面有空会另开一文介绍。
本文只介绍HTTP请求的生命周期和切入点。
Kong中的**HTTP Module** 定义了如下接口用于为HTTP请求编写插件
函数名 | 语义 | 适用协议 | 描述 |
---|---|---|---|
init_worker | https://github.com/openresty/lua-nginx-module#init_worker_by_lua_block | * | Nginx worker进程启动时执行 |
certificate | https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block | https, grpcs, wss | SSL握手时执行 |
rewrite | https://github.com/openresty/lua-nginx-module#rewrite_by_lua_block | * | 用于在Kong收到客户端请求时的重写阶段执行。在此阶段Server和Consumer还没被识别,因此只能用于全局插件。 |
access | https://github.com/openresty/lua-nginx-module#access_by_lua_block | http(s), grpc(s), ws(s) | 在收到请求后,转发给upstream前执行. |
ws_handshake | https://github.com/openresty/lua-nginx-module#access_by_lua_block | ws(s) | 在每个WebSocket请求完成 WebSocket 握手前执行. |
response | https://github.com/openresty/lua-nginx-module#access_by_lua_block | http(s), grpc(s) | 从upstream收到相应后,转发给客户端前执行。 |
header_filter | https://github.com/openresty/lua-nginx-module#header_filter_by_lua_block | http(s), grpc(s) | 从upstream收到所有应答header时执行。 |
ws_client_frame | https://github.com/openresty/lua-nginx-module#content_by_lua_block | ws(s) | 从客户端收到WebSocket Message时执行。 |
ws_upstream_frame | https://github.com/openresty/lua-nginx-module#content_by_lua_block | ws(s) | 从upstream收到WebSocket Message时执行。 |
body_filter | https://github.com/openresty/lua-nginx-module#body_filter_by_lua_block | http(s), grpc(s) | 每收到upstream应答的chunk时执行. 由于响应被流式传输回客户端,因此它可能会超出缓冲区大小并被逐块流式传输。 这个函数在一个请求中可能会被多次执行。 |
log | https://github.com/openresty/lua-nginx-module#log_by_lua_block | http(s), grpc(s) | 最后一个response返回给客户端后执行。 |
ws_close | https://github.com/openresty/lua-nginx-module#log_by_lua_block | ws(s) | WebSocket关闭时执行。 |
这里开发一个不带配置的建议插件,并在HTTP请求常见切入点做一些处理逻辑。
目录结构:
my-plugins/
└── my-first-plugin
├── handler.lua
├── schema.lua
schema.lua:
//schema.lua
local typedefs = require "kong.db.schema.typedefs"
local schema = {
name = "my-first-plugin",
fields = {
{
consumer = typedefs.no_consumer,
},
{
-- 插件只在nginx http模块中生效
protocols = typedefs.protocols_http,
},
{
config = {
type = "record",
fields = {
},
},
},
},
}
return schema
handler.lua:
//handler.lua
local MyFirstHandler = {
-- 插件的优先级,决定了插件的执行顺序;数字越大,优先级越高,越早执行
PRIORITY = 1101,
-- 插件的版本号
VERSION = "0.1.0-1",
}
-- 在Nginx worker启动时执行
function MyFirstHandler:init_worker()
kong.log("data:init_worker") -- 用来确认是否加载成功的日志
end
-- 收到请求,还没进入server处理时执行,
-- 此处判断路径如果不是/sayHello和/sayBye直接返回字符串"only support /sayHello and /sayBye"
function MyFirstHandler:rewrite()
kong.log("MyFirstHandler:rewrite")
local rawPath = kong.request.get_raw_path() -- 使用PDK获取请求URL
kong.log("rewrite rawpath: " .. rawPath)
if rawPath ~= "/sayHello" and rawPath ~= "/sayBye" then
kong.log("not support rawPath: " .. rawPath)
return kong.response.exit(404, "only support /sayHello and /sayBye")
end
kong.log("rewrite finish")
end
function MyFirstHandler:access()
kong.log("access")
kong.service.request.set_header("req-key", "plugin-header-value")
end
-- 注意,即使rewrite中使用了kong.response.exit, 这里也会执行
function MyFirstHandler:header_filter()
kong.log("header_filter")
local header = kong.service.response.get_header("rsp-key")
if header ~= nil then
kong.log(header)
kong.response.set_header("rsp-key", header .. " modify by plugin")
end
end
function MyFirstHandler:body_filter()
kong.log("body_filter")
end
return MyFirstHandler
这里使用python的flask框架搭建了一个web服务,接受两个路由 sayHello
和 sayBye
s
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
from flask import Flask, request
app = Flask(__name__)
@app.route("/sayHello")
def sayHello():
req_header = request.headers.get("req-key")
print(req_header)
name = request.args.get("name")
return "Hello " + name, 200, [('rsp-key', 'rsp-head-by-server')]
@app.route("/sayBye")
def sayBye():
req_header = request.headers.get("req-key")
print(req_header)
name = request.args.get("name")
return "Bye " + name, 200, [('rsp-key', 'rsp-head-by-server')]
app.run(host="0.0.0.0", port=5000, debug=True)
并未这个服务配置 upstream、server、router
确保在未配置插件是,可以正常通过 http://{kong网关IP}:8000/sayHello
访问
新建/修改 /etc/kong/kong.conf
[windealli@VM-52-29-tencentos kong]$ cat etc/kong/kong.conf
plugins = bundled,my-first-plugin # 指定了要加载的插件
lua_package_path = /kong/plugins/?.lua;; # 指定了自定义插件的目录
[windealli@VM-52-29-tencentos kong]$
修改 lua_kong/kong/constants.lua
如果不修改constants.lua,会出现konga的info页面可以看到插件my-first-plugin,但是在添加插件页面却找不到。
# 在plugins中添加我们的插件
local plugins = {
"my-first-plugin",
...
}
直接将插件配置到全局
1) 验证rewrite
查看服务端日志打印了插件添加的header
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。