최근 마이크로서비스 아키텍처(MSA)에서 API 게이트웨이는 인증, 로깅, 속도 제한 등 공통 기능을 처리하는 핵심 컴포넌트로 자리 잡았습니다. 특히 Apache APISIX는 Lua 스크립트를 통해 동적이고 유연한 라우팅 및 플러그인 개발이 가능하여 높은 인기를 얻고 있습니다.
이 글에서는 APISIX의 강력한 Lua 실행 환경을 활용하여, MongoDB를 위한 REST API 서버인 RESTHeart와 연동하는 실전 예제를 다룹니다. 이 구조를 통해 API 게어트웨이 단계에서 직접 MongoDB 데이터를 조회, 등록, 수정, 삭제하는 동적 CRUD(Create, Read, Update, Delete) 엔드포인트를 구현하는 방법을 상세히 소개합니다.
아키텍처 개요
우리가 구현할 시스템의 구조는 매우 간단하면서도 강력합니다.
[Client]
↔ [APISIX (Lua Plugin)]
↔ [RESTHeart (MongoDB REST API)]
↔ [MongoDB]
- Client: API 게이트웨이에 표준 HTTP 요청을 보냅니다.
- APISIX (Lua Plugin):
- 클라이언트의 요청(HTTP Method, Query Parameter, Body)을 분석합니다.
- 요청에 맞춰 내부적으로 RESTHeart API를 호출할 URL과 데이터를 구성합니다.
- RESTHeart에 HTTP 요청을 보내고 응답을 받습니다.
- 받은 응답을 가공하여 최종적으로 클라이언트에게 전달합니다.
- RESTHeart: APISIX로부터 받은 요청을 해석하여 실제 MongoDB 쿼리를 실행하고, 결과를 JSON 형태로 반환합니다.
이 구조의 핵심은 클라이언트가 RESTHeart의 존재를 전혀 알 필요가 없다는 점입니다. 모든 복잡성은 APISIX의 Lua 플러그인 레이어에서 추상화됩니다.
1. APISIX Lua 플러그인 개발: 동적 CRUD 처리
먼저, 클라이언트의 HTTP 요청 메서드(GET, POST, PUT, DELETE)에 따라 RESTHeart에 적절한 API를 호출하는 Lua 플러그인을 작성합니다.
1-1. 플러그인 기본 구조 (rest-crud.lua
)
아래 코드는 resty.http
라이브러리를 사용하여 RESTHeart와 통신하는 커스텀 플러그인의 전체 구조입니다.
-- resty.http: non-blocking HTTP client
-- cjson.safe: safe and fast JSON encoding/decoding
local http = require("resty.http")
local cjson = require("cjson.safe")
local _M = {}
-- access 단계에서 실행될 함수
function _M.access(conf, ctx)
-- 1. 클라이언트 요청 분석
local method = ctx.var.request_method
local uri_args = ngx.req.get_uri_args()
-- 쿼리 파라미터에서 MongoDB의 collection과 document ID 추출
local path = uri_args["path"] or "/users" -- 기본 collection은 'users'
local id = uri_args["id"]
-- 2. RESTHeart API URL 구성
local restheart_url = "http://restheart.default.svc" .. path -- Kubernetes 내부 서비스 주소 예시
if id then
restheart_url = restheart_url .. "/" .. id
end
-- 3. HTTP 클라이언트 생성 및 요청 분기
local httpc = http.new()
local res, err
if method == "GET" then
res, err = httpc:request_uri(restheart_url, { method = "GET" })
elseif method == "POST" then
ngx.req.read_body()
local body_data = ngx.req.get_body_data()
res, err = httpc:request_uri(restheart_url, {
method = "POST",
body = body_data,
headers = { ["Content-Type"] = "application/json" }
})
elseif method == "PUT" then
ngx.req.read_body()
local body_data = ngx.req.get_body_data()
res, err = httpc:request_uri(restheart_url, {
method = "PUT",
body = body_data,
headers = { ["Content-Type"] = "application/json" }
})
elseif method == "DELETE" then
res, err = httpc:request_uri(restheart_url, { method = "DELETE" })
end
-- 4. 예외 처리 및 응답 반환
if not res then
ngx.log(ngx.ERR, "request to RESTHeart failed: ", err)
return ngx.exit(502, cjson.encode({ message = "Bad Gateway: Failed to connect to backend service." }))
end
-- RESTHeart의 응답을 클라이언트에게 그대로 전달
ngx.status = res.status
ngx.header["Content-Type"] = res.headers["Content-Type"] or "application/json"
ngx.say(res.body)
-- 요청 처리를 여기서 종료
return ngx.exit(res.status)
end
return _M
2. APISIX에 플러그인 등록 및 적용
개발한 Lua 플러그인을 APISIX가 인식하도록 설정해야 합니다.
- 파일 저장: 작성한
rest-crud.lua
파일을 APISIX의 플러그인 디렉토리(예:/usr/local/apisix/plugins/
)에 저장합니다. config.yaml
설정: APISIX의 메인 설정 파일인config.yaml
에 커스텀 플러그인을 추가합니다.plugins: - ... # 기존 플러그인들 - rest-crud # 새로 추가한 플러그인 이름
- APISIX 재시작:
apisix restart
명령으로 설정을 다시 로드합니다. - Route에 플러그인 적용: Admin API를 통해 특정 경로(
/crud
)에rest-crud
플러그인을 활성화하는 라우팅 규칙을 생성합니다.curl -i -X PUT http://127.0.0.1:9180/apisix/admin/routes/1 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ -H 'Content-Type: application/json' -d ' { "uri": "/crud", "plugins": { "rest-crud": {} }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:80": 1 -- upstream은 형식적으로만 필요, 실제 요청은 플러그인이 처리 } } }'
3. 클라이언트 API 사용 예시
이제 클라이언트는 APISIX의 /crud
엔드포인트를 통해 MongoDB 데이터를 간편하게 조작할 수 있습니다.
데이터 조회 (GET)
# 'users' collection에서 '_id'가 'user123'인 문서 조회
curl "http://127.0.0.1:9080/crud?path=/users&id=user123"
데이터 등록 (POST)
curl -X POST "http://127.0.0.1:9080/crud?path=/users" \
-H "Content-Type: application/json" \
-d '{"_id": "user123", "name": "홍길동", "email": "hong@test.com"}'
데이터 수정 (PUT)
curl -X PUT "http://127.0.0.1:9080/crud?path=/users&id=user123" \
-H "Content-Type: application/json" \
-d '{"name": "김철수"}'
데이터 삭제 (DELETE)
curl -X DELETE "http://127.0.0.1:9080/crud?path=/users&id=user123"
4. 실전 적용을 위한 보완 팁
- 인증 정보 추가: 만약 RESTHeart가 인증(Basic Auth 등)을 요구한다면, Lua 코드에서
headers
테이블에Authorization
헤더를 추가해야 합니다.lua headers = { ["Content-Type"] = "application/json", ["Authorization"] = "Basic " .. ngx.encode_base64("user:password") }
- 설정 값 외부화: RESTHeart의 URL과 같은 설정 값을 코드에 하드코딩하는 대신, 플러그인 설정(
conf
파라미터)을 통해 주입받도록 수정하면 재사용성이 높아집니다. - 에러 핸들링 강화:
res.status
코드를 검사하여 4xx, 5xx 에러에 대해 일관된 형식의 JSON 에러 메시지를 생성하면 클라이언트가 오류를 처리하기 용이합니다. - 성능 최적화:
httpc:set_keepalive()
를 사용하여 RESTHeart와의 연결을 재사용하고,httpc:set_timeout()
으로 타임아웃을 설정하여 안정성을 높일 수 있습니다.
이처럼 APISIX와 Lua를 조합하면, 기존 백엔드 서비스의 변경 없이도 API 게이트웨이 레벨에서 강력하고 동적인 데이터 처리 로직을 구현할 수 있습니다. 이는 MSA 환경에서 서비스 간 의존성을 줄이고, 공통 로직을 중앙에서 관리하는 효율적인 아키텍처를 구축하는 데 큰 도움이 됩니다.