Apisix 是一个用使用 lua 语言编写的网关控制器,相比官网介绍的 apisix 是一个网关,apisix 的实际用途更像是一个控制器。因为其本身代码不承载流量。apisix 运行于 openresty 之上,openresty 运行于 nginx 之上。
openresty OpenResty® 的目标是让你的 Web 服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
简单来说,即 openresty 对 nginx 做了充分的扩展,引出了许多 hook 点,你可以使用 lua 语言对这些 hook 进行实现。
Apisix 在 openresty 的基础上,实现了 openresty 许多 hook,实现了动态配置 nginx 以应对复杂的网关需求。
举例:apisix 中的转发(负载均衡)逻辑实际上是使用了lua-resty-core/ngx/balancer[1]
.
├── apisix # apisix lua源码
│ ├── admin # apisix admin api
│ ├── balancer # upstream 负载均衡
│ ├── cli # command line interface
│ ├── control # control api
│ ├── core # 一些配置,etcd交互,请求解析等
│ ├── discovery # 服务发现
│ ├── http # 路由匹配等
│ ├── plugins # 自带的插件在该目录
│ ├── ssl # ssl sni 相关
│ ├── stream # 流处理相关,包含了流处理的一些插件
│ └── utils
├── benchmark
├── bin # apisix 运行入口(develop)
├── ci
├── conf # 配置文件
├── docs
├── example
├── kubernetes # kubernetes 部署文件
├── logos
├── rockspec # luarocks 依赖描述文件
├── t # test
├── utils
...
└── Makefile # make
Apisix 使用 luarocks 进行包管理。因为 lua 是脚本语言,其运行需要使用 lua 解释器解释执行,lua 还提供了 luajit 即时编译。
首次运行 apisix 需要安装 lua luarocks 等,以及 apisix 依赖:
make deps
make deps 会在将 apisix 的所有依赖下载至当前目录的 deps 中。
启动运行:
make run
Makefile 中提供了快速的启动运行方式,其调用了bin/apisix start
,apisix start 内部调用了 openresty 。
在 developenv.lua#L34[2]时,lua package loader 会被配置env.lua#L46-L51[3]寻找 deps 文件夹下的依赖。
使用 make run 非常不便于了解 apisix 是如何真正运行的。但是apisix-docker/alpine/Dockerfile[4]告诉了 apisix 是如何启动的,以及和 openresty 是如何配合的。
apisix文件实质上为一个 bash 文件,内部逻辑为判断当前 apisix 文件路径,寻找 openresty 路径以确定 luajit 位置使用 luajit 启动 apisix;或者直接使用 lua 启动 apisix。下级调用至./apisix/cli/apisix.lua[5]
#!/bin/bash
...
# use the luajit of openresty
echo "$LUAJIT_BIN $APISIX_LUA $*"
exec $LUAJIT_BIN $APISIX_LUA $*
elif [[ "$LUA_VERSION" =~ "Lua 5.1" ]]; then
# OpenResty version is not 1.19, use Lua 5.1 by default
echo "lua $APISIX_LUA $*"
exec lua $APISIX_LUA $*
...
apisix.lua[6] 包含如下命令:
conf/config.yaml
生成 nginx config 文件。供 openresty(nginx)使用。openresty -p /usr/local/apisix -g 'daemon off;'
启动 openresty 。使用 make init
会执行 apisix init ,生成 nginx 配置文件。通过阅读 nginx 文件,可以了解 apisix 整个流程。
下面 nginx 文件节选了生成的 nginx.conf 文件中关键内容。
# Configuration File - Nginx Server Configs
# This is a read-only file, do not try to modify it.
# https://github.com/apache/apisix/blob/master/conf/config-default.yaml#L158-L175
# config.yaml 该节中的配置会被设置至对应的位置
# 下面节是 nginx_config.main_configuration_snippet 中的内容。
# main configuration snippet starts
# main configuration snippet ends
http {
...
# http configuration snippet starts
# http configuration snippet ends
# 参考:https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#http-subsystem
# 这里是 apisix 中对 ngx balancer 的实现。实现了 proxy bypass 的路由选择。对应apisix 中负载均衡 upstream 功能。
# apisix 中的负载均衡实现在 https://github.com/apache/apisix/tree/master/apisix/balancer 中,目前提供了5 种算法。
upstream apisix_backend {
server 0.0.0.1; # 随便填一个无效的值
balancer_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L709
apisix.http_balancer_phase()
}
...
}
# https://openresty-reference.readthedocs.io/en/latest/Directives/#init_by_lua_block
init_by_lua_block {
require "resty.core"
apisix = require("apisix")
local dns_resolver = { "127.0.0.53", }
local args = {
dns_resolver = dns_resolver,
}
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L61
apisix.http_init(args)
}
init_worker_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L90
apisix.http_init_worker()
}
exit_worker_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L137
apisix.http_exit_worker()
}
server {
listen 127.0.0.1:9090;
access_log off;
location / {
content_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L773
apisix.http_control()
}
}
...
}
server {
listen 127.0.0.1:9091;
access_log off;
location / {
content_by_lua_block {
local prometheus = require("apisix.plugins.prometheus")
prometheus.export_metrics()
}
}
location = /apisix/nginx_status {
allow 127.0.0.0/24;
deny all;
stub_status;
}
}
server {
listen 9080 default_server reuseport;
listen 9443 ssl default_server http2 reuseport;
listen [::]:9080 default_server reuseport;
listen [::]:9443 ssl default_server http2 reuseport;
server_name _;
# apisix ssl 服务端证书配置
ssl_certificate cert/ssl_PLACE_HOLDER.crt;
ssl_certificate_key cert/ssl_PLACE_HOLDER.key;
...
# http server configuration snippet starts
# http server configuration snippet ends
location = /apisix/nginx_status {
allow 127.0.0.0/24;
deny all;
access_log off;
stub_status;
}
location /apisix/admin {
set $upstream_scheme 'http';
set $upstream_host $http_host;
set $upstream_uri '';
allow 127.0.0.0/24;
deny all;
content_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L752
apisix.http_admin()
}
}
ssl_certificate_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L142
apisix.http_ssl_phase()
}
proxy_ssl_name $upstream_host;
proxy_ssl_server_name on;
location / {
...
access_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L341
apisix.http_access_phase()
}
...
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L709
proxy_pass $upstream_scheme://apisix_backend$upstream_uri;
mirror /proxy_mirror;
header_filter_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L575
apisix.http_header_filter_phase()
}
body_filter_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L612
apisix.http_body_filter_phase()
}
log_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L670
apisix.http_log_phase()
}
}
# 对于 grpc 类型的请求,会被跳转至此处执行
location @grpc_pass {
access_by_lua_block {
# https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L553
apisix.grpc_access_phase()
}
grpc_set_header Content-Type application/grpc;
grpc_socket_keepalive on;
grpc_pass $upstream_scheme://apisix_backend;
header_filter_by_lua_block {
apisix.http_header_filter_phase()
}
body_filter_by_lua_block {
apisix.http_body_filter_phase()
}
log_by_lua_block {
apisix.http_log_phase()
}
}
...
}
# http end configuration snippet starts
# http end configuration snippet ends
}
从上一节已经了解到 apisix 通过在 nginx 中不同阶段设置 hook 完成的自身功能
apisix 的功能通过不同的插件完成,apisix 对这些插件进行组织(admin api),并根据插件的描述,在上述的不同时机调用插件,完成功能。
在源码中,所有的插件均有一个统一的入口。
function common_phase(phase_name)[7]
在不同的阶段传入阶段名称进行该阶段的插件调用。其内部调用了 plugin.run_plugin(phase_name, nil, api_ctx)
来运行插件。
对于插件,实现名称为阶段的函数以在该阶段被调用。举例:
basic_auth
插件中,实现了函数function \_M.rewrite(conf, ctx)[8]limit-conn
中实现了function \_M.access(conf, ctx)[9]目前已知的阶段有:
apisix 启动时调用:
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()
plugin.init_worker()
router.http_init_worker()
require("apisix.http.service").init_worker()
plugin_config.init_worker()
require("apisix.consumer").init_worker()
if core.config == require("apisix.core.config_yaml") then
core.config.init_worker()
end
require("apisix.debug").init_worker()
apisix_upstream.init_worker()
require("apisix.plugins.ext-plugin.init").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
通过查看 nginx.conf 我们可以分析出一个请求进入 apisix 后的处理流程。
proxy_pass
组件实际执行了请求转发的功能,apisix 仅对其作了配置。"Server", APISIX
X-APISIX-Upstream-Status
admin api 是 apisix 的控制面,这里进行了整个 apisix 的配置。
其入口为 apisix/init.lua#L752[18],内部根据apisix/admin/init.lua#L375配置的路由进行分发处理。
apisix/admin/init.lua#L44在内部进行了二次分发,将请求路由至对应的模块进行处理。
以 enable batch-requests plugin 为例:
http 请求:PUT http://127.0.0.1:9080/apisix/admin/plugin_metadata/batch-requests
对应 apisix/admin/plugin_metadata.lua#L92
function _M.put(plugin_name, conf)
local plugin_name, err = check_conf(plugin_name, conf)
if not plugin_name then
return 400, err
end
local key = "/plugin_metadata/" .. plugin_name
core.log.info("key: ", key)
local res, err = core.etcd.set(key, conf)
if not res then
core.log.error("failed to put plugin metadata[", key, "]: ", err)
return 500, {error_msg = err}
end
return res.status, res.body
end
将在 etcd 中该 plugin_metadata 目录中写入该插件名称(key)和配置(value)
在插件执行运行时使用 plugin.plugin_metadata(plugin_name)[19] 读取出来。
control-api[20] 有两个用途
监听地址为 127.0.0.1:9090,因其包含敏感数据所以仅允许 local 访问。
入口为apisix.http_control()[21]
其将已注册的插件中实现了 control_api() 方法的插件进行执行注册。
以 server_info 插件为例:apisix/plugins/server-info.lua#L194[22]
function _M.control_api()
return {
{
methods = {"GET"},
uris ={"/v1/server_info"},
handler = get_server_info,
}
}
end
在 control api 上注册了路径 /v1/server_info
并指定使用 get_server_info
函数进行处理。
[1]
lua-resty-core/ngx/balancer: https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md
[2]
env.lua#L34: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/cli/env.lua#L34
[3]
env.lua#L46-L51: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/cli/env.lua#L46-L51
[4]
apisix-docker/alpine/Dockerfile: https://github.com/apache/apisix-docker/blob/054c0284225482de55a8a7e913816c0dc9bb969e/alpine/Dockerfile#L49
[5]
apisix.lua: https://github.com/apache/apisix/blob/master/apisix/cli/apisix.lua
[6]
apisix命令: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/cli/ops.lua#L825
[7]
function: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L324
[8]
rewrite: https://github.com/apache/apisix/blob/71bc27cc41cf52ba1a41816311412527ae278045/apisix/plugins/basic-auth.lua#L128
[9]
access: https://github.com/apache/apisix/blob/71bc27cc41cf52ba1a41816311412527ae278045/apisix/plugins/limit-conn.lua#L52
[10]
constants.lua: https://github.com/apache/apisix/blob/master/apisix/constants.lua
[11]
http_admin: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L752
[12]
http_access_phase: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L341
[13]
http_balancer_phase: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L709
[14]
set_current_peer: https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#set_current_peer
[15]
http_header_filter_phase: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L575
[16]
http_body_filter_phase: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L612
[17]
http_log_phase: https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L670
[18]
apisix/init.lua: https://github.com/apache/apisix/blob/a461c9856d7e1951b0307809edc573fd88ec0a52/apisix/init.lua#L752
[19]
plugin_name: https://github.com/apache/apisix/blob/a04f3649b79bc89bb941f3727459ed74ce8b94c6/apisix/plugins/batch-requests.lua#L208
[20]
control-api: https://apisix.apache.org/docs/apisix/control-api
[21]
http_control(): https://github.com/apache/apisix/blob/e7d26dc4f0bd690c288867a248a69f0efeaea733/apisix/init.lua#L773
[22]
server-info.lua#L194: https://github.com/apache/apisix/blob/a04f3649b79bc89bb941f3727459ed74ce8b94c6/apisix/plugins/server-info.lua#L194