プロンプトとコンテキスト
可観測性(オブザーバビリティ)エージェントは、複数のディストリビューションおよびカーネルバージョンにわたってシステムコールのレイテンシを測定する必要があります。カーネルごとに個別にビルドするとリリース管理が破綻し、ハードコードされた構造体オフセットはアップグレード後に破損します。BTF、CO-REの再配置、およびlibbpfの責務を説明した上で、検証可能なビルド、ロード、イベント処理、および互換性のパスを設計してください。
面接官が見ているポイント
- BTF型情報、コンパイラの再配置レコード、ロード時のパッチ適用の関係についての理解。
- メモリレイアウトのポータビリティと、ベリファイア、ヘルパー、kfunc、プログラムタイプに関する制約の違いの理解。
- イベントのライフサイクル、リングバッファまたはperfバッファによる配信、およびドロップ(損失)の計上設計。
- BTFの欠落、フィールドの不在、権限(ケーパビリティ)不足、ロード失敗への対処。
最初に明確にすべき質問
- ターゲットカーネルでBTFが有効化されており、
/sys/kernel/btf/vmlinuxが公開されていますか? - 利用可能なプローブタイプ(tracepoints、kprobes、fentry、LSMなど)は何であり、権限の境界はどうなっていますか?
- イベント発生頻度、レイテンシの目標値、許容されるドロップ率、およびユーザー空間での消費モデルはどうなっていますか?
- サポートすべきアーキテクチャ、カーネルバージョン、およびコンテナ環境は何ですか?
- フィールドが存在しない場合やベリファイアがプログラムを拒絶した場合、エージェントはメトリクスを無効化すべきですか、それともフォールバックプローブを使用すべきですか?
30秒の回答フレームワーク
BTFとCO-RE再配置データを含む単一のBPFオブジェクトをコンパイルし、ロード時にlibbpfが実行中カーネルのBTFからフィールドオフセットをパッチします。生成されたスケルトンがマップ、プログラム、リングバッファを管理し、権限、BTF、プログラムタイプを検証します。欠落したフィールドやロード失敗は記録され、メトリクスを無効化するか安定したtracepointに切り替えます。エージェントが偽造された値を出力することはありません。ベンチマークでは複数のカーネル、並行イベント、ドロップ、アンロード時の復旧をカバーします。
ステップごとの詳細な回答
ステップ 1: コンパイル時とロード時の責務を分離する
コンパイラはBPFプログラム、型情報、CO-RE再配置レコードをオブジェクトに出力します。ロード時にlibbpfはターゲットカーネルのBTFを使用して構造体、フィールド、オフセットを解決し、命令のイミディエイト値やオフセットを更新します。CO-REによってカーネルごとのビルドは削減されますが、すべてのヘルパー、kfunc、プログラムタイプが利用可能になるわけではありません。
ステップ 2: 安定したプローブ境界を選択する
安定したtracepointsまたはfentryを優先し、ターゲットカーネルによって公開されているイベントフィールドを確認します。kprobesはより多くのパスをカバーしますが、シンボルやパラメータのレイアウトへの依存度が高くなります。固定オフセットの代わりにCO-REマクロを使用してフィールドアクセスを記述します。ベリファイアの負荷とイベントサイズを削減するため、必要なフィールドのみを読み取ります。
ステップ 3: イベント構造と転送を設計する
BPF内でpid、タイムスタンプ、システムコール識別子、および最小限のレイテンシデータを記録し、リングバッファまたはperfバッファを介して送信します。固定長イベントまたは明示的なバージョンフィールドを使用し、ユーザー空間で長さとバージョンを検証します。CPU、プロセス、キューごとにドロップを追跡し、バッファオーバーフローを黙ってレイテンシゼロに変換してはなりません。
ステップ 4: ベリファイアの制約を設計に組み込む
境界チェック、ループ制限、ポインタ型、ヘルパーの戻り値は、ベリファイアを満たす必要があります。複雑なパース処理はユーザー空間に移し、BPF内での証明不可能な探索を避けます。コンパイラおよびスケルトンによって生成されたマップ定義が、プログラム内のアクセスと一致していることを確認します。診断のために、ベリファイアの失敗サマリーとカーネルバージョンをログに記録します。
ステップ 5: BTFとフィールドの差異を処理する
ターゲットのBTFが読み取り可能であることを確認し、オプションのCO-REフィールドまたは機能プローブを使用してフィールドの存在を検出します。レイアウトの変更により再配置が失敗する場合は、メトリクスを無効化するか互換性のあるtracepointを選択します。読み取れないフィールドをデフォルト値で埋めて正確なレイテンシとして報告してはなりません。マルチカーネルCIマトリクスでアーキテクチャの違いを検証します。
ステップ 6: ビルドとリリースのパスを設計する
clang、libbpf、スケルトン生成ツールのバージョンを固定し、BTFを含むオブジェクトを生成してそのビルドIDを記録します。ユーザー空間ローダー、BPFオブジェクト、最小限の権限要件をパッケージ化して配布します。ロード前にセルフチェックを実行します。コンテナへのデプロイでは、必要なBPFファイルシステム、権限、カーネル設定を明記し、実用的な失敗理由を提供する必要があります。
ステップ 7: パフォーマンス、正確性、ロールバックを検証する
ディストリビューション、アーキテクチャ、カーネルにわたって、独立したツールを用いてフィールド値とイベント数を比較します。高いイベントレート下で、CPU、メモリ、リングバッファの損失、ユーザー空間の遅延、テールレイテンシの負荷テストを実施します。ロールバックパスとして古いオブジェクトまたは安定したプローブを保持し、アンロード時にリンク、マップ、スレッドが解放されることを確認します。
質の高い回答例
BTFとCO-RE再配置を含む単一のオブジェクトをコンパイルし、libbpfにターゲットカーネルのBTFからオフセットをパッチさせます。プローブには安定したtracepointsまたはfentryを優先し、BPFは小さくバージョニングされたイベントを発行し、ユーザー空間のスケルトンがドロップをカウントしながらリングバッファ経由でそれをデコードします。起動時チェックでは、BTF、プログラムタイプ、ヘルパーのケーパビリティ、フィールドの存在を網羅します。再配置やベリファイアの失敗時にはメトリクスを無効化するかプローブを切り替え、決してデータを偽造しません。CIはアーキテクチャ、カーネル、高イベントレート、アンロード復旧を網羅し、ロールバック用に古いプローブを保持します。
よくある間違い
- CO-REをすべてのヘルパー、kfunc、ベリファイア制約に対する互換性ソリューションとして扱うこと。
- 構造体オフセットをハードコードし、BTFや再配置を迂回すること。
- イベントのバージョンや長さのチェックを省略し、切り詰められたデータを有効なものとして扱うこと。
- バッファのドロップを無視し、観測の欠落をレイテンシゼロとして報告すること。
- 欠落したフィールドをデフォルト値で埋めて互換性の失敗を隠すこと。
フォローアップの質問と回答
フォローアップ 1: CO-REの再配置はいつ行われますか?
コンパイル時にBPFオブジェクト内に再配置レコードが書き込まれます。ロード時にlibbpfがターゲットカーネルのBTFを用いてこれらを解決し、フィールド情報をパッチします。実行時にイベントごとにオフセットが再計算されるわけではありません。
フォローアップ 2: vmlinux BTFがなくても実行できますか?
利用可能なBTF、プローブタイプ、ディストリビューション、およびlibbpfの機能に依存します。起動時チェックによって何が安全かを確定させる必要があります。フィールドが解決できない場合は、プログラムを無効化するか、そのフィールドに依存しない安定したプローブを使用します。
フォローアップ 3: すべての箇所でkprobesを使用しないのはなぜですか?
kprobesは柔軟ですが、シンボル、パラメータ、インライン展開への依存度が高いため、アップグレード時のセマンティクスが不安定になりがちです。tracepointsやfentryを使用すると、可用性のチェックは依然として必要ですが、メンテナンスコストを削減できます。
フォローアップ 4: 報告の欠落がないことをどのように証明しますか?
BPFイベント数、リングバッファのドロップ数、ユーザー空間の消費量、および独立したシステムコール数をCPUおよび時間枠ごとに照合します。不一致がある場合は、サイレントにゼロにするのではなく、欠落または縮退状態として表面化させる必要があります。
フォローアップ 5: ベリファイアの拒絶をどのようにデバッグしますか?
ローダーのログ、カーネルバージョン、ターゲットアーキテクチャ、BTF ID、およびベリファイアのサマリーを保存します。最小限の再現環境を作成して、ポインタの境界、ループ制限、ヘルパーの使用、またはマップ定義に問題を絞り込み、修正後にマルチカーネルマトリクスを再実行します。