kubectl debug で、BPF(eBPF) Tools を動かす

# 概要

kubectl debug を使うと、デバッグ用のコンテナを起動して、Pod 内の調査を行うことができます。
たとえばシェル(bash や shell)が入っていないコンテナに対しても、シェルが入った別コンテナを立てることで Linux コマンドを使った調査が可能です。

その kubectl debug で、BPF Tools が入ったイメージを使えたら面白いのではないか、という思いは昔(4年前くらい)からあったのですが、当時は kubectl debug が alpha ステージだったので、構想のみでした。
すでに kubectl debug の機能が成熟して、さまざま Profile(kubectl debug の Profile)が出てきた現在、kubectl debug コマンドを使ったデバッグコンテナイメージ構築に挑戦しました。

# 利用する BPF Tools

BPF とは...を説明すると長くなるので省略します。
> 詳しくはこちらを参照
> https://ebpf.io/what-is-ebpf/
>
> 拙作ですがこちらも参考になるかもしれません
> https://go-vargo.hatenablog.com/entry/2020/03/29/210304

BPF の機能はさまざまな領域にわたるのですが、ここでの目的は主に Performance Tuning や Observability, Profiling などの用途で使います。

具体的には、BPF Tools として有名な [BCC](https://github.com/iovisor/bcc) と [bpftrace](https://github.com/bpftrace/bpftrace) をデバッグコンテナ上で動かせるようにします。

# 得られる効果

kubectl debug を使うことで、デバッグコンテナを立ち上がることができ、その中で今回使う BCC や bpftrace を動かすことができます。

```bash
$ kubectl debug -it <pod name> --image govargo/bpf-tools --profile sysadmin --target <target container> -- bash

## example from bcc
execsnoop

## other example from bpftrace
tcplife.bt
```

~~これの素晴らしいところとして、Pod に対して実行できるところが挙げられます。~~
~~というのも、これまでも BPF Tools を Kubernetes 上で動かす試みやツールはありましたが、BPF が Linux Kernel の機能を使うため、ホストのカーネルを共有する(具体的にいうと、特定ディレクトリをマウントする)必要がありました。~~
~~そのため、BPF Tools のコマンドで見られる情報が Pod ではなく、Node(Host) 全体のものになってしまうことになり、見たい情報を絞ることがなかなか難しく、Pod の情報が見たいのに、Host 全体の情報が見えてしまう、という難点がありました。~~

~~kubectl debug を使うことで、自然と Pod に対する実行になります。~~

[2024/10/26 追記]
ホスト上でわざとプロセス(sleep, curl など)を作成して確認したところ、ホスト全体の情報を取っていることを確認しました。
これは推測ですが、Linux カーネルの情報を利用している以上、(情報を)取っている対象がホスト全体になるからと思われます。
Pod の情報だけを取る場合は、BPF Tools の方を修正する必要性がありそうです。
(修正するイメージは厳密には湧いていないですが、コンテナの PID のみを対象とするような修正)

ただ、現状ホスト全体の情報が見えるとはいえ、気軽に BCC や bpftrace を使えるというメリットがあります。
せっかくなので、動くイメージを作ってみました。

![2024-10-2617.34.24-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/4120db1e-da00-fc3f-4c2c-7bb7db1e5479.gif)

# 前提

以下のプラットフォームやバージョンで試しています。

* Worker Node の OS: Ubuntu 22.04 LTS
* Kubernetes: GKE 1.30.4-gke(※1)
* Kernel Version: 5.15.0-1064-gke

※1 特に大切なのが Kernel Version ですが、GKE を使うと、Node の Kernel Version が xxx-gke となり、テストできているのは GKE のみになります。
(他プラットフォームだとどうなるかは未知数です)

BPF CO-RE(Compile Once - Run Everywhere) を使っているので、多少のカーネルバージョンの差異があっても動く気はしますが、それでも Linux Kernel の機能を使っている以上、破壊的な変更や差異があると動かない気はします。
そのため、上記以外でもし動かしてみたい方がいらっしゃる場合は自己責任でお願いします。

# デバッグ用コンテナイメージ

kubectl debug で動かすデバッグ用コンテナイメージは以下のリポジトリにまとめています。

https://github.com/govargo/bpf-tools/tree/main

この Dockerfile を作るにあたって工夫・苦労した点についてまとめます。

## BCC のインストール

BCC のインストール方法は下記の公式リポジトリで案内されています。

> https://github.com/iovisor/bcc/blob/master/INSTALL.md#ubuntu---binary

```bash
# 引用元: https://github.com/iovisor/bcc/blob/master/INSTALL.md#ubuntu---binary
$ sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
```

この方法を試したのですが、Kernel Version が合わないからか、あるいはコンテナ化しているからか、Warning か Error ばかり出てうまく行きませんでした。
(ちなみにどんなエラーだったかは忘れました...)

詰まっていたところに以下の記事を読み、BPF CO-RE 版があることを知りました。

[libbpfとclangでポータブルなBPF CO-REバイナリ作成](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0694)

BPF CO-RE 版のバイナリをビルドするようにしたところ Kernel Header のインストールも不要かつ、エラーも出なくなりました。

```Dockerfile
RUN apt-get update && apt-get install -y \
      build-essential libbpf-dev clang llvm linux-tools-common git make linux-libc-dev

# Build libbpf bcc tools
RUN git clone --recursive https://github.com/iovisor/bcc.git && cd bcc/libbpf-tools && \
    make bashreadline bindsnoop biolatency biopattern...
```

## bpftrace のインストール

bpftrace のインストール方法も下記の公式リポジトリで案内されています。

> https://github.com/bpftrace/bpftrace/blob/master/INSTALL.md#ubuntu-packages

この方法も試したのですが、bpftrace が上手く動きませんでした。
apt-get でインストールできるバージョンは v0.14.0 と古く(最新版は v0.21.2)、最新版バイナリをダウンロードして解凍・展開する方法に切り替えました。
※ 最新版は v0.21.2 ですが、書いた当時は v0.20.4 が最新だったため、v0.20.4 のままになっています

Before:
```Dockerfile
RUN apt-get update && apt-get install -y \
      procps util-linux numactl strace dstat sysstat nicstat cpuid linux-tools-common bpftrace
```

After:
```
RUN apt-get update && apt-get install -y curl xz-utils
RUN curl -LO https://github.com/bpftrace/bpftrace/releases/download/v0.20.4/binary_tools_man-bundle.tar.xz && \
    xz -dv binary_tools_man-bundle.tar.xz && tar xfv binary_tools_man-bundle.tar
```

bpftrace のインストール以外にも、カーネルヘッダーのインストールも行なっています。

```Dockerfile
ARG KERNEL_VERSION=5.15.0-1064-gke

# Install debug tools
RUN apt-get update && apt-get install -y linux-headers-${KERNEL_VERSION} linux-libc-dev libc6-dev \
...
```

どうも bpftrace からカーネルヘッダーの参照を行なっているらしく、ない場合は実行はできるものの、WARNING が表示されます。

```bash
tcplife.bt 
WARNING: Could not find kernel headers in  or . To specify a particular path to kernel headers, set the env variables BPFTRACE_KERNEL_SOURCE and, optionally, BPFTRACE_KERNEL_BUILD if the kernel was built in a different directory than its source. To create kernel headers run 'modprobe kheaders', which will create a tar file at /sys/kernel/kheaders.tar.xz
Attaching 3 probes...
```

そのため、カーネルヘッダーのインストールはそのまま残しています。

## kubectl debug の注意事項

kubectl debug コマンドでは、フラグによって Profile を選択可能です。

> https://kubernetes.io/docs/reference/kubectl/generated/kubectl_debug/#options

```bash
--profile string     Default: "legacy"
```

Option には以下のものがあります。

> Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".

BCC や bpftrace を使うためには特権が必要になるため、`sysadmin` が必要になります。
kubectl で sysadmin profile がサポートされているのは 1.30 以降のため、それ以降の kubectl バイナリが必要です。

## debugfs ファイルシステムのマウント

BPF Tools のいくつかのコマンドは `/sys/kernel/debug` を参照する必要があるものがあります。
`/sys/kernel/debug` には debugfs ファイルシステムへのマウントが必要ですが、コンテナイメージ内では debugfs へのマウントがありません。

そこで、`.bashrc` に以下の行を追加することで、bash でユーザーがコンテナに入るときに mount コマンドを実行するようにしています。
(bash なので、shell で入ってこられたら実行されないと言えば、mount されないのですが...)

```Dockerfile
RUN echo "mount -t debugfs none /sys/kernel/debug" >> ~/.bashrc
```

# 終わりに

自分が特に BCC や bpftrace を触っていたのが、4年前の 2020 年あたりでしたが、久しぶりに触って、BPF CO-RE の存在が素晴らしいなと思いました。

4 年前に夢描いていたことが実現して、個人的には感無量です。

今回 kubectl debug 用のデバッグコンテナを作ったものの、まだまだ BPF や BPF Tools を活用しきれていない気がしているので、今後も勉強していきたいです。

Dockerfile も本当は使わないけど慣習的に入れている無駄なパッケージもある気がしているので、もう少しスリム化できないか検討したいところです。

以上になります。