API网关Kong学习笔记(二十二):Kong 1.0.3源代码快速走读

作者:李佶澳  更新时间:2019-05-20 14:49:50 +0800

  项目    kong    刷新

目录

说明

快速阅读一下kong 1.0.3的代码,第一是重新梳理思路,之前折腾0.14.1的时候是摸黑探路,相关笔记比较散乱,第二是kong 0.x的版本已经不再维护了,1.x相比0.x有一些比较显著的变化,譬如原先让人感到混乱的 kong/db和kong/dao现在只有一个了,插件实现全部切换成pdk了。

相关笔记

2019-05-06 16:28:56:kong 1.1.x有了一个重大变换,实现了db-less模式,可以不使用数据库了,见笔记二十六:查看全部笔记如果是刚开始学习kong,直接从1.x开始,0.x已经不再维护,0.15是0.x的最后一个版本。

前19篇笔记是刚开始接触kong时记录的,使用的版本是0.14.1,当时对kong一知半解,笔记比较杂乱。第二十篇开始是再次折腾时的笔记,使用的版本是1.0.3,笔记相对条理一些。

从0.x到1.x需要关注的变化有:

  1. 插件全部使用pdk
  2. 0.x中不鼓励使用的特性都被移除了;
  3. 全部使用kong.db,以前独立的dao彻底清除,代码简洁清晰了。

从nginx.conf入手

这里查阅的nginx.conf是《使用kong源代码生成镜像的Dockerfile》中生成的nginx.conf,nginx.conf只是引用nginx-kong.conf:

worker_processes auto;
daemon off;

pid pids/nginx.pid;
error_log logs/error.log info;

worker_rlimit_nofile 1048576;

events {
    worker_connections 16384;
    multi_accept on;
}

http {
    include 'nginx-kong.conf';
}

如果对nginx.conf、nginx-kong.conf感到疑惑、不明所以,推荐阅读:API网关Kong学习笔记(六):Kong数据平面的事件、初始化与插件加载

主体配置nginx-kong.conf

如果对nginx-kong.conf中的内容感到疑惑,推荐阅读:API网关Kong学习笔记(一):Nginx、OpenResty和Kong入门,基础概念和安装部署Web开发平台OpenResty(一):学习资料、基本组成与使用方法

nginx-kong.conf的内容结构和以前一致:

...
init_by_lua_block {
    Kong = require 'kong'
    Kong.init()
}

init_worker_by_lua_block {
    Kong.init_worker()
}

upstream kong_upstream {
    server 0.0.0.1;
    balancer_by_lua_block {
        Kong.balancer()
    }
    keepalive 60;
}

server {
    ssl_certificate_by_lua_block {
        Kong.ssl_certificate()
    }
}

server {
    server_name kong;
    listen 0.0.0.0:8000;
    listen 0.0.0.0:8443 ssl;
    ...

    ssl_certificate_by_lua_block {
        Kong.ssl_certificate()
    }
    ...
    
    location / {
        ...
        rewrite_by_lua_block {
            Kong.rewrite()
        }

        access_by_lua_block {
            Kong.access()
        }
        ...
        header_filter_by_lua_block {
            Kong.header_filter()
        }

        body_filter_by_lua_block {
            Kong.body_filter()
        }

        log_by_lua_block {
            Kong.log()
        }
    }

    location = /kong_error_handler {
        ...
        content_by_lua_block {
            Kong.handle_error()
        }

        header_filter_by_lua_block {
            Kong.header_filter()
        }

        body_filter_by_lua_block {
            Kong.body_filter()
        }

        log_by_lua_block {
            Kong.log()
        }
    }
}

server {
    server_name kong_admin;
    listen 0.0.0.0:8001;
    listen 0.0.0.0:8444 ssl;
    ...
    location / {
        default_type application/json;
        content_by_lua_block {
            Kong.serve_admin_api()
        }
    }
    ...
}

OpenResty的XX_by_lua_block指令的作用时机见Web开发平台OpenResty(二):组成、工作过程与原理: NginxLuaModule实现的指令与作用位置。XX_by_lua_block指令中调用的函数在 kong/init.lua中实现。

找到入口后剩下的事情就是读代码。笔记里没法记得太详细,只记录一些关键点。

初始化过程:Kong.init()

Kong读取的配置文件时prefix目录下的.kong_env,这个文件是kong prepare的时候生成的,是kong启动时真正使用的配置文件,如果这文件不存在,先查找/etc/kong/kong.conf,然后查找/etc/kong.conf

数据库的初始化

从kong 1.x开始操作数据库的代码全部都在kong/db中,kong/db/init.lua中的 DB.new()实例化一个DB对象。

-- kong/db/init.lua: 45
function DB.new(kong_config, strategy)
-- ...省略--
  local self   = {
    daos       = daos,       -- each of those has the connector singleton
    strategies = strategies,
    connector  = connector,
    strategy   = strategy,
    errors     = errors,
    infos      = connector:infos(),
    kong_config = kong_config,
  }
-- ...省略--

关于DB对象的时候要注意,它的元方法__index被更改过,不是默认的__index:

-- kong/db/init.lua: 39
local DB = {}
DB.__index = function(self, k)
  return DB[k] or rawget(self, "daos")[k]
end

-- kong/db/init.lua: 45
function DB.new(kong_config, strategy)
  -- 省略 ---
  return setmetatable(self, DB)

因此有些操作读取的变量是DB对象的成员daos的field,譬如db.plugins

-- kong/db/init.lua: 344
loaded_plugins = assert(db.plugins:load_plugin_schemas(config.loaded_plugins))

daos中存放的每个表的操作句柄,是在kong/db/init.lua中用kong/db/dao.lua中的new函数实例化的:

-- kong/db/init.lua: 108
for _, schema in pairs(schemas) do
  local strategy = strategies[schema.name]
  if not strategy then
    return nil, fmt("no strategy found for schema '%s'", schema.name)
  end
  daos[schema.name] = DAO.new(self, schema, strategy, errors)
end

DAO.new()是kong/db/dao.lua中的_M.new()

被实例化的表,即shcema中存放的entity有下面几个:

-- kong/db/init.lua: 23
local CORE_ENTITIES = {
  "consumers",
  "services",
  "routes",
  "certificates",
  "snis",
  "upstreams",
  "targets",
  "plugins",
  "cluster_ca",
}

-- kong/db/init.lua: 67
for _, entity_name in ipairs(CORE_ENTITIES) do
  local entity_schema = require("kong.db.schema.entities." .. entity_name)

  -- validate core entities schema via metaschema
  local ok, err_t = MetaSchema:validate(entity_schema)
  if not ok then
    return nil, fmt("schema of entity '%s' is invalid: %s", entity_name,
                    tostring(errors:schema_violation(err_t)))
  end
  local entity, err = Entity.new(entity_schema)
  if not entity then
    return nil, fmt("schema of entity '%s' is invalid: %s", entity_name,
                    err)
  end
  schemas[entity_name] = entity
end

entity的初始化以及dao的实现以后有时间再记录, API网关Kong学习笔记(六)- 数据库操作封装API网关Kong学习笔记(十三):向数据库中插入记录的过程分析中有0.x版本的分析,那时候还有两套dao实现,混在一起没有现在看起来清晰,当时的笔记是在一知半解的情况记录的,不是特别条理,但思路和方向基本正确 。

插件的加载、使用和实现

关心的功能都是用插件实现的,从这个角度来看,kong就是一堆插件的集合。对kong进行定制开发主要也是开发新的插件或者更改已有的插件。

kong集成的插件

配置文件中的plugins配置为bundled时,加载kong/constants.lua的plugins中的所有插件,否则只加载指定的插件。

.kong_env中的配置:

plugins = bundled

kong/constans.lua中的plugins:

-- kong/constans.lua
local plugins = {
  "jwt",
  "acl",
  "correlation-id",
  "cors",
  "oauth2",
  "tcp-log",
  "udp-log",
  "file-log",
  "http-log",
  "key-auth",
  "hmac-auth",
  "basic-auth",
  "ip-restriction",
  "request-transformer",
  "response-transformer",
  "request-size-limiting",
  "rate-limiting",
  "response-ratelimiting",
  "syslog",
  "loggly",
  "datadog",
  "ldap-auth",
  "statsd",
  "bot-detection",
  "aws-lambda",
  "request-termination",
  -- external plugins
  "azure-functions",
  "zipkin",
  "pre-function",
  "post-function",
  "prometheus",
}

加载的插件保存在conf.loaded_plugins中:

-- kong/constans.lua
conf.loaded_plugins = setmetatable(plugins, _nop_tostring_mt)

在初始化函数Kong.init()中,插件是最后加载的,保存在init.lua的局部变量loaded_plugins中:

-- kong/init.lua: 343
-- Load plugins as late as possible so that everything is set up
loaded_plugins = assert(db.plugins:load_plugin_schemas(config.loaded_plugins))
sort_plugins_for_execution(config, db, loaded_plugins)

初始化完成之后,用插件时都是从loaded_plugins中取出,例如在Kong.rewrite()中:

-- kong/init.lua: 645
-- we're just using the iterator, as in this rewrite phase no consumer nor
-- route will have been identified, hence we'll just be executing the global
-- plugins
for plugin, plugin_conf in plugins_iterator(ctx, loaded_plugins,
                                            configured_plugins, true) do
  kong_global.set_named_ctx(kong, "plugin", plugin_conf)
  kong_global.set_namespaced_log(kong, plugin.name)

  plugin.handler:rewrite(plugin_conf)

  kong_global.reset_log(kong)
end

kong的插件实现

插件的代码结构和以前相同,都是位于kong/plugins目录中,每个插件一个目录,目录结构如下:

▾ plugins/
  ▾ acl/
    ▾ migrations/
        000_base_acl.lua
        001_14_to_15.lua
        init.lua
      acls.lua
      api.lua
      daos.lua
      groups.lua
      handler.lua
      schema.lua
  ▾ aws-lambda/
      handler.lua
      schema.lua
      v4.lua

handlers.lua中实现被在kong/init.lua中调用的方法:

 plugin.handler:init_worker()
 plugin.handler:certificate(plugin_conf)
 plugin.handler:rewrite(plugin_conf)
 plugin.handler:preread(plugin_conf)
 local err = coroutine.wrap(plugin.handler.access)(plugin.handler, plugin_conf)
 plugin.handler:header_filter(plugin_conf)
 plugin.handler:body_filter(plugin_conf)
 plugin.handler:log(plugin_conf)

如果插件要存取数据库,在migrations目录中创建包含数据库表创建和更新语句的lua文件。

schema.lua格式有变化,比方说以前是这样的:

local Errors = require "kong.dao.errors"

return {
    no_consumer = true,
    fields = {
        regex = { type = "string",required = true },
        replacement = { type = "string", required = true },
        flag = {type = "string", default="redirect", required =true},
    },
    self_check = function(schema, plugin_t, dao, is_update)
        if plugin_t.regex == "" or plugin_t.replacement == "" then
            return false, Errors.schema "must set regex and replacement"
        end
        if plugin_t.flag ~= "redirect" and plugin_t.flag ~= "permanent" then
            return false, Errors.schema "flag should be redirect or permanent"
        end
        return true
    end
}

现在是:

local typedefs = require "kong.db.schema.typedefs"

return {
    name = "http-redirect",
    fields = {
      { consumer = typedefs.no_consumer },
      { run_on = typedefs.run_on_first },
      { config = {
          type = "record",
          fields = {
            { regex   = { type = "string",required = true  },},
            { replace = { type = "string",required = true  },},
            { flag = {type="string", default="redirect", required =true},},
          }
        }
      }
    },
}

Kong的插件机制单独写一篇分析。

管理平面的实现

前面只是简单浏览了kong的数据平面的实现代码,没有涉及kong的管理平面。kong的管理平面提供了kong的管理API,管理平面使用另一个端口,它的入口函数是Kong.serve_admin_api()

server {
    server_name kong_admin;
    listen 0.0.0.0:8001;
    listen 0.0.0.0:8444 ssl;
    ...
    location / {
        default_type application/json;
        content_by_lua_block {
            Kong.serve_admin_api()
        }
    }
    ...
}

Kong.serve_admin_api位于kong/init.lua中:

-- kong/init.lua: 792
function Kong.serve_admin_api(options)
  kong_global.set_phase(kong, PHASES.admin_api)

  options = options or {}

  header["Access-Control-Allow-Origin"] = options.allow_origin or "*"

  if ngx.req.get_method() == "OPTIONS" then
    header["Access-Control-Allow-Methods"] = "GET, HEAD, PUT, PATCH, POST, DELETE"
    header["Access-Control-Allow-Headers"] = "Content-Type"

    return ngx.exit(204)
  end

  return lapis.serve("kong.api")
end

这里不分析管理平面的实现,有时间的时候单独记录一篇,这篇笔记到此为止。

参考

  1. 使用kong源代码生成镜像的Dockerfile
  2. API网关Kong学习笔记(六):Kong数据平面的事件、初始化与插件加载
  3. Web开发平台OpenResty(一):学习资料、基本组成与使用方法
  4. API网关Kong学习笔记(一):Nginx、OpenResty和Kong入门,基础概念和安装部署
  5. Web开发平台OpenResty(二):组成、工作过程与原理: NginxLuaModule实现的指令与作用位置
  6. API网关Kong学习笔记(十三):向数据库中插入记录的过程分析
  7. API网关Kong学习笔记(六):Kong数据平面的事件、初始化与插件加载:数据库操作封装

站长微信(朋友圈有精华,一般不闲聊)

推荐阅读

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作

友情链接:  李佶澳的博客  小鸟笔记  软件手册  编程手册  运营手册  爱马影视  网络课程  奇技淫巧  课程文档  精选文章  发现知识星球  百度搜索 谷歌搜索