2020年は色々やったんですが、不甲斐なさも残りました。2021年も頑張ります(1行で去年の総括と今年の抱負)。
で、RustとBPF CO-RE、2つのsota(2020年末に覚えた言葉の一つ) をブログに書いて気炎を上げていきたい。
(はじめに: 半分自分メモのつもりなんです! という言い訳をしておきます。認識や用語など間違いがあれば突っ込んで...)
BPF CO-RE、コレってなんですか
上記記事に書いてある通り(さらに言えば Why We Switched from BCC to libbpf for Linux BPF Performance Analysis | PingCAP の通り)、BCCのプロダクション利用には、コンパイラやヘッダファイルなどたくさんの依存、実行時にコンパイルをすることによるオーバヘッドなど多くの問題があったが*1、そう言った実行時にコンパイルしないといけない問題や、環境依存が大きい問題は BTF 、 BPF CO-RE と、libbpf経由での利用などで解消する見込みなのでそっちを掘って行ったほうがいいという話がある。
ぼくもRuby版BCCポートを開発していたので、BCCのdeprecated化には遺憾ではあるが、これがsotaってことなんですね...。
で、libbpfでのツール実装サンプルはbccのリポジトリに、すでにある。お馴染み execsnoop などが一通り移植完了している模様。
はい、Cなんです。ワンバイナリ*2にしたいのでそうなのでしょう...。
複雑なツールをすべてCで作ることは、単純なメモリや文字列の扱い一つとっても難しい場面が多く、保守性も下がるだろう。
BPFプログラム部分はC言語と言ってもDSLのようなもので、複雑なことはしないにしても、できればBPFを利用する側では他の言語の力を借りたい。
libbpf-rs の利用
Rustの libbpf_rs クレートを用いると、RustでBPFツールを作成できる。しかも、基本的にCO-REなバイナリを作れる。
まずはexamplesにあるやつについて、環境を作って試してみる。
Ubuntu Groovy環境の用意
期待を大きくしておいてなんだけど、そもそも、現状BTF + BPF CO-REなバイナリを動かすのは大変である。 CONFIG_DEBUG_INFO_BTF
が有効になったカーネルを作るのがそもそも結構大変で、それが有効になったカーネルには /sys/kernel/btf/vmlinux
というファイルがsysfsに生えている。
$ ls -l /sys/kernel/btf/vmlinux -r--r--r-- 1 root root 3565534 Jan 5 09:28 /sys/kernel/btf/vmlinux
これが、UbuntuでいうとGroovy(20.10)以降の標準Linuxカーネルではデフォルトで有効になっており、いきなり使えて便利なので、今回はこの環境を使う。読者の皆様はVagrantなどで適宜試されたい。なお、Groovyでは、CのBPFプログラムのビルドで便利な libbpf-dev
ヘッダもパッケージになってすぐに入れられる。
ということでUbuntu Groovyで必要なパッケージを入れます。
$ sudo apt install libelf-dev libgcc-s1 clang libbpf-dev ## Rustもセットアップしちゃう $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $ . $HOME/.cargo/env
runqslower を動かしてみる
libbps_rs をチェックアウト。
$ git clone https://github.com/libbpf/libbpf-rs.git $ cd libbpf-rs/examples/runqslower
libbpf-cargo
を入れて、まずはbpfプログラムのみをビルドしてみる。
$ cargo install libbpf-cargo $ cargo libbpf build
target配下に runqslower.bpf.o
がコンパイルされている。
$ ls -l ../../target/bpf/ total 932 -rw-rw-r-- 1 vagrant vagrant 951944 Jan 6 09:49 runqslower.bpf.o
また、BPFプログラムにアクセスするコードのskelも自動生成してくれる。
$ cargo libbpf gen Warning: unrecognized map: .maps Warning: unrecognized map: license $ ls -l src/bpf/ total 2692 -rw-rw-r-- 1 vagrant vagrant 192 Jan 6 09:50 mod.rs -rw-rw-r-- 1 vagrant vagrant 2281 Jan 6 09:48 runqslower.bpf.c -rw-rw-r-- 1 vagrant vagrant 234 Jan 6 09:48 runqslower.h -rw-rw-r-- 1 vagrant vagrant 6680 Jan 6 09:50 runqslower.skel.rs lrwxrwxrwx 1 vagrant vagrant 13 Jan 6 09:48 vmlinux.h -> vmlinux_505.h -rw-rw-r-- 1 vagrant vagrant 2734479 Jan 6 09:48 vmlinux_505.h
mod.rs
と runqslower.skel.rs
がそれで、このファイルが定義する構造体にパラメータを渡し、ロードしてアタッチする。以下は抜粋。
mod bpf; use bpf::*; let mut skel_builder = RunqslowerSkelBuilder::default(); let mut open_skel = skel_builder.open()?; open_skel.rodata().min_us = opts.latency; //... let mut skel = open_skel.load()?; skel.attach()?; let perf = PerfBufferBuilder::new(skel.maps().events()) .sample_cb(handle_event) .lost_cb(handle_lost_events) .build()?; loop { perf.poll(Duration::from_millis(100))?; }
skelがあれば cargo build
でプログラム本体もビルドできる。
$ ls -l ../../target/debug/ total 17856 drwxrwxr-x 28 vagrant vagrant 4096 Jan 6 09:53 build drwxrwxr-x 2 vagrant vagrant 20480 Jan 6 09:54 deps drwxrwxr-x 2 vagrant vagrant 4096 Jan 6 09:53 examples drwxrwxr-x 4 vagrant vagrant 4096 Jan 6 09:54 incremental -rwxrwxr-x 2 vagrant vagrant 18245232 Jan 6 09:54 runqslower -rw-rw-r-- 1 vagrant vagrant 773 Jan 6 09:54 runqslower.d
実行。少し負荷をかけてみれば色々出てきて、ちゃんと遅いrun queueを検知できていそう。
$ sudo ../../target/debug/runqslower Tracing run queue latency higher than 10000 us TIME COMM TID LAT(us) 09:55:13 kworker/11:0 38523 12390 09:56:42 http 71282 36409 09:57:05 kworker/6:1 48057 14452 09:58:59 dockerd 73130 34074 09:58:59 dockerd 73129 15471 09:58:59 dockerd 73131 48768 09:58:59 kworker/u30:2 71779 12502 09:58:59 containerd 71511 11318 09:58:59 dockerd 73251 88511 09:58:59 containerd-shim 76464 17829 09:58:59 containerd-shim 75362 17918 09:58:59 containerd-shim 79165 17978 09:58:59 sshd 2470 21413 09:58:59 ksoftirqd/9 66 38959 09:58:59 containerd-shim 77094 10517
バイナリの素性
このバイナリは、リンクはこういう感じで、
$ ldd ../../target/debug/runqslower linux-vdso.so.1 (0x00007ffd75af9000) libelf.so.1 => /lib/x86_64-linux-gnu/libelf.so.1 (0x00007fb5944fe000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fb5944e1000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb5944c6000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fb5944bb000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb594499000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb594493000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb5942a7000) /lib64/ld-linux-x86-64.so.2 (0x00007fb5948ac000)
起動した際には、 /sys/kernel/btf/vmlinux
のみを読み取り、カーネルヘッダなどを改めて利用していないことがわかる。sysfsのファイルにだけ依存するため、コンテナにポン置きしても動かすことが容易であろうと考えられる*3。
$ sudo strace -y -e file ../../target/debug/runqslower execve("../../target/debug/runqslower", ["../../target/debug/runqslower"], 0x7ffe8c882688 /* 13 vars */) = 0 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3</etc/ld.so.cache> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libelf.so.1", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/libelf-0.181.so> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libz.so.1", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/libz.so.1.2.11> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/libgcc_s.so.1> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/librt.so.1", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/librt-2.32.so> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/libpthread-2.32.so> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/libdl-2.32.so> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3</usr/lib/x86_64-linux-gnu/libc-2.32.so> openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3</proc/84098/maps> access("/sys/kernel/btf/vmlinux", R_OK) = 0 openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3</sys/kernel/btf/vmlinux> openat(AT_FDCWD, "/sys/devices/system/cpu/possible", O_RDONLY) = 5</sys/devices/system/cpu/possible> Tracing run queue latency higher than 10000 us TIME COMM TID LAT(us) openat(AT_FDCWD, "/sys/devices/system/cpu/online", O_RDONLY) = 14</sys/devices/system/cpu/online> openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 29</usr/share/zoneinfo/Etc/UTC> 10:01:53 systemd-journal 478 55310 ...
次は、Cのlibbpf toolsをRustに移植してみたりしたい。
追記: id:y_uuki さんの質問、ご指摘を踏まえいくつか注釈をつけています @ 2021/01/06 19:47