使用golang进行kong限流插件开发

1 篇文章 0 订阅

个人博客原文地址:http://www.lampnick.com/php/950

rate limiting限流仓库地址

https://github.com/lampnick/kong-rate-limiting-golang

前置条件

  1. 获取编译基础镜像(Go插件是使用-buildmode=plugin标志编译的,该标志允许插件服务器动态加载它们。)
docker pull kong/go-plugin-tool:latest-centos-7
  1. go-pluginserver可执行文件路径
默认路径:/usr/local/bin
自定义路径有两种方式:
1.环境变量增加go_pluginserver_exe
2.kong配置文件设置go_pluginserver_exe
  1. go_plugins_dir go插件目录
kong配置文件中设置go插件的路径,默认值为off,表明禁用go插件支持
  1. 将你编译好的go插件复制到go_plugins_dir
  2. 和lua插件开发一样,需要将你的go插件名称配置到plugins属性中,这样kong才会加载你的插件
  3. 允许你的go插件使用常规模式,可通过Admin API或者声明式配置文件管理

构建Go Plugin Server和你的go插件

go插件服务器( Go Plugin Server )是一个典型的go应用,可使用如下流程

  1. 初始化go.mod
Nick-Mac:kong-go-plugin nick$ go mod init kong-go-plugin
go: creating new go.mod: module kong-go-plugin

Nick-Mac:kong-go-plugin nick$ cat go.mod 
module kong-go-plugin

go 1.13
  1. 获取Go Plugin Server
Nick-Mac:kong-go-plugin nick$ go get -d -v github.com/Kong/go-pluginserver
go: downloading github.com/Kong/go-pluginserver v0.5.1
go: extracting github.com/Kong/go-pluginserver v0.5.1
go: downloading github.com/Kong/go-pdk v0.5.0
go: extracting github.com/Kong/go-pdk v0.5.0

Nick-Mac:kong-go-plugin nick$ cat go.mod 
module kong-go-plugin

go 1.13

require github.com/Kong/go-pluginserver v0.5.1 // indirect
  1. 构建Go Plugin Server,会在当前目录下生成go-pluginserver可执行文件
Nick-Mac:kong-go-plugin nick$ go build github.com/Kong/go-pluginserver
go: finding github.com/Kong/go-pdk v0.5.0

Nick-Mac:kong-go-plugin nick$ ll
total 29952
drwxr-xr-x  5 nick  staff   160B  9  7 11:23 .
drwxr-xr-x  9 nick  staff   288B  9  7 11:14 ..
-rwxr-xr-x  1 nick  staff    15M  9  7 11:23 go-pluginserver
-rw-r--r--  1 nick  staff    91B  9  7 11:20 go.mod
-rw-r--r--  1 nick  staff   1.2K  9  7 11:20 go.sum
  1. 接下来将构建一个go插件,克隆下面的示例仓库,使用使用-buildmode plugin 进行构建,会在当前目录生成一个.so文件
1.克隆
Nick-Mac:kong-go-plugin-example nick$ git clone https://github.com/Kong/go-plugins.git
Cloning into 'go-plugins'...
remote: Enumerating objects: 35, done.
remote: Counting objects: 100% (35/35), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 35 (delta 13), reused 21 (delta 5), pack-reused 0
Unpacking objects: 100% (35/35), done.
2.切到示例目录
Nick-Mac:kong-go-plugin-example nick$ cd go-plugins/
3.查看有哪些文件
Nick-Mac:go-plugins nick$ ll
total 48
drwxr-xr-x   9 nick  staff   288B  9  7 11:27 .
drwxr-xr-x   3 nick  staff    96B  9  7 11:27 ..
drwxr-xr-x  12 nick  staff   384B  9  7 11:27 .git
-rw-r--r--   1 nick  staff   166B  9  7 11:27 Makefile
-rw-r--r--   1 nick  staff   367B  9  7 11:27 README.md
-rw-r--r--   1 nick  staff   536B  9  7 11:27 go-hello.go
-rw-r--r--   1 nick  staff   1.2K  9  7 11:27 go-log.go
-rw-r--r--   1 nick  staff    82B  9  7 11:27 go.mod
-rw-r--r--   1 nick  staff   1.1K  9  7 11:27 go.sum
4.开始构建
Nick-Mac:go-plugins nick$ go build -buildmode plugin go-hello.go
5.查看文件多了一个go-hello.so
Nick-Mac:go-plugins nick$ ll
total 4960
drwxr-xr-x  10 nick  staff   320B  9  7 11:30 .
drwxr-xr-x   3 nick  staff    96B  9  7 11:27 ..
drwxr-xr-x  12 nick  staff   384B  9  7 11:27 .git
-rw-r--r--   1 nick  staff   166B  9  7 11:27 Makefile
-rw-r--r--   1 nick  staff   367B  9  7 11:27 README.md
-rw-r--r--   1 nick  staff   536B  9  7 11:27 go-hello.go
-rw-r--r--   1 nick  staff   2.4M  9  7 11:30 go-hello.so
-rw-r--r--   1 nick  staff   1.2K  9  7 11:27 go-log.go
-rw-r--r--   1 nick  staff    82B  9  7 11:27 go.mod
-rw-r--r--   1 nick  staff   1.1K  9  7 11:27 go.sum

环境一致性约束

  • 所有公共库需要版本一致
    • Kong/go-pdk
    • 所有go库文件 (像fmt, rpc, reflect等)
    • 操作系统库文件, 像libpthread, libc, ld-xxxx等
  • go编译器版本完全一致
  • go环境变量,像 G O R O O T 、 GOROOT、 GOROOTGOPATH等一致

典型的由于环境不一致出现的错误信息如下

failed to open plugin kong: plugin.Open("/path/go-plugins/go-hello"): plugin was built with a different version of package github.com/Kong/go-pdk/bridge

开发go插件

go插件开发流程
  1. 定义一个结构体类型保存配置文件
用lua写的插件通过schema来指定怎样读取和验证来自数据库和Admin API中的配置数据。由于GO是静态类型语言,都需要用配置结构体定义
type MyConfig struct {
    Path   string //这里配置的会在konga添加插件时显示出来
    Reopen bool
}
公有属性将会被配置数据填充,如果希望在数据库中使用不同的名称,可以使用encoding/json加tag的方式
type MyConfig struct {
    Path   string `json:my_file_path`
    Reopen bool   `json:reopen`
}
  1. 使用New()创建一个实例
你的go插件必须定义一个名叫New的函数来创建这个类型的实例并返回一个interface{}类型
func New() interface{} {
    return &MyConfig{}
}
  1. 添加处理阶段方法
你可以在请求的生命周期的各个阶段实现自定义的逻辑。如在"access"阶段,定义一个名为Access的方法
func (conf *MyConfig) Access (kong *pdk.PDK) {
  ...
}
你可以实现自定义逻辑的阶段方法有如下几种
Certificate
Rewrite
Access
Preread
Log
  1. 编译go插件
  2. 将生成的.so文件放到go_plugins_dir定义的目录中
部署
  • kong配置文件修改
plugins = bundled,nick-rate-limiting
go_plugins_dir = /etc/kong/plugins
go_pluginserver_exe = /usr/local/bin/go-pluginserver
  • 构建go-pluginserver
在go-pluginserver中执行go build github.com/Kong/go-pluginserver
会生成 go-pluginserver文件,复制到/usr/local/bin目录
  • 编译go插件
go build -buildmode plugin  custom-rate-limiting.go && cp custom-rate-limiting.so
  • 将生成的.so文件放到go_plugins_dir定义的目录中
cp custom-rate-limiting.so ../plugins/
  • 重启kong
kong prepare && kong reload
- 在konga中配置插件
- 测试请求是否正常,规则是否生效

问题

  • kong start -c /etc/kong/kong.conf --vv时报错sh: /usr/local/bin/go-pluginserver: cannot execute binary file
由于在mac上交叉编译
解决方法:
直接在kong机器上编译go-pluginserver
  • 插件报错
2020/09/07 15:35:52 failed to open plugin go-hello: plugin.Open("/etc/kong/plugins/go-hello.so"): /etc/kong/plugins/go-hello.so: invalid ELF header
nginx: [error] init_by_lua error: /usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:481: bad argument #1 to 'ipairs' (table expected, got nil)
stack traceback:
	[C]: in function 'ipairs'
	/usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:481: in function 'get_plugin'
	/usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:515: in function 'load_plugin'
	/usr/local/share/lua/5.1/kong/db/dao/plugins.lua:151: in function 'load_plugin_handler'
	/usr/local/share/lua/5.1/kong/db/dao/plugins.lua:227: in function 'load_plugin'
	/usr/local/share/lua/5.1/kong/db/dao/plugins.lua:275: in function 'load_plugin_schemas'
	/usr/local/share/lua/5.1/kong/init.lua:484: in function 'init'
	init_by_lua:3: in main chunk

stack traceback:
	[C]: in function 'error'
	/usr/local/share/lua/5.1/kong/cmd/start.lua:75: in function 'cmd_exec'
	/usr/local/share/lua/5.1/kong/cmd/init.lua:88: in function </usr/local/share/lua/5.1/kong/cmd/init.lua:88>
	[C]: in function 'xpcall'
	/usr/local/share/lua/5.1/kong/cmd/init.lua:88: in function </usr/local/share/lua/5.1/kong/cmd/init.lua:45>
	/usr/local/bin/kong:9: in function 'file_gen'
	init_worker_by_lua:49: in function <init_worker_by_lua:47>
	[C]: in function 'xpcall'
	init_worker_by_lua:56: in function <init_worker_by_lua:54>
解决方案:
直接在kong机器上编译
  • 访问报错:message: “An unexpected error occurred”
查看kong管理地址中错误日志路径
http://10.5.24.224:8001/
"nginx_err_logs": "/usr/local/kong/logs/error.log",
tail -f /usr/local/kong/logs/error.log 日志报错如下
2020/09/07 16:21:22 [notice] 122415#0: *29 [kong] go.lua:97 go-pluginserver terminated: exit 0, context: ngx.timer
2020/09/07 16:21:22 [notice] 122415#0: *29 [kong] go.lua:86 Starting go-pluginserver, context: ngx.timer
2020/09/07 16:21:22 [crit] 122415#0: *29381 connect() to unix:/usr/local/kong/go_pluginserver.sock failed (2: No such file or directory), client: 10.5.216.251, server: kong, request: "GET /api/index.php?r=site/login-data HTTP/1.1", host: "hd.myscrm.cn:8000"
2020/09/07 16:21:22 [error] 122415#0: *29381 [kong] go.lua:140 [go-hello] trying to connect: no such file or directory, client: 10.5.216.251, server: kong, request: "GET /api/index.php?r=site/login-data HTTP/1.1", host: "hd.myscrm.cn:8000"
2020/09/07 16:21:22 [error] 122415#0: *29381 [kong] go.lua:429 [go-hello] starting instance: no such file or directory, client: 10.5.216.251, server: kong, request: "GET /api/index.php?r=site/login-data HTTP/1.1", host: "hd.myscrm.cn:8000"
2020/09/07 16:21:22 [error] 122415#0: *29381 lua coroutine: runtime error: /usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:432: no such file or directory
stack traceback:
coroutine 0:
	[C]: in function 'error'
	/usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:432: in function 'get_instance'
	/usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:498: in function </usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:497>
coroutine 1:
	[C]: in function 'resume'
	coroutine.wrap:21: in function <coroutine.wrap:21>
	/usr/local/share/lua/5.1/kong/init.lua:757: in function 'access'
	access_by_lua(nginx-kong.conf:87):2: in main chunk, client: 10.5.216.251, server: kong, request: "GET /api/index.php?r=site/login-data HTTP/1.1", host: "hd.myscrm.cn:8000"
2020/09/07 16:21:22 [error] 122415#0: *29381 [kong] init.lua:759 [go-hello] /usr/local/share/lua/5.1/kong/db/dao/plugins/go.lua:432: no such file or directory, client: 10.5.216.251, server: kong, request: "GET /api/index.php?r=site/login-data HTTP/1.1", host: "hd.myscrm.cn:8000"

相关截图

  • konga json配置

    [{
        "type": "header,query,body",
        "key": "orderId",
        "value": "orderId1,orderId2,orderId3"
    }, {
        "type": "query",
        "key": "username",
        "value": "nick,jack,star"
    }]
    
  • konga 配置
    image

  • postman 显示header
    image

  • siege压测效果图
    image

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Golang是一种开源的编程语言,它具有简洁、高效和并发性强的特点。在Golang中,可以使用一些库来实现IP地址限流的功能。 一种常见的实现方式是使用令牌桶算法。令牌桶算法是一种基于令牌的限流算法,它通过维护一个固定容量的令牌桶来控制请求的速率。每当有请求到达时,算法会尝试从令牌桶中获取一个令牌,如果获取成功,则允许请求通过;如果获取失败,则拒绝请求。 在Golang中,可以使用一些第三方库来实现IP地址限流,例如"golang.org/x/time/rate"。这个库提供了一个RateLimiter类型,可以用于实现令牌桶算法。 下面是一个简单的示例代码,演示了如何使用"golang.org/x/time/rate"库来实现IP地址限流: ```go package main import ( "fmt" "net" "net/http" "time" "golang.org/x/time/rate" ) func main() { // 创建一个RateLimiter,设置每秒允许的请求数为10 limiter := rate.NewLimiter(10, 1) // 创建一个HTTP服务器 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // 获取请求的IP地址 ip, _, _ := net.SplitHostPort(r.RemoteAddr) // 判断IP地址是否超过限流速率 if limiter.Allow() { // 允许请求通过 fmt.Fprintf(w, "Hello, World!") } else { // 拒绝请求 http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) } }) // 启动HTTP服务器 http.ListenAndServe(":8080", nil) } ``` 在上面的代码中,我们创建了一个RateLimiter,设置每秒允许的请求数为10。然后,在处理HTTP请求时,我们获取请求的IP地址,并使用RateLimiter判断是否允许请求通过。如果超过限流速率,则返回HTTP状态码429。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值