nginx でゲートウェイを立てる — キー・クォータ・バージョン
目標
nginxでAPIゲートウェイの原理を作ります。パスプロキシ、APIキー認証(401)、コンシューマーごとのクォータ(429)、バージョンルーティングとSunset、リクエストIDの伝播とログ、アップストリームのタイムアウト(504)です。
なぜ重要なのか
認証・呼び出し量の制限・バージョンは、業務と無関係に、すべてのAPIで同じように必要です。APIサーバーごとに別々に実装すると少しずつ違ってしまい、1つのコンシューマーの暴走がすべてを遅くし、誰が古いバージョンを使っているのか誰にもわからなくなります。ゲートウェイは、これを業務ロジックなしで1か所から統制します。
ステップ
- アップストリームを2つ起動してください:
nohup python3 /opt/lab/fixtures/eaimw/gateway/api.py --port 9601 --version v1 > /root/eaimw/gw/v1.out 2>&1 &、… --port 9602 --version v2 …。2つをcurlで1回ずつ呼び出し、レスポンスJSONの2行を/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;の2行を置き、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を設定してください。1つのコンシューマーが集中して送ったら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> <소비자> <상태> "<요청줄>"'(プレースホルダーはリクエスト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 ""を忘れてキーがアップストリームに漏れること。
アップストリームの2つのバージョンを起動する
api.pyでv1(9601)・v2(9602)を起動し、レスポンスJSONの2行を/root/eaimw/gw/up.txtに保存してください。
api.pyは受け取ったリクエストをJSONで映して返します。curl -sで2つのポートを呼び出し、>>で1つのファイルにまとめます。
パスでバージョンを分けて渡す
/root/eaimw/gw/nginx.confで、8090の/v1/→9601、/v2/→9602を、パスそのままで渡してください。
upstreamブロック2つとlocation 2つです。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ごとに共通で入れてください。