[Learning eBPF] eBPF 학습 시리즈 공통 실습 환경 구성하기

bocopile·2026년 7월 18일

Learning-eBPF

목록 보기
2/14

eBPF는 Linux 커널 기능이라 VM 없이는 못 돌린다

bpf() syscall과 eBPF verifier는 Linux 커널 안에 있는 코드라서, macOS(Darwin)나 Windows(NT)에는 없다. 그래서 두 OS에서 eBPF 예제를 실행하려면 결국 Linux 커널을 하나 띄워야 한다.

플랫폼별로 다른 도구를 쓰는 이유

macOS와 Linux 호스트는 같은 ebpf-lab.yaml을 재사용하고, Windows만 별도 절차(WSL2)를 쓴다.

세 화살표는 “이 호스트에서 이 도구로 Linux 환경을 얻는다”는 뜻이다. 다만 도착점이 완전히 같지는 않다 — macOS·Linux 호스트는 Lima가 Ubuntu 26.04 커널까지 통째로 띄우지만, Windows(WSL2)는 Ubuntu 26.04 userspace를 Microsoft가 관리하는 별도의 WSL2 커널 위에 얹는다. 커널 출처는 다르지만, 세 경로 모두 CONFIG_BPF* 설정과 커널 헤더/BTF 확보 절차를 거쳐야 한다는 실습 조건은 같게 맞춘다는 것이 이 선택의 핵심이다.

Linux 호스트는 이미 Linux 커널을 갖고 있는데도 굳이 다시 VM을 만든다. 배포판마다 기본 커널 버전, CONFIG_BPF* 빌드 설정, 커널 헤더 설치 방법이 다르므로, 호스트의 네이티브 커널을 그대로 쓰면 macOS 사용자가 보는 결과와 Linux 사용자가 보는 결과가 배포판 차이 때문에 갈릴 수 있다(확인 필요 — 실제로 어떤 배포판 조합에서 어떤 차이가 나는지는 아직 재현 검증하지 않았다). Lima는 macOS와 Linux 호스트를 둘 다 지원하는 도구라서, vmType(vz 또는 qemu)만 바꾸고 같은 ebpf-lab.yaml을 재사용하면 두 호스트 모두 같은 게스트 커널에 도착한다.

macOS에는 Lima 외에 UTM, Multipass도 있지만, 이 시리즈는 YAML 재현성과 headless 자동화에 유리한 Lima를 쓴다.

Windows는 사정이 다르다. Lima 공식 문서는 Windows를 host OS 목록에 올려 두긴 하지만 “untested”로 표시한다 — 지원 대상에서 아예 빠진 것은 아니지만 macOS/Linux만큼 검증되지 않았다는 뜻이고, Lima의 vmType 선택 흐름도 자체가 “host가 Windows면 WSL2를 쓰라”로 안내한다. 이 시리즈는 이 안내를 그대로 따라 Windows 절만 ebpf-lab.yaml을 쓰지 않고 wsl --install/wsl --update로 WSL2를 직접 쓰는 절차를 밟는다.

아래 절차가 확인하는 것

VM을 띄운 뒤에 오는 단계들은 장식이 아니라 각각 이후 주차가 실패했을 때 “환경 문제부터 배제”할 수 있게 하는 검증이다.

  • linux-headers-$(uname -r) 설치: BCC는 런타임에 clang으로 eBPF C 코드를 컴파일하면서 이 커널 헤더를 참조한다 — 없으면 2주차의 BCC 예제가 컴파일 단계에서 막힌다. (libbpf/CO-RE는 이 헤더 대신 BTF에서 생성한 vmlinux.h를 쓴다 — 5주차에서 다룬다.)
  • modprobe configs/modprobe kheaders와 tracefs 마운트: modprobe configs는 /proc/config.gz를 열어 커널 설정을 노출한다 — Ubuntu는 커널 패키지가 /boot/config-$(uname -r)를 이미 따로 설치해 두므로 아래 CONFIG_BPF 확인은 보통 이 modprobe 없이도 되지만, 그 파일이 없는 환경을 위한 보험으로 남겨 둔다. modprobe kheaders는 kheaders.tar.xz 경로를, tracefs 마운트는 bpf_trace_printk()의 관측 통로(trace_pipe)를 연다 — 이 경로들이 없으면 로드는 돼도 결과를 볼 방법이 없다.
  • CONFIG_BPF/CONFIG_BPF_SYSCALL/CONFIG_BPF_EVENTS 확인: 커널이 애초에 eBPF를 지원하도록 빌드됐는지 VM을 쓰기 시작하는 시점에 확인한다. 이 세 값이 y가 아니면 이후 무엇을 해도 실패하므로, 다른 원인을 찾기 전에 먼저 배제해야 하는 조건이다.
  • sudo bpftrace -e 'BEGIN { printf("hello world\n"); }': 이 시리즈가 쓰는 가장 단순한 eBPF program을 실제로 하나 로드·실행해 본다. BCC나 libbpf 같은 특정 챕터의 툴체인과 무관하게 “이 VM이 eBPF를 로드할 수 있는 상태”임을 직접 증명하는 최소 공통 확인이라서, 각 절의 마지막 단계로 둔다.

macOS

Homebrew가 설치된 macOS(Intel 또는 Apple Silicon)라면, 디스크 20GB 이상·메모리 4GB 이상의 여유만 있으면 된다.

먼저 Lima를 설치한다.

brew install lima
limactl --version

VM 설정 파일을 만든다. arch를 지정하지 않으면 호스트 아키텍처를 그대로 쓴다(Apple Silicon → aarch64, Intel → x86_64) — 책과 동일한 x86_64 출력이 필요할 때만 arch: x86_64를 추가한다.

cat > ebpf-lab.yaml <<'YAML'
minimumLimaVersion: 2.0.0

base:
- template:_images/ubuntu-26.04

cpus: 4
memory: "4GiB"
disk: "20GiB"

mounts: []

provision:
- mode: system
  script: |
    #!/bin/bash
    set -eux -o pipefail
    export DEBIAN_FRONTEND=noninteractive

    apt-get update
    apt-get install -y --no-install-recommends \
      ca-certificates \
      git \
      python3 \
      bpftrace \
      linux-headers-$(uname -r)

    modprobe configs || true
    modprobe kheaders || true

    if [ ! -e /sys/kernel/tracing/trace_pipe ] && [ ! -e /sys/kernel/debug/tracing/trace_pipe ]; then
      mkdir -p /sys/kernel/tracing
      mount -t tracefs tracefs /sys/kernel/tracing || true
    fi

probes:
- mode: readiness
  description: "kernel BPF support and bpftrace are ready"
  script: |
    #!/bin/bash
    set -eux -o pipefail
    python3 --version
    bpftrace --version
    grep -qw CONFIG_BPF=y /boot/config-$(uname -r)
YAML

VM을 시작하고 셸에 접속한다. 이후 명령은 전부 이 VM 셸 안에서 실행한다.

limactl start --name ebpf-lab ./ebpf-lab.yaml
limactl shell ebpf-lab

“아래 절차가 확인하는 것”에서 짚은 대로, 커널이 실제로 eBPF를 지원하도록 빌드됐는지 여기서 먼저 확인한다.

uname -m
uname -r
test -e /lib/modules/$(uname -r)/build && echo "headers ok"
for key in CONFIG_BPF CONFIG_BPF_SYSCALL CONFIG_BPF_EVENTS; do
  grep -w "$key=y" /boot/config-$(uname -r) || true
done

마지막으로 가장 단순한 eBPF program을 하나 실행해서 이 VM이 실제로 eBPF를 로드할 수 있는지 확인한다. Ctrl+C로 종료한다.

sudo bpftrace -e 'BEGIN { printf("hello world\n"); }'

VM을 잠깐 멈췄다가 다시 쓰고 싶다면 디스크를 보존한 채 정지·재시작하고, 아예 지우고 다시 만들고 싶다면 삭제한다.

# 정지/재시작(디스크 보존)
limactl stop ebpf-lab
limactl start ebpf-lab
# 완전 삭제
limactl stop ebpf-lab
limactl delete ebpf-lab
limactl list

자주 틀리는 지점

커널 헤더를 못 찾는다(Unable to find kernel headers)

/lib/modules/$(uname -r)/build가 없다는 뜻이다. 헤더 패키지를 다시 설치하고, 그래도 안 되면 재부팅한다.

sudo apt-get update
sudo apt-get install -y linux-headers-$(uname -r)
test -e /lib/modules/$(uname -r)/build && echo "headers ok"
sudo reboot   # 그래도 안 되면

Operation not permitted가 난다

sudo로 실행했는지, 커널 설정이 실제로 켜져 있는지 다시 확인한다.

dmesg | tail -50
grep -w "CONFIG_BPF=y" /boot/config-$(uname -r)
grep -w "CONFIG_BPF_SYSCALL=y" /boot/config-$(uname -r)

예제를 실행해도 출력이 없다

echo는 shell builtin이라 새 프로세스를 만들지 않는다. ls, id처럼 실제 실행 파일을 불러야 이벤트가 잡힌다.

/bin/ls >/dev/null
/usr/bin/id >/dev/null
ls -l /sys/kernel/tracing/trace_pipe /sys/kernel/debug/tracing/trace_pipe 2>/dev/null || true

Apple Silicon에서 아키텍처 종속 예제가 다르게 동작한다

책의 일부 예제(hello-tail.py)는 execve의 syscall opcode(59)를 x86_64 기준으로 코드에 직접 써 넣는데, aarch64에서 이 값은 221이라 같은 코드가 다르게 동작한다. arch: x86_64를 추가하면 책과 같은 출력을 재현할 수 있지만, Apple Silicon에서는 이 설정이 QEMU 에뮬레이션을 강제해 VM 전체가 눈에 띄게 느려진다 — 대부분의 실습은 네이티브 아키텍처로도 충분하므로, opcode 하드코딩처럼 아키텍처에 종속된 코드를 만났을 때만 이 비용을 치른다.

# ebpf-lab.yaml에 arch: x86_64 추가 후
limactl stop ebpf-lab
limactl delete ebpf-lab
limactl start --name ebpf-lab ./ebpf-lab.yaml

Windows(WSL2)

Windows 10(빌드 19041 이상) 또는 Windows 11에서 BIOS/UEFI virtualization을 켜 두고, 관리자 권한 PowerShell과 디스크 20GB 이상의 여유가 있으면 된다. WSL 기본 사용자는 sudo 비밀번호가 필요하므로 배포판을 설치할 때 정한 계정 비밀번호를 기억해 둔다.

관리자 권한 PowerShell에서 WSL2와 Ubuntu 26.04를 설치한다.

wsl --install -d Ubuntu-26.04

이미 설치돼 있다면 아래로 확인한다.

wsl --list --online
wsl --list --verbose

WSL 커널을 최신으로 올린다.

wsl --update
wsl --shutdown

이후 명령은 Ubuntu-26.04 터미널 안에서 실행한다. WSL2는 일반 Ubuntu처럼 linux-headers-$(uname -r)를 설치하는 흐름이 아니므로, 대신 kheaders 경로를 확인해 둔다.

sudo apt-get update
sudo apt-get install -y ca-certificates git python3 bpftrace
sudo modprobe configs || true
sudo modprobe kheaders || true

test -e /sys/kernel/kheaders.tar.xz && echo "kheaders ok" || echo "kheaders missing"

tracefs/debugfs를 마운트하고 커널을 확인한다.

sudo mkdir -p /sys/kernel/tracing /sys/kernel/debug
sudo mount -t tracefs tracefs /sys/kernel/tracing 2>/dev/null || true
sudo mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true

uname -m
uname -r

macOS 절과 마찬가지로, 가장 단순한 eBPF program을 하나 실행해 본다. Ctrl+C로 종료한다.

sudo bpftrace -e 'BEGIN { printf("hello world\n"); }'

예제 코드는 /mnt/c/...가 아니라 WSL 홈 디렉터리(~) 아래에서 받는다.

배포판만 종료하거나, WSL 전체를 재시작하거나, 완전히 지울 수 있다.

wsl --terminate Ubuntu-26.04    # 배포판만 종료
wsl --shutdown                  # WSL 전체 재시작
wsl --unregister Ubuntu-26.04   # 완전 삭제(복구 불가)

자주 틀리는 지점

WSL1로 설치돼 있다

wsl --list --verbose
wsl --set-version Ubuntu-26.04 2

kheaders missing이 계속된다

커널 소스에서 직접 헤더를 추출한다.

git ls-remote --heads https://github.com/microsoft/WSL2-Linux-Kernel.git   # 브랜치명 확인
sudo apt-get install -y build-essential flex bison libssl-dev libelf-dev dwarves bc
git clone --depth=1 --branch linux-msft-wsl-<커널버전>.y \
  https://github.com/microsoft/WSL2-Linux-Kernel.git
cd WSL2-Linux-Kernel
sudo make headers_install ARCH=x86_64 INSTALL_HDR_PATH=/usr

trace 출력이 안 보인다

sudo mount -t tracefs tracefs /sys/kernel/tracing 2>/dev/null || true
sudo mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true
ls -l /sys/kernel/tracing/trace_pipe /sys/kernel/debug/tracing/trace_pipe 2>/dev/null || true
/bin/ls >/dev/null

Operation not permitted 또는 PID가 ps와 안 맞는다

Operation not permitted는 root(sudo)로 실행하면 해결된다. PID가 안 맞는 건 WSL2의 PID namespace 구조 차이 때문이다(microsoft/WSL#12408) — 정확한 PID 매칭보다 이벤트가 도착하는지로 성공을 판단한다.

Linux 호스트

Linux(x86_64 또는 aarch64)에서 하드웨어 virtualization이 켜져 있고 QEMU가 설치돼 있으면 된다. 현재 사용자가 /dev/kvm에 접근할 수 있어야 하고(kvm group), 디스크 20GB 이상·메모리 4GB 이상의 여유가 필요하다.

QEMU를 설치한다.

sudo apt-get update && sudo apt-get install -y qemu-system-x86 qemu-utils   # Debian/Ubuntu
sudo dnf install -y qemu-system-x86 qemu-img                                # Fedora
sudo pacman -S --needed qemu-base qemu-system-x86                           # Arch

KVM 권한을 확인하고, 없으면 추가한다.

ls -l /dev/kvm
test -r /dev/kvm -a -w /dev/kvm && echo "kvm ok"
sudo usermod -aG kvm "$USER"   # 권한 없으면, 이후 재로그인 또는 newgrp kvm

Lima를 설치한다.

brew install lima
limactl --version

Homebrew를 안 쓴다면 GitHub release binary를 받는다.

VERSION=$(curl -fsSL https://api.github.com/repos/lima-vm/lima/releases/latest | jq -r .tag_name)
curl -fsSL "https://github.com/lima-vm/lima/releases/download/${VERSION}/lima-${VERSION#v}-$(uname -s)-$(uname -m).tar.gz" \
  | sudo tar Cxzvm /usr/local

macOS 절에서 만든 것과 같은 ebpf-lab.yaml을 그대로 재사용한다 — 이미 Linux 커널이 있는 호스트에서도 새로 VM을 만드는 이유는 위 “플랫폼별로 다른 도구를 쓰는 이유”에서 설명한 재현성 때문이다. vmType을 명시하려면 한 줄만 추가한다.

echo 'vmType: qemu' >> ebpf-lab.yaml

VM을 시작하고 접속한다. 이후 커널 확인과 hello world 검증은 macOS 절과 동일하다.

limactl start --name ebpf-lab ./ebpf-lab.yaml
limactl shell ebpf-lab

macOS 절과 같은 방식으로 정지·재시작하거나 완전히 지울 수 있다.

# 정지/재시작(디스크 보존)
limactl stop ebpf-lab
limactl start ebpf-lab
# 완전 삭제
limactl stop ebpf-lab
limactl delete ebpf-lab
limactl list

자주 틀리는 지점

Could not access KVM kernel module: Permission denied

groups
ls -l /dev/kvm
sudo usermod -aG kvm "$USER"   # 이후 재로그인 또는 newgrp kvm 필요

aarch64 호스트에서 아키텍처 종속 예제가 다르게 동작한다

macOS Apple Silicon과 같은 이유다. ebpf-lab.yaml에 arch: x86_64를 추가한 뒤 VM을 재생성한다.

limactl stop ebpf-lab
limactl delete ebpf-lab
limactl start --name ebpf-lab ./ebpf-lab.yaml

이 문서가 다루지 않는 것

eBPF program을 실행할 때 필요한 권한(CAP_BPF, CAP_PERFMON, CAP_NET_ADMIN 조합)과 Operation not permitted 대응은 이 문서의 범위 밖이다. 이 조합은 그 장이 로드하는 program 종류(tracing인지 networking인지)에 따라 달라지므로, 각 주차 글이 자기 장의 코드를 기준으로 직접 설명한다. BCC 설치처럼 특정 챕터의 툴체인 설치와 실행 절차도 마찬가지로 그 장의 BLOG.md가 다룬다(예: week02-hello-world/BLOG.md). 예제 코드는 github.com/lizrice/learning-ebpf 저장소에 챕터별 디렉터리(chapter2, chapter3, …)로 나뉘어 있다는 사실만 여기서 짚고, 정확한 디렉터리 이름은 각 주차 글이 자기 장을 가리킬 때 직접 밝힌다.

검증 범위와 한계

이 문서의 안내는 특정 VM·커널·배포판 조합으로 직접 재현 검증한 결과가 아니다(확인 필요). 배포판마다 패키지 이름과 커널 헤더 요구 사항이 다를 수 있으므로, 이 문서는 특정 환경에서의 설치·실행 성공을 보장하지 않는다. 재현에 실패한 조합을 발견하면 위 “자주 틀리는 지점”에 먼저 기록하고, 그 원인이 위 설명(재현성을 위한 VM 통일, arch 트레이드오프 등)과 어긋나는지 확인한다.

참고 자료

profile
DevOps Engineer

0개의 댓글