LabHub
Get started
배우기 러닝패스 코스

Building an EAI Middleware Layer

The Gatekeeper Doesn't Do the Work Inside

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

API 게이트웨이는 HTTP API 앞에 서서 누가(인증), 얼마나(쿼터), 어느 버전으로(라우팅) 부르는지를 한 곳에서 정한다. EAI 허브가 전문을 번역해 시스템 사이를 잇는다면, 게이트웨이는 업무 로직을 모른 채 호출의 문지기 역할만 한다. 제품(Kong, Apigee 등)을 쓰든 nginx 로 만들든 하는 일의 원리는 같다.

왜 이게 필요했나

은행이 제휴사·핀테크에 조회 API 를 열었다고 하자. 처음에는 API 서버가 직접 키를 확인하고, 호출 수를 세고, 옛 버전 요청을 새 버전으로 돌렸다. API 서버가 셋으로 늘자 세 곳의 키 확인 코드가 조금씩 달라졌다. 한 제휴사가 버그로 초당 수천 건을 쏟아내자 모든 제휴사가 함께 느려졌다. v1 을 내리려고 보니 누가 아직 v1 을 쓰는지 아무도 몰랐다.

이 세 가지 — 인증, 호출량 제한, 버전 — 는 업무와 무관하게 모든 API 에 똑같이 필요하다. 그래서 API 서버마다 구현하지 않고 앞단 한 곳에 모은다. 반대로 게이트웨이에 업무 규칙(잔액 확인, 한도 계산)을 넣기 시작하면 게이트웨이가 두 번째 애플리케이션 서버가 된다. 문지기는 신원을 보고 문을 열 뿐, 방 안의 일을 하지 않는다.

어떻게 동작하나

이 실습은 제품 없이 nginx 의 기본 모듈로 원리를 만든다. 공식 문서의 지시어만 쓴다.

인증 — API 키를 소비자 이름으로. map 은 요청 값(여기서는 X-API-Key 헤더, 변수 $http_x_api_key)을 다른 변수로 바꾸는 표다. 키 → 소비자 이름 표를 파일로 두고 include 한다. 표에 없는 키는 기본값(빈 값)이 되고, 빈 소비자는 401 로 돌려보낸다. 업스트림에는 소비자 이름X-Consumer 로 넘기고 키 자체는 넘기지 않는다 — proxy_set_header 의 값을 빈 문자열로 주면 그 헤더는 업스트림에 전달되지 않는다. 비밀은 게이트웨이에서 멈추고, 업스트림 로그에 키가 새지 않는다. (API 키는 호출하는 프로그램을 식별할 뿐 사용자를 인증하지 않는다. 사용자 단위 권한이 필요하면 토큰을 쓴다 — 이 코스 범위 밖이다.)

쿼터 — 소비자별 속도 제한(rate limit). limit_req 모듈은 문서의 표현대로 "누설 버킷(leaky bucket)" 방식으로 요청 처리 속도를 제한한다. limit_req_zone $consumer zone=… rate=5r/s소비자 이름을 열쇠로 영역을 만들면 제한이 소비자마다 따로 걸린다(열쇠를 IP 로 하면 한 NAT 뒤의 모든 제휴사가 한 버킷을 나눠 쓴다). 문서에 따르면 열쇠가 빈 요청은 세지 않는다. burst 는 순간적으로 몰린 요청을 몇 개까지 줄 세울지, nodelay 는 줄 세운 요청을 늦추지 않고 바로 처리할지를 정한다. 한도를 넘은 요청은 기본으로 503 을 받는데, limit_req_status 429 로 바꾼다 — 429 Too Many Requests 는 RFC 6585 가 "보낸 요청이 너무 많다" 는 뜻으로 정의한 상태코드라, 호출자가 "서버가 아프다(503)" 와 "내가 너무 많이 보냈다(429)" 를 구분할 수 있다.

버전 — 경로와 헤더. 경로에 버전을 넣는 방식(/v1/…, /v2/…)은 눈에 보이고 캐시·로그에서 구분이 쉽다. 헤더로 고르는 방식(X-API-Version: 2)은 경로를 바꾸지 않는다. 이 실습은 둘 다 받는다: 버전 없는 /api/… 는 헤더로 고르고, 헤더가 없으면 v1 이다. 그리고 내릴 버전에는 Sunset 헤더(RFC 8594)를 붙여 "이 자원은 이 시각 이후 응답하지 않을 수 있다" 고 알린다. 값은 HTTP 날짜 형식이다. 응답마다 붙어 있으니 호출자의 로그와 모니터링이 스스로 알아챈다 — 공지 메일보다 확실하다.

요청 ID. 호출자가 X-Request-ID 를 보냈으면 그대로 넘기고, 없으면 게이트웨이가 만든다. nginx 의 $request_id문서상 무작위 16바이트를 16진수로 적은 고유 식별자다. 이 ID 를 접근 로그에 소비자·상태와 함께 남기면, 제휴사가 "어제 14시에 429 를 받았다" 고 할 때 그 줄을 바로 찾는다(7모듈의 GUID 와 같은 생각을 HTTP 경계에 적용한 것이다).

타임아웃. 업스트림이 멈추면 게이트웨이의 연결도 같이 멈춘다. proxy_read_timeout 은 업스트림에서 두 번의 읽기 사이에 기다리는 최대 시간이고, 넘으면 게이트웨이가 504 를 돌려준다. 기본값은 60초인데, 호출자가 10초 만에 포기하는 API 라면 게이트웨이가 60초를 붙들고 있을 이유가 없다(4모듈의 '안쪽일수록 짧게').

EAI 허브와 무엇이 다른가. 허브는 전문을 번역하고(형식·코드), 동기·비동기를 바꾸고, 여러 시스템을 조합한다. 게이트웨이는 HTTP 요청을 거의 그대로 통과시키면서 통제만 한다. 현장에서는 둘이 함께 있다 — 외부 제휴사 → 게이트웨이 → 허브 → 계정계.

현장에서 만나는 모습

첫째, IP 로 쿼터를 거는 실수. 대형 제휴사가 NAT 뒤에서 여러 서비스를 돌려 서로의 한도를 잡아먹는다. 소비자 식별이 먼저다. 둘째, 한도 초과를 503 으로 돌려주는 설정. 호출자의 재시도 로직이 "서버 장애" 로 보고 더 세게 재시도한다. 셋째, 업스트림 로그에 API 키가 남는 것 — 로그 수집 시스템에 제휴사 키가 평문으로 쌓인다. 넷째, 버전 폐기 공지를 메일로만 하는 것. 담당자가 바뀐 제휴사는 모르고 있다가 폐기 날 장애를 겪는다.

다음 실습에서 할 것

업스트림 픽스처(v1·v2, 받은 헤더를 비춰 주는 API)를 띄우고, nginx 설정 nginx.conf 를 단계마다 키운다 — 경로 프록시, API 키 인증과 소비자 이름 전달, 소비자별 쿼터와 429, 헤더 기반 버전 라우팅과 Sunset, 요청 ID 와 접근 로그, 업스트림 타임아웃. 채점기는 여러분의 설정 파일을 복사해 자기 포트로 새로 띄워 요청을 흘려 본다.