Kong Gateway アーキテクチャ:API ゲートウェイが Nginx の内側に宿る仕組み
前提知識
- ›リバースプロキシと API ゲートウェイの基本的な理解
- ›Nginx の概念(ワーカープロセス、設定ディレクティブ、upstream/downstream)への慣れ
- ›Lua の基本的な構文(テーブル、メタテーブル、require/モジュールシステム)
Kong Gateway アーキテクチャ:API ゲートウェイが Nginx の内側に宿る仕組み
多くの API ゲートウェイは、HTTP トラフィックをプロキシするスタンドアロンアプリケーションです。しかし Kong は違います。Nginx の前に置かれるだけでなく、Nginx の内側に宿っています。Kong のリクエスト処理ロジックはすべて、Nginx ワーカープロセス内で Lua コードとして実行され、Nginx のリクエストライフサイクルの適切なタイミングで呼び出されます。このアーキテクチャにより、Nginx のイベントループが持つ高いパフォーマンスを活かしながら、完全にプログラム可能なプラグインシステムという柔軟性を実現しています。
全 7 回シリーズの第 1 回となるこの記事では、Kong が Nginx に組み込まれる仕組み、Lua ハンドラと Nginx フェーズのマッピング、コードベースの構成、そして後続のリクエスト処理すべてが依存するランタイム環境のブートストラップについて解説します。
OpenResty:Lua アプリケーションサーバーとしての Nginx
Kong は Nginx を直接使用しません。Nginx に lua-nginx-module をバンドルしたディストリビューションである OpenResty を使用します。これにより、Nginx ワーカープロセス内で Lua コードを実行できます。OpenResty は Nginx の内部フェーズを *_by_lua_block ディレクティブとして公開しており、リクエスト処理の特定のタイミングで Lua コードを実行するフックとして機能します。
これは後付けのスクリプト機能ではありません。lua-nginx-module はコルーチンベースの cosocket API を提供しており、Nginx のイベントループを離れることなく、データベースクエリや HTTP 呼び出し、DNS ルックアップといったノンブロッキング I/O を Lua コードから実行できます。upstream API を呼び出したりデータベースにクエリを投げたりする Kong プラグインはすべて、この cosocket レイヤーを通じて動作します。そのため、1 つのワーカープロセスで何千もの同時接続を処理できます。
重要なポイントは、Kong の Lua コードは Nginx プロセスと並んで実行されるのではなく、内側で実行されるということです。プロセス間通信のオーバーヘッドもシリアライゼーションの境界もありません。ngx グローバルはどこでも利用でき、共有メモリゾーン(lua_shared_dict)によってワーカー間で高速にデータを共有できます。
flowchart TD
subgraph "Nginx Master Process"
A[Master: manages workers]
end
subgraph "Nginx Worker Process 1"
B[Event Loop]
C[lua-nginx-module]
D[Kong Lua Code]
B --> C --> D
end
subgraph "Nginx Worker Process N"
E[Event Loop]
F[lua-nginx-module]
G[Kong Lua Code]
E --> F --> G
end
A --> B
A --> E
H[lua_shared_dict: kong_db_cache] <-.-> D
H <-.-> G
フェーズ駆動型ライフサイクル:Kong が Nginx に組み込まれる仕組み
Kong が Nginx に組み込まれる起点が、kong/templates/nginx_kong.lua の Nginx テンプレートです。各 *_by_lua_block ディレクティブがグローバルな Kong テーブルのメソッドに処理を委譲しています:
init_by_lua_block {
Kong = require 'kong'
Kong.init()
}
init_worker_by_lua_block {
Kong.init_worker()
}
server ブロックのさらに下、145〜163 行目では、リクエスト処理フェーズも同様に接続されています:
rewrite_by_lua_block { Kong.rewrite() }
access_by_lua_block { Kong.access() }
header_filter_by_lua_block { Kong.header_filter() }
body_filter_by_lua_block { Kong.body_filter() }
log_by_lua_block { Kong.log() }
balancer は upstream ブロック内の 85 行目で balancer_by_lua_block を通じて接続されています。フェーズの対応関係をまとめると次の通りです:
| Nginx ディレクティブ | Kong ハンドラ | 責務 |
|---|---|---|
init_by_lua_block |
Kong.init() |
スキーマの読み込み、DB 接続、ルーターの構築 |
init_worker_by_lua_block |
Kong.init_worker() |
タイマーのセットアップ、キャッシュのウォームアップ、クラスタリング |
ssl_certificate_by_lua_block |
Kong.ssl_certificate() |
動的 TLS 証明書の選択 |
rewrite_by_lua_block |
Kong.rewrite() |
ワークスペースの検出、グローバルプラグインの実行 |
access_by_lua_block |
Kong.access() |
ルーティング、認証、プラグインの実行 |
balancer_by_lua_block |
Kong.balancer() |
upstream ターゲットの選択、リトライ |
header_filter_by_lua_block |
Kong.header_filter() |
レスポンスヘッダーの操作 |
body_filter_by_lua_block |
Kong.body_filter() |
レスポンスボディの変換 |
log_by_lua_block |
Kong.log() |
ロギング、メトリクス、クリーンアップ |
これらすべてのハンドラは、コードベース全体の中心となるファイル kong/init.lua で定義されています。
flowchart LR
A[Client Request] --> B[rewrite]
B --> C[access]
C --> D[balancer]
D --> E[Upstream]
E --> F[header_filter]
F --> G[body_filter]
G --> H[log]
H --> I[Client Response]
ディレクトリ構成とモジュールマップ
kong/ ディレクトリは責務ごとに整理されています。コードベースを読み解くうえで、このマップを把握しておくことは欠かせません:
| ディレクトリ | 目的 |
|---|---|
kong/cmd/ |
CLI コマンド(start、stop、reload、migrations) |
kong/conf_loader/ |
設定の読み込み、検証、マージ |
kong/db/ |
データベースレイヤー:スキーマ、DAO、ストラテジ(Postgres、LMDB) |
kong/runloop/ |
コアリクエスト処理:ハンドラ、ルーター、バランサー、プラグインイテレータ |
kong/router/ |
ルートマッチングエンジン(トラディショナルおよびエクスプレッションベース) |
kong/pdk/ |
Plugin Development Kit — プラグイン向けの安定した API サーフェス |
kong/plugins/ |
バンドルされたプラグイン実装(45 個) |
kong/clustering/ |
ハイブリッドモード:CP/DP 通信、設定同期 |
kong/llm/ |
AI ゲートウェイ:LLM プロバイダードライバーとアダプター |
kong/api/ |
Admin API エンドポイントの生成とルーティング |
kong/tools/ |
ユーティリティモジュール(gzip、文字列、UUID、時間など) |
kong/templates/ |
Nginx 設定テンプレートとデフォルト設定 |
バンドルされたプラグインは kong/constants.lua でリストアップされており、jwt や key-auth から新しい ai-proxy、ai-prompt-guard まで 45 個のプラグイン名が並んでいます。このリストが初期化時のプラグイン検出を駆動します:
local plugins = {
"jwt", "acl", "correlation-id", "cors", "oauth2",
-- ... 36 more plugins ...
"ai-proxy", "ai-prompt-decorator", "standard-webhooks", "redirect"
}
ヒント:
constants.luaはブックマークしておく価値があります。プラグインリストの他に、CORE_ENTITIES(スキーマの読み込み順序は重要で、依存関係のあるものが先になります)、ENTITY_CACHE_STOREマッピング、プロトコル定義、クラスタリング定数なども定義されています。
グローバル Kong オブジェクトとランタイムパッチ
リクエスト処理を始めるにあたって、Kong には 2 つのものが必要です。共有名前空間として機能するグローバルな kong オブジェクトと、Lua/ngx の動作を調整するランタイムパッチです。
グローバル kong オブジェクトは kong/global.lua で _GLOBAL.new() を通じて生成されます:
function _GLOBAL.new()
return {
version = KONG_VERSION,
version_num = KONG_VERSION_NUM,
configuration = nil,
}
end
この素のテーブルは初期化の過程で段階的に埋められていきます。リクエストが処理される時点では、kong.db、kong.cache、kong.core_cache、kong.worker_events、kong.cluster_events、kong.dns、kong.router などがすべてこのオブジェクトにアタッチされています。PDK の名前空間(kong.request、kong.response、kong.service など)は、161 行目で _GLOBAL.init_pdk() を通じて初期化されます。
sequenceDiagram
participant init.lua
participant global.lua
participant PDK
init.lua->>global.lua: _GLOBAL.new()
global.lua-->>init.lua: bare kong table {version, version_num}
init.lua->>global.lua: _GLOBAL.init_pdk(kong, config)
global.lua->>PDK: PDK.new(config, kong)
PDK-->>global.lua: attaches kong.request, kong.response, etc.
init.lua->>init.lua: kong.db = DB.new(config)
init.lua->>init.lua: kong.dns = dns_client
kong/globalpatches.lua のランタイムパッチは、最初の require 時に一度だけ適用されます。特に注目すべきパッチが 3 つあります:
-
init フェーズでのブロッキングスリープ(51〜83 行目):
ngx.sleepは設計上ノンブロッキングですが、init_worker中はコルーチンベースのスリープが使えません。Kong はこれを LuaSocket のブロッキングsocket.sleep()に置き換えています。ただし、ngx.get_phase()がinitまたはinit_workerを返す場合にのみ適用されます。 -
cJSON の精度:
cjson_safe.encode_number_precision(16)により、浮動小数点数が JSON のラウンドトリップで精度を失わないようにしています。 -
Protobuf のデフォルト値:
pb.option("decode_default_array")により、protobuf メッセージ内の空の配列が JSON エンコード時にnilではなく[]としてデコードされます。
ヒント: globalpatches ファイルでは
_G._KONGも設定されます。これは_NAMEと_VERSIONだけを持つ最小限のテーブルです。_G._KONG(グローバルメタデータマーカー)とkongPDK オブジェクトを混同しないようにしましょう。
リクエストライフサイクルの概要
アーキテクチャの全体像が見えたところで、1 つの HTTP リクエストの流れを大まかに追ってみましょう。クライアントが Kong のプロキシポートに GET /api/users を送信する場合を考えます:
-
rewrite フェーズ(
Kong.rewrite()):リクエストコンテキスト(ngx.ctx)のセットアップ、タイミングの記録、ワークスペースの検出、グローバルスコープのプラグインの実行を行います。 -
access フェーズ(
Kong.access()):runloop のaccess.before()がルーターを実行し、リクエストを Route と Service にマッチさせます。プラグインは優先度順に実行され、認証プラグインがクレデンシャルを検証し、レートリミッターがクォータを確認し、トランスフォーマーがリクエストを変換します。upstream の接続情報を使って balancer の準備も行います。 -
balancer フェーズ(
Kong.balancer()):具体的な upstream ターゲット(IP:port)を選択し、DNS 解決、タイムアウトの設定、コネクションプーリングの管理を行います。リトライ時にはこのフェーズが再実行されます。 -
Upstream:Nginx が選択した upstream ターゲットにリクエストを転送します。
-
header filter フェーズ(
Kong.header_filter()):upstream からのレスポンスヘッダーを処理します。プラグインはヘッダーの追加、削除、変更が可能です。 -
body filter フェーズ(
Kong.body_filter()):レスポンスボディをストリーミングチャンクで処理します。チャンク形式のレスポンスでは複数回呼び出される場合があります。 -
log フェーズ(
Kong.log()):すべてのタイミングデータが確定します。ロギングプラグイン(http-log、file-log、datadog)がリクエストレコードをシリアライズして送信します。コンテキストが解放されます。
sequenceDiagram
participant Client
participant Rewrite as Kong.rewrite()
participant Access as Kong.access()
participant Balancer as Kong.balancer()
participant Upstream
participant HdrFilter as Kong.header_filter()
participant BodyFilter as Kong.body_filter()
participant Log as Kong.log()
Client->>Rewrite: GET /api/users
Rewrite->>Access: workspace set
Access->>Access: Router match → Route + Service
Access->>Access: Plugins execute (auth, rate-limit, ...)
Access->>Balancer: balancer_data prepared
Balancer->>Upstream: IP:port selected
Upstream-->>HdrFilter: 200 OK + headers
HdrFilter-->>BodyFilter: modified headers
BodyFilter-->>Log: body chunks streamed
Log-->>Client: response delivered
各フェーズハンドラは kong/init.lua で確認できるパターンに従っています。タイミングマーカーのセットアップ、runloop.<phase>.before() の呼び出し、プラグインのイテレーション、runloop.<phase>.after() の呼び出し、タイミングの記録という流れです。この「サンドイッチパターン」が Kong の拡張性の骨格を成しています。runloop がインフラを提供し、プラグインが振る舞いを提供する構造です。
次回の内容
この記事では基盤となる知識を整理しました。Kong は Nginx ワーカープロセス内で動作する Lua コードであり、リクエスト処理のあらゆるフェーズにフックしています。第 2 回では、kong start と入力した瞬間からブートシーケンスを完全に追います。CLI のディスパッチ、設定の読み込み、Nginx テンプレートのレンダリング、そしてトラフィックを処理するための準備を整える init/init_worker フェーズまでを解説します。