Read OSS

`kong start` からトラフィックを受け付けるまで:起動シーケンスの全容

中級

前提知識

  • 第1回:アーキテクチャとNginxインテグレーション(フェーズモデルの理解)
  • Luaモジュールシステムとメタテーブルへの慣れ
  • プロセス管理(マスター/ワーカーモデル)の基本的な理解

kong start からトラフィックを受け付けるまで:起動シーケンスの全容

Part 1 で確認したように、Kong は Nginx の内部で動作します。では、そもそもどうやって Nginx に組み込まれるのでしょうか?その答えは、想像以上に長いパイプラインを辿ります。シェルスクリプト → resty CLI → Lua コマンドディスパッチ → 設定読み込み → テンプレートレンダリング → Nginx 起動 → Lua フェーズ初期化という流れです。各ステージは前のステージの結果を前提としており、どこか一箇所でも失敗すると起動プロセス全体が中断されます。

この記事では、ターミナルに kong start と入力した瞬間から、最初のリクエストを受け付けられるようになるまでのパイプライン全体を追います。

CLI ディスパッチ:シェルから Lua へ

エントリーポイントは bin/kong です。これは resty CLI(OpenResty のコマンドラインツール)で実行される Lua スクリプトです。シバン行 #!/usr/bin/env resty により、このファイルは resty が管理する一時的な Nginx プロセス内で実行されます。

スクリプトは arg[1] からサブコマンド(startstopreloadmigrations など)を解析し、19〜35行目のハードコードされたコマンドテーブルに対して検証します。そして巧みな手法を使います。インライン Lua 文字列を構築し、resty 経由で実行するのです。

local inline_code = string.format([[
  setmetatable(_G, nil)
  package.path = (os.getenv("KONG_LUA_PATH_OVERRIDE") or "") .. "./?.lua;./?/init.lua;" .. package.path
  require("kong.cmd.init")("%s", %s)
]], cmd_name, args_str)

このインラインコード(135〜141行目)は、Nginx 設定ディレクティブを注入した新しい resty プロセスに渡されます。このような間接的な仕組みになっているのは、コマンドによっては CLI 実行時であっても lmdb_*lua_ssl_* といった Nginx ディレクティブが必要になるためです。

sequenceDiagram
    participant Shell
    participant bin/kong as bin/kong (resty)
    participant cmd/init as kong.cmd.init
    participant cmd/start as kong.cmd.start

    Shell->>bin/kong: kong start -c kong.conf
    bin/kong->>bin/kong: parse args, validate "start"
    bin/kong->>bin/kong: inject_confs.compile_confs()
    bin/kong->>Shell: resty -e 'require("kong.cmd.init")("start", {...})'
    Shell->>cmd/init: dispatch("start", args)
    cmd/init->>cmd/start: require("kong.cmd.start")
    cmd/start->>cmd/start: execute(args)

kong/cmd/init.lua のディスパッチャーはシンプルです。コマンドモジュールを require して、その execute 関数を呼び出すだけです。

return function(cmd_name, args)
  local cmd = require("kong.cmd." .. cmd_name)
  -- ... xpcall(function() cmd_exec(args) end, ...)
end

設定読み込みパイプライン

kong/cmd/start.luastart コマンドの execute 関数は、まず設定の読み込みから始まります。

local conf = assert(conf_loader(args.conf, {
  prefix = args.prefix
}, { starting = true }))

kong/conf_loader/init.luaconf_loader 関数は、複数ステージからなるマージパイプラインを実装しています。

flowchart TD
    A["kong_defaults.lua<br>(hardcoded defaults)"] --> E[Merged Config]
    B["kong.conf file<br>(user overrides)"] --> E
    C["KONG_* env vars<br>(highest precedence)"] --> E
    D["custom_conf table<br>(programmatic overrides)"] --> E
    E --> F["check_and_parse()<br>(validation & type coercion)"]
    F --> G["aliased_properties()<br>(backward compat)"]
    G --> H["deprecated_properties()<br>(warnings)"]
    H --> I["dynamic_properties()<br>(Nginx directive injection)"]
    I --> J["process_secrets.resolve()<br>(vault/secret resolution)"]
    J --> K["Frozen immutable<br>configuration table"]

デフォルト値は kong/templates/kong_defaults.lua から取得します。これは INI 形式の設定ファイルを模した Lua 文字列です。まずこのデフォルト値が解析されてベースレイヤーを形成します。

次に kong.conf によるユーザー設定が上書きされ、さらに環境変数が適用されます。KONG_ プレフィックスの規約により、KONG_DATABASE=offdatabase プロパティを上書きします。265〜280行目の3レイヤーマージでは、動的な Nginx ディレクティブも処理されます。たとえば nginx_http_lua_shared_dict のようなプロパティは、構造化されたディレクティブテーブルに解析されます。

kong.conf_loader.parsecheck_and_parse 関数は、kong/conf_loader/constants.lua の型定義に照らして全プロパティを検証します。文字列はブール値・数値・配列に型変換され、無効な値には明確なエラーメッセージが出力されます。

ヒント: 設定の問題をデバッグするときは、KONG_LOG_LEVEL=debug を設定して reading config file at というメッセージを探しましょう。Kong は設定パイプラインの各ステップ(デフォルトパスの検索も含む)をすべてログに記録します。

テンプレートレンダリングと Nginx の起動

検証済みの設定が揃ったら、start.lua はプレフィックスディレクトリを準備して Nginx を起動します。プレフィックスディレクトリ(デフォルト:/usr/local/kong)には、レンダリングされた nginx.conf、PID ファイル、ログファイル、Unix ソケットが格納されます。

59行目の呼び出し:

assert(prefix_handler.prepare_prefix(conf, args.nginx_conf, nil, nil,
       args.nginx_conf_flags))

これにより、kong/templates/nginx_kong.lua テンプレートがレンダリングされます。このテンプレートは ${{VARIABLE}} による変数展開と > if condition then の制御構文を使って実際の nginx.conf を生成する Lua 文字列です。ロール(traditional/CP/DP)や有効なリスナー、SSL 設定などに応じた条件分岐セクションが含まれています。

テンプレートレンダリングが完了すると、99行目nginx_signals.start(conf) で Nginx が起動します。これにより Nginx マスタープロセスが立ち上がり、ワーカープロセスをフォークします。各ワーカーは、Part 1 で解説した Lua フェーズフックの実行を開始します。

sequenceDiagram
    participant start.lua
    participant prefix_handler
    participant nginx_signals
    participant NginxMaster as Nginx Master
    participant Worker as Nginx Workers

    start.lua->>start.lua: conf_loader(kong.conf)
    start.lua->>prefix_handler: prepare_prefix(conf)
    prefix_handler->>prefix_handler: render nginx_kong.lua template
    prefix_handler->>prefix_handler: write nginx.conf to prefix/
    start.lua->>nginx_signals: start(conf)
    nginx_signals->>NginxMaster: exec("nginx -p prefix/")
    NginxMaster->>NginxMaster: init_by_lua_block → Kong.init()
    NginxMaster->>Worker: fork workers
    Worker->>Worker: init_worker_by_lua_block → Kong.init_worker()

init フェーズ:Kong.init()

init_by_lua_block ディレクティブは、ワーカーがフォークされるに、Nginx マスタープロセス内で Kong.init() を実行します。つまり、ここで行われる処理はコピーオンライト方式によってすべてのワーカーに共有されます。

Kong.init() は175行の関数で、以下の処理を順に実行します。

  1. 設定の読み込み — プレフィックス準備時に書き込まれた .kong_env ファイルから読み込む(648行目)
  2. PDK の初期化kong_global.init_pdk(kong, config) を実行(665行目)
  3. データベースコネクタの作成DB.new(config) で作成し接続(669〜693行目)
  4. マイグレーション状態の確認 — Postgres 使用時、スキーマが最新かどうかを検証(674〜691行目)
  5. クラスタリングの初期化 — CP または DP として動作する場合、クラスタリングモジュールと必要に応じて RPC 同期システムをインスタンス化(701〜713行目
  6. プラグインスキーマの読み込みdb.plugins:load_plugin_schemas(config.loaded_plugins) を実行(718行目)
  7. ルーターとプラグインイテレーターの構築(751〜763行目)— DB レスモードの場合は宣言的設定の解析(724〜745行目)

ロール判定は 201〜218行目でシンプルに定義されています。

is_data_plane = function(config) return config.role == "data_plane" end
is_control_plane = function(config) return config.role == "control_plane" end
is_dbless = function(config) return config.database == "off" end

これらのフラグによって初期化パスが変わります。Control Plane はルーター構築をスキップします(トラフィックをプロキシしないため)。DB レスモードの Data Plane は、Postgres に接続する代わりに YAML から宣言的設定を解析します。

flowchart TD
    A[Kong.init] --> B[Load config from .kong_env]
    B --> C[Init PDK]
    C --> D[Create DB connector]
    D --> E{DB-less?}
    E -->|Yes| F[Parse declarative config]
    E -->|No| G[Check migrations]
    G --> H[Connect to DB]
    F --> I{CP or DP?}
    H --> I
    I -->|CP/DP| J[Init clustering module]
    I -->|Traditional| K[Skip clustering]
    J --> L[Load plugin schemas]
    K --> L
    L --> M[Build router + plugins iterator]
    M --> N[Close DB connection]

init_worker フェーズ:Kong.init_worker()

マスターがワーカーをフォークした後、各ワーカーは Kong.init_worker() を実行します。init() とは異なり、このコードは各ワーカープロセスで独立して実行され、Nginx のタイマーやイベント API を利用できます。

813〜1024行目のこの関数が担う処理は以下のとおりです。

  1. タイマーシステムの起動lua-resty-timer-ng ライブラリを kong.timer にアタッチ(828〜832行目)
  2. DB ワーカーの初期化kong.db:init_worker() を実行(836行目)
  3. ワーカーイベント — Unix ソケット経由のワーカー間通信(856行目)
  4. クラスターイベント — Postgres モードでのキャッシュ無効化のためのノード間通信(864行目)
  5. キャッシュの初期化kong.cache(プラグインデータ)と kong.core_cache(ルーティングデータ)を共有メモリディクショナリで作成(872〜886行目)
  6. 宣言的設定の読み込み — DB レスモードでは、init() で解析した設定を LMDB に読み込む(912〜959行目)
  7. キャッシュのウォームアップ — アクセス頻度の高いエンティティをキャッシュにプリロード(964行目)
  8. ルーターとプラグインイテレーターの再構築 — 各ワーカーが最新のルーターを持つよう保証(970〜982行目)
  9. プラグインの init_worker ハンドラー呼び出し — 各プラグインの init_worker メソッドを実行(987〜995行目)
  10. RPC と同期の初期化 — インクリメンタル同期を用いたハイブリッドモード向け(1001〜1013行目)

ルーターとプラグインの再構築における結果整合性モデルは重要なポイントです。traditional(Postgres)モードでは、バックグラウンドタイマーが定期的に設定の変更を確認し、必要に応じてルーターを再構築します。この処理は、runloop ハンドラーの init_worker.before()925〜1030行目で実装されています。

ヒント: stash_init_worker_error 関数(168〜183行目)は Kong のセーフティネットです。init_worker のいずれかのステップが失敗した場合、エラーはスタッシュされ、その後のすべてのリクエストで ALERT としてログに記録されます。ノードは動作を続けますが、再起動が必要であることをオペレーターに警告し続けます。

まとめ

起動シーケンスは2つのプロセスと複数の Lua モジュール境界にまたがりますが、設計の考え方は一貫しています。各ステージが次のステージに必要なものを生成するという流れです。

ステージ プロセス 主な成果物
CLI ディスパッチ resty(一時 Nginx) 解析済み引数、インライン Lua コード
設定読み込み resty(一時 Nginx) 検証済みの不変設定テーブル
テンプレートレンダリング resty(一時 Nginx) プレフィックスディレクトリ内の nginx.conf
Nginx 起動 Nginx マスター 稼働中のマスタープロセス
Kong.init() Nginx マスター(フォーク前) DB 接続済み、スキーマ読み込み済み、ルーター構築済み
Kong.init_worker() 各 Nginx ワーカー タイマー、キャッシュ、イベント、プラグインの初期化完了

Part 3 では、runloop の全フェーズ(rewrite から log まで)を通じてリクエストを追います。初期化時に構築されたインフラが実際のトラフィック処理でどう動くかを見ていきます。