前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你实现一个Kong网关插件

手把手教你实现一个Kong网关插件

原创
作者头像
windealli
发布2023-10-12 19:28:10
1.8K0
发布2023-10-12 19:28:10
举报
文章被收录于专栏:windealli

前言

Kong Gateway 是一个轻量、快速、灵活的基于Nginx开发云原生 API 网关。在云原生领域,Kong Gateway 越来受欢迎。

Kong提供了插件化能力,在对后台业务服务代码无侵入的条件下,可以在接入层方便地引入认证鉴权、安全防护、流量控制都能功能。这也是其受欢迎的原因之一。

Kong Gateway 官方已经提供了一系列常用的插件,但是业务开发中有时需要定制自己的插件。本文将介绍如何编写 Kong 的自定义插件,以及如何将插件集成到 Kong 网关中

搭建开发环境

1. 自定义Docker网络

代码语言:javascript
复制
docker network create kong-net

2. 启动 PostgreSQL 数据库容器

代码语言:javascript
复制
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

3. 准备数据库

代码语言:javascript
复制
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

4. 启动Kong网关容器

网关容器Kong-Gateway的启动包含三个步骤

  • 第一步: 不容目录映射启动容器
  • 第二步: 拷贝关键配置目录
  • 第三步: 删除就容器,使用目录映射启动新容器。

第三步是为了方便后面配置我们自定义的插件,第一、二步是服务与第三步的,否则容器无法启动。

1) 不带目录映射启动容器

代码语言:javascript
复制
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 把容器中需要修改的配置文件拷贝到宿主机

代码语言:javascript
复制
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容器

先删除旧容器

代码语言:javascript
复制
docker ps -a # 得到容器ID
docker stop ${container_id}
docker rm ${container_id}

启动带目录映射的Kong-gateway新容器

代码语言:javascript
复制
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

5. 安装UI管理工具Konga

代码语言:javascript
复制
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插件需要以特定的文件结构组织。

一个最简单的插件必须包含下面两个文件:

  • handler.lua: 插件的核心。它是一个要实现的接口,其中每个函数将在请求/连接生命周期中的所需时刻运行。
  • schema.lua: 定义插件的运行规则,定义插件所需要的配置结构(以便用户在使用时配置)
代码语言:javascript
复制
[my-first-plugin]$ tree
.
├── handler.lua  # 插件的核心,逻辑实现。
└── schema.lua   # 定义配置结构和运行规则。

如果需要与Kong进行更深入的交互,实现更加复杂的插件。比如需要与数据库交互,需要公开管理API的endpoint。 这时就需要更加复杂的文件结构。

代码语言:javascript
复制
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请求常见切入点做一些处理逻辑。

目录结构:

代码语言:javascript
复制
my-plugins/
└── my-first-plugin
    ├── handler.lua
    ├── schema.lua

schema.lua:

代码语言:javascript
复制
//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:

代码语言:javascript
复制
//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

测试验证

测试准备:upstream服务Demo

这里使用python的flask框架搭建了一个web服务,接受两个路由 sayHellosayByes

代码语言:javascript
复制
#!/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

代码语言:javascript
复制
[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,但是在添加插件页面却找不到。

代码语言:javascript
复制
# 在plugins中添加我们的插件
local plugins = {
  "my-first-plugin",
 ...
}

使用插件

直接将插件配置到全局

验证

1) 验证rewrite

  1. 验证完整链路

查看服务端日志打印了插件添加的header

我的公众号

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 搭建开发环境
    • 1. 自定义Docker网络
      • 2. 启动 PostgreSQL 数据库容器
        • 3. 准备数据库
          • 4. 启动Kong网关容器
            • 5. 安装UI管理工具Konga
            • 编写插件
              • 插件的文件结构
                • 生命周期和切入点
                  • 简单插件的编码示例
                  • 测试验证
                    • 测试准备:upstream服务Demo
                      • 安装插件
                        • 使用插件
                          • 验证
                          • 我的公众号
                          相关产品与服务
                          微服务引擎 TSE
                          微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档