Como Construir um Plugin do Apache APISIX do Zero ao Um?
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.
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:
- Determinar a categoria do plugin.
- 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.
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 arquivoMakefile
. - 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.
- Aqui criamos o arquivo
file-logger.lua
no diretórioapisix/plugins/
. - 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.
-
Prepare um upstream de teste localmente (o upstream de teste usado neste artigo é
127.0.0.1:3030/api/hello
, que criei localmente). -
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. -
Execute o comando
curl
para enviar uma solicitação à rota para testar se o pluginfile-logger
foi iniciado.curl -i http://127.0.0.1:9080/api/hello HTTP/1.1 200 OK ... hello, world
-
No arquivo
logs/error.log
haverá um registro: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âmetrosconf
ectx
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 comandoapisix 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.
-
Após cada solicitação aceita, enviar os dados de log para o
path
configurado pelo plugin.- Primeiro, obtenha o valor de
path
nofile-logger
por meio deconf
na fase de log. - Em seguida, a biblioteca IO do Lua é usada para criar, abrir, escrever, atualizar o cache e fechar o arquivo.
- Primeiro, obtenha o valor de
-
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
-
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.