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를 로드할 수 있는 상태”임을 직접 증명하는 최소 공통 확인이라서, 각 절의 마지막 단계로 둔다.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

책의 일부 예제(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 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 # 완전 삭제(복구 불가)
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
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(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 deniedgroups
ls -l /dev/kvm
sudo usermod -aG kvm "$USER" # 이후 재로그인 또는 newgrp kvm 필요
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 트레이드오프 등)과 어긋나는지 확인한다.