目录

apisix 开发自定义插件

分享如何在 docker 环境部署 apisix 和如何开发 lua 和 go 语言的插件以及如何使用这些自定义插件的过程,希望能帮助到你。

如何部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 1. 下载官方 docker compose 项目
$ git clone https://github.com/apache/apisix-docker.git
$ cd apisix-docker/example
# 2. run docker compose
$ docker-compose -p docker-apisix up -d
# check docker ps
$  docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                 NAMES
629eb9d85656   627d00c649fc   "sh -c '/usr/bin/api…"   24 seconds ago   Up 20 seconds   0.0.0.0:9080->9080/tcp, :::9080->9080/tcp, 0.0.0.0:9091-9092->9091-9092/tcp, :::9091-9092->9091-9092/tcp, 0.0.0.0:9443->9443/tcp, :::9443->9443/tcp   docker-apisix_apisix_1
c3cbca636e65   13afb861111c   "/run.sh"                24 seconds ago   Up 22 seconds   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp                                                                                                             docker-apisix_grafana_1
4a5cb9ad6239   5b0292a5e821   "/usr/local/apisix-d…"   24 seconds ago   Up 22 seconds   0.0.0.0:9000->9000/tcp, :::9000->9000/tcp                                                                                                             docker-apisix_apisix-dashboard_1
6430826c4095   8c7e00e786b8   "/opt/bitnami/script…"   24 seconds ago   Up 21 seconds   0.0.0.0:2379->2379/tcp, :::2379->2379/tcp, 2380/tcp                                                                                                   docker-apisix_etcd_1
c086d6e4fbd9   a618f5685492   "/bin/prometheus --c…"   24 seconds ago   Up 22 seconds   0.0.0.0:9090->9090/tcp, :::9090->9090/tcp                                                                                                             docker-apisix_prometheus_1
1e6ea10c008f   7d0cdcc60a96   "/docker-entrypoint.…"   24 seconds ago   Up 21 seconds   0.0.0.0:9082->80/tcp, :::9082->80/tcp                                                                                                                 docker-apisix_web2_1
d4891bd0744e   7d0cdcc60a96   "/docker-entrypoint.…"   24 seconds ago   Up 22 seconds   0.0.0.0:9081->80/tcp, :::9081->80/tcp                                                                                                                 docker-apisix_web1_1

部署完成,可以通过 localhost:9000 访问 dashboard。

配置

默认配置文件在 apisix-docker/example/apisix_conf 目录下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apisix:
  node_listen: 9080              # APISIX listening port
  enable_ipv6: false

  allow_admin:                  # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow
    - 0.0.0.0/0              # We need to restrict ip access rules for security. 0.0.0.0/0 is for test.

  admin_key:
    - name: "admin"
      key: edd1c9f034335f136f87ad84b625c8f1 # 这个值需要改的否则有安全隐患
      role: admin                 # admin: manage all configuration data
                                  # viewer: only can view configuration data
    - name: "viewer"
      key: 4054f7cf07e344346cd3f287985e76a2
      role: viewer
  
  enable_control: true
  control:
    ip: "0.0.0.0"
    port: 9092

etcd:
  host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
    - "http://etcd:2379"     # multiple etcd address
  prefix: "/apisix"               # apisix configurations prefix
  timeout: 30                     # 30 seconds

plugin_attr:
  prometheus:
    export_addr:
      ip: "0.0.0.0"
      port: 9091

插件

lua

官方开发教程

示例插件:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
local ngx = ngx
local core = require("apisix.core")
local plugin = require("apisix.plugin")
local upstream = require("apisix.upstream")

-- 定义配置,即使用插件时 配置一些自定义字段,如鉴权的 key,需要校验的 header 之类的
local schema = {
	type = "object",
	properties = {
		value = {type = "array", minItems = 1}
	},
	required = {"value"}
}

-- 这个需要了解一下干什么的
local metadata_schema = {
	type = "object",
	properties = {
		ikey = {type = "number", minimum = 0},
		skey = {type = "string"}
	},
	required = {"ikey", "skey"}
}

local plugin_name = "block_by_lua"

local _M = {
	version = 0.1,
	priority = 0,
	name = plugin_name,
	schema = schema,
	metadata_schema = metadata_schema
}

function _M.check_schema(conf, schema_type)
	if schema_type == core.schema.TYPE_METADATA then
		return core.schema.check(metadata_schema, conf)
	end
	return core.schema.check(schema, conf)
end

function _M.init()
	-- call this function when plugin is loaded
	core.log.info(plugin_name, "loaded!")
end

function _M.destroy()
	-- call this function when plugin is unloaded
end

-- uri 重写阶段 如果不需要就不用定义这个方法
--[[
function _M.rewrite(conf, ctx)
	core.log.warn("plugin rewrite phase, conf: ", core.json.encode(conf))
	core.log.warn("conf_type: ", ctx.conf_type)
	core.log.warn("conf_id: ", ctx.conf_id)
	core.log.warn("conf_version: ", ctx.conf_version)
end
--]]

--命中服务 & 调服务前
function _M.access(conf, ctx)
	core.log.warn("plugin access phase, conf: ", core.json.encode(conf))
	core.log.warn("plugin access phase, ctx: ", core.json.encode(ctx, true))
	-- return 200, {message = "hit example plugin"}
	-- 1. extract from header
	local pass = core.request.header(ctx, "X-Block-Pass")
	if not pass then
		core.response.set_header("X-Block-Flag", "Block By Lua Ext")
        -- 返回 http 状态码 则这次请求截止到当前插件,不会往下走
		return 403, {message = "Missing pass value in header"}
	end

	for _, val in pairs(conf.value) do
		if val == pass then
            -- return 空表示通过 
			return
		end
	end

	core.response.set_header("X-Block-Flag", "Block By Lua Ext")
	return 403, {message = "Invalid pass value in header."}
end

local function hello()
	local args = ngx.req.get_uri_args()
	if args["json"] then
		return 200, {msg = "world"}
	else
		return 200, "world\n"
	end
end

function _M.control_api()
	return {
		{
            -- 注册 controller api 用于探测插件是否插入成功,也可以用于内部一些返回 token 之类的用处
			methods = {"GET"},
			uris = {"/v1/plugin/example-plugin/hello"},
			handler = hello
		}
	}
end

return _M

如何安装:

  1. 创建目录
1
2
3
4
5
6
7
├── example
│   └── apisix
│       ├── plugins
│       │   └── 3rd-party.lua
│       └── stream
│           └── plugins
│               └── 3rd-party.lua
  1. 配置文件(config.yaml)添加插件目录
1
2
3
apisix:
    ...
    extra_lua_path: "/path/to/example/?.lua"
  1. 开启插件(config.yaml)
1
2
3
4
5
apisix:
...
plugins: # 从 config-default.yaml 文件复制出来,然后加上自己的插件
  ...
  - your-plugin     

Q:如何在dashboard 看到自己的插件?

A: 目前自定义插件不支持自动同步到 dashboard,需要手动添加,步骤如下:

  1. 在 apisix 机器上执行如下命令获取最新 json scheme:

    1
    
    $ curl 127.0.0.1:9092/v1/schema > scheme.json
    
2.  将 `scheme.json` 复制到 dashboard 机器上 `conf` 目录下与原有的文件替换,重启 dashboard 服务。

go

官方开发教程

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package plugins

import (
	"encoding/json"
	"net/http"

	pkgHTTP "github.com/apache/apisix-go-plugin-runner/pkg/http"
	"github.com/apache/apisix-go-plugin-runner/pkg/log"
	"github.com/apache/apisix-go-plugin-runner/pkg/plugin"
)

// 初始化
func init() {
    // 注册插件
	err := plugin.RegisterPlugin(&BlockReq{})
	if err != nil {
		log.Fatalf("failed to register plugin block-req: %s", err)
	}
}

// LimitReq is a demo for a real world plugin
type BlockReq struct {
}

// 与 lua 插件内 scheme 一样
type BlockReqConf struct {
	Key   string   `json:"key"`
	Value []string `json:"value"`
}

func (p *BlockReq) Name() string {
	return "blcok-req"
}

// ParseConf is called when the configuration is changed. And its output is unique per route.
func (p *BlockReq) ParseConf(in []byte) (interface{}, error) {
	conf := BlockReqConf{}
	err := json.Unmarshal(in, &conf)

	return conf, err
}

// Filter is called when a request hits the route
func (p *BlockReq) Filter(conf interface{}, w http.ResponseWriter, r pkgHTTP.Request) {
	b := conf.(BlockReqConf)
	val := r.Header().Get(b.Key)
	for _, v := range b.Value {
		if val == v {
			r.Header().Set("X-Block-Value", v)
			return
		}
	}
	// block request
    // 只要写 response 的 header 或body,请求将停在这里不会往下传递,直接响应回去
	w.Header().Add("X-Block-Req", "Block by Go ext.")
	w.WriteHeader(http.StatusForbidden)
}

编译部署

  1. 用官方提供的 Makefile 进行 build(注意编译环境和apisix 运行的环境,指定对应的 GOOS,GOARCH)
  2. 将编译好的二进制文件打包到 apisix 的容器内
  3. 修改配置文件
1
2
ext-plugin:
  cmd: ["/path/to/apisix-go-plugin-runner/go-runner", "run"]

注意:一个 go-runner 内可以注册多个插件,所以不需要拥有多个 go-runner ,所有的插件在一个项目里 然后统一编译部署即可

使用

lua 插件都运行在各自的 runner 内,所以使用的时候不能直接在 dashboard 中使用自定义的插件(lua 的自定义插件是可以的),需要在 ext-plugin-pre-req, ext-plugin-post-req 两个插件内配置使用,这两插件只有运行时间不一样,一个在所有插件之前 一个在所有插件之后。使用时配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
"plugins": {
    "ext-plugin-pre-req": {
      "conf": [
        {
          "name": "blcok-req", // 注册的 go 插件名字
          "value": "{\"key\":\"pass\", \"value\":[\"word\",\"port\"]}" // 该插件的 conf,这里需要将 json 进行转义
        }
      ],
      "disable": false
    }
  }

wasm

apisix 开始支持 wasm 插件,但是官方给出的示例和文档还不够完善,这块还在研究中,之后会补齐。