Como Construir um Plugin do Apache APISIX do Zero ao Um?

Qi Guo

Qi Guo

February 16, 2022

Ecosystem

Nos últimos meses, os usuários da comunidade adicionaram muitos plugins ao Apache APISIX, enriquecendo o ecossistema do Apache APISIX. Do ponto de vista do usuário, o surgimento de plugins mais diversos é certamente uma coisa boa, pois atendem a mais expectativas dos usuários em relação a um gateway que seja um processador "tudo-em-um" e "multifuncional", além de aprimorar o alto desempenho e a baixa latência do Apache APISIX.

Nenhum dos artigos no blog do Apache APISIX parece detalhar o processo de desenvolvimento de plugins. Então, vamos dar uma olhada no processo do ponto de vista de um desenvolvedor de plugins e ver como um plugin nasce!

Este artigo documenta o processo de desenvolvimento do plugin file-logger por um engenheiro de front-end sem experiência em back-end. Antes de mergulhar nos detalhes do processo de implementação, vamos apresentar brevemente a funcionalidade do file-logger.

Introdução ao plugin file-logger

O file-logger suporta a geração de formatos de log personalizados usando metadados de plugins do Apache APISIX. Os usuários podem anexar dados de solicitação e resposta em formato JSON a arquivos de log por meio do plugin file-logger, ou enviar o fluxo de dados de log para um local especificado.

Imagine o seguinte: ao monitorar o log de acesso de uma rota, não nos importamos apenas com o valor de certos dados de solicitação e resposta, mas também queremos escrever os dados de log em um arquivo especificado. É aqui que o plugin file-logger pode ser usado para ajudar a alcançar esses objetivos.

como funciona

Podemos usar o file-logger para escrever dados de log em um arquivo de log específico, simplificando o processo de monitoramento e depuração.

Como implementar um plugin?

Após a introdução das funcionalidades do file-logger, você terá uma melhor compreensão deste plugin. A seguir, está uma explicação detalhada de como eu, um desenvolvedor de front-end sem experiência em servidor, desenvolvi o plugin para o Apache APISIX e adicionei os testes correspondentes.

Confirmar o nome e a prioridade do plugin

Abra o Guia de Desenvolvimento de Plugins do Apache APISIX e, em ordem de prioridade, você precisa determinar as seguintes duas coisas:

  1. Determinar a categoria do plugin.
  2. Priorizar os plugins e atualizar o arquivo conf/config-default.yaml.

Como este desenvolvimento do file-logger é um plugin do tipo log, eu me referi ao nome e à ordenação dos plugins de log existentes do Apache APISIX e coloquei o file-logger aqui.

posição do file-logger

Após consultar outros autores de plugins e membros entusiastas da comunidade, o nome file-logger e a prioridade 399 do plugin foram finalmente confirmados.

Observe que a prioridade do plugin está relacionada à ordem de execução; quanto maior o valor da prioridade, mais à frente será a execução. E a ordenação dos nomes dos plugins não está relacionada à ordem de execução.

Criar um arquivo de plugin mínimo executável

Após confirmar o nome e a prioridade do plugin, você pode criar nosso arquivo de código do plugin no diretório apisix/plugins/. Há dois pontos a serem observados aqui:

  • Se o arquivo de código do plugin for criado diretamente no diretório apisix/plugins/, não há necessidade de alterar o arquivo Makefile.
  • Se o seu plugin tiver seu próprio diretório de código, você precisará atualizar o arquivo Makefile. Consulte o Guia de Desenvolvimento de Plugins do Apache APISIX para etapas detalhadas.
  1. Aqui criamos o arquivo file-logger.lua no diretório apisix/plugins/.
  2. Em seguida, completaremos uma versão inicializada com base no example-plugin.
-- Introduzir o módulo que precisamos no cabeçalho local log_util = require("apisix.utils.log-util") local core = require("apisix.core") local plugin = require("apisix.plugin") local ngx = ngx -- Declarar o nome do plugin local plugin_name = "file-logger" -- Definir o formato do esquema do plugin local schema = { type = "object", properties = { path = { type = "string" }, }, required = {"path"} } -- Esquema de metadados do plugin local metadata_schema = { type = "object", properties = { log_format = log_util.metadata_schema_log_format } } local _M = { version = 0.1, priority = 399, name = plugin_name, schema = schema, metadata_schema = metadata_schema } -- Verificar se a configuração do plugin está correta 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 -- Fase de log function _M.log(conf, ctx) core.log.warn("conf: ", core.json.encode(conf)) core.log.warn("ctx: ", core.json.encode(ctx, true)) end return _M

Uma vez que o arquivo de plugin mínimo disponível esteja pronto, os dados de configuração do plugin e os dados relacionados à solicitação podem ser enviados para o arquivo error.log por meio de core.log.warn(core.json.encode(conf)) e core.log.warn("ctx: ", core.json.encode(ctx, true)).

Habilitar e testar o plugin

A seguir estão algumas etapas para teste. Para testar se o plugin pode imprimir com sucesso os dados do plugin e as informações de dados relacionados à solicitação que configuramos para ele no arquivo de log de erro, precisamos habilitar o plugin e criar uma rota de teste.

  1. Prepare um upstream de teste localmente (o upstream de teste usado neste artigo é 127.0.0.1:3030/api/hello, que criei localmente).

  2. Crie uma rota por meio do comando curl e habilite nosso novo plugin.

    curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "plugins": { "file-logger": { "path": "logs/file.log" } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:3030": 1 } }, "uri": "/api/hello" }'

    Você verá então um código de status 200, indicando que a rota foi criada com sucesso.

  3. Execute o comando curl para enviar uma solicitação à rota para testar se o plugin file-logger foi iniciado.

    curl -i http://127.0.0.1:9080/api/hello HTTP/1.1 200 OK ... hello, world
  4. No arquivo logs/error.log haverá um registro:

    registro em logs/error.log

    Como você pode ver, o path: logs/file.log que configuramos para o plugin no parâmetro conf foi salvo com sucesso. Neste ponto, criamos com sucesso um plugin mínimo utilizável que imprime os parâmetros conf e ctx na fase de log.

    Depois disso, podemos escrever a funcionalidade principal para o plugin file-logger.lua diretamente em seu arquivo de código. Aqui, podemos executar diretamente o comando apisix reload para recarregar o código mais recente do plugin sem reiniciar o Apache APISIX.

Escrever a função principal para o plugin file-logger

A função principal do plugin file-logger é escrever dados de log. Após perguntar a outras pessoas da comunidade e verificar as informações, aprendi sobre a biblioteca IO do Lua e confirmei que a lógica da função do plugin é aproximadamente as seguintes etapas.

  1. Após cada solicitação aceita, enviar os dados de log para o path configurado pelo plugin.

    1. Primeiro, obtenha o valor de path no file-logger por meio de conf na fase de log.
    2. Em seguida, a biblioteca IO do Lua é usada para criar, abrir, escrever, atualizar o cache e fechar o arquivo.
  2. Lidar com erros como falha ao abrir o arquivo, falha ao criar o arquivo, etc.

    local function write_file_data(conf, log_message) local msg, err = core.json.encode(log_message) if err then return core.log.error("message json serialization failed, error info : ", err) end local file, err = io_open(conf.path, 'a+') if not file then core.log.error("failed to open file: ", conf.path, ", error info: ", err) else local ok, err = file:write(msg, '\n') if not ok then core.log.error("failed to write file: ", conf.path, ", error info: ", err) else file:flush() end file:close() end end
  3. Referenciando o código-fonte do plugin http-logger, concluí o método de passar os dados de log para os dados de log de escrita e alguns julgamentos e processamentos dos metadados.

    function _M.log(conf, ctx) local metadata = plugin.plugin_metadata(plugin_name) local entry if metadata and metadata.value.log_format and core.table.nkeys(metadata.value.log_format) > 0 then entry = log_util.get_custom_format_log(ctx, metadata.value.log_format) else entry = log_util.get_full_log(ngx, conf) end write_file_data(conf, entry) end

Validar e adicionar testes

Validar os registros de log

Como o plugin file-logger foi habilitado quando a rota de teste foi criada e o caminho foi configurado como logs/file.log, podemos simplesmente enviar uma solicitação para a rota de teste para verificar os resultados da coleta de log neste momento.

curl -i http://127.0.0.1:9080/api/hello

No arquivo correspondente logs/file.log, podemos ver que cada registro é salvo em formato JSON. Após formatar um dos dados, ele se parece com isso.

{ "server": { "hostname": "....", "version": "2.11.0" }, "client_ip": "127.0.0.1", "upstream": "127.0.0.1:3030", "route_id": "1", "start_time": 1641285122961, "latency": 13.999938964844, "response": { "status": 200, "size": 252, "headers": { "server": "APISIX/2.11.0", "content-type": "application/json; charset=utf-8", "date": "Tue, 04 Jan 2022 08:32:02 GMT", "vary": "Accept-Encoding", "content-length": "19", "connection": "close", "etag": "\"13-5j0ZZR0tI549fSRsYxl8c9vAU78\"" } }, "service_id": "", "request": { "querystring": {}, "size": 87, "method": "GET", "headers": { "host": "127.0.0.1:9080", "accept": "*/*", "user-agent": "curl/7.77.0" }, "url": "http://127.0.0.1:9080/api/hello", "uri": "/api/hello" } }

Isso conclui a verificação da coleta de registros de log. Os resultados da verificação indicam que o plugin foi iniciado com sucesso e retornou os dados apropriados.

Adicionar mais testes para o plugin

Para a parte do código add_block_preprocessor, eu estava confuso quando comecei a escrevê-lo porque não tinha experiência anterior com Perl. Após pesquisar, percebi a maneira correta de usá-lo: se não escrevermos asserções request e no_error_log na seção de dados, então a asserção padrão é a seguinte.

--- request GET /t --- no_error_log [error]

Após considerar alguns outros arquivos de teste de log, criei o arquivo file-logger.t no diretório t/plugin/.

Cada arquivo de teste é dividido por **DATA** em uma seção de preâmbulo e uma seção de dados. Como não há uma classificação clara de documentos relacionados a testes no site oficial, você pode consultar os materiais relacionados no final do artigo para mais detalhes. Aqui está um dos casos de teste que concluí após consultar os materiais relevantes.

use t::APISIX 'no_plan'; no_long_string(); no_root_location(); add_block_preprocessor(sub { my ($block) = @_; if (! $block->request) { $block->set_value("request", "GET /t"); } if (! $block->no_error_log && ! $block->error_log) { $block->set_value("no_error_log", "[error]"); } }); run_tests; __DATA__ === TEST 1: sanity --- config location /t { content_by_lua_block { local configs = { -- full configuration { path = "file.log" }, -- property "path" is required { path = nil } } local plugin = require("apisix.plugins.file-logger") for i = 1, #configs do ok, err = plugin.check_schema(configs[i]) if err then ngx.say(err) else ngx.say("done") end end } } --- response_body_like done property "path" is required

Isso conclui a sessão de teste de adição de plugin.

Resumo

O acima é todo o processo de implementação de um plugin do Apache APISIX do zero como um iniciante no back-end. Eu realmente encontrei muitos obstáculos no processo de desenvolvimento do plugin, mas felizmente há muitos irmãos entusiastas na comunidade do Apache APISIX para me ajudar a resolver os problemas, o que tornou o desenvolvimento e teste do plugin file-logger relativamente tranquilo. Se você estiver interessado neste plugin, ou quiser ver os detalhes do plugin, você pode consultar a documentação oficial do Apache APISIX.

O Apache APISIX também está trabalhando atualmente em outros plugins para suportar mais serviços de integração, então, se você estiver interessado, sinta-se à vontade para iniciar uma discussão no GitHub Discussion, ou via lista de e-mails.

Referências

Tags: