nginx でゲートウェイを立てる — キー・クォータ・バージョン
한국어 원문으로 표시합니다.
목표
nginx 로 API 게이트웨이의 원리를 만든다 — 경로 프록시, API 키 인증(401), 소비자별 쿼터(429), 버전 라우팅과 Sunset, 요청 ID 전파와 로그, 업스트림 타임아웃(504).
왜 중요한가
인증·호출량 제한·버전은 업무와 무관하게 모든 API 에 똑같이 필요하다. API 서버마다 따로 구현하면 조금씩 달라지고, 한 소비자의 폭주가 모두를 느리게 만들고, 누가 옛 버전을 쓰는지 아무도 모르게 된다. 게이트웨이는 이것을 업무 로직 없이 한 곳에서 통제한다.
단계
- 업스트림 두 개를 띄운다:
nohup python3 /opt/lab/fixtures/eaimw/gateway/api.py --port 9601 --version v1 > /root/eaimw/gw/v1.out 2>&1 &,… --port 9602 --version v2 …. 둘을curl로 한 번씩 불러 응답 JSON 두 줄을/root/eaimw/gw/up.txt에 저장한다. /root/eaimw/gw/nginx.conf를 쓴다(완전한 설정 파일:pid /root/eaimw/gw/nginx.pid;, 로그는/root/eaimw/gw/logs/,listen 8090;, 업스트림127.0.0.1:9601·127.0.0.1:9602)./v1/은 v1 으로,/v2/는 v2 로 경로 그대로 넘긴다.nginx -p /root/eaimw/gw -c /root/eaimw/gw/nginx.conf로 띄운다.- API 키 인증:
/root/eaimw/gw/keys.map에key-channel-7f3a channel;·key-partner-19c2 partner;두 줄을 두고map $http_x_api_key $consumer로 include 한다. 소비자가 없으면(키 없음·틀린 키) 401 과 JSON 본문을 돌려준다. 업스트림에는X-Consumer: <소비자>를 넘기고X-API-Key는 넘기지 않는다. - 쿼터:
limit_req_zone $consumer … rate=5r/s,limit_req … burst=5 nodelay,limit_req_status 429. 한 소비자가 몰아 보내면 429, 다른 소비자는 영향이 없어야 한다. - 버전:
/api/는X-API-Version: 2면 v2, 없으면 v1 으로 넘긴다(map $http_x_api_version). v1 으로 가는 응답(/v1/·/api/의 v1)에는Sunset: Thu, 31 Dec 2026 23:59:59 GMT헤더를 붙이고 v2 에는 붙이지 않는다. - 요청 ID: 들어온
X-Request-ID가 있으면 그대로, 없으면$request_id를 업스트림에X-Request-ID로 넘긴다. 접근 로그 형식을'<요청ID> <소비자> <상태> "<요청줄>"'(예:log_format gw '$rid $consumer $status "$request"';)로/root/eaimw/gw/logs/access.log에 남긴다. proxy_read_timeout 2s로, 업스트림이 느리면(/v1/slow는 5초) 2초 안팎에 504 를 돌려준다.
참고
- 설정을 바꾼 뒤:
nginx -t -p /root/eaimw/gw -c /root/eaimw/gw/nginx.conf && nginx -s reload -p /root/eaimw/gw -c /root/eaimw/gw/nginx.conf - 확인:
curl -s -H 'X-API-Key: key-channel-7f3a' localhost:8090/v1/hello | jq .headers - 채점기는 설정 파일을 임시 디렉터리로 복사해
8090·9601·9602와/root/eaimw/gw/경로를 자기 값으로 바꿔 새로 띄운다. 이 숫자와 경로를 설정에 글자 그대로 쓰세요. map의 값에는 변수를 쓸 수 있다("" $request_id;).add_header는 기본적으로 2xx·3xx 응답에만 붙는다.- 흔한 실수: 쿼터 열쇠를
$binary_remote_addr로 두는 것(소비자 구분이 안 된다),limit_req_status를 빠뜨려 503 이 나가는 것,proxy_set_header X-API-Key ""를 잊어 키가 업스트림에 새는 것.
업스트림 두 버전을 띄운다
api.py 로 v1(9601)·v2(9602)를 띄우고 응답 JSON 두 줄을 /root/eaimw/gw/up.txt 에 저장한다.
api.py 는 받은 요청을 JSON 으로 비춰 줍니다. curl -s 로 두 포트를 부르고 >> 로 한 파일에 모읍니다.
경로로 버전을 나눠 넘긴다
/root/eaimw/gw/nginx.conf 로 8090 에서 /v1/→9601, /v2/→9602 를 경로 그대로 넘긴다.
upstream 블록 두 개와 location 두 개입니다. proxy_pass 에 URI 부분을 붙이지 않으면 요청 경로가 그대로 넘어갑니다.
API 키로 소비자를 식별한다
/root/eaimw/gw/keys.map 을 include 한 map 으로 소비자를 정하고, 없으면 401 JSON, 있으면 X-Consumer 만 넘기고 키는 떼어 낸다.
map $http_x_api_key $consumer { default ""; include …; } 뒤 server 에서 if ($consumer = "") { return 401 '…'; }. 헤더를 떼려면 proxy_set_header 에 빈 값을 줍니다.
소비자마다 쿼터를 건다
limit_req_zone $consumer rate=5r/s, burst=5 nodelay, limit_req_status 429 로 소비자별 쿼터를 건다.
zone 의 열쇠가 무엇이냐가 전부입니다. 한도 초과의 기본 상태코드는 503 이라 429 로 바꿔야 호출자가 '내가 많이 보냈다' 를 압니다.
헤더로 버전을 고르고, 내릴 버전을 알린다
/api/ 는 X-API-Version: 2 면 v2, 없으면 v1 으로. v1 응답에는 Sunset 헤더를 붙인다.
map $http_x_api_version $api_ver 로 업스트림 이름을 고르고 proxy_pass http://$api_ver; 합니다. Sunset 도 map 으로 v1 일 때만 값을 주면, 빈 값인 add_header 는 붙지 않습니다.
요청 ID 를 잇고 로그에 남긴다
X-Request-ID 가 있으면 그대로, 없으면 $request_id 를 넘기고, '<요청ID> <소비자> <상태> "<요청줄>"' 형식으로 접근 로그를 남긴다.
map $http_x_request_id $rid { "" $request_id; default $http_x_request_id; } 처럼 map 값에 변수를 쓸 수 있습니다. log_format 을 정의하고 access_log 에 그 이름을 붙이세요.
느린 업스트림은 빨리 끊는다
proxy_read_timeout 2s 로 /v1/slow(5초)를 2초 안팎에 504 로 끊는다.
기본값은 60초입니다. 호출자가 먼저 포기하는 API 라면 게이트웨이가 더 오래 붙들 이유가 없습니다. location 마다 공통으로 넣으세요.