TT Lab
はじめる
学ぶ 学習パス コース

EAI 中間層をつくる

nginx でゲートウェイを立てる — キー・クォータ・バージョン

TT Labで続きを見る

目標

nginxでAPIゲートウェイの原理を作ります。パスプロキシ、APIキー認証(401)、コンシューマーごとのクォータ(429)、バージョンルーティングとSunset、リクエストIDの伝播とログ、アップストリームのタイムアウト(504)です。

なぜ重要なのか

認証・呼び出し量の制限・バージョンは、業務と無関係に、すべてのAPIで同じように必要です。APIサーバーごとに別々に実装すると少しずつ違ってしまい、1つのコンシューマーの暴走がすべてを遅くし、誰が古いバージョンを使っているのか誰にもわからなくなります。ゲートウェイは、これを業務ロジックなしで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に保存します。
  2. /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で起動します。
  3. 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は渡しません。
  4. クォータ: limit_req_zone $consumer … rate=5r/s、limit_req … burst=5 nodelay、limit_req_status 429を設定してください。1つのコンシューマーが集中して送ったら429になり、別のコンシューマーには影響がないようにします。
  5. バージョン: /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には付けません。
  6. リクエストID: 受信したX-Request-IDがあればそのまま、なければ$request_idを、アップストリームにX-Request-IDとして渡してください。アクセスログの形式を'<요청ID> <소비자> <상태> "<요청줄>"'(プレースホルダーはリクエストID、コンシューマー、ステータス、リクエスト行です。例: log_format gw '$rid $consumer $status "$request"';)にして、/root/eaimw/gw/logs/access.logに残します。
  7. proxy_read_timeout 2sで、アップストリームが遅い場合(/v1/slowは5秒)に、2秒前後で504を返すようにしてください。

参考

アップストリームの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ごとに共通で入れてください。