前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >APISIX初探

APISIX初探

作者头像
云原生小白
发布2021-08-13 15:13:56
5.6K0
发布2021-08-13 15:13:56
举报
文章被收录于专栏:LokiLokiLoki

Apisix简介

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 是如何配合的。

bin/apisix

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/cli/apisix.lua

apisix.lua[6] 包含如下命令:

  1. apisix init , 初始化 nginx 配置,通过读取 conf/config.yaml 生成 nginx config 文件。供 openresty(nginx)使用。
  2. apisix init_etcd,初始化 etcd 配置,用于与 etcd 同步数据。
  3. apisix start;实际执行了 openresty -p /usr/local/apisix -g 'daemon off;' 启动 openresty 。

nginx.conf

使用 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]

目前已知的阶段有:

  • preread
  • ssl
  • access
  • balancer
  • rewrite
  • header_filter
  • body_filter
  • log

初始化

apisix 启动时调用:

  • apisix init,解析配置,生成 nginx.conf 文件
  • apisix init_etcd,初始化 etcd 中的存储目录。参见apisix/constants.lua[10]
  • 启动 openresty(nginx)
  • apisix.http_init(args),初始化 nginx 相关。
    • 设置 dns resolver
    • 启动 "privileged agent"
  • apisix.http_init_worker(),apisix 的核心初始化逻辑。
    • 初始化 openresty worker event
    • discovery.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()

        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
  • apisix.http_exit_worker()
    • 停止 "privileged agent"

请求调用链分析

通过查看 nginx.conf 我们可以分析出一个请求进入 apisix 后的处理流程。

  • 请求进入 nginx
  • 如果是 /apisix/admin 路径请求,则进入 apisix.http_admin()[11],完成后返回
  • 如果是常规请求(/)则继续
  • 进入 apisix.http_access_phase()[12] 阶段
    • 初始化 api_ctx 上下文
    • 包含 client tls 验证
    • 是否为 apisix 已经注册的路径,对请求进行匹配,内部使用了基于 “基数树”(Radix tree) 的 oenresty 路由组件进行匹配。
    • 向上下文注入该请求匹配的 apisix route ,service 等信息用于后续阶段使用。
    • 向上下文注入该请求相关的插件,例如:请求对应的路由存在插件,若请求存在对应的 service 则加入 service 定义的插件,以及全局插件。
    • 调用 "rewrite" 阶段的插件。
    • 调用 "access" 阶段的插件。
    • 获取 upstream
    • 执行 loadbalancer 选择 server
    • 调用 "balancer" 阶段插件
    • 判断 upstream ,根据 upstream 类型,grpc dubbo 等进入 @grpc_pass @dubbo_pass 等不同的后续处理流程。这些配置可在 nginx.conf 中查看。
  • 进入 apisix.http_balancer_phase()[13] 阶段
    • 此阶段是 openresty balancer 的实现,主要功能是执行实际的流量转发配置,http grpc 等类型的请求均会经过该阶段
    • 如果上线文中存在 pick_server 即在上个阶段中执行 loadbalancer 后选择出的 server, 则配置 nginx 直接转发至该 server。注意:NGINX proxy_pass 组件实际执行了请求转发的功能,apisix 仅对其作了配置。
    • 如果不存在 pick_server,则再次执行 loadbalancer
    • 调用 openresty set_current_peer(server, ctx)[14] 完成 proxy_pass 配置
    • 执行 proxy_pass
  • 进入 apisix.http_header_filter_phase[15] 阶段,该阶段主要对响应 header 做更改
    • 设置头 "Server", APISIX
    • 设置上游状态头:X-APISIX-Upstream-Status
    • 执行 “header_filter” 阶段的插件
  • 进入 apisix.http_body_filter_phase()[16] 阶段
    • 执行 “body_filter” 阶段的插件
  • 进入 apisix.http_log_phase()[17] 阶段
    • 执行 “log” 阶段的插件
    • 回收 api_ctx 上下文

Admin API

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

control-api[20] 有两个用途

  • 引出内部的状态
  • 控制当前 apisix 的行为

监听地址为 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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云原生小白 微信公众号,前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Apisix简介
  • 目录结构
  • 启动
    • bin/apisix
      • apisix/cli/apisix.lua
      • nginx.conf
      • 处理阶段&插件
      • 初始化
      • 请求调用链分析
      • Admin API
      • Control API
        • 参考资料
        相关产品与服务
        负载均衡
        负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档