Amazon Linux 2環境からeBPFプログラミングでHello Worldしてみた

2022.07.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

コンサル部のとばち(@toda_kk)です。

eBPFよくわからん

みなさま、eBPFとは何かご存じでしょうか? 近年では、WebAssembly(WASM)と共にクラウドネイティブ関連の技術として注目されており、特にネットワークやObservability(可観測性)の観点から言及されることが多いように思います。

eBPF(extended Berkeley Packet Filter)とは、パケットキャプチャ・フィルタリングの技術であるBPFを拡張して、あらゆるLinuxカーネルにおけるイベントをフックして処理を実行できるようにした機能です。

ちょっとイメージがつきづらいかもしれませんが、筆者もLinuxカーネルやシステムプログラミングに詳しくないため正直よくわかっておりません。

ちなみに、AWSとeBPFの関わりという点で言うと、2019年のAWS re:InventにてLinuxパフォーマンスの分野で有名なBrendan Gregg(当時はNetflix、現在はIntel)によるセッションが行われていました。

セッション動画: AWS re:Invent 2019: [REPEAT 1] BPF performance analysis at Netflix (OPN303-R1) - YouTube

BCCライブラリを使ってみる

eBPFはLinuxカーネルの機能のひとつで、通常はC言語を使ってeBPFプログラムを実装するようです。

より簡単に実装できるように、IO VisorからBCC(BPF Compiler Collection)というライブラリが用意されています。

これは、Python/Luaをフロントエンドとして用いてeBPFプログラミングができるようにしたものです。たぶん。もし間違ってたら @toda_kk までマサカリ投げてください。

本記事ではこのBCCを使い、Pythonを用いてeBPFプログラミングでHello Worldをやってみます。実行環境としては、Amazon Linux 2環境のEC2インスタンスを使います。

参考資料

本記事では、下記の資料を参考にしながら実際に動作を試してみました。

実行環境

  • Amazon Linux 2
  • Kurnel: 5.10
    • AMI名: amzn2-ami-kernel-5.10-hvm-2.0.20220606.1-x86_64-gp2
  • Python: 3.7.10

Amazon Linux 2環境にBCCをインストールする

下記ページで、さまざまなLinuxディストリビューションにおけるインストール方法が記載されています。

Amazon Linux 2の場合はamazon-linux-extrasによるインストール方法が提供されています。ありがたいですねぇ。

amazon-linux-extrasを使ったインストール

$ sudo amazon-linux-extras install BCC

eBPFプログラミングでHello Worldしてみる

それでは、実際に動作を試していきます。

BCCではさまざまなサンプルが提供されており、今回は /examples/tracing/hello_fields.py をそのまま実行してみたいと思います。

こちらのコードの内容については、上述の参考資料による解説がわかりやすいので、是非そちらをご参照ください。

hello_fields.py

#!/usr/bin/python
#
# This is a Hello World example that formats output as fields.

from bcc import BPF
from bcc.utils import printb

# define BPF program
prog = """
int hello(void *ctx) {
    bpf_trace_printk("Hello, World!\\n");
    return 0;
}
"""

# load BPF program
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")

# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))

# format output
while 1:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
    except ValueError:
        continue
    except KeyboardInterrupt:
        exit()
    printb(b"%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))

ざっくり言うと、clone(2)というシステムコールが実行されたときにHellow, World!というメッセージが出力されるようになっています。

cloneは、プロセスを新たに作成するときに使われるシステムコールのようです。

progに格納されているのがeBPFプログラムの本体です。load BPF programの箇所ではこのプログラムを読み取った後、attach_kprobeによってcloneシステムコールをフックするようにプログラムをアタッチしています。

さて、このスクリプトを実行してみます。

hello_fields.py実行

$ sudo -E python3 hello_fields.py

ここで、別のターミナルを開いて動作確認したいのですが、EC2インスタンスなのでAWS Systems ManagerのSession Manager機能を使って接続してみます。その上で、Session Managerから何らかのコマンドを実行してみると、上記のPythonスクリプトを実行した側のターミナルで下記のように表示されます。

Hello Worldできた

TIME(s)            COMM             PID    MESSAGE
21590.132909000    amazon-ssm-agen  3621   Hello, World!
21604.590865000    ssm-agent-worke  370    Hello, World!
21604.609502000    ssm-session-wor  19221  Hello, World!
21604.609909000    ssm-session-wor  19221  Hello, World!
21604.610129000    ssm-session-wor  19221  Hello, World!
21604.610317000    ssm-session-wor  19221  Hello, World!
21604.623901000    ssm-session-wor  19222  Hello, World!
21604.748651000    ssm-session-wor  19223  Hello, World!
21604.749428000    ssm-session-wor  19222  Hello, World!
21604.751601000    ssm-session-wor  19225  Hello, World!
21604.752733000    ssm-session-wor  19229  Hello, World!
21604.755244000    ssm-session-wor  19229  Hello, World!
21604.757715000    ssm-session-wor  19229  Hello, World!
21604.760633000    ssm-session-wor  19229  Hello, World!
21618.784489000    sh               19233  Hello, World!

コマンド実行によってプロセスが作成されたことを検知して、メッセージが表示されることが確認できました。

このようにBCCを使うことにより、簡単にeBPFプログラミングでHello Worldできました!

フックするシステムコールを変更してみる

せっかくなので、別のシステムコールでも動作するのか試してみます。

先ほどのPythonスクリプトを変更し、実行を検知したいシステムコールをcloneからopen(2)に変更してみます。

hello_open.py

# アタッチするeventをopenシステムコールに変更
b.attach_kprobe(event=b.get_syscall_fnname("open"), fn_name="hello")

このスクリプトを先ほどと同じように実行してみます。

hello_open.py実行

$ sudo -E python3 hello_open.py

その上で、別のターミナルから今度はvimコマンドを使って何らかのファイルを開いてみます。すると、Pythonスクリプトを実行した側のターミナルで下記のように表示されました。

vim実行によるメッセージが大量に表示される

IME(s)            COMM             PID    MESSAGE
23576.489393000    vim              19476  Hello, World!
23576.489769000    vim              19476  Hello, World!
23576.489940000    vim              19476  Hello, World!
23576.490050000    vim              19476  Hello, World!
23576.490147000    vim              19476  Hello, World!
23576.490248000    vim              19476  Hello, World!
23576.490345000    vim              19476  Hello, World!
# (以下略)

openシステムコールの実行を検知して、指定した処理が実行されていることが確認できました。

BCCのサンプルを眺めるとeBPFプログラミングのイメージがつきやすい

今回はBCCが用意しているサンプルをほとんどそのまま使って動作を確認してみましたが、このほかにもたくさんのサンプルが提供されています。

これらのサンプルを眺めているだけでも、eBPFによって何ができるのか、どう実装すれば良いのか、イメージがつきやすいのではないかと個人的には思っています。

みなさんも是非BCCを活用して、良きeBPFライフを送ってください!

以上、コンサル部のとばち(@toda_kk)でした。