apisix硬核介绍

Apisix

架构

实现原理

控制面: adminAPI 和 ETCD

apisix使用etcd实现持久化存储。由adminAPI 写入etcd:

vbnet 复制代码
# 通过和adminAPI 的管理端口9180交互,实现apisix的配置,最终这些配置信息会存储到etcd中。
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
  "id": "getting-started-ip",
  "uri": "/ip",
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "httpbin.org:80": 1
    }
  }
}'

etcdctl 命令查看当前存储的配置信息

sql 复制代码
etcdctl get /apisix --prefix --keys-only
 
所有路由
etcdctl get /apisix/routes --prefix
所有上游
etcdctl get /apisix/upstreams --prefix
所有服务
etcdctl get /apisix/services --prefix
所有消费者
etcdctl get /apisix/consumers --prefix
插件配置
etcdctl get /apisix/plugins --prefix
全局插件
etcdctl get /apisix/global_rules --prefix
 
查看具体路由:
etcdctl get /apisix/routes/getting-started-ip         
/apisix/routes/getting-started-ip
{"id":"getting-started-ip","create_time":1755680835,"update_time":1755681281,"uri":"/ip","name":"test ip","plugins":{"key-auth":{"_meta":{"disable":false},"key":"abc"}},"upstream":{"nodes":{"httpbin.org:80":1},"timeout":{"connect":6,"send":6,"read":6},"type":"roundrobin","scheme":"http","pass_host":"pass","keepalive_pool":{"idle_timeout":60,"requests":1000,"size":320}},"status":1}
 

数据面: openresty

程序入口:

apisix/apisix/init.lua at master · apache/apisix · GitHub

nginx.conf 配置代码片段,查看nginx master和nginx worker 是如何启动的,lua脚本插件是如何加载的:

ini 复制代码
  # lua apisix的配置,包括nginx master的启动,lua插件的加载。
  init_by_lua_block {
        require "resty.core"
        apisix = require("apisix")
 
        local dns_resolver = { "127.0.0.11", }
        local args = {
            dns_resolver = dns_resolver,
        }
        apisix.http_init(args)
 
        -- set apisix_lua_home into constants module
        -- it may be used by plugins to determine the work path of apisix
        local constants = require("apisix.constants")
        constants.apisix_lua_home = "/usr/local/apisix"
    }
    
    #nginx worker 的启动函数入口
    init_worker_by_lua_block {
        apisix.http_init_worker()
    }
 
    # nginx worker 函数退出入口
    exit_worker_by_lua_block {
        apisix.http_exit_worker()
    }

init.lua 代码片段展示 nginx master进程和 nginx worker进程的启动过程。

lua 复制代码
# # APISIX 的 Nginx master 进程启动阶段
function _M.http_init(args)
    core.resolver.init_resolver(args)
    core.id.init()
    core.env.init()
 
    local process = require("ngx.process")
    local ok, err = process.enable_privileged_agent()
    if not ok then
        core.log.error("failed to enable privileged_agent: ", err)
    end
 
    if core.config.init then
        local ok, err = core.config.init()
        if not ok then
            core.log.error("failed to load the configuration: ", err)
        end
    end
 
    xrpc.init()
end
 
# APISIX 的 Nginx worker 进程启动阶段
function _M.http_init_worker()
    local seed, err = core.utils.get_seed_from_urandom()
    if not seed then
        core.log.warn('failed to get seed from urandom: ', err)
        seed = ngx_now() * 1000 + ngx.worker.pid()
    end
    math.randomseed(seed)
    -- for testing only
    core.log.info("random test in [1, 10000]: ", math.random(1, 10000))
 
    require("apisix.events").init_worker()
 
    local discovery = require("apisix.discovery.init").discovery
    if discovery and discovery.init_worker then
        discovery.init_worker()
    end
    require("apisix.balancer").init_worker()
    load_balancer = require("apisix.balancer")
    require("apisix.admin.init").init_worker()
 
    require("apisix.timers").init_worker()
 
    require("apisix.debug").init_worker()
 
    if core.config.init_worker then
        local ok, err = core.config.init_worker()
        if not ok then
            core.log.error("failed to init worker process of ", core.config.type,
                           " config center, err: ", err)
        end
    end
 
    plugin.init_worker()
    router.http_init_worker()
    require("apisix.http.service").init_worker()
    plugin_config.init_worker()
    require("apisix.consumer").init_worker()
    consumer_group.init_worker()
    apisix_secret.init_worker()
 
    apisix_global_rules.init_worker()
 
    apisix_upstream.init_worker()
    require("apisix.plugins.ext-plugin.init").init_worker()
 
    control_api_router.init_worker()
    local_conf = core.config.local_conf()
 
    if local_conf.apisix and local_conf.apisix.enable_server_tokens == false then
        ver_header = "APISIX"
    end
 
    -- To ensure that all workers related to Prometheus metrics are initialized,
    -- we need to put the initialization of the Prometheus plugin here.
    plugin.init_prometheus()
end

插件实现

默认内置插件路径:/usr/local/apisix/apisix/plugins,里面存储了默认自带的插件文件。这些插件可以在apisix config.yaml中选择指定,默认是加载全部。

插件执行阶段 ,APISIX 采用了 OpenResty 的执行阶段概念,允许插件在不同的处理时机介入

执行阶段(Phases)

● rewrite: 请求处理早期阶段,常用于修改上游 URI 或参数,或进行身份验证(如你提供的 key-auth 插件)。

● access: 请求转发到上游之前的最主要阶段,常用于权限校验、限流、流量控制等。

● header_filter: 处理来自上游服务的响应头之后,可用于修改响应头。

● body_filter: 处理来自上游服务的响应体之后,可用于修改响应体。

● log: 请求结束后,用于记录日志、发送指标等收尾工作。

● balancer: 用于实现自定义的负载均衡算法

插件内容模版举例:

以下举例key-auth.lua 插件的实现代码:其核心功能是验证客户端请求是否携带了一个有效的 API Key。如果Key有效,则请求被放行,并将对应的消费者(Consumer)信息附加到请求上下文中;如果无效,则请求被拒绝并返回 401 Unauthorized

ini 复制代码
local ngx = ngx
local core = require("apisix.core")
local plugin = require("apisix.plugin")
local upstream = require("apisix.upstream")
 
# 插件schema定义
local schema = {
    type = "object",
    properties = {
        i = {type = "number", minimum = 0},
        s = {type = "string"},
        t = {type = "array", minItems = 1},
        ip = {type = "string"},
        port = {type = "integer"},
    },
    required = {"i"},
}
 
local metadata_schema = {
    type = "object",
    properties = {
        ikey = {type = "number", minimum = 0},
        skey = {type = "string"},
    },
    required = {"ikey", "skey"},
}
 
local plugin_name = "example-plugin"
 
# 插件注册
local _M = {
    version = 0.1,
    priority = 0,
    name = plugin_name,
    schema = schema,
    metadata_schema = metadata_schema,
}
 
# 检查插件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
    local attr = plugin.plugin_attr(plugin_name)
    if attr then
        core.log.info(plugin_name, " get plugin attr val: ", attr.val)
    end
end
 
 
function _M.destroy()
    -- call this function when plugin is unloaded
end
 
# request进入apisix阶段的处理,修改请求或者是重定向,早期的认证等。
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
 
# rquest 请求转发到上游之前的最主要阶段,一般是权限校验、限流、流量控制等
function _M.access(conf, ctx)
    core.log.warn("plugin access phase, conf: ", core.json.encode(conf))
    -- return 200, {message = "hit example plugin"}
 
    if not conf.ip then
        return
    end
 
    local up_conf = {
        type = "roundrobin",
        nodes = {
            {host = conf.ip, port = conf.port, weight = 1}
        }
    }
 
    local ok, err = upstream.check_schema(up_conf)
    if not ok then
        return 500, err
    end
 
    local matched_route = ctx.matched_route
    upstream.set(ctx, up_conf.type .. "#route_" .. matched_route.value.id,
                 ctx.conf_version, up_conf)
    return
end
# 处理来自上游服务的response,可用于修改响应头。
function _M.header_filter(conf, ctx)
    core.log.warn("plugin header_filter phase, conf: ", core.json.encode(conf))
end
 
# 处理来自上游服务的response,可用于修改response body。
function _M.body_filter(conf, ctx)
    core.log.warn("plugin body_filter phase, eof: ", ngx.arg[2],
                  ", conf: ", core.json.encode(conf))
end
 
 
function _M.delayed_body_filter(conf, ctx)
    core.log.warn("plugin delayed_body_filter phase, eof: ", ngx.arg[2],
                  ", conf: ", core.json.encode(conf))
end
 
#示例作用:打印日志,这是进行最终审计、统计或发送数据到外部系统。
function _M.log(conf, ctx)
    core.log.warn("plugin log phase, conf: ", core.json.encode(conf))
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
 
# 配置插件的管理接口,例如该插件可以通过GET "/v1/plugin/example-plugin/hello" 访问
function _M.control_api()
    return {
        {
            methods = {"GET"},
            uris = {"/v1/plugin/example-plugin/hello"},
            handler = hello,
        }
    }
end
 
return _M

核心概念

upstream

定义反向代理的后端服务目标集群。例如:创建一个转发到"httpbin.org:80"的后端服务。

vbnet 复制代码
curl "http://127.0.0.1:9180/apisix/admin/upstreams/1" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "type": "roundrobin",
  "nodes": {
    "httpbin.org:80": 1
  }
}'
 
{"key":"/apisix/upstreams/1","value":{"scheme":"http","id":"1","hash_on":"vars","nodes":{"httpbin.org:80":1},"type":"roundrobin","pass_host":"pass","create_time":1755591817,"update_time":1755591817}}

route

定义请求的匹配规则,并将其转发到指定的上游服务。例如:创建一个路由,访问url是 "/http-server/*" , 使用后端服务upstream 是1。

vbnet 复制代码
curl "http://127.0.0.1:9180/apisix/admin/routes/1" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "methods": ["GET"],
  "uri": "/http-server/*",
  "upstream_id": "1"
}'
{"key":"/apisix/routes/1","value":{"id":"1","uri":"/anything/*","status":1,"upstream_id":"1","update_time":1755591831,"host":"example.com","create_time":1755591831,"methods":["GET"],"priority":0}}

plugin

可动态启用的功能模块,用于扩展API网关的处理能力,如认证、限流、日志等。

bash 复制代码
查看默认加载了哪些插件:
curl -i http://127.0.0.1:9180/apisix/admin/plugins/list -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1"
vbnet 复制代码
# 使用rate limit plugin,限制请求一分钟之内只能请求2次。
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $Key" -X PUT -d '
{
    "uri": "/index.html",
    "plugins": {
        "limit-count": {
            "count": 2,
            "time_window": 60,
            "rejected_code": 503,
            "key_type": "var",
            "key": "remote_addr"
        }
    },
  "upstream_id": "1"
}'
 
{"key":"/apisix/routes/1","value":{"upstream_id":"1","id":"1","priority":0,"status":1,"uri":"/index.html","create_time":1755591831,"plugins":{"limit-count":{"time_window":60,"rejected_code":503,"count":2,"key":"remote_addr","key_type":"var","policy":"local","show_limit_quota_header":true,"allow_degradation":false}},"update_time":1755592072}}

service

一组 upstream+plugin的抽象。用于将不同的请求路由到不同的 service 上。服务由路由中公共的插件配置、上游目标信息组合而成。服务与路由、上游关联,一个服务可对应一组上游节点、可被多条路由绑定。

vbnet 复制代码
# 创建service:
curl http://127.0.0.1:9180/apisix/admin/services/200 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
    "plugins": {
        "limit-count": {
            "count": 2,
            "time_window": 60,
            "rejected_code": 503,
            "key": "remote_addr"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'
 
# 创建路由1
curl http://127.0.0.1:9180/apisix/admin/routes/100 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
    "methods": ["GET"],
    "uri": "/index.html",
    "service_id": "200"
}'
# 创建路由2
 
curl http://127.0.0.1:9180/apisix/admin/routes/200 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
    "methods": ["GET"],
    "uri": "/foo/index.html",
    "service_id": "200"
}'
 

consumer

当一个请求在 Route 上通过认证插件(如 key-auth, jwt-auth)成功找到对应的 Consumer 后,APISIX 会将该 Consumer 身上配置的所有插件配置都加载过来,并与当前 Route 的插件配置合并后一起执行。且Route 上的 plugin 配置优先级高于 Consumer 上的插件配置,最终会将合集一起执行。

vbnet 复制代码
# 创建一个consumer,支持jwt-auth
curl -X PUT http://127.0.0.1:9180/apisix/admin/consumers/kakalzhou \
  -H 'X-API-KEY: TWgCqkDFYNiZLrQwUsGiNkVHXyLHNmdP' \
  -d '{
    "username": "kakalzhou",
    "plugins": {
        "jwt-auth": {
            "key": "user-key",
            "algorithm": "HS256"
        }
    }
}'
 
{"key":"/apisix/consumers/kakalzhou","value":{"plugins":{"jwt-auth":{"base64_secret":false,"lifetime_grace_period":0,"key":"user-key","algorithm":"HS256","exp":86400,"secret":"h9Z5B9BQsBY3OvE7zAkUQ+j1EfBcgS7NwJMo6V/zqDXyNoyOJXe0SiC08ZwPfw7o"}},"update_time":1755594679,"create_time":1755594679,"username":"kakalzhou"}}
 
 
# 创建路由,访问这个路由的时候,需要带上jwt-auth header。
curl "http://127.0.0.1:9180/apisix/admin/routes/get-jwt-token" -X PATCH \
  -H "X-API-KEY: TWgCqkDFYNiZLrQwUsGiNkVHXyLHNmdP" \
  -d '{
    "uri": "/headers",
    "plugins": {
      "jwt-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'
 
{"key":"/apisix/routes/get-jwt-token","value":{"update_time":1755603251,"id":"get-jwt-token","upstream":{"scheme":"http","pass_host":"pass","hash_on":"vars","nodes":{"httpbin.org:80":1},"type":"roundrobin"},"plugins":{"public-api":{},"jwt-auth":{"header":"authorization","hide_credentials":false,"cookie":"jwt","store_in_ctx":false,"key_claim_name":"key","query":"jwt"}},"uri":"/headers","create_time":1755595211,"status":1,"priority":0}}
 
 
# 请求使用:
curl -i http://127.0.0.1:9080/headers \
  -H 'Authorization: eyJhbGci...YyZzA'

部署配置

云原生模式部署

apisix 部署
arduino 复制代码
helm repo add apisix https://charts.apiseven.com
helm repo update
helm install apisix apisix/apisix --create-namespace  --namespace apisix
apisix dashborad部署
arduino 复制代码
helm repo add apisix https://charts.apiseven.com
helm repo update
helm install apisix-dashboard apisix/apisix-dashboard --create-namespace --namespace apisix

创建完之后等待pod启动成功:

dashboard 访问
arduino 复制代码
export POD_NAME=$(kubectl get pods --namespace apisix -l "app.kubernetes.io/name=apisix-dashboard,app.kubernetes.io/instance=apisix-dashboard" -o jsonpath="{.items[0].metadata.name}")
 
export CONTAINER_PORT=$(kubectl get pod --namespace apisix $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
 
kubectl --namespace apisix port-forward $POD_NAME 8080:$CONTAINER_PORT
 
访问:
http://127.0.0.1:8080 
admin/admin
配置信息
vbnet 复制代码
kubectl get cm -n apisix apisix:可以获取admin API KEY
kubectl get cm -n apisix apisix-dashboard: 可以获取dashboard登陆信息

服务暴露:

arduino 复制代码
#admin API,通过此svc配置apisix
kubectl get svc -n apisix apisix-admin
 
# gateway API ,通过此svc访问apisix代理的后端服务
kubectl get svc -n apisix apisix-gateway

apisix server 配置文件config.yaml示例

yaml 复制代码
 
apisix:
  node_listen: 9080              # APISIX listening port
  enable_ipv6: false
 
  enable_control: true
  control:
    ip: "0.0.0.0"
    port: 9092
 
deployment:
  admin:
    allow_admin:               # https://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
  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
 

通过dashboard 配置

配置upstream
配置路由
  1. 设置路由信息

设置路由名称,不绑定服务模式,后续自己选择upstream和插件信息。

配置路由匹配条件:路径,host信息等。

  1. 设置上游服务

选择上一步配置好的上游服务web-test,也可以自定义写对应的upstream信息。

配置consumer

如果要开启plugin,需要先配置consumer消费者,在consumer中对某个plugin进行配置,例如创建一个consumer apikey,配置key-auth 插件的key值。

配置插件

在配置路由的时候,选择需要的插件,例如开启key-auth插件,相关的key-auth配置已经在上一步consumer中配置好了,无需再填写。

查看

也可以选择上线下线操作和yaml配置信息查看

测试

因为配置了host,测试的时候需要在本地hosts中配置域名解析。也配置了key-auth,所以header 中需要带auth key

arduino 复制代码
curl -i "http://web.test.com:9080/hello3" -H 'apikey: test-web-key'

自定义插件开发

配置APISIX

  1. 创建lua 插件对应的configmap
csharp 复制代码
kubectl create configmap tencent-scf-event --from-file=./plugins/tencent-scf-event.lua -n apisix
  1. 修改部署的helm values.yaml 文件
yaml 复制代码
  # 开启插件
  plugins: [
    "tencent-scf-event"
  ] 
  
  # 配置自定义插件
  customPlugins:
    # -- Whether to configure some custom plugins
    enabled: true
    plugins:
      # -- plugin name.
      - name: "tencent-scf-event"
        # -- plugin attrs
        attrs: {}
        # -- plugin codes can be saved inside configmap object.
        configMap:
          # -- name of configmap.
          name: "tencent-scf-event"
          mounts:
            - key: "tencent-scf-event.lua"
              path: "/usr/local/apisix/apisix/plugins/tencent-scf-event.lua"
  1. 更新helm 包
arduino 复制代码
helm upgrade apisix apisix/apisix --create-namespace  --namespace apisix -f values.yaml 

配置SCF event函数插件

vbnet 复制代码
# 配置scf-event插件和路由
curl -i http://127.0.0.1:9180/apisix/admin/routes/test-scf-event \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
    "uri": "/testEvent",
    "plugins": {
        "tencent-scf-event": {
            "secret_id": "ak",
            "secret_key": "sk",
            "region": "ap-singapore",
            "function_name": "helloworld-test"
        }
    },
  "upstream_id": "581867095923360779"
}'

测试 访问

配置SCF web函数插件

vbnet 复制代码
# 配置scf-web插件和路由
curl -i http://127.0.0.1:9180/apisix/admin/routes/test-scf-web \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
    "uri": "/testWeb",
    "plugins": {
        "tencent-scf-web": {
            "secret_id": "ak",
            "secret_key": "sk",
            "region": "ap-singapore",
            "uin":"300000010788",
"web_function_url":"https://1303257938-csxkqunlap.ap-singapore.tencentscf.com"
        }
    },
  "upstream_id": "581867095923360779"
}'

测试访问

相关推荐
ftpeak4 分钟前
Rust Web开发指南 第六章(动态网页模板技术-MiniJinja速成教程)
开发语言·前端·后端·rust·web
编码浪子10 分钟前
趣味学Rust基础篇(数据类型)
开发语言·后端·rust
南囝coding22 分钟前
Claude Code 官方内部团队最佳实践!
前端·后端·程序员
IT_陈寒1 小时前
Python性能优化必知必会:7个让代码快3倍的底层技巧与实战案例
前端·人工智能·后端
玦尘、1 小时前
微服务相关面试题
微服务·云原生·架构
Amctwd1 小时前
【后端】微服务后端鉴权方案
微服务·架构·状态模式
拾忆,想起2 小时前
Redis发布订阅:实时消息系统的极简解决方案
java·开发语言·数据库·redis·后端·缓存·性能优化
SimonKing2 小时前
想搭建知识库?Dify、MaxKB、Pandawiki 到底哪家强?
java·后端·程序员
程序员清风2 小时前
为什么Tomcat可以把线程数设置为200,而不是2N?
java·后端·面试
MrSYJ2 小时前
nimbus-jose-jwt你都会吗?
java·后端·微服务