Анализ исходного кода процесса запуска APISIX (v1.5)

Lua

0x00 среда

Я установил версию 1.5 apisix из исходного кода на macos, etcd и другие службы развернуты с помощью docker-compose, у apisix есть руководство по развертыванию докеров. Развертывание среды здесь не описывается.

исходный код apix v1.5:GitHub.com/Apache/API…

Код отладки, который я использовал во время анализа:GitHub.com/Тайчжоу Сангграсу…Эта часть кода делит процесс запуска на несколько шагов и подготавливает среду отладки для облегчения пошаговой отладки и уточнения процесса запуска.

0x01 начало

Среда установки apisix находится в/usr/local/Cellar/apisix/apache-apisix-1.5, команда запуска./bin/apisix restart

Команда запуска соответствует/usr/local/Cellar/apisix/apache-apisix-1.5/bin/apisixскрипт, первая строка кода скрипта

#!/usr/bin/env lua

В Unix-подобных операционных системах добавьте первую строку скрипта#!(это называется шебанг или фунт бенг), за которым следует абсолютный путь интерпретатора, используемого для интерпретации скрипта, но, очевидно, за ним не следует абсолютный путь.

env — это исполняемая команда, которая сообщает операционной системе о необходимости поиска в переменной окружения $PATH текущей системы.

#查看PATH环境变量
echo $PATH
#输出
/usr/local/opt/openssl@1.1/bin:/usr/local/Cellar/openresty@1.15.8.3/1.15.8.3/openresty/luajit/bin:/usr/local/Cellar/apisix/apache-apisix-1.5/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Applications/Wireshark.app/Contents/MacOS

env lua: относится к поиску интерпретатора (инструкции) с именем lua по пути Path, настроенному в текущей среде env.

#查看lua指令位置
which lua
#输出
/usr/local/Cellar/apisix/apache-apisix-1.5

Как видите, lua находится в переменной окружения $PATH. Если интерпретатор lua в этом пути удален, а в других путях нет интерпретатора lua, то при запуске apisix будет выдано сообщение об ошибке

env: lua: No such file or directory

Основное преимущество такого способа записи состоит в том, чтобы сделать этот сценарий применимым к разным системам, разным средам и разным разрешениям пользователей, если программа-интерпретатор правильно записывает свой собственный путь в переменную окружения $PATH.

0x02 start

Функция, соответствующая команде запуска

function _M.start(...)
    -- check running
    local pid_path = apisix_home .. "/logs/nginx.pid"
    local pid, err = read_file(pid_path)  -- 读取/usr/local/apisix/logs/nginx.pid
    ……
end

Приведенный выше код включаетapisix_homeОн был определен в другом месте скрипта, поэтому причина размещенияapisix_homeПеременная вынесена отдельно, так как эта переменная является текущим каталогом установки apisix.Получить это значение громоздко, так как в разных системах и разных методах установки каталог установки apisix будет неизвестен, поэтому его нужно извлечь и отдельно записать кусок кода для определения этого значения.

apisix_home: определить каталог установки apisix.
local apisix_home = "/usr/local/apisix"  --linux环境中默认的安装地址

/usr/local/apisixЭто каталог установки по умолчанию для среды Linux, но он не является абсолютным, и он также может быть установлен пользователем в других местах.Например, моя локальная установка находится не в этом каталоге.

Так что аписикс правapisix_homeПосле выполнения обработки необходимо вызвать функцию для выполненияpwdкоманда, чтобы получить абсолютный путь, где находится скрипт, процесс выглядит следующим образом

--我的启动命令是./bin/apisix start
--如果启动命令以'./'开头
if script_path:sub(1, 2) == './' then
    --调用系统的cmd命令,查看当前所在路径
    apisix_home = trim(excute_cmd("pwd"))  
    if not apisix_home then
        error("failed to fetch current path")
    end
    
    --判断是否安装在root跟路径下
    if string.match(apisix_home, '^/[root][^/]+') then
            is_root_path = true
    end
end

excute_cmdФункция выглядит следующим образом

-- 注意:`excute_cmd`返回值的末尾会有换行符,建议使用`trim`函数处理返回值。
local function excute_cmd(cmd)
    --使用io.popen函数来执行命令(和直接在命令行界面执行命令的结果上一样的,不过使用函数这种方式的结果保存在文件中),在这里执行的就是下面的pwd命令
    local t, err = io.popen(cmd)  --这里返回的t其实是一个文件描述符
    if not t then
        return nil, "failed to execute command: " .. cmd .. ", error info:" .. err
    end
  
    local data = t:read("*all")   --执行read命令读取文件
    t:close()
    return data   --在我的机器上,模拟apisix脚本所在位置,执行pwd命令,返回如下
  								--/usr/local/Cellar/apisix/apache-apisix-1.5/bin\n
                  --结尾的\n是换行符
end

Используйте подключаемый модуль IDEA+EmmyLua для создания среды отладки lua-скриптов, поместитеexcute_cmdФункция подхвачена и отлажена следующим образом

объяснение команды io.popen()

  • Прототип: io.popen ([прог [ режим]])
  • Объяснение: Запустить программу в дополнительном процессеprog, и возвращает дескриптор файла для prog. С точки зрения непрофессионала, эта функция может использоваться для вызова команды (программы) и возврата дескриптора файла, связанного с программой, обычно результат вывода вызванной функции.Режим открытия файла определяется параметромmodeХорошо, есть значение"r"а также"w"Существует два типа, соответственно указывающих, что они открываются в режимах чтения и записи, а по умолчанию установлен режим чтения.

После вышеуказанной обработкиapisix_homeуже правильноapisixкаталог установки. В моем окружении,apisix_home=/usr/local/Cellar/apisix/apache-apisix-1.5, продолжайте смотреть на функцию запуска.

read_file: Прочитайте nginx.pid и оцените, запущен ли apisix
function _M.start(...)
    -- check running
    local pid_path = apisix_home .. "/logs/nginx.pid"
    local pid, err = read_file(pid_path)  -- 读取/usr/local/apisix/logs/nginx.pid
    --如果nginx的进程id存在
    if pid then
        local hd = io.popen("lsof -p " .. pid)
        local res = hd:read("*a")
        if res and res ~= "" then
            print("APISIX is running...")
            return nil
        end
    end
    
    --启动apisix
    init(...)
    --启动etcd
    init_etcd(...)

    local cmd = openresty_args
    -- print(cmd)
    os.execute(cmd)
end

nginx.pidНа самом деле начинаяnginxАвтоматически генерируется, в котором хранится текущийnginxИдентификационный номер процесса, то есть pid. Мой локальный apisix запустился, посмотритеnginx.pid

В строке 4 нужно найти каталог среды выполнения apisix.

Здесь используется функция io.read(), и переданные параметры"*a"а также"*all"Во-вторых, я отлаживаю и сравниваю результаты, чтобы увидеть, что эти два параметра должны иметь одинаковое значение, начиная с текущей позиции, читать весь файл. Если в конце файла или если файл пуст, вызов вернет пустую строку. В конце файла возвращается пустая строка.

"*all"Он написан на lua5.1, см.:woohoo.rua.org/bulk/21.1.contract…

"*a"Он написан на lua5.2, см.:woohoo.rua.org/manual/5.2/…

В строке 13 нужно определить, запущен ли nginx, если да, распечатать лог и выйти.

init: запустить apix

Функция start показывает, что вызывается функция init, а три точки (...) в параметрах указывают на то, что функция принимает другое количество аргументов.

local function init()
    --判断apisix的安装目录是否是根路径
    if is_root_path then
        print('Warning! Running apisix under /root is only suitable for development environments'
            .. ' and it is dangerous to do so. It is recommended to run APISIX in a directory other than /root.')
    end

    -- read_yaml_conf
    --读取配置文件
    local yaml_conf, err = read_yaml_conf()
    if not yaml_conf then
        error("failed to read local yaml config of apisix: " .. err)
    end
    -- print("etcd: ", yaml_conf.etcd.host)

    ……
end

Первый взглядread_yaml_conf()функция

local function read_yaml_conf()
    --调用profile模块
    local profile = require("apisix.core.profile")
    --给profile模块的元表apisix_home属性赋值,其实就是让profile模块知道apisix的安装路径
    profile.apisix_home = apisix_home .. "/"
    --profile模块yaml_path函数就是用 
    --apisix_home  .. "conf/" .. "config" .. ".yaml" 
    --进行拼接,在我的环境下,这个拼接结果就是一个字符串:
    --"/usr/local/Cellar/apisix/apache-apisix-1.5/conf/config.yaml"
    --即apisix的配置文件所在位置
    local local_conf_path = profile:yaml_path("config")
    --读取配置文件
    local ymal_conf, err = read_file(local_conf_path)
    if not ymal_conf then
        return nil, err
    end
    -- 解析tinyyaml模块来yaml配置文件
    return yaml.parse(ymal_conf)
end

Этот раздел в основном основан на пути установки аписикса, прошивке конфигурационного файлаconfig.yamlАбсолютный путь io, затем прочитайте файл io, дайтеtinyyamlмодуль для анализа конфигурации yaml, и в результате конфигурация yaml анализируется как значение типа table,yaml_confИнформация о стеке выглядит следующим образом, век должен быть поставленconfig.yamlПреобразован в табличную форму.

продолжать вниз

local function init()
    --查看openresty编译信息
    local or_ver = excute_cmd("openresty -V 2>&1")
    local with_module_status = true
    --校验是否安装了http_stub_status_module模块
    if or_ver and not or_ver:find("http_stub_status_module", 1, true) then
        io.stderr:write("'http_stub_status_module' module is missing in ",
                        "your openresty, please check it out. Without this ",
                        "module, there will be fewer monitoring indicators.\n")
        with_module_status = false
    end
        ……
end

Этот абзац эквивалентен выполнениюopenresty -V 2>&1 | grep http_stub_status_moduleКоманда в основном предназначена для проверки того, компилируется ли компиляция openresty во время компиляции.http_stub_status_moduleЕсли модуль не скомпилирован, показатели мониторинга будут снижены.

Продолжайте, далее следует подготовить системный контекст, этот абзац в основном предназначен дляyaml_confВыполните проверку и преобразование параметров и получите информацию, относящуюся к среде выполнения системы, и, наконец, получите полную табличную форму.sys_conf

local function init()
    ……
    -- Using template.render
    --准备上下文信息
    local sys_conf = {
        lua_path = pkg_path_org,
        lua_cpath = pkg_cpath_org,
        --获取当前系统相关信息
        os_name = trim(excute_cmd("uname")),
        apisix_lua_home = apisix_home,
        with_module_status = with_module_status,
        error_log = {level = "warn"},
    }
    --一系列校验
    if not yaml_conf.apisix then
        error("failed to read `apisix` field from yaml file")
    end

    if not yaml_conf.nginx_config then
        error("failed to read `nginx_config` field from yaml file")
    end
    --校验是否是32位机器
    if is_32bit_arch() then
        --worker_rlimit_core: nginx的配置,设置每个worker最大能打开的核心文件数,用于在不重启主进程的情况下增加限制。
        sys_conf["worker_rlimit_core"] = "4G"
    else
        sys_conf["worker_rlimit_core"] = "16G"
    end
    --yaml_conf中的配置信息转移到sys_conf
    for k,v in pairs(yaml_conf.apisix) do
        sys_conf[k] = v
    end
    for k,v in pairs(yaml_conf.nginx_config) do
        sys_conf[k] = v
    end
    --参数校验&优化参数
    local wrn = sys_conf["worker_rlimit_nofile"]
    local wc = sys_conf["event"]["worker_connections"]
    if not wrn or wrn <= wc then
        -- ensure the number of fds is slightly larger than the number of conn
        sys_conf["worker_rlimit_nofile"] = wc + 128
    end
    --是否开启dev模式
    if(sys_conf["enable_dev_mode"] == true) then
        --开启dev模式的话,设置worker数量
        sys_conf["worker_processes"] = 1
        sys_conf["enable_reuseport"] = false
    else
        sys_conf["worker_processes"] = "auto"
    end
    --是否配置外置dns解析
    local dns_resolver = sys_conf["dns_resolver"]
    if not dns_resolver or #dns_resolver == 0 then
        --如果没有配置dns解析,则使用默认的解析
        local dns_addrs, err = local_dns_resolver("/etc/resolv.conf")
        if not dns_addrs then
            error("failed to import local DNS: " .. err)
        end

        if #dns_addrs == 0 then
            error("local DNS is empty")
        end
        sys_conf["dns_resolver"] = dns_addrs
    end
    --到这里,sys_conf信息基本准备完毕
    ……
end

Хотя эта часть выглядит большим количеством кода, она не сложна.Часть ее состоит в проверке параметров, а часть – в получении информации о сервере во время выполнения.

В основном все дело в подготовкеsys_confпродолжать.

yaml_confИнформация из пользовательской конфигурации, даsys_confчасть этого, в соответствии с параметрами конфигурации пользователя, например, включать ли режим разработки и т. д.sys_confнастройка некоторых параметров.

Так что здесь в основном на основеyaml_conf, среда операционной системы, информация о компиляции openresty,apisixСреда установки, переменная среды PATH lua и другие параметры, сборкаsys_conf,а такжеsys_confбудет использоваться для определенияnginxповедения, т. е. превращается вnginx.confконфигурационный файл.

завершенныйsys_confИнформация о стеке выглядит следующим образом

продолжайте, следующее должно положитьsys_confПреобразован в файл конфигурации nginx.conf с использованием библиотеки функций.lua-resty-template, справочник по использованию шаблонаGitHub.com/Не грусти/Руа-…

 local function init()
    ……
    --获取模板渲染引擎,ngx_tpl是在代码中定义的nginx-template模板,太长了,在这里就不展现了
    local conf_render = template.compile(ngx_tpl)
    --执行模板渲染,其实就是把sys_conf中的参数渲染进ngxconf中
    local ngxconf = conf_render(sys_conf)

    --把ngxconf配置写入文件,即nginx.conf
    local ok, err = write_file(apisix_home .. "/conf/nginx.conf", ngxconf)
    if not ok then
        error("failed to update nginx.conf: " .. err)
    end

    --获取openresty版本号,调用的是local 函数,其实就是执行cmd命令
    local op_ver = get_openresty_version()
    --在我的环境中,op_ver = 1.15.8.3
    if op_ver == nil then
        io.stderr:write("can not find openresty\n")
        return
    end

    local need_ver = "1.15.8"
    --校验openresty版本号,校验的方式就是把op_ver和need_ver用 . 分割成数组,然后挨个比较大小
    if not check_or_version(op_ver, need_ver) then
        io.stderr:write("openresty version must >=", need_ver, " current ", op_ver, "\n")
        return
    end
end

Этот абзац, в том числе и предыдущий абзац, использует некоторые локальные функции,get_openresty_version(),check_or_versionИ так далее, по сути, аналогично тем функциям, которые были введены ранее, это все операции io и cmd.

ngxconf – это результат, отображаемый механизмом шаблонов. Это значение типа string. Содержимое выглядит следующим образом.

Затем содержимое выводится в файл, а окончательный сгенерированныйnginx.confФайл выглядит следующим образом

# Configuration File - Nginx Server Configs
# This is a read-only file, do not try to modify it.

master_process on;

worker_processes auto;

error_log logs/error.log warn;
pid logs/nginx.pid;

worker_rlimit_nofile 20480;

events {
    accept_mutex off;
    worker_connections 10620;
}

worker_rlimit_core  16G;

worker_shutdown_timeout 240s;

env APISIX_PROFILE;


http {
    lua_package_path  "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;/usr/local/Cellar/apisix/apache-apisix-1.5/?.lua;/usr/local/Cellar/apisix/apache-apisix-1.5/?/init.lua;;./?.lua;/usr/local/Cellar/openresty@1.15.8.3/1.15.8.3/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/Cellar/openresty@1.15.8.3/1.15.8.3/openresty/luajit/share/lua/5.1/?.lua;/usr/local/Cellar/openresty@1.15.8.3/1.15.8.3/openresty/luajit/share/lua/5.1/?/init.lua;/Users/tuzhengsong/Library/Application Support/JetBrains/IntelliJIdea2020.2/plugins/intellij-emmylua/classes/debugger/mobdebug/?.lua;/Users/tuzhengsong/IdeaProjects/apisix-learning/src/?.lua;/Users/tuzhengsong/IdeaProjects/apisix-learning/?.lua;;/usr/local/Cellar/apisix/apache-apisix-1.5/deps/share/lua/5.1/?.lua;/usr/local/Cellar/apisix/apache-apisix-1.5/deps/share/lua/5.1/?/?.lua;/usr/local/Cellar/apisix/apache-apisix-1.5/deps/share/lua/5.1/?.lua;";
    lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/Cellar/openresty@1.15.8.3/1.15.8.3/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";

    lua_shared_dict plugin-limit-req     10m;
    lua_shared_dict plugin-limit-count   10m;
    lua_shared_dict prometheus-metrics   10m;
    lua_shared_dict plugin-limit-conn    10m;
    lua_shared_dict upstream-healthcheck 10m;
    lua_shared_dict worker-events        10m;
    lua_shared_dict lrucache-lock        10m;
    lua_shared_dict skywalking-tracing-buffer    100m;


    # for openid-connect plugin
    lua_shared_dict discovery             1m; # cache for discovery metadata documents
    lua_shared_dict jwks                  1m; # cache for JWKs
    lua_shared_dict introspection        10m; # cache for JWT verification results

    # for custom shared dict

    # for proxy cache
    proxy_cache_path /tmp/disk_cache_one levels=1:2 keys_zone=disk_cache_one:50m inactive=1d max_size=1G;

    # for proxy cache
    map $upstream_cache_zone $upstream_cache_zone_info {
        disk_cache_one /tmp/disk_cache_one,1:2;
    }

    lua_ssl_verify_depth 5;
    ssl_session_timeout 86400;

    underscores_in_headers on;

    lua_socket_log_errors off;

    resolver 192.168.1.1 192.168.0.1 valid=30;
    resolver_timeout 5;

    lua_http10_buffering off;

    lua_regex_match_limit 100000;
    lua_regex_cache_max_entries 8192;

    log_format main '$remote_addr - $remote_user [$time_local] $http_host "$request" $status $body_bytes_sent $request_time "$http_referer" "$http_user_agent" $upstream_addr $upstream_status $upstream_response_time';

    access_log logs/access.log main buffer=16384 flush=3;
    open_file_cache  max=1000 inactive=60;
    client_max_body_size 0;
    keepalive_timeout 60s;
    client_header_timeout 60s;
    client_body_timeout 60s;
    send_timeout 10s;

    server_tokens off;
    more_set_headers 'Server: APISIX web server';

    include mime.types;
    charset utf-8;

    real_ip_header X-Real-IP;

    set_real_ip_from 127.0.0.1;
    set_real_ip_from unix:;

    upstream apisix_backend {
        server 0.0.0.1;
        balancer_by_lua_block {
            apisix.http_balancer_phase()
        }

        keepalive 320;
    }

    init_by_lua_block {
        require "resty.core"
        apisix = require("apisix")

        local dns_resolver = { "192.168.1.1", "192.168.0.1", }
        local args = {
            dns_resolver = dns_resolver,
        }
        apisix.http_init(args)
    }

    init_worker_by_lua_block {
        apisix.http_init_worker()
    }


    server {
        listen 9080 reuseport;
        listen 9443 ssl http2 reuseport;


        listen [::]:9080 reuseport;
        listen [::]:9443 ssl http2 reuseport;

        ssl_certificate      cert/apisix.crt;
        ssl_certificate_key  cert/apisix.key;
        ssl_session_cache    shared:SSL:20m;
        ssl_session_timeout 10m;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers on;

        location = /apisix/nginx_status {
            allow 127.0.0.0/24;
            deny all;
            access_log off;
            stub_status;
        }

        location /apisix/admin {
                allow 127.0.0.0/24;
                deny all;

            content_by_lua_block {
                apisix.http_admin()
            }
        }

        location /apisix/dashboard {
                allow 127.0.0.0/24;
                deny all;

            alias dashboard/;

            try_files $uri $uri/index.html /index.html =404;
        }

        ssl_certificate_by_lua_block {
            apisix.http_ssl_phase()
        }

        location / {
            set $upstream_mirror_host        '';
            set $upstream_scheme             'http';
            set $upstream_host               $host;
            set $upstream_upgrade            '';
            set $upstream_connection         '';
            set $upstream_uri                '';

            access_by_lua_block {
                apisix.http_access_phase()
            }

            proxy_http_version 1.1;
            proxy_set_header   Host              $upstream_host;
            proxy_set_header   Upgrade           $upstream_upgrade;
            proxy_set_header   Connection        $upstream_connection;
            proxy_set_header   X-Real-IP         $remote_addr;
            proxy_pass_header  Server;
            proxy_pass_header  Date;

            ### the following x-forwarded-* headers is to send to upstream server

            set $var_x_forwarded_for        $remote_addr;
            set $var_x_forwarded_proto      $scheme;
            set $var_x_forwarded_host       $host;
            set $var_x_forwarded_port       $server_port;

            if ($http_x_forwarded_for != "") {
                set $var_x_forwarded_for "${http_x_forwarded_for}, ${realip_remote_addr}";
            }
            if ($http_x_forwarded_proto != "") {
                set $var_x_forwarded_proto $http_x_forwarded_proto;
            }
            if ($http_x_forwarded_host != "") {
                set $var_x_forwarded_host $http_x_forwarded_host;
            }
            if ($http_x_forwarded_port != "") {
                set $var_x_forwarded_port $http_x_forwarded_port;
            }

            proxy_set_header   X-Forwarded-For      $var_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto    $var_x_forwarded_proto;
            proxy_set_header   X-Forwarded-Host     $var_x_forwarded_host;
            proxy_set_header   X-Forwarded-Port     $var_x_forwarded_port;

            ###  the following configuration is to cache response content from upstream server

            set $upstream_cache_zone            off;
            set $upstream_cache_key             '';
            set $upstream_cache_bypass          '';
            set $upstream_no_cache              '';
            set $upstream_hdr_expires           '';
            set $upstream_hdr_cache_control     '';

            proxy_cache                         $upstream_cache_zone;
            proxy_cache_valid                   any 10s;
            proxy_cache_min_uses                1;
            proxy_cache_methods                 GET HEAD;
            proxy_cache_lock_timeout            5s;
            proxy_cache_use_stale               off;
            proxy_cache_key                     $upstream_cache_key;
            proxy_no_cache                      $upstream_no_cache;
            proxy_cache_bypass                  $upstream_cache_bypass;

            proxy_hide_header                   Cache-Control;
            proxy_hide_header                   Expires;
            add_header      Cache-Control       $upstream_hdr_cache_control;
            add_header      Expires             $upstream_hdr_expires;
            add_header      Apisix-Cache-Status $upstream_cache_status always;

            proxy_pass      $upstream_scheme://apisix_backend$upstream_uri;
            mirror          /proxy_mirror;

            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()
            }
        }

        location @grpc_pass {

            access_by_lua_block {
                apisix.grpc_access_phase()
            }

            grpc_set_header   Content-Type application/grpc;
            grpc_socket_keepalive on;
            grpc_pass         grpc://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()
            }
        }

        location = /proxy_mirror {
            internal;

            if ($upstream_mirror_host = "") {
                return 200;
            }

            proxy_pass $upstream_mirror_host$request_uri;
        }
    }
}

На этом процесс запуска не закончен, он просто генерирует требуемыйnginx.confконфигурационный файл.

init_etcd: запустить etcd

Следующее, чтобы начать etcd, этот раздел в основном для чтенияyaml_confв настройках, связанных с etcd.

local function init_etcd(show_output)
    -- 读取yaml_conf
    local yaml_conf, err = read_yaml_conf()
    --一系列的参数校验
    if not yaml_conf then
        error("failed to read local yaml config of apisix: " .. err)
    end

    if not yaml_conf.apisix then
        error("failed to read `apisix` field from yaml file when init etcd")
    end

    if yaml_conf.apisix.config_center ~= "etcd" then
        return true
    end

    if not yaml_conf.etcd then
        error("failed to read `etcd` field from yaml file when init etcd")
    end
    --最终拿到根etcd相关的参数
    local etcd_conf = yaml_conf.etcd
    ……
end

Информация о стеке конфигурации etcd_conf выглядит следующим образом.

Продолжать

local function init_etcd(show_output)
    ……
    local timeout = etcd_conf.timeout or 3
    local uri
    --将旧的单个etcd配置转换为多个etcd配置
    if type(yaml_conf.etcd.host) == "string" then
        yaml_conf.etcd.host = {yaml_conf.etcd.host}
    end
    --获取配置的etcd集群的多个地址
    local host_count = #(yaml_conf.etcd.host)

    -- 检查用户是否已启用etcd v2协议
    for index, host in ipairs(yaml_conf.etcd.host) do
        uri = host .. "/v2/keys"
        --这里就是拼接cmd命令了,在我的环境中,拼接出来的命令就是
        --curl -i -m 60 -o /dev/null -s -w %{http_code} http://127.0.0.1:2379/v2/keys
        --这个命令相当于获取curl访问结果的http状态码
        local cmd = "curl -i -m ".. timeout * 2 .. " -o /dev/null -s -w %{http_code} " .. uri  
        --执行命令
        local res = excute_cmd(cmd)
        --校验返回结果
        if res == "404" then
            --io.stderr:标准错误输出
            io.stderr:write(string.format("failed: please make sure that you have enabled the v2 protocol of etcd on %s.\n", host))
            return
        end
    end
    ……
end

Здесь, в соответствии с информацией о хосте настроенного кластера etcd (их может быть несколько), используйте команду curl для доступа, получите код состояния результата доступа и проверьте, включил ли пользователь протокол v2 etcd.

эквивалентно выполнениюcurl -i -m 60 -o /dev/null -s -w %{http_code} http://127.0.0.1:2379/v2/keys

Эффект следующий

Продолжать

local function init_etcd(show_output)
    ……
    local etcd_ok = false
    --遍历etcd集群的主机地址
    for index, host in ipairs(yaml_conf.etcd.host) do

        local is_success = true
        --host --> http://127.0.0.1:2379
        --etcd_conf.prefix --> /apisix
        uri = host .. "/v2/keys" .. (etcd_conf.prefix or "")
        --准备在etcd中创建一些目录
        for _, dir_name in ipairs({"/routes", "/upstreams", "/services",
                                   "/plugins", "/consumers", "/node_status",
                                   "/ssl", "/global_rules", "/stream_routes",
                                   "/proto"}) do
            --拼接cmd命令
            local cmd = "curl " .. uri .. dir_name
                    .. "?prev_exist=false -X PUT -d dir=true "
                    .. "--connect-timeout " .. timeout
                    .. " --max-time " .. timeout * 2 .. " --retry 1 2>&1"
            --cmd示例: curl http://127.0.0.1:2379/v2/keys/apisix/routes?prev_exist=false -X PUT -d dir=true --connect-timeout 30 --max-time 60 --retry 1 2>&1
            local res = excute_cmd(cmd)
            --curl命令的返回结果中进行字符串匹配,找到有没有index或者createdIndex的关键词
            --这个判断条件的意思是:在返回结果中即没有匹配上createdIndex(创建成功时返回的res中含有的关键词),也没有匹配上index(重复创建时返回的关键词)
            if not res:find("index", 1, true)
                    and not res:find("createdIndex", 1, true) then
                --如果进入这里,说明本次循环(目录,routers之类的)中,当前要创建的资源实际上无法创建了,因为这个返回的结果既不是创建成功的结果,也不是重复创建的提示,那么这个资源无法创建了
                is_success = false
                --如果当前已经遍历到了配置的最后一个etcd host地址,那么直接报错,因为没有机会再次在其他host上创建这个资源了
                if (index == host_count) then
                    error(cmd .. "\n" .. res)
                end
                --如果是否的话,则退出当前循环,继续创建目录中的其他资源
                break
            end
            --是否输出cmd和res
            if show_output then
                print(cmd)
                print(res)
            end
        end
        --如果在一个host上创建成功,则退出循环(host),标识etcd_ok资源初始化成功
        if is_success then
            etcd_ok = true
            break
        end
    end
    
    --虽然可能会配置了etcd集群的多个host地址,但是只要有一个执行成功了初始化,就算完成了
    if not etcd_ok then
        error("none of the configured etcd works well")
    end
end

Выведенная команда cmd:

curl http://127.0.0.1:2379/v2/keys/apisix/routes?prev_exist=false -X PUT -d dir=true --connect-timeout 30 --max-time 60 --retry 1 2>&1

Следующие параметры в этой команде — это команды, полученные API etcd v2.

  • prev_exist: проверить, существует ли ключ: если prev_exist равен true, это запрос на обновление; если prev_exist равен false, это запрос на создание
  • dir=true: создать каталог

Команда cmd выражается как: создать ресурс каталога на API, После успешного создания соответствующая конфигурация в etcd выглядит следующим образом

Эффект выполнения cmd следующийВозвращается, когда создание успешно

{"action":"set","node":{"key":"/apiseven/routes","dir":true,"modifiedIndex":91,"createdIndex":91}}

Возвращает, если такое же значение уже было создано

{"errorCode":102,"message":"Not a file","cause":"/apiseven/upstreams","index":89}

Поэтому при проверке результата выполнения cmd он будет соответствоватьindexа такжеcreatedIndex, если он соответствует одному из них, это означает, что каталог был успешно создан.

Логика двухуровневого цикла в этом процессе немного коварна.Утверждается, что все ресурсы каталога должны быть успешно созданы в сконфигурированном кластере etcd.Хотя питомник не создается на том же хосте, при условии, что он создан успешно , Тогда etcd гарантирует возможную согласованность.

На данный момент две важные ссылки в процессе запуска apisix

  • Создайте nginx.conf
  • Инициализировать ресурсы каталога etcd

Завершено.

openresty_args: запустить openresty

Последняя часть стартовой функции выглядит следующим образом:

local openresty_args = [[openresty  -p ]] .. apisix_home .. [[ -c ]]
                       .. apisix_home .. [[/conf/nginx.conf]]

function _M.start(...)

    local cmd = openresty_args
    -- print(cmd)
    os.execute(cmd)
end

По сути, это запуск опенрестов, аписикс — это приложение, построенное на опенресах

Команда cmd склеивается следующим образом:

openresty  -p /usr/local/Cellar/apisix/apache-apisix-1.5 -c /usr/local/Cellar/apisix/apache-apisix-1.5/conf/nginx.conf
  • -p указывает каталог проекта
  • -c указывает файл конфигурации

0x03 end

Если есть ошибки, указывайте на них, обсуждайте и учитесь вместе.