コンテンツにスキップ

実行時証拠の観測環境の検証

  • 状態: 計測完了
  • 開始日: 2026年9月22日
  • 最終更新: 2026年9月25日

目的

実行時証拠の網羅性の検証で採用を決めた実行時証拠を実装する前に、観測に必要な権限、常駐時の負荷、実運用環境での開始条件と利用経路を確かめる。

  • これまでの調査との関係

  • 今回の検証を判断材料にするもの

    • 必要な権限の集合と、権限不足によって欠ける証拠を整理する
    • 既定のサンプリング間隔、バッファ、停止条件の候補を決める
    • 判定不能の表示文言と、その状態を解消するための条件を整理する

方針

  • ローカル環境から実運用環境へ進む

    • ローカル環境で権限と負荷を測ってから、実運用環境で同じ権限の成立性と観測の利用経路を確かめる
    • 過去の計測値と今回の計測値を分け、異なる構成の負荷を差分の基準にしない
  • 実運用環境では観測できた事実を示す

    • 実運用環境では、どのパッケージが実際に使われたかを確定するために網羅性の検証で用いたstraceによる正解データを取得しない
    • 証拠が付いた検出結果の割合は記録するが、実際に使われたパッケージ全体を確定できないため、見逃しが少ないことの証明としては扱わない
    • 確認できた証拠と、権限不足や収集の欠落などの観測品質を併記する
  • 観測の範囲を限定する

    • 実運用環境ではコンテナと観測先の設定を変更せず、観測を時間と出力容量で区切る
    • 再起動をまたぐ確認は、通常のデプロイまたは事前に調整した再起動に合わせる

検証項目

  1. 必要な権限をどこまで限定できるか
    • 操作ごとの成否と実効権限を記録し、権限不足で取得できない証拠を特定する
  2. 実装で採用する観測方式の常駐負荷はどの程度か
    • procfsを一定間隔で読む処理と、exec/openのトレースポイントをeBPFで観測する処理について、CPU時間、メモリ、イベント量、欠落、出力容量を測る
    • bpftrace自身のユーザー空間のCPUとメモリは実装と異なるため、別に上限の目安として示す
  3. 実装で採用する観測方式は通常処理へどの程度影響するか
    • procfsの定期読み取りとeBPFによるexec/openの観測が運用処理の所要時間とWeb応答時間に与える影響を、観測なしの場合と比較する
    • ハーネスと実装で同じ観測の仕組みを使うため、対象の処理が受ける影響を実装時の見積もりとする
  4. 稼働中コンテナへの途中参加で何を示せるか
    • コンテナ別に利用確認の証拠と観測条件による制約を整理する
  5. 観測を続けた状態での再起動から判定できるか
    • 再起動前後の言語パッケージの証拠と、判定不能の表示が変わる条件を確かめる
  6. 実装の要件を整理する
    • 権限、既定設定、停止条件、表示文言と解消条件を根拠付きで整理する

検証方法

用語

  • 観測方式

    • 実装で採用するprocfsの定期読み取りと、exec/openのトレースポイントをeBPFで観測する処理を指す
  • 観測プロセス

    • procfsとDocker APIから情報を取得する収集器、およびイベントを取得するbpftraceのプロセスを指す
  • 権限条件

    • 観測プロセスに与える権限の組み合わせを指し、指定した設定と実際に有効になった権限を区別する
  • 観測構成

    • 負荷を比較するための「観測なし」「procfsのみ」「イベント併用」の組み合わせを指す
  • 従来のルール

  • 読み取り専用の追加手法込み

  • イベント証拠込み

  • 途中参加(attach_running)

    • 稼働中のコンテナに途中から観測を始める条件を指す
  • 起動前からの観測(startup)

    • 対象が起動する前に観測を開始し、起動時の活動を観測できる状態を保つ条件を指す
  • 判定不能

    • 確認証拠がなく、対象コンテナが有効な観測開始前に起動していた言語パッケージについて、起動時からの観測条件が成立していないことを示す表示を指す
  • CPU時間

    • 観測プロセスが実際にCPUを使って処理していた時間の合計で、待機時間は含まず、観測プロセス専用のcgroupのCPU使用量カウンタから読み取った値を用い、300秒の観測窓で1.5秒なら1コアの0.5%の使用に相当する
  • サンプリング間隔と読み取り回数

    • サンプリング間隔は計測で指定した値(30秒と10秒)で、読み取り回数は観測窓の長さを間隔で割った回数となり、300秒の観測窓では30秒間隔で10回、10秒間隔で30回となる

権限の確認

イベント観測の権限候補を比較するには、非特権ユーザーの作成やfile capabilityの付与を繰り返す必要があるため、次の4条件はローカル環境で比較する。実運用環境ではこの比較を行わず、ローカル環境で成立した最小の権限候補だけを管理者権限の基準と比較して再確認する。各条件は試験する権限の候補であり、成功を確認した条件ではない。

権限条件 実行条件 確認する点
管理者権限 rootで実行 基準となる操作の成否
BPFと性能観測の権限 非rootでCAP_BPFとCAP_PERFMONを付与 ファイルアクセスやセキュリティ制限による拒否の有無
読み取り権限を追加 非rootで上記にCAP_DAC_READ_SEARCHを追加 読み取り制限を越える権限の追加で成立するか
システム管理の権限 非rootでCAP_SYS_ADMINを付与 BPF操作とファイルアクセスそれぞれの成否

各権限条件で次の4操作を確認し、操作と権限条件の組み合わせごとに結果を残す。

操作 確認すること 失敗時に分ける原因
BPFプログラムのロード ロードに到達したか、成功したか 権限、検証器、セキュリティ制限、資源制限
tracepointへの取り付け 取り付けの成否と時刻 BPF・性能観測の権限、ファイルアクセス、セキュリティ制限
イベントバッファの作成・読み出し バッファ確保とイベントの取得 資源制限、作成失敗、読み出し失敗
cgroup IDとコンテナの対応表の作成 cgroupの列挙、cgroup IDの取得、対応表ファイルの保存 ファイルアクセス、対応付けに必要な入力の不足

イベント観測には、cgroup IDとコンテナを対応付ける表を保存したファイルが必要であり、その作成にも権限が必要になる。rootが先に作った対応表を渡して非rootで観測できても、その非rootの権限条件だけで観測に必要な処理が成立したとは言えない。このため、対応表を作る処理も観測と同じ権限条件で実行して判定する。

  • 条件の実体を確認する

    • 専用に用意したbpftraceのコピーへ条件ごとの権限を付け、導入済みの本体は変更しない
    • 実効権限、適用されたセキュリティ制限、資源制限、実行ファイルの版とハッシュを保存する
    • 対応表の作成とイベント観測の両方について、試験する権限条件での成否を記録する
    • 今回のイベントバッファはbpftraceのperf bufferとして記録する
  • 失敗と未実施を区別する

    • 各操作を成功、失敗、未到達、未実施に分け、取得できたエラー番号とログを関連付ける
    • エラー番号を取得できない場合は不明とし、エラー文から推測して埋めない
    • セキュリティ設定などを変更した試験は別条件として記録する
  • procfs側と合わせて必要権限を整理する

    • procfs側は既存のローカル環境の結果と現在の環境差を確認し、CAP_SYS_PTRACEとCAP_DAC_READ_SEARCHを含む候補をイベント側の成立条件と合わせる
    • プロセスを分ける場合は役割ごとの権限を示す
    • Docker APIへのアクセス条件は権限の集合とは別に記録し、読み取り操作だけを使うことと接続先が許可する操作範囲を区別する

負荷と通常処理への影響

測る対象は、実装で採用するprocfsの定期読み取りと、exec/openのトレースポイントをeBPFで観測する処理の負荷である。ハーネスと実装で同じ観測の仕組みを使うため、運用処理とWeb応答が受ける影響は実装時の見積もりになる。一方、bpftrace自身のユーザー空間のCPUとメモリは実装と異なるため、別に上限の目安として示し、実装の負荷とは扱わない。

ローカル環境では、次の条件を組み合わせて比較する。観測窓と基準権限は計測前に固定する案である。

比較する項目 条件
対象アプリ PythonのWebアプリ、Node.jsのWebアプリ、Javaアプリ
観測構成 観測なし、procfsのみ、イベント併用
サンプリング間隔 30秒、10秒
反復 各条件2回
開始条件と観測窓の案 attach_runningで300秒
基準権限の案 管理者権限
イベントバッファ イベント併用では512ページ
  • 比較条件をそろえる

    • 観測なしでは収集器とbpftraceを起動せず、アプリと処理時間の記録だけを動かす
    • 観測なしにはサンプリング間隔がないため、30秒と10秒の比較ごとに独立した対照計測を置く
    • procfsのみは読み取り専用の追加手法込みの構成とし、従来のルールだけの負荷とは扱わない
    • イベント併用はprocfsの収集にイベント観測を加え、イベント証拠込みの確認に必要な入力を取得する
    • 3アプリ、3構成、2つの間隔の比較、各2回で計36回とし、300秒の案では観測窓の合計を180分とする
  • 実行条件を固定する

    • 同一アプリではイメージ、入力、運用処理の周期、Webリクエスト列、並行数を固定する
    • 反復ごとに構成の実施順を入れ替え、同じ機械で直列に計測して背景負荷とキャッシュ状態を記録する
    • ビルド、脆弱性スキャン、観測結果の変換とパッケージへの照合は観測窓の外で行う
    • 処理時間の計測方法を全構成でそろえ、正解データ取得のための追跡は並行して行わない

観測方式による通常処理への影響、収集処理の資源使用量、bpftrace自身の負荷の目安を区別し、次の指標を記録する。

対象 指標 値の位置付け
収集器 専用cgroupのCPU時間とメモリのピーク procfsの定期読み取りとDocker APIによる情報取得にかかる資源使用量
bpftrace 別の専用cgroupのCPU時間とメモリのピーク、起動準備と観測中の内訳 bpftrace自身のユーザー空間の負荷を別に示すための上限の目安であり、実装の負荷ではない
Docker daemon CPU時間の増分と、他のDocker操作が混在する可能性 観測に伴う間接的な負荷と、他の操作による影響
イベント 全体と対象コンテナ別の件数、毎秒の件数 eBPFによる観測が扱うイベント量
観測品質 欠落件数、欠落通知、変換失敗、不完全なイベント、未計測の項目 各構成で得られた証拠の制約
出力 標準出力と標準エラー別の容量増分、毎秒の増分、保存物全体の増分 計測時の出力量と保存容量の見積もり
通常処理 curl・git・opensslの所要時間とWeb応答時間、成功・失敗・タイムアウト procfsの定期読み取りとeBPFによる観測が対象の処理に与える影響の実装時の見積もり
  • 値の意味を保つ
    • 通常処理の観測なしとの差を、実装で採用する観測方式が対象の処理に与える影響の見積もりとして示す
    • bpftrace自身のユーザー空間のCPUとメモリは上限の目安として別に示し、実装の資源使用量に置き換えない
    • 観測プロセスのcgroupで測ったCPU時間だけを、eBPFのカーネル内処理を含む観測方式全体のCPU時間とは扱わない
    • CPU時間とメモリの取得成否を分け、取得できなかった値を0として扱わない
    • メモリのピークは各計測専用cgroupの存続期間全体の値とし、前後差では示さない
    • CPU使用率は1コア相当の値とホスト全体に対する値を区別する
    • 各処理の件数、中央値、95パーセンタイル、最大値、観測なしとの差を反復ごとに示す
    • 失敗とタイムアウトは成功時の所要時間分布から分け、件数を省略しない
    • 2回の反復は差の傾向を確認する規模とし、統計的な有意差や長期の変動を確定しない

実運用環境での4段階

ローカル環境で計測と停止監視を準備し、同じDockerのコンテナIDのまま再起動する場合(docker restart)と、デプロイによってコンテナが作り直されてコンテナIDが変わる場合を追跡できることを確かめてから、実運用環境へ進む。

  1. 事前確認

    • カーネル、Docker、BTF、対象tracepoint、観測ツール、cgroup、procfs、セキュリティ制限、空き容量を記録する
    • ローカル環境でビルドした観測プログラムの対象構成とハッシュを確認する
    • コンテナ操作を含まない事前確認から短い取り付け試験へ進み、事前確認の成功と実際の取り付け・読み出しの成功を分ける
    • 既存のスキャン経路を確認し、観測先の設定を変更せずに稼働イメージを指定して取得できることを確かめる
  2. 稼働中コンテナへの途中参加

    • 稼働中の4コンテナを1つの収集器で観測し、attach_runningで300秒と900秒を各1回計測する
    • 管理者権限でprocfsとイベントを併用し、間隔30秒とバッファ512ページを初期候補としてローカル環境の結果から条件を固定する
    • 観測窓の外で取得したTrivyのHIGH・CRITICALの検出結果に対し、コンテナ別に証拠の内訳と観測品質を示す
    • 同一イメージの比較では保存済みスキャンを共用し、スキャンと脆弱性データベースの版を記録する
    • 収集器全体の負荷を各コンテナへ重複計上しない
  3. 観測を続けたままの再起動

    • 対象のWebアプリ1コンテナについて、通常のデプロイまたは調整済みの再起動の前からイベント観測と対象追跡を続ける
    • 再起動前300秒と再起動後300秒を目安とし、待機を含む総観測時間は最大1800秒を初期案とする
    • 観測のためにアプリの起動を待機させる仕組みは追加しない
    • 同じコンテナIDでの再起動とコンテナIDが変わる再作成を分け、コンテナID、イメージの識別情報、起動時刻、プロセス世代、cgroup IDとコンテナの対応表の有効期間を記録する
    • 旧世代の証拠を新世代へ持ち越さず、取り付け、再起動、対象検出、最初の配置読み取り、最初の言語パッケージ証拠を時系列で並べる
    • イメージが変わった場合は別にスキャンし、共通するパッケージと追加・削除されたパッケージを分ける
    • 予定した操作が観測窓に入らない場合は停止し、再起動をまたぐ確認を未実施として残す
  4. 最小権限の再確認

    • ローカル環境で成立した最小権限候補について、実運用環境で同じ4操作とprocfsの読み取りを確かめる
    • 条件ごとの観測窓は60秒を初期案とし、操作の成否と読み出しを確認する範囲に限定する
    • 管理者権限の基準と非rootの候補で実効権限、セキュリティ制限、資源制限を比較する
    • 試さなかった条件は未実施とし、ローカル環境の結果を実運用環境の結果として転記しない

再起動は起動時から判定できる条件を整える機会であり、すべての言語パッケージに証拠が付く保証ではない。観測が途切れた区間や、最初の配置読み取りより前の対応付けの制約も記録する。

安全策と停止条件

実運用環境の観測には、次の停止条件を用意する。数値は初期案であり、実施前にローカル環境の結果と実運用環境の状態を踏まえて固定する。

監視項目 停止条件の案 到達時の扱い
bpftraceのCPU 10秒ごとの計測で1コア相当を超える状態が3区間連続 計測プロセスを停止
欠落の継続 欠落通知などの増加が10秒区間で3回連続 停止し、欠落区間を記録
出力容量 1回の計測で1 GiB、保存先全体で4 GiB いずれかの上限で停止
空き容量 保存先の空きが20 GiB未満 開始しない、実行中なら停止
観測時間 各観測窓の上限、再起動確認は最大1800秒 時間切れで停止
サービス異常 新たな応答失敗または運用者の停止指示 直ちに停止
  • 停止と終了を確認する
    • 実行中に取得できない欠落指標は監視可能と扱わず、終了後の集計で補う
    • 停止時にはログと最終カウンタを保存し、イベント観測の解除と計測プロセスの終了を確認する
    • 終了しない場合の期限付き終了も計測プロセスに限定し、コンテナやDocker daemonは停止しない
    • 記録の転送と照合の後に、転送したプログラム、権限付きコピー、一時ファイル、計測用cgroupを削除する

記録するもの

  • 環境と入力

    • 計測の識別情報、観測構成、権限条件、反復、開始条件、観測窓、間隔、バッファを記録する
    • コンテナとイメージの識別情報、世代、ツールと入力の版、スキャン時刻とデータベースの版を保存する
  • 観測の成立性と負荷

    • 操作別の成否、実効権限、拒否の原因、観測方式による通常処理への影響、収集処理の資源使用量、bpftrace自身の負荷の上限の目安、欠落、出力増分を記録する
    • 早期停止、未計測、未実施の理由と、観測解除・後片付けの確認を残す
  • 途中参加と再起動前後の証拠

    • 証拠の種類と時刻、対応するパッケージ、コンテナ世代、表示区分の変化を記録する
    • 証拠が付かない原因を、観測開始前の起動、収集の欠落、コンテナへの帰属、配置情報、パッケージへの対応付けに分ける

検出結果は、次の順序で重複なく分類する。元の判定と観測品質は別に保持し、パッケージ数と検出結果の件数を分けて集計する。

表示区分 分類条件 表示が意味すること
確認済み 当該世代とイメージに対応する肯定的な証拠がある 証拠が示す粒度で利用を確認
判定不能(観測開始前に起動) 確認済みではなく、言語パッケージの対象コンテナが有効な観測開始前に起動 起動時からの観測条件が成立していない
証拠なし 上記以外で確認証拠がない 未使用や安全を意味せず、観測上の制約を別記
  • 集計の解釈
    • コンテナの起動時刻だけで個別パッケージの観測開始前の利用を確定しない
    • 途中参加でも肯定的な証拠がある対象は、その証拠の粒度を示して確認済みに分類する
    • HIGH・CRITICALの対象が0件なら対象0件と記録し、証拠が付いた割合を100%としない
    • 再起動前後でイメージが変わった場合は対象を分け、件数の増加だけで証拠取得が改善したと判断しない

実装の要件の整理

計測結果をもとに実装の要件を整理し、確認した範囲、提案、未確認の項目を分けて示す。

実装の要件 根拠として示すもの
必要な権限の集合 操作別の成否、実効権限、実運用環境での再確認
権限不足で欠ける証拠の扱い 読み取りや取り付けの失敗と、取得できない証拠の対応
既定の間隔とバッファの候補 30秒・10秒、512ページでの観測方式による通常処理への影響、収集処理の資源使用量、欠落、別記したbpftrace自身の負荷の上限の目安
停止条件と出力管理 CPU、欠落、容量の推移と停止の記録
判定不能の表示文言と解消条件 途中参加から再起動後までの観測の連続性と証拠の変化
実装に残る要件 対象追跡、帰属、配置読み取り、対応付け、権限で成立しなかった箇所
  • 表示文言の案

    • 状態は「判定不能(観測開始前に起動)」とする
    • 解消条件は「観測を継続した状態で次にコンテナが再起動すると、起動時からの利用を判定できる」とし、対象追跡、収集、対応付けが成立する条件を併記する
    • 権限不足や欠落が残る場合は別の理由として示し、再起動だけですべて確認済みになるとは説明しない
  • 結論の適用範囲

    • 最小権限は試した集合の中で成立した候補とし、すべての組み合わせの最小性を主張しない
    • 512ページ以外を比較しないため、バッファの最適値や最小値とはしない
    • 通常処理への影響は採用する観測方式の実装時の見積もりとし、KestreLynx全体の負荷の保証値とはしない
    • bpftrace自身のユーザー空間のCPUとメモリは上限の目安にとどめ、実装の負荷とは扱わない
    • 短い観測から長時間の資源量を推計する場合は参考値とし、長期運用の実測と分ける
    • 結果表、根拠となる記録、未実施の理由、実装の要件と提案がそろった時点を完了とする

結果

結論

  • 非rootでの観測: procfs側はCAP_SYS_PTRACEとCAP_DAC_READ_SEARCHでrootと同じ情報を取得できたため、収集器をrootで動かさない構成を採用できるが、Docker APIへのアクセスは別の権限として扱う必要がある
  • イベント観測の権限: bpftraceではCAP_BPF、CAP_PERFMON、CAP_DAC_READ_SEARCH、CAP_DAC_OVERRIDEで必要な4操作がローカル環境と実運用環境の両方で成立したが、CAP_DAC_OVERRIDEなしではbpftraceの自己チェックが停止するため、実装でもこの権限が必要かは未確定である
  • 常駐負荷と既定の間隔: ローカル環境では30秒間隔のprocfs収集器が1コアの1%未満、イベント観測のbpftraceが数%のCPU使用に収まり、通常処理への影響は概ね反復間のばらつきの範囲でJavaアプリのcurlのみ+1.0〜2.2 msだったため、確認した範囲では常駐させる前提で許容できる水準であり、既定の間隔は30秒を候補にできる
  • 初回読み取りとメモリの扱い: 実運用環境では収集器の初回読み取りに17〜27秒かかるため起動時の負荷を考慮する必要がある一方、イベント観測のメモリ増加の大半はトレースファイルのページキャッシュによるもので、同じトレースファイルを出力しない実装ではこの増加は発生しない見込みであり、bpftrace自身の資源使用量は実装の負荷を見積もる上限の目安として扱う
  • 常駐観測と判定不能の運用: 観測を続けたまま再起動しても再作成しても世代を分けてコンテナを追跡でき、起動したアプリの「判定不能(観測開始前に起動)」は次の起動で解消したため、常駐観測、判定不能の表示と解消という条件は実運用環境で成り立つ
  • 優先順位付けへの有用性: HIGH・CRITICALの脆弱性の検出結果としてElasticsearchは再起動後に19件すべて、Node.jsのバックエンドはアプリが読み込んだjs-yamlとmulterの2パッケージに対応する6件に証拠が付き、同梱ツールの未使用の依存と区別できたため、実際に使われているパッケージの脆弱性を見分ける材料として実用的であり、これらの件数はパッケージ数ではない
  • 適用範囲と限界: 途中参加したNode.jsは次の起動まで利用を判定できなかったうえ、実運用環境の確認は各条件1回・少数コンテナに限られるため、今回の結果だけで網羅性や長期運用時の負荷を保証することはできない

イベント観測の権限

管理者権限と非rootにcapabilityを付与した5条件を、ローカル環境と実運用環境で比較した。各操作の成否と停止した理由は両環境で同じだった。表の失敗には前処理や起動時の自己チェックで停止した場合も含む。

権限条件 BPFプログラムのロード tracepointへの取り付け イベントバッファの作成・読み出し cgroup IDとコンテナの対応表の作成 停止した理由
管理者権限 成功 成功 成功 成功 なし
CAP_BPF + CAP_PERFMON 失敗 未到達 未到達 成功 tracefsのファイル読み取りを拒否
上記にCAP_DAC_READ_SEARCHを追加 失敗 未到達 未到達 成功 bpftraceの自己チェックがCAP_DAC_OVERRIDEを要求
上記にさらにCAP_DAC_OVERRIDEを追加 成功 成功 成功 成功 なし
CAP_SYS_ADMINのみ 失敗 未到達 未到達 成功 tracefsのファイル読み取りを拒否
  • 成立した候補: CAP_BPF、CAP_PERFMON、CAP_DAC_READ_SEARCH、CAP_DAC_OVERRIDEで4操作が成功したが、試した条件内の候補であり、すべての組み合わせに対する最小権限は確定していない
  • 読み取り権限の制約: bpftraceの自己チェックがカーネル操作の前に停止するため、CAP_DAC_READ_SEARCHの追加だけで足りるかは未確認で、CAP_SYS_ADMIN単独でもtracefsのファイル権限を越えられなかった
  • AppArmorの適用範囲: 実運用環境の試験プロセスはunconfinedであり、AppArmorのプロファイルが適用された場合の成立性は確認していない

ローカル環境の負荷と通常処理への影響

WSL2のLinux 6.6、perf_event_paranoid=2の環境で、PythonのWebアプリ、Node.jsのWebアプリ、Javaアプリを計測した。観測なし、procfsのみ、イベント併用を、30秒と10秒の間隔で各2反復比較し、2回目は構成の実施順を逆にした。各runはattach_runningで300秒、イベントバッファは512ページとし、全36 runが早期停止なく完了した。

表は3アプリ・各2反復の最小値から最大値を示す。収集器はprocfsのみとイベント併用の両構成を含み、bpftraceは別の専用cgroupで計測した。CPU時間は観測窓内、メモリのピークは各計測専用cgroupの存続期間全体の値である。

対象 サンプリング間隔 CPU時間 メモリのピーク
procfs収集器 30秒 1.44〜2.14秒 42〜49 MB
procfs収集器 10秒 4.49〜6.31秒 46〜53 MB
eBPF観測(bpftrace) 30秒 8.89〜12.22秒 93〜237 MB
eBPF観測(bpftrace) 10秒 9.48〜14.10秒 96〜117 MB
  • 読み取り回数: 30秒間隔では10回、10秒間隔では30回読み取り、procfsとDocker APIから情報を取得する収集器のCPU時間は1回あたり約0.14〜0.21秒だった
  • 外れ値: Node.jsのイベント併用・30秒間隔の1本でbpftraceのメモリが237 MBとなり、同条件のもう1本は107 MBで、原因は未特定だった
  • イベント量と欠落: イベント併用時の全体は953〜1429件/秒、対象コンテナは58〜192件/秒で、記録された欠落イベントは全条件で0件だった
  • 通常処理の比較: 同じアプリ・間隔の比較・反復に対応する観測なしを基準に、curl・git・opensslの所要時間とWeb応答時間の中央値を比較し、差は概ね±1 ms程度で反復間の変動と同程度だった
  • Javaアプリのcurl: イベント併用の4本すべてで+1.0〜2.2 msとなり、対応する観測なしの中央値は11.8〜13.7 msだった
  • Web応答と解釈の範囲: 観測なしを含む全36 runで応答失敗は0件だったが、各条件2反復では統計的な有意差や長期運用時の変動は確定できない

実運用環境と計測条件

  • 環境: Ubuntu 26.04 LTS、Linux 7.0、Docker 29系、bpftrace 0.25.0、cgroup v2を使用し、BTFと必要なtracepoint、perf_event_paranoid=4、AppArmor有効を確認した
  • セキュリティ制限: unprivileged_bpf_disabled=2、ptrace_scope=1で、procfsにhidepidの指定はなく、カーネルのlockdownは無効だった
  • 対象と観測構成: 稼働中のElasticsearch、Node.jsのバックエンド、Node.jsのフロントエンド(Next.js)、静的Goバイナリのサービスを、管理者権限の収集器とイベント観測で、間隔30秒・バッファ512ページで計測した
  • スキャンと解釈の範囲: TrivyのHIGH・CRITICALの脆弱性の検出結果と証拠を照合したが、straceによる正解データは取得しておらず、確認済みの件数はパッケージ数でも実際に使われたパッケージ全体の網羅性でもない

途中参加と再起動・再作成をまたぐ観測

途中参加は300秒と900秒を各1回計測し、両方で同じ分類となった。表の件数はHIGH・CRITICALの脆弱性の検出結果であり、パッケージ数ではない。

対象 検出結果 確認済み 判定不能(観測開始前に起動) 証拠なし
Elasticsearch 19 17 2 0
Node.jsのバックエンド 27 0 27 0
Node.jsのフロントエンド(Next.js) 19 0 19 0
静的Goバイナリのサービス 38 0 24 14

実運用環境で、再起動と再作成をそれぞれ1200秒の連続観測で確認した。再起動ではElasticsearchとNode.jsの3コンテナが同じコンテナIDを保ち、再作成ではNode.jsの2コンテナのIDが変わり、どちらも操作前後の世代を分けて観測・照合できた。操作前の分類は上表と同じで、操作後の結果を次に示す。こちらもHIGH・CRITICALの脆弱性の検出結果の件数である。

対象 操作後の状態 検出結果 確認済み 判定不能(観測開始前に起動) 証拠なし
Elasticsearch 再起動後 19 19 0 0
Node.jsのバックエンド 再起動後・再作成後とも同じ 27 6 0 21
Node.jsのフロントエンド(Next.js) 再起動後(サーバー未起動、結論から除外) 19 0 0 19
Node.jsのフロントエンド(Next.js) 再作成後(サーバー起動済み) 19 2 0 17
  • Elasticsearch: 再起動後にbcprov-jdk18onのファイルオープンで残る2件を確認し、再作成の観測では操作対象ではなく確認済み17件・判定不能2件だった
  • バックエンドの証拠: 再起動後と再作成後のどちらもアプリが読み込んだjs-yamlとmulterの2パッケージのファイルオープンで6件の脆弱性の検出結果に証拠が付き、残る21件は/appのアプリ依存ではなくイメージに同梱されたパッケージ管理ツールのnpmとpnpmの依存だった
  • バックエンドの未使用の確認: 再起動をまたぐ観測で/usr/local/lib/node_modules/npmと/usr/local/lib/node_modules/pnpm配下を開いた49,006件はすべて収集器による配置の読み取りで、コンテナ内のどのプロセスによるオープンもなく0件だったため、これらの依存に対応する21件に利用の証拠が付かないのは正しい結果である
  • バックエンドの件数の解釈: 27件のうち6件という内訳は、検出された脆弱性のうち観測中にアプリが使ったパッケージに対応するものが6件だったことを示し、見逃しの多さを示すものではないが、正解データを取得していないため見逃しがないとは言えない
  • フロントエンドの最初の再起動: 起動処理による.next/server内の約150 GBのページキャッシュの書き換えが観測窓内に終わらずサーバーが起動しなかったため、この結果は結論から除外した
  • フロントエンドの再作成後: 再作成完了の5秒後にHTTP 200を確認し、サーバーが起動した状態でnextのオープン415件から2件を確認し、ホスト側のリクエストループでは296組が成功して再作成中に2件が失敗した
  • フロントエンドの未使用の依存: 残る17件は同梱のnpmの依存であるbrace-expansion、ip-address、pacote、picomatch、sigstore、tarと、/app/node_modules/.pnpmのsharp、postcss、nanoidに対応し、収集器以外によるこれらのファイルオープンは0件で観測窓内に読み込まれていなかった
  • 未使用の解釈: バックエンドとフロントエンドの未使用の確認は今回の観測窓内に限り、他の処理や入力でも使われないことや安全であることを意味しない
  • 静的Goバイナリ: 再起動せず同じ世代のまま確認済み0件・判定不能24件・証拠なし14件であった。判定不能の24件は同じイメージに含まれるTrivyのバイナリ(/usr/local/bin/trivy)のGoモジュールに対応し、サービス本体のバイナリ(/usr/local/bin/kestrelynx)に該当するHIGH・CRITICALの検出結果は0件であった。Trivyはスキャン時だけ実行され、観測中は動いていなかったため、確認済みにならなかったのは実行状況どおりの結果である。Goのモジュールは、Trivyが報告するバイナリのパスと実行中のプロセスの実行ファイルを照合すれば、そのバイナリに含まれるものとして判定でき、この方法は合成ケースで確認済みである
  • 証拠なしの分類: ハーネスはバックエンドの21件を「パスを対応付けられない」と分類したが、対象のファイルオープン自体が観測されておらず、対応付けの失敗と証拠なしを分ける修正が必要になる

procfsの非root条件

実運用環境の稼働中のコンテナで、procfsのみの4条件を比較した。各条件は60秒、30秒間隔で、コンテナごとに2回読み取った。非root条件ではDocker APIへアクセスできるdockerグループの一般ユーザーを使い、専用の収集器のコピーにcapabilityを付与した。

権限条件 有効な読み取り プロセスの記録 mapsの項目 パッケージDBの読み取り 待ち受けポート プロセスに対応付いた待ち受けポート
管理者権限 12/12 18 174 6対象で成功 32 20
CAP_SYS_PTRACE + CAP_DAC_READ_SEARCH 12/12 18 174 6対象で成功 32 20
CAP_SYS_PTRACEのみ 12/12 18 174 6対象で成功 32 4
capabilityなし 0/12 18(識別情報のみ) 0 取得なし 0 0
  • rootとの一致: CAP_SYS_PTRACEとCAP_DAC_READ_SEARCHで表の全項目がrootと一致し、既存のローカル環境の結果とも一致した
  • 権限を外した影響: CAP_SYS_PTRACEだけでは同じユーザーのプロセスを除き/proc/<pid>/fdを読めず対応付けが20件から4件へ減り、capabilityなしではプロセス観測とパッケージDBの読み取りが失敗して有効な読み取りが0件になった
  • 実行条件: 収集器の実効権限が各条件と一致し、すべてunconfinedで動くことを確認したが、Docker APIへのアクセスはdockerグループ所属によるものでcapabilityとは別に扱う必要がある

実運用環境での負荷

表は各観測の収集器とbpftraceの値を示し、途中参加と再起動は4コンテナをまとめて観測した。CPU時間は各観測窓内、メモリのピークは各計測専用cgroupの存続期間全体の値であり、コンテナごとの負荷ではない。

観測条件 観測窓 収集器のCPU時間 収集器のメモリのピーク eBPF観測のCPU時間 eBPF観測のメモリのピーク
途中参加 300秒 36.5秒 223 MB 13.4秒 192 MB
途中参加 900秒 58.0秒 217 MB 39.7秒 702 MB
再起動をまたぐ観測 1200秒 81.1秒 309 MB 61.3秒 839 MB
再作成をまたぐ観測 1200秒 27.2秒 228 MB 16.1秒 156 MB
  • イベント量と欠落: 途中参加の300秒で1962件/秒、900秒で1954件/秒、再起動時は2081件/秒、再作成時は約407件/秒で、欠落イベントはいずれも0件だった
  • bpftraceのメモリ: 再起動時の60秒ごとの計測では匿名メモリが40 MBで一定の一方、トレース出力のページキャッシュは182 MBから776 MBへ増え、メモリ増加の大半を占めた
  • 収集器のメモリと読み取り: 匿名メモリは約20 MB、ページキャッシュは39〜157 MBで、途中参加の4コンテナの配置とパッケージDBの初回読み取りは経過時間で17〜27秒、以降は全体で約1.8秒だった
  • 資源使用量の解釈: 初回読み取りの経過時間はCPU時間とは異なり、cgroupのメモリのピークはプロセス自身のメモリ増加を示さず、観測プロセスのCPU時間だけではeBPFのカーネル内処理を含む全体のCPU時間を表さない
  • 実装への適用範囲: ローカル環境と実運用環境のbpftraceを含む値はユーザー空間のコストを含む上限の目安であり、対象数と構成が異なる両環境を負荷の差分の基準にはしない

Java応答時間の訂正(2026年9月30日の再計測)

  • 上記の結論とJavaアプリのcurlの結果にある+1.0〜2.2 msは、curlの起動を含むプロセス全体の所要時間の差であり、JavaアプリのHTTP応答時間の増加を示す値ではない
  • ローカルでSensorなし、Sensorあり・eBPFなし、Sensorあり・eBPFありの3条件を交互に実施し、Sensorありでは30秒間隔で観測状態になってからcurlのtime_totalを測り、連続5,000件を各条件8回、150 ms間隔の1,000件を各条件4回、計36回を比較した
  • Sensorあり・eBPFありとSensorなしの応答時間の中央値の差は、連続送信では同じ速さの区分内で−0.11〜+0.20 ms、150 ms間隔では−0.35〜+0.30 msで、回ごとのばらつきの範囲に収まり、一貫した応答遅延の増加は見られなかった
  • 全36回で失敗・タイムアウトは0件、eBPFの欠落は0件
  • Sensorあり・eBPFありでp95・p99がやや高い回もあったが、回数が少なく傾向は断定できない

完了した作業

  • ローカル環境でイベント観測の権限を確認し、観測方式の負荷と通常処理への影響を比較した
  • 実運用環境でカーネル、観測に必要な機能、セキュリティ制限、観測とスキャンの利用経路を事前確認した
  • 実運用環境で稼働中コンテナへの途中参加による証拠と負荷を確認した
  • 実運用環境で再起動と再作成をまたいで観測し、世代の分離、証拠の変化、判定不能の解消、負荷と観測品質を確認した
  • バックエンドとフロントエンドで証拠が付かなかった依存の観測窓内の未使用と、ハーネスの分類の不具合を確認した
  • 実運用環境でイベント観測とprocfsの権限条件を比較した

更新履歴

2026年10月13日

  • Java応答時間の解釈を訂正し、再計測の結果を追記

2026年9月22日

  • 計画を記録

2026年9月23日

  • ローカル環境と実運用環境の結果を記録

2026年9月25日

  • 静的Goの結果の解釈を訂正

この記事は、KestreLynxの開発記録です。KestreLynxについて