0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【cilium/ebpf】k8s環境でeBPFプログラムを実行してみた

Posted at

はじめに

以前に作成したeBPFプログラムをKubernetesクラスタのDaemonSetとして起動します。

今回は、eBPFプログラムのコンパイルにebpf-goを利用します。
ebpf-goに付属するbpf2goというコード生成ツールを利用すると、必要なeBPFバイトコードをすべて内包した単一のバイナリとして、eBPFプログラムをコンパイルすることができます。
詳細は公式ドキュメントを参照して下さい。

実行環境

実行環境はM3 Macです。colimaでk3sクラスタを起動します。

colima start --cpu 2 --memory 6 --disk 80 --arch x86_64 --kubernetes --runtime containerd

コンテナイメージを格納するためのイメージレジストリをk3sにデプロイします。DaemonSetの検証に利用します。

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: private-registry
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: private-registry
  template:
    metadata:
      labels:
        app: private-registry
    spec:
      containers:
      - name: registry
        image: registry:2
        ports:
        - containerPort: 5000
        volumeMounts:
        - name: registry-storage
          mountPath: /var/lib/registry
      volumes:
      - name: registry-storage
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: private-registry-service
  namespace: kube-system
spec:
  type: NodePort
  selector:
    app: private-registry
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 5000
    nodePort: 30500
EOF

ディレクトリ構成

ユーザー空間プログラムをGo (cilium/ebpf)で再実装します。cmd/agent-checker/main.goが該当します。

.
├── Containerfile      # コンテナイメージ
├── daemonset.yaml     # k8sマニフェスト
├── cmd                # ユーザー空間プログラムをGo(cilium/ebpf)で再実装
│   └── agent-checker
│       └── main.go
├── ebpf               # 以前の記事で作成したeBPFプログラム
│   ├── agent.c
│   └── bpf_common.h
├── go.mod
└── go.sum

コード解説

ebpf/agent.c

以前に作成した内容から変更はありません。所定のディレクトリに配置します。

ebpf/bpf_common.h

こちらも内容に変更はありません。

cmd/agent-checker/main.go

cilium/ebpf/cmd/bpf2goを使ってコンパイルされたeBPFオブジェクトをロードし、カーネルにアタッチします。

//go:generateディレクティブを設定した状態でgo generateコマンドを実行すると、eBPFプログラムのCコードがコンパイルされ、Goのコードとして埋め込まれます。

package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/rlimit"
)

// bpf2goのコンパイル指定
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang bpf ../../ebpf/agent.c -- -I../../ebpf/

const (
	// eBPFマップの統計情報を表示する間隔(秒)を取得するための環境変数名
	intervalEnvVar = "CHECKER_INTERVAL_SECONDS"
	// 環境変数が設定されていない場合のデフォルトの間隔(秒)
	defaultInterval = 30
)

func main() {
	// OSシグナルハンドリングの設定 (Ctrl+Cなどで終了できるようにする)
	stopper := make(chan os.Signal, 1)
	signal.Notify(stopper, os.Interrupt, syscall.SIGTERM)

	// eBPFプログラムのためのメモリロック上限を緩和
	// eBPFプログラムとマップをカーネルにロードするために必要なメモリを、プロセスが確保できるようにするため
	if err := rlimit.RemoveMemlock(); err != nil {
		log.Fatal(err)
	}

	// bpf2goで生成されたeBPFオブジェクトをロード
	objs := bpfObjects{}
	if err := loadBpfObjects(&objs, nil); err != nil {
		log.Fatalf("loading objects: %v", err)
	}
	// main関数終了時に、ロードしたeBPFオブジェクトをクリーンアップする
	defer objs.Close()

	// tcp_sendmsg 関数にkprobeをアタッチ
	kpSend, err := link.Kprobe("tcp_sendmsg", objs.TraceTcpSendmsg, nil)
	if err != nil {
		log.Fatalf("attaching kprobe tcp_sendmsg: %s", err)
	}
	// main関数終了時にkprobeをデタッチ
	defer kpSend.Close()

	// tcp_cleanup_rbuf 関数もアタッチ
	kpRecv, err := link.Kprobe("tcp_cleanup_rbuf", objs.TraceTcpCleanupRbuf, nil)
	if err != nil {
		log.Fatalf("attaching kprobe tcp_cleanup_rbuf: %s", err)
	}
	defer kpRecv.Close()

	log.Println("Successfully loaded and attached BPF programs. Watching TCP traffic...")
	log.Println("Press Ctrl+C to exit.")

	// 環境変数からタイマーの間隔を読み込む
	intervalStr := os.Getenv(intervalEnvVar)
	interval, err := strconv.Atoi(intervalStr)
	if err != nil || interval <= 0 {
		log.Printf("Invalid or no interval set via %s. Using default: %d seconds", intervalEnvVar, defaultInterval)
		interval = defaultInterval
	} else {
		log.Printf("Interval set to %d seconds via %s", interval, intervalEnvVar)
	}

	// 定期的にeBPFマップの内容を表示するためのタイマーを設定
	ticker := time.NewTicker(time.Duration(interval) * time.Second)
	defer ticker.Stop()

	// メインループ:タイマーの通知か、OSの終了シグナルを待つ
	for {
		select {
		case <-ticker.C:
			printStats(&objs)
		case <-stopper:
			log.Println("Received signal, detaching and cleaning up...")
			return
		}
	}
}

// eBPFマップから統計情報を読み出して標準出力する
func printStats(objs *bpfObjects) {
	now := time.Now().Format("2006-01-02 15:04:05")
	fmt.Printf("\n--- Start printStats (%s) ---", now)
	fmt.Printf("\n--- TX Stats ---\n")
	printMap(objs.TxStats)

	fmt.Printf("--- RX Stats ---\n")
	printMap(objs.RxStats)
}

// 指定されたeBPFマップの内容をイテレートして標準出力する
func printMap(statsMap *ebpf.Map) {
	var key bpfIpKeyT
	var val bpfValT
	iter := statsMap.Iterate()

	// iter.Next() を使ってマップの全エントリをループ処理
	for iter.Next(&key, &val) {
		// C言語版のaddr_to_str関数と同様に、[16]byteのIPアドレスを文字列に変換
		saddr := net.IP(key.Saddr[:]).String()
		daddr := net.IP(key.Daddr[:]).String()
		fmt.Printf("  %s -> %s : %d bytes\n", saddr, daddr, val.Bytes)
	}

	if err := iter.Err(); err != nil {
		log.Printf("Error iterating map: %v", err)
	}
}

go.mod

Goプロジェクトの設定ファイルです。go mod tidyコマンドを実行して、go.sumを生成して下さい。

module github.com/t-matsu200/k8s-agent-checker

go 1.24.4

require github.com/cilium/ebpf v0.19.0

require golang.org/x/sys v0.31.0 // indirect

Containerfile

cilium/ebpf-builderイメージには、eBPFプログラムの開発に必要なClang, LLVM, libbpf, Goなどのツールチェーン一式が含まれています。

cilium/ebpf-builderイメージをeBPFプログラムのコンパイル環境として利用します。

FROM ghcr.io/cilium/ebpf-builder:1755266860 AS builder

# clangとllvm-stripコマンドのバージョンを17に指定
RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-17 90
RUN update-alternatives --install /usr/bin/llvm-strip llvm-strip /usr/bin/llvm-strip-17 90

# bpftoolのインストール
RUN git clone --recurse-submodules https://github.com/libbpf/bpftool.git /tmp/bpftool \
    && cd /tmp/bpftool/src \
    && make install \
    && rm -r /tmp/bpftool

WORKDIR /app

# Goモジュールのダウンロード
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# BTF情報からvmlinux.hを生成
RUN bpftool btf dump file /sys/kernel/btf/vmlinux format c > ./ebpf/vmlinux.h

# go generate を実行してeBPFプログラムをコンパイル
RUN BPF2GO_CFLAGS="-O2 -g -Wall -Werror -D__TARGET_ARCH_x86 $(CFLAGS)" go generate ./cmd/agent-checker/...

# CGO_ENABLED=0 で静的にリンクされたバイナリをビルドします
# https://ebpf-go.dev/guides/portable-ebpf/
RUN CGO_ENABLED=0 go build -buildvcs=false -o /app/agent-checker ./cmd/agent-checker/...

FROM alpine:3.22

COPY --from=builder /app/agent-checker /usr/local/bin/agent-checker

ENTRYPOINT ["/usr/local/bin/agent-checker"]

daemonset.yaml

コンパイルしたeBPFプログラムを実行するDaemonSetのマニフェストです。設定内容についてはコメントを参照して下さい。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: agent-checker
  namespace: kube-system
  labels:
    app: agent-checker
spec:
  selector:
    matchLabels:
      app: agent-checker
  template:
    metadata:
      labels:
        app: agent-checker
    spec:
      # コントロールプレーンノードを含む、すべてのノードでPodがスケジュールされるように設定
      tolerations:
      - operator: "Exists"
      # ホストOS上で実行されている全てのプロセスを正しく監視できるようにするため、ホストのPID名前空間を使用します
      hostPID: true
      containers:
      - name: agent-checker
        image: localhost:30500/ebpf-agent-checker
        imagePullPolicy: Always
        env:
        - name: CHECKER_INTERVAL_SECONDS
          value: "60"
        securityContext:
          # eBPFプログラムのロードと実行に必要なケーパビリティを設定
          # - SYS_ADMIN: BPFやperfイベントへのアクセスなど、多くの管理操作に必要です
          # - BPF: eBPFマップの作成やプログラムのロードに直接必要です
          # - PERFMON: kprobeなどのトレースポイントにアクセスするために必要です
          capabilities:
            drop:
            - ALL
            add:
            - SYS_ADMIN
            - BPF
            - PERFMON
        # eBPFプログラムのロードとデバッグに必要なホストのディレクトリをマウントします
        volumeMounts:
        - name: sys-fs-cgroup
          mountPath: /sys/fs/cgroup
          readOnly: true
        - name: sys-kernel-debug
          mountPath: /sys/kernel/debug
          readOnly: true
        - name: sys-kernel-tracing
          mountPath: /sys/kernel/tracing
          readOnly: true
        - name: sys-fs-bpf
          mountPath: /sys/fs/bpf
          readOnly: true

      # ホストからマウントするボリュームを定義します
      volumes:
      - name: sys-fs-cgroup
        hostPath:
          path: /sys/fs/cgroup
      - name: sys-kernel-debug
        hostPath:
          path: /sys/kernel/debug
      - name: sys-kernel-tracing
        hostPath:
          path: /sys/kernel/tracing
      - name: sys-fs-bpf
        hostPath:
          path: /sys/fs/bpf

動作確認

コンテナイメージをビルドして、ローカルのイメージレジストリにプッシュします。

nerdctl build -f Containerfile -t localhost:30500/ebpf-agent-checker .
nerdctl push localhost:30500/ebpf-agent-checker --insecure-registry

DaemonSetのマニフェストをk3sに適用します。

kubectl apply -f daemonset.yaml

agent-checker Podの実行ログを確認します。

kubectl logs -n kube-system -l app=agent-checker --tail=50 -f

以下のようなログが出力されました。

2025/09/14 15:40:19 Successfully loaded and attached BPF programs. Watching TCP traffic...
2025/09/14 15:40:19 Press Ctrl+C to exit.
2025/09/14 15:40:19 Interval set to 60 seconds via CHECKER_INTERVAL_SECONDS

--- Start printStats (2025-09-14 15:41:19) ---
--- TX Stats ---
  ::1 -> ::1 : 113183 bytes
  10.42.0.182 -> 10.43.0.1 : 15990 bytes
  10.42.0.1 -> 10.42.0.184 : 21430 bytes
  ...
--- RX Stats ---
  10.42.0.187 -> 192.168.5.1 : 181 bytes
  10.42.0.186 -> 10.42.0.1 : 4932 bytes
  ...

クラスタ内で curl コマンドを実行するなどしてTCP通信を発生させると、60秒ごとに統計情報が出力されます。

まとめ

cilium/ebpfライブラリとビルダーイメージを活用することで、eBPFプログラムの開発からKubernetesへのデプロイまでをスムーズに行うことができました。
次回は、eBPFプログラムで収集したデータをメトリクスとして公開するためのk8sカスタムコントローラーを作成したいと思います。

参考情報

以下、参考情報です。

DaemonSetがマウントしているファイルシステム

sysfsと呼ばれる、Linuxカーネルによって提供される仮想ファイルシステムです。

パス 説明
/sys/fs/cgroup リソース制御(cgroup)機能へのインターフェース
/sys/kernel/debug カーネルのデバッグ情報へのインターフェース
/sys/kernel/tracing トレーシング機能(ftrace)へのインターフェース
/sys/fs/bpf BPFオブジェクト管理機能へのインターフェース

詳細はmanページを参照して下さい。

bpf2goのコンパイルの仕組み

公式ドキュメントに、コンパイルしたCのコードをGoに取り込む仕組みについて、簡単に記述されています。

今回はコンテナイメージのビルド時にコンパイルしていますが、ローカル環境でコンパイルすると自動生成された各種コードを手元で確認することができます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?