LinuxカーネルのコアであるeBPFを実装したSysdigとFalco

By 清水 孝郎 - JUNE 20, 2022
Topics: Sysdig機能

SHARE:

本文の内容は、2019年2月27日にSysdigのGianluca Borelloが投稿したブログ(https://sysdig.com/blog/sysdig-and-falco-now-powered-by-ebpf/)を元に日本語に翻訳・再構成した内容となっております。

最近、Sysdigでは、Linuxカーネルのコア部分であるeBPFを活用するようにエージェントを調整することにより、コアインストルメンテーションテクノロジーが非常に興味深い変化を遂げました。Sysdigは、Sysdigカーネルモジュールベースのアーキテクチャーの代替としてeBPFをサポートするようになりました。 本日、統合とeBPFの内部動作に関する詳細を共有できることを嬉しく思います。 このエキサイティングな技術を祝うために、eBPF専用の一連の記事を公開しています。

このブログでは、eBPFのハイレベルの概要から始め、新しいeBPFベースのSysdigアーキテクチャーと、トレースおよびセキュリティツールのeBPFエコシステムにどのように適合するかについて説明します。 次のブログでは、eBPFテクノロジーの詳細を述べ、より細かいレベルの観点からeBPFをプログラムする方法を示します。

eBPFの紹介

まず、eBPFとは何ですか? Extended Berkeley Packet Filter(eBPF)は、ネットワーキングやトレースを含むさまざまなアプリケーションのためにLinuxカーネルで使用される最新かつ強力なテクノロジーです。eBPFの中核で、ユーザー(場合によっては特権を持つ)は、カーネルに汎用コードに近いものを挿入できます。そのようなコードはある時点で実行され、通常はカーネルで特定のイベントが発生した後に実行されます。これらのイベントは通常、ネットワークとトレースに関連しています(ただし、これらに限定されません)。

eBPF自体の説明に時間を割きすぎないようにしたいと思います。eBPFはかなり一般的な技術であり、そのため、すでに豊富な情報とドキュメントが簡単に見つかります。 非常に良い出発点であるいくつかの例を次に示します。

ここでは、説明をスムーズにするために、eBPFアーキテクチャーに関する十分な詳細を提供することに焦点を当てます。より完全に理解するために、オンラインで入手可能な優れた資料を深く掘り下げることをお勧めします。

eBPFの目的

前述のように、eBPFの最終的な目標は、カーネルでユーザーコードを実行できるようにすることです。 これは、理論的には、カーネルがその機能を拡張するためにユーザーに伝統的に提供するもの、つまりローダブルカーネルモジュールに非常に似ているように思えます。ローダブルカーネルモジュールは、実行時にinsmod/modprobeコマンドを使用してカーネル内にロードされたコンパイル済みの汎用Cコードで構成されます。カーネルモジュールのコードは通常、特定のイベントの発生時に自動的に呼び出されるように、さまざまなカーネルサブシステムにフックします。これは、たとえば新しいハードウェアデバイスやトレース機能のサポートを実装したい開発者にとって便利です。

eBPFとのこの類似は、真実から遠く離れることはできません! eBPFの大きな利点は、カーネルモジュールとは異なり、完全に安全に実行できると見なされたコードのみを実行できる事にあります。具体的には、これはカーネルクラッシュやカーネルの不安定性を引き起こさないことを意味します。これは、eBPFの最大のセールスポイントの1つです。また、いくつかの深刻な柔軟性をあきらめずに他の技術で達成することは現在困難なことです。

eBPFプログラムの作成

その安全性を保証するために、eBPFは基本的にカーネル内のプロセス仮想マシンとして実装されます。この仮想マシンは、ユーザーに代わって安全なプログラムを実行します。 eBPFは、比較的簡単に理解して処理できるRISCのような命令のカスタムセットを使用して、ユーザーに仮想プロセッサを公開できます。また、仮想CPUレジスタのセットとスタックメモリ領域も提供します。その結果、デベロッパーはeBPFバイトコードでプログラムを設計および作成し、この仮想環境で実行し、プログラムを仮想マシンに渡すことができます。eBPFフレームワークは、実行の観点から安全であることを確認した後にのみ、これらのプログラムの実行を開始します。検証チェックには、特定のeBPFプログラムが無限ループに入らないこと、無効なメモリにアクセスできないこと、およびその他の重要な不変条件を確認することが含まれます。

デベロッパーが新しいプログラムを作成するときに、eBPFバイトコードを最初から作成する必要は必ずしもないことに着目することが非常に重要です。eBPFデベロッパーは実際にLLVM(低レベル仮想マシン)のeBPFバックエンドを実装しました。つまり、Clangを使用してeBPFオブジェクトファイル内の標準Cコードのサブセットをコンパイルし、検証およびさらなる使用のためにカーネル内にロードします 。CからeBPFへの変換には重要な注意点がありますが、Cなどの使い慣れたプログラミング言語で新しいインスツルメンテーションコードを比較的簡単に書くことができます。また、eBPFのユースケースを大幅に拡張する事もできます。

これらがすべて抽象的すぎると思われる場合でも、心配しないでください。シリーズの次のパートでは、eBPFプログラムを作成するための基礎をさらに深く掘り下げます。プログラムの安全性の確認方法など、上記のすべてのコンポーネントが実際にどのように相互作用し、機能するかについて、最前列で見るが如く説明していきます。

eBPFのアーキテクチャー

eBPFが非常に興味深いのは、これらのプログラムを静的トレースポイント、動的カーネルおよびユーザープローブ、および他の多くのフックポイントにアタッチすることにより、カーネルのさまざまな実行ポイントで実行できることです。接続されたeBPFプログラムが実行されると、カーネル自体からの関連データを入力として受け取ります。たとえば、システムコールトレースポイントを介してシステムコールの実行にアタッチされている場合、カーネルへのシステムコールを呼び出すユーザー空間プロセスによって渡されたシステムコール引数を受け取ります。プログラムはこの入力を使用して、システムの状態を能動的に変更できます。例としては、ネットワークパケットをフィルタリングするためのネットワークのユースケースで一般的に使われます。または、一連のメトリクスを受動的に計算する事もできます。これは、トレーシングのユースケースでは一般的です。後者の場合、これらのメトリクスは、「eBPFマップ」と呼ばれるデータ構造を使用してユーザー空間に送信できます。eBPFマップは多かれ少なかれ一般的なキー/値のデータ構造であり、ユーザー空間とカーネル間で共有され、低スループットのデータフローを可能にします。

eBPFトレースのほとんどのユースケースでは、eBPFプログラムはカーネル内の非常にビジーな(頻度の点で)実行ポイントに接続され、(ネットワークパケットの送信やシステムコールの実行など)送信されたネットワークパケット数や実行されたシステムコール数などの数値統計をタイプ別に計算します。これらはマップに配置されるため、トレースプロセスを実行するユーザーアプリケーションは、自分のペースで取得できます。

下図は、eBPFインストルメンテーションパイプラインの典型的なアーキテクチャがどのようなものかを示しています。
eBPF architecture
カーネル側で良好なパフォーマンスを保証するために、eBPFプログラムのRISC命令セットは、カーネル内に組み込まれたJITステップを介してネイティブマシンコードに比較的簡単に変換できるほど単純です。これは、プログラムの安全性を確認した直後に、ランタイムが仮想マシンを介してeBPFバイトコードを実行しなければならないというパフォーマンスオーバーヘッドを実際に受けないことを意味します。ストレートネイティブマシンコードを実行するだけで、パフォーマンスが大幅に向上します。

Sysdig, Falco, eBPF

eBPFの概要とその機能について簡単に説明したので、Sysdigアーキテクチャーにどのように適合するか、そしてこの最近のeBPF採択への取り組みの背後にある動機について見てみましょう。

Sysdigにおけるインストルメンテーションの歴史

この記事のほとんどの読者が既によく知っていることを願いますが、オープンソースsysdigは、高性能のシステムコールトレーサーです。ステートフルフィルター、コンテナ、Luaスクリプト用の非常に広範なユーザースペースサポートを備えています。 コミュニティの人々はそれを愛しています。非常に使いやすく、かなり強力です。 Sysdigは、コンテナフレンドリーな仕組みを確立させるために膨大な労力を払いました。これは、トラブルシューティングエコシステムで非常に独創的なものです。

eBPFがまだリリースされていない2013年に、sysdigオープンソースツールの開発を開始しました。当時、カーネルで利用可能なすべてのネイティブインスツルメンテーションテクノロジーは、遅すぎる(例:ptrace/auditd)か、機能が制限されているかでした(例:perfで使用されるKprobeベースのイベントトレーサーでした。 インストルメンテーションフェーズでカスタムロジックを導入する機能が大幅に制限されていました)。 このため、最初の実装は、外部カーネルモジュールとして実装されたデバイスドライバーを介したシステムコールインストルメンテーションで構成されていました。Sysdigカーネルモジュールは、静的トレースポイント(raw syscalls/sysenterおよびrawsyscalls/sysexit)を使用してシステムコールをインターセプトします。そして、次に、ゼロから作成した非常にシンプルで高速なcpuごとのリングバッファーを介して、データをユーザースペースにプッシュします。

Sysdigインテリジェンスのほとんどは、ユーザー空間に実装されます。各システムコールは、コンテキスト(プロセスメタデータ、コンテナおよびオーケストレータメタデータ、ファイル/接続メタデータなど)を個々のイベントにアタッチするステートマシンを通過します。このコンテキストを使用して、意味的にリッチな方法でフィルタリングとスクリプトを作成できます。たとえば、分離されたwrite()をインターセプトするだけで、Sysdigはwrite()が参照するファイル/ネットワーク接続、プロセス、およびDockerコンテナを知ることができます(straceと比較してください)。

カーネルインスツルメンテーションがSysdigアーキテクチャー全体の中でどのような位置づけにあるかを下図で見ることができます
Sysdig kernel instrumentation

詳細については、ブログでカーネルインストルメンテーションの詳細をご覧ください。

カーネルモジュールインストルメンテーションに関する質問

私たちは、確かにカーネルモジュールの実装の選択が間違っていると言うことはできません。現時点でも数千のマシンとユーザーが24時間年中無休で本番環境で広く使用しています。 実際、人気のあるCNCFセキュリティツールプロジェクトであるFalco、Sysdig MonitorSysdig Secureなど、Sysdigエコシステムの他のツールやソリューションのベースです。

しかしながら、6年は長い時間です。この期間中、コンテナが主流となり、運用チームとサービスプロバイダーがクラウドネイティブアーキテクチャーに移行するにつれて、ユーザーから多くの質問とフィードバックが寄せられました。

  • 安定性:いったんロードされると、カーネルモジュールは無制限に実行され、カーネル自体の一部として実行されます。 バグがシステム障害を引き起こす可能性はありますか?
  • セキュリティ:同じ理由で、カーネルモジュールは特権プロセッサモードで実行され、カーネル内のすべての重要なデータ構造に直接アクセスするため、悪意のあるユーザーがセキュリティバグを悪用してサービス拒否または特権エスカレーションを引き起こす可能性がありますか?
  • 互換性:最新のコンテナ指向のLinuxディストリビューションでは、セキュリティを強化するために、適切なデジタル署名なしでカーネルモジュールをロードできません。カーネルモジュールを完全に許可しないものもあります。Sysdigは、Google Cloud PlatformのCOSProject Atomic Hostなどのイミュータブルなインフラストラクチャーへのデプロイメントをどのように効果的にサポートできますか?

eBPFインストルメンテーションに向けて

上記の質問が非常に重要となり始めたため、我々は、現在利用可能なインストルメンテーション技術をもう一度見直すことにしました。いくつかの実験的な試みを行い、eBPFを深く掘り下げた後、eBPFテクノロジーがとても適していると感じました。そして、システムコールインスツルメンテーションを一連のeBPFプログラムに移植することを選択しました。 同時に、私たちの目標は、より高いレベルの抽象化を可能な限り手つかずに保つことでした(前の図のscap以上に)。

私たちのeBPFの作業は、Linuxカーネルバージョン4.11を中心に2017年に始まりました。最初は、当時のeBPF仮想マシンの制限により、いくつかの課題に直面していました。私たちはそれを改善することを決定し、ほとんどの作業をLinuxにアップストリームすることができました。実装したパッチには、eBPFプログラムの文字列をネイティブに処理する機能、コンパイル時にeBPFプログラムからサイズが不明な任意のメモリを間接参照する機能(両方ともシステムコール引数を適切に処理するために不可欠)などが含まれます。次のパートでは、eBPFプログラムに関する実践的な作業を行います。これらのパッチの範囲を説明し、さらに明らかにしていきます。

最終的な実装は、カーネルのeBPFトレースサポートを多用するeBPFプログラムの実質的なコレクションとなりました。 Sysdigは、他のツールを補完するものとしてeBPFエコシステムにうまく適合させました。これは、汎用のトラブルシューティングに役立つ、コンテナ対応の高性能システムコールトレーサーのポジションを保持します。

eBPFの採用から自動的に得られるもう1つの利点は、Sysdigが他の優れたeBPFトレース機能をさらに活用できることです。たとえば、ユーザープローブを使用して、ユーザー空間アプリケーションの特定の実行ポイントにeBPFプログラムをアタッチするのは比較的簡単です。これにより、非常に興味深いトラブルシューティング/セキュリティ監査のユースケースが可能になります。開発パイプラインにはすでに共有できるものがあります!さらに、eBPFプログラムのネイティブヘルパー機能を使用して、実行中のプロセスのスタックトレースをキャプチャーし、Sysdigの典型的なシステムコールイベントストリームを増強できます。 これにより、ユーザーはさらに多くのトラブルシューティング情報を入手できます。

Sysdig eBPF アーキテクチャー
eBPFを使用したSysdigのアーキテクチャは次のようになります:
Sysdig eBPF architecture

実装の中核は、インストルメンテーションを担当するカスタムeBPFプログラムのコレクションです。これらのプログラムは、前述のとおり、Cプログラミング言語のサブセットで記述されています。 これらは、最新バージョンのClangとLLVMを使用してコンパイルされ、高レベルのCコードをeBPFバイトコードに変換します。Sysdigがカーネルをインスツルメントするさまざまな実行ポイントごとに1つのeBPFプログラムがあります。 現在、Sysdigは次の静的トレースポイントにeBPFプログラムをアタッチできます。
  • System call entry path
  • System call exit path
  • Process context switch
  • Process termination
  • Minor and major page faults
  • Process signal delivery
将来的には、これらの実行ポイントは、特にセキュリティ監査のユースケースのために拡張する可能性も視野に入れています。

前述のように、各プログラムは実行ポイント固有のデータ(たとえば、システムコール、呼び出しプロセスによって渡された引数)を入力に取り込み、それらの処理を開始します。処理は、システムコールのタイプによって異なります。単純なシステムコールの場合、引数はイベントフレーム全体が形成されるまで、一時ストレージに使用されるeBPFマップにそのままコピーされます。他のより複雑な呼び出しの場合、eBPFプログラムには、引数を変換または拡張するロジックが含まれています。これにより、ユーザー空間のSysdigアプリケーションはデータを完全に活用できます。この追加データには次のものが含まれます。

  • ネットワーク接続に関連付けられたデータ(TCP / UDP IPv4 / IPv6タプル、UNIXソケット名など)
  • プロセスに関する非常に詳細なメトリクス(メモリカウンター、ページフォルト、ソケットキューの長さなど)
  • システムコールを発行するプロセスが属するcgroupなどのコンテナー固有のデータ、およびプロセスが存在する名前空間
  • 次のパートで説明するように、eBPFプログラムでできることには制限があるため、この情報の一部はeBPFコードから取得するのは簡単ではありません。
eBPFプログラムは、特定のシステムコールに必要なすべてのデータをキャプチャーすると、特別なネイティブBPF関数を使用して、Sysdigユーザー空間アプリケーションが非常に高いスループットで読み取ることができるCPUごとのリングバッファーのセットにデータをプッシュします。 これは、SysdigでのeBPFの使用が、eBPFマップを使用してカーネル空間で生成された「小さなデータ」をユーザー空間と共有する典型的なパラダイムと異なるところです。 これにより、eBPF側に重い集約の負荷がかかります。

Sysdigアーキテクチャーでは、eBPFマップはユーザースペースとの共有目的に最小限使用されます。すべてのデータは、特定のユースケースに合わせて調整されたスケーラブルなリングバッファーを通過します。これにより、Sysdigは最小限のオーバーヘッドで非常に大量のデータをカーネルからユーザースペースにエクスポートできます。その後、Sysdigプロセスメモリでシステムの状態を再構築できます。これが、最終的に強力なフィルタリングおよびチゼルマシンを動かす物となります。

パフォーマンスの観点から、結果は良好です! 以下に、SysdigのeBPFインスツルメンテーションのインスツルメンテーションオーバーヘッドが、クラシックカーネルモジュールインスツルメンテーションよりわずかに大きいだけであることがわかります:
Sysdig eBPF overhead

これらの数値は、straceなどの他のシステムコールトレーサーと比較すると、ごくわずかです。 それでもオーバーヘッドが許容できない場合は、カーネル側でより積極的なフィルタリングを直接実行することで、Sysdigをさらに少ない消費量に調整することももちろん可能です。

使用方法

SysdigにおけるeBPFインスツルメンテーションの使用は、設計上、平穏でほぼ透過的です。このプロジェクトの全体的な目標は、ユーザーがユーザーインターフェイスを一切変更せずに、基になるインスツルメンテーションソースを変更できるようにすることでした。これは、eBPFをサポートする最新のLinuxカーネル(4.14以降)で実行しているユーザーは、コマンドラインで SYSDIG_BPF_PROBE 環境変数を指定して標準のSysdigコンテナを実行するだけで、次の例に示すようにインストルメンテーションを自動的にeBPFへ切り替える事ができます。:

docker run -i -t --name sysdig --privileged -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro -v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro --net=host -e SYSDIG_BPF_PROBE="" sysdig/sysdig
* Setting up /usr/src links from host
* Mounting debugfs
Found kernel config at /host/boot/config-4.18.0-1-amd64
* Trying to compile BPF probe sysdig-probe-bpf (sysdig-probe-bpf-0.23.1-x86_64-4.18.0-1-amd64-0fad68c2978502d00e890af0fa93db9c.o)
* BPF probe located, it's now possible to start sysdig
root@sid:/# sysdig -pc container.name=sysdig
17 20:23:52.548797444 0 sysdig (37b2c84a4369) sysdig (5424:1796) > switch next=0 pgft_maj=0 pgft_min=1115 vm_size=146348 vm_rss=10092 vm_swap=0
236 20:23:52.579481674 0 sysdig (37b2c84a4369) sysdig (5424:1796) > switch next=0 pgft_maj=0 pgft_min=1130 vm_size=146348 vm_rss=10092 vm_swap=0
615 20:23:52.611252990 0 sysdig (37b2c84a4369) sysdig (5424:1796) > switch next=0 pgft_maj=0 pgft_min=1136 vm_size=146348 vm_rss=10092 vm_swap=0
772 20:23:52.642855149 0 sysdig (37b2c84a4369) sysdig (5424:1796) > switch next=0 pgft_maj=0 pgft_min=1141 vm_size=146348 vm_rss=10092 vm_swap=0
...

カーネルバージョンの正確な要件など、詳細については、内部で何が起こっているのかを理解したいデベロッパーや上級ユーザー向けのeBPFドキュメントページで入手できます。

Falco、Sysdig Monitor、およびSysdig Secure agentでも同じワークフローを使用できます。 SYSDIG_BPF_PROBE  環境変数を渡してそれぞれのコンテナを開始するだけです。カーネルモジュールではなく、eBPFインスツルメンテーション上で純粋で実行させ、完全な機能を備えたSysdigインスタンスを稼働させる事ができます!

eBPFエコシステム

eBPFの人気を考えると、SysdigをeBPFエコシステムのその他のツールと比較する価値があります。新しいeBPFベースのツールとフレームワークが非常に速いペースで導入されているため、完全な比較は不可能です。 トレースに関しては、少なくとも2つの最大かつ最も人気のあるeBPFプロジェクトと比較できます。 これらは:

bccは基本的にeBPFプログラムを作成するためのフレームワークです。eBPF自体を処理する複雑さのかなりの部分を抽象化します。特に、bccは、LLVMを使用したCからのeBPFプログラムのコンパイル、およびeBPFプログラムをカーネルにロードして対象のサブシステムにアタッチする実際のメカニズムを簡素化します。さらに、bccは、CではなくPythonとLuaでeBPFプログラムを作成する機能も提供します。さらに、bccは、マップを操作するときにオブジェクト指向の素晴らしいバインディングを提供します。

このため、適度に複雑なeBPFプログラムを作成する場合、bccは良い選択です。デフォルトでは、フレームワークには、1回限りのトラブルシューティングの使用例に使用できる多数の小さなeBPFプログラムが付属しています。以下は、そのようなツールの包括的な図です:

eBPF tools
対照的に、Sysdigはシステムコールインターフェイス(黄色のボックス)を専門としています。SysdigのeBPFサポートは、コンテナ情報を含む追加のメタデータで多くのシステムコールを強化します。 さらに、データを豊富なトレースファイルに保存する機能、強力なステートフルフィルター、簡単なチゼルスクリプトなどの優れた機能セットを提供しています。

bccの問題の1つは、PythonとLuaのバインディングにもかかわらず、eBPFプログラムの作成が依然として非常に複雑になる可能性があることです。 ユーザーは、後で説明するように、eBPFプログラムの動作に関する多くの前提、特に制限を念頭に置く必要があります。この状況を簡素化するために、新しいツールbpftraceが作成されました。bpftraceはbccの上にあります。ユーザーにbcc APIに対して独自のプログラムを作成するように要求する代わりに、より表現力豊かな高レベルの構文を提供します。構文は、一般的なトレースフレームワークDTraceの構文と非常によく似ています。 これにより、比較的単純なワンライナーを介して高度なトレースを行うことが効果的になります。

これらのツールはすべて、システムトラブルシューティングツールボックスに適していると考えています。多くの場合、問題を解決するのに十分なデータと機能が提供されるため、ユーザーは通常、Sysdigなどの汎用システムコールトレーサーツールを使用してトラブルシューティングを開始します。 問題をさらに詳しく調査する必要がある場合は、bpftrace one-linersとbccスクリプトの組み合わせを使用するか、新しいスクリプトを作成して、特定のカーネルサブシステムまたはユーザー空間の動作をトラブルシューティングできます。

この投稿の時点では、Sysdigと同等の機能を備えた他のeBPFベースのシステムコールトレーサーの存在は認識していません。この作業を完了し、コミュニティと共有できることに興奮しています。

特定のトラブルシューティングユースケース以外では、CNCFサンドボックスの寄付プロジェクトであるFalcoがeBPFを使用できるようになりました。 Falcoは、Linux、コンテナ、およびKubernetesエコシステムで現在eBPFを使用しているコンテナ向けの唯一の主要なセキュリティおよび振る舞いベースの監視ツールです。

商用提供の観点から見ると、Sysdigは、マイクロサービス向けのフル機能のコンテナモニタリングおよびコンテナセキュリティソリューションでeBPFトレース機能を出荷している最初の数少ない企業の1つです。お客様は、eBPFで完全に強化されたSysdig MonitorとSysdig Secureを採用できるようになりました。

まとめ

これで、このeBPFシリーズの最初の部分は終わりです。その中で、eBPFと新しいSysdigアーキテクチャーをハイレベルで見てきました。先に述べたように、これはこの魅力的な技術について知っておくべきことの表面をなぞりました。

このシリーズの第2部では、eBPFプログラムを作成することの意味をさらに深く掘り下げ、いくつかのバンプ(および成功)を説明します。