Apache APISIX 플러그인을 0에서 1로 만드는 방법은?
지난 몇 달 동안 커뮤니티 사용자들이 Apache APISIX에 많은 플러그인을 추가하여 Apache APISIX 생태계를 풍부하게 만들었습니다. 사용자 관점에서 더 다양한 플러그인의 등장은 확실히 좋은 일입니다. 이는 Apache APISIX의 고성능과 낮은 지연 시간을 완벽하게 유지하면서, "원스톱" 및 "다기능" 프로세서로서의 사용자 기대를 더 많이 충족시켜주기 때문입니다.
Apache APISIX 블로그의 어떤 글도 플러그인 개발 과정에 대해 자세히 다루지 않는 것 같습니다. 그래서 이번에는 플러그인 개발자의 관점에서 플러그인이 어떻게 탄생하는지 살펴보겠습니다!
이 글은 백엔드 경험이 없는 프론트엔드 엔지니어가 file-logger
플러그인을 개발한 과정을 기록한 것입니다. 구현 과정의 세부 사항을 파헤치기 전에, file-logger
의 기능을 간단히 소개하겠습니다.
file-logger 플러그인 소개
file-logger
는 Apache APISIX 플러그인 메타데이터를 사용하여 사용자 정의 로그 형식을 생성하는 것을 지원합니다. 사용자는 file-logger
플러그인을 통해 요청 및 응답 데이터를 JSON 형식으로 로그 파일에 추가하거나, 로그 데이터 스트림을 지정된 위치로 푸시할 수 있습니다.
상상해보세요: 라우트의 액세스 로그를 모니터링할 때, 우리는 특정 요청 및 응답 데이터의 값뿐만 아니라 로그 데이터를 지정된 파일에 기록하고 싶어합니다. 이때 file-logger
플러그인을 사용하여 이러한 목표를 달성할 수 있습니다.
file-logger
를 사용하여 로그 데이터를 특정 로그 파일에 기록함으로써 모니터링 및 디버깅 과정을 단순화할 수 있습니다.
플러그인을 어떻게 구현할까요?
file-logger
의 기능을 소개한 후, 이 플러그인에 대해 더 잘 이해하셨을 것입니다. 다음은 서버 측 경험이 없는 프론트엔드 개발자인 제가 Apache APISIX용 플러그인을 개발하고 해당 테스트를 추가한 방법에 대한 자세한 설명입니다.
플러그인의 이름과 우선순위 확인
Apache APISIX 플러그인 개발 가이드를 열고 우선순위에 따라 다음 두 가지를 결정해야 합니다:
- 플러그인 카테고리를 결정합니다.
- 플러그인의 우선순위를 정하고
conf/config-default.yaml
파일을 업데이트합니다.
이번 file-logger
개발은 로깅 유형의 플러그인이므로, 기존 Apache APISIX 로깅 플러그인의 이름과 순서를 참고하여 file-logger
를 배치했습니다.
다른 플러그인 작성자 및 커뮤니티의 열성적인 멤버들과 상의한 후, 플러그인의 이름 file-logger
와 우선순위 399
가 최종 확정되었습니다.
플러그인의 우선순위는 실행 순서와 관련이 있습니다. 우선순위 값이 높을수록 실행 순서가 앞쪽입니다. 그리고 플러그인 이름의 순서는 실행 순서와 관련이 없습니다.
최소 실행 가능한 플러그인 파일 생성
플러그인 이름과 우선순위를 확인한 후, apisix/plugins/
디렉토리에 플러그인 코드 파일을 생성할 수 있습니다. 여기서 주의할 점은 두 가지입니다:
- 플러그인 코드 파일을
apisix/plugins/
디렉토리에 직접 생성하는 경우,Makefile
파일을 변경할 필요가 없습니다. - 플러그인이 자신의 코드 디렉토리를 가지고 있다면,
Makefile
파일을 업데이트해야 합니다. 자세한 단계는 Apache APISIX 플러그인 개발 가이드를 참조하세요.
- 여기서는
apisix/plugins/
디렉토리에file-logger.lua
파일을 생성합니다. - 그런 다음
example-plugin
을 기반으로 초기화된 버전을 완성합니다.
-- 헤더에 필요한 모듈을 가져옵니다.
local log_util = require("apisix.utils.log-util")
local core = require("apisix.core")
local plugin = require("apisix.plugin")
local ngx = ngx
-- 플러그인의 이름을 선언합니다.
local plugin_name = "file-logger"
-- 플러그인 스키마 형식을 정의합니다.
local schema = {
type = "object",
properties = {
path = {
type = "string"
},
},
required = {"path"}
}
-- 플러그인 메타데이터 스키마
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
}
-- 플러그인 구성이 올바른지 확인합니다.
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
-- 로그 단계
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
최소한의 사용 가능한 플러그인 파일이 준비되면, core.log.warn(core.json.encode(conf))
와 core.log.warn("ctx: ", core.json.encode(ctx, true))
를 통해 플러그인의 구성 데이터와 요청 관련 데이터를 error.log
파일에 출력할 수 있습니다.
플러그인 활성화 및 테스트
다음은 테스트를 위한 몇 가지 단계입니다. 플러그인이 우리가 구성한 플러그인 데이터와 요청 관련 데이터 정보를 오류 로그 파일에 성공적으로 출력할 수 있는지 테스트하기 위해, 플러그인을 활성화하고 테스트 라우트를 생성해야 합니다.
-
로컬에서 테스트 업스트림을 준비합니다(이 글에서 사용한 테스트 업스트림은
127.0.0.1:3030/api/hello
로, 제가 로컬에서 생성했습니다). -
curl
명령을 통해 라우트를 생성하고 새로운 플러그인을 활성화합니다.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" }'
그러면 상태 코드
200
이 표시되며, 이는 라우트가 성공적으로 생성되었음을 나타냅니다. -
curl
명령을 실행하여 라우트에 요청을 보내file-logger
플러그인이 시작되었는지 테스트합니다.curl -i http://127.0.0.1:9080/api/hello HTTP/1.1 200 OK ... hello, world
-
logs/error.log
파일에 다음과 같은 기록이 있습니다:보시다시피, 플러그인에 대해 구성한
path: logs/file.log
가 성공적으로 저장되었습니다. 이 시점에서 우리는conf
및ctx
매개변수를 로깅 단계에서 출력하는 최소한의 사용 가능한 플러그인을 성공적으로 생성했습니다.이후,
file-logger.lua
플러그인의 코드 파일에 직접 핵심 기능을 작성할 수 있습니다. 여기서는 Apache APISIX를 재시작하지 않고도apisix reload
명령을 실행하여 최신 플러그인 코드를 다시 로드할 수 있습니다.
file-logger 플러그인의 핵심 기능 작성
file-logger
플러그인의 주요 기능은 로그 데이터를 작성하는 것입니다. 커뮤니티의 다른 사람들에게 물어보고 자료를 확인한 후, Lua의 IO 라이브러리에 대해 알게 되었고, 플러그인의 기능 로직이 대략 다음과 같은 단계임을 확인했습니다.
-
각 수락된 요청 후, 플러그인에 구성된
path
에 로그 데이터를 출력합니다.- 먼저, 로깅 단계에서
conf
를 통해file-logger
의path
값을 가져옵니다. - 그런 다음, Lua IO 라이브러리를 사용하여 파일을 생성, 열기, 쓰기, 캐시 새로 고침 및 닫기를 수행합니다.
- 먼저, 로깅 단계에서
-
파일 열기 실패, 파일 생성 실패 등의 오류를 처리합니다.
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
-
http-logger
플러그인의 소스 코드를 참고하여, 로그 데이터를 전달하고 메타데이터의 일부 판단 및 처리를 완료했습니다.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
검증 및 테스트 추가
로그 기록 검증
테스트 라우트를 생성할 때 file-logger
플러그인이 활성화되었고, 경로가 logs/file.log
로 구성되었으므로, 이제 테스트 라우트에 요청을 보내 로그 수집 결과를 검증할 수 있습니다.
curl -i http://127.0.0.1:9080/api/hello
해당 logs/file.log
에서 각 기록이 JSON 형식으로 저장된 것을 확인할 수 있습니다. 데이터 중 하나를 포맷하면 다음과 같습니다.
{
"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"
}
}
이로써 로그 기록 수집에 대한 검증이 완료되었습니다. 검증 결과는 플러그인이 성공적으로 시작되었고 적절한 데이터를 반환했음을 나타냅니다.
플러그인에 대한 더 많은 테스트 추가
add_block_preprocessor
부분의 코드를 처음 작성할 때는 Perl에 대한 경험이 없어 혼란스러웠습니다. 조사한 후, 올바른 사용 방법을 알게 되었습니다: 데이터 섹션에 request
어설션과 no_error_log
어설션을 작성하지 않으면 기본 어설션은 다음과 같습니다.
--- request
GET /t
--- no_error_log
[error]
다른 로깅 테스트 파일을 참고하여, t/plugin/
디렉토리에 file-logger.t
파일을 생성했습니다.
각 테스트 파일은 **DATA**
로 구분되어 전처리 섹션과 데이터 섹션으로 나뉩니다. 공식 웹사이트에 테스트 관련 문서의 명확한 분류가 없으므로, 더 자세한 내용은 글 끝의 관련 자료를 참조하세요. 관련 자료를 참고하여 완성한 테스트 케이스 중 하나는 다음과 같습니다.
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
이로써 플러그인 추가 테스트 세션이 마무리되었습니다.
요약
위는 백엔드 초보자가 0부터 Apache APISIX 플러그인을 구현한 전체 과정입니다. 플러그인 개발 과정에서 많은 문제에 부딪혔지만, 다행히 Apache APISIX 커뮤니티의 많은 열성적인 형제들이 문제를 해결하는 데 도움을 주어 file-logger
플러그인의 개발과 테스트가 전반적으로 순조롭게 진행될 수 있었습니다. 이 플러그인에 관심이 있거나 플러그인의 세부 사항을 보고 싶다면, 공식 Apache APISIX 문서를 참조하세요.
Apache APISIX는 현재 더 많은 통합 서비스를 지원하기 위해 다른 플러그인을 개발 중입니다. 관심이 있다면 GitHub Discussion에서 논의를 시작하거나, 메일링 리스트를 통해 문의하세요.