eBPF는 커널(kernel)에서 동적으로 실행할 수 있는 사용자 정의 코드를 작성할 수 있게 해주는 기술이다. 쉽게 말해, 개발자가 작성한 코드를 커널에 직접 로드하여 커널의 동작 방식을 변경할 수 있도록 해주는 기술이다.
커널은 운영체제의 핵심 구성 요소로서 하드웨어와 애플리케이션 사이를 연결하고 시스템 자원을 관리하는 역할을 한다.
eBPF의 가장 큰 특징은 커널 내부에서 실행된다는 점이다. 이 덕분에 기존에는 어려웠던 방식으로 시스템을 관찰하고 제어할 수 있게 된다. 특히 애플리케이션을 수정하거나 재설정할 필요 없이도 다양한 정보를 수집하고 동작을 분석할 수 있다는 장점이 있다.
이러한 특성 덕분에 eBPF는 고성능 네트워킹, 시스템 관측(Observability), 보안 분야에서
예를 들어 eBPF를 활용하면 다음과 같은 작업을 수행할 수 있다.
eBPF는 BSD Packet Filter에서 시작된 기술이다. 이 개념은 1993년 Lawrence Berkeley National Laboratory의 Steven McCanne과 Van Jacobson이 작성한 논문에서 처음 소개되었다.
이 논문에서는 필터 프로그램을 실행할 수 있는 가상의 머신(pseudomachine) 개념을 설명한다. 여기서 말하는 필터는 네트워크 패킷을 받아들일지(accept) 혹은 거부할지(reject) 결정하는 작은 프로그램을 의미한다.
이 프로그램들은 BPF instruction set이라는 명령어 집합으로 작성되며, 이는 32비트 기반의 범용 명령어 세트로 구성되어 있다. 형태는 어셈블리 언어와 매우 유사하다.
논문에 등장하는 간단한 예제 코드는 다음과 같다.
ldh [12]
jeq #ETHERTYPE IP, L1, L2
L1: ret #TRUE
L2: ret #0
이 코드는 IP(Internet Protocol) 패킷이 아닌 네트워크 패킷을 걸러내는 필터다.
동작 과정을 간단히 살펴보면 다음과 같다.
1. 입력 데이터는 이더넷(Ethernet) 패킷이다.
2. 첫 번째 명령어 ldh [12]는 패킷의 12번째 바이트 위치에서 2바이트 값을 읽어온다.
3. 다음 명령어 jeq는 읽어온 값이 IP 패킷을 나타내는 값인지 비교한다.
4. 값이 일치하면 L1으로 점프하여 패킷을 허용하고(#TRUE 반환)
5. 일치하지 않으면 L2로 이동하여 패킷을 거부한다(0 반환)
LDH: 메모리 또는 패킷 데이터의 주소에서 2바이트(Halfword, 16비트) 크기의 데이터를 로드(읽기)하는 eBPF 어셈블리 명령어
JEQ: (Jump if Equal) 비교하여 분기처리하는 명령어
여기서 중요한 점은 위 명령어들이 커널 내부에서 실행됐다는 점이다. 이는 eBPF 에 핵심 기술이 된다.
이후 BPF 는 tcpdump 유틸리티에서 패킷 캡처, seccomp-bpf 에서 어플리케이션에 대한 시스템콜 허용/차단 등 단순히 패킷 분기가 아닌 다양한 분야에서 사용되기 시작한다.
BPF는 시간이 지나면서 발전하여 2014년 Linux 커널 3.18 버전부터 “extended BPF”, 즉 eBPF로 확장되었다. 이 과정에서는 기존 BPF 구조를 크게 개선하는 여러 중요한 변화가 이루어졌다.
64비트 시스템에서 더 효율적으로 동작하도록 명령어 체계가 개선되었다. 또한 BPF 인터프리터도 전면적으로 다시 작성되었다.
eBPF에서는 Maps라는 새로운 데이터 구조가 도입되었다. eBPF Map은 '커널에서 실행되는 BPF 프로그램'과 '사용자 공간(User Space) 애플리케이션' 이 두 영역이 데이터를 공유할 수 있도록 해주는 핵심 구조다.
bpf() 시스템 콜의 추가다. 이 시스템 콜을 통해 사용자 공간 프로그램이 커널 내부의 eBPF 프로그램과 상호작용할 수 있게 되었다.
eBPF 프로그램이 더 다양한 작업을 수행할 수 있도록 BPF Helper 함수들도 추가되었다.
검증을 통과한 프로그램만 커널에서 실행될 수 있도록 안전 장치가 추가되었다.
eBPF가 오늘날과 같은 강력한 기술로 자리 잡기까지는 여러 단계의 발전이 있었다. 그 과정에서 중요한 역할을 한 기능 중 하나가 kprobes(kernel probes)이다.
kprobes는 2005년부터 Linux 커널에 존재하던 기능이다. 이 기능을 이용하면 커널 코드의 거의 모든 명령어 위치에 트랩을 설정할 수 있다.
개발자는 여기에 커널 모듈을 작성해 특정 함수나 코드를 연결할 수 있으며, 이를 통해 다음과 같은 작업을 수행할 수 있었다.
하지만 이 방식은 커널 모듈을 직접 작성해야 한다는 부담이 있었다.
2015년에는 eBPF 프로그램을 kprobes에 연결할 수 있는 기능이 추가되었다.
이 변화는 Linux에서 트레이싱을 수행하는 방식에 큰 혁신을 가져왔다. 기존에는 커널 모듈을 작성해야 했던 작업을 eBPF 프로그램으로 훨씬 안전하고 쉽게 수행할 수 있게 되었기 때문이다.
커널 네트워크 스택에도 다양한 eBPF를 통해 네트워크를 처리하는 기술도 발달하였다. 2016년에는 eBPF 기반 도구들이 실제 프로덕션 환경에서 사용되기 시작했다. 이후 대기업(Netflix, Meta) 등에서 사용하고, 그리고 Cilium 과 같은 네트워크 관련 오픈소스 프로젝트도 등장하였다.
eBPF라는 이름은 원래 extended Berkeley Packet Filter의 약자다. 하지만 오늘날 eBPF의 활용 범위는 단순한 패킷 필터링을 훨씬 넘어서는 수준으로 확장되었다.
현재 eBPF는 다음과 같은 다양한 영역에서 사용된다.
• 네트워킹
• 시스템 관측(Observability)
• 보안
• 성능 분석
이처럼 활용 범위가 크게 넓어지면서 “Packet Filter”라는 이름은 기술의 실제 의미를 거의 설명하지 못하게 되었다. 그래서 오늘날에는 eBPF라는 약어 자체가 특정 기능을 의미하기보다는 하나의 독립적인 기술 이름처럼 사용되는 경우가 많다.
커널 내부에서는 BPF라는 이름을 사용한다. Linux 커널 소스 코드나 eBPF 프로그래밍 환경에서는 주로 “BPF”라는 이름이 사용된다는 것이다.
예를 들어 다음과 같은 명명 규칙을 볼 수 있다.
• eBPF 프로그램과 상호작용하는 시스템 콜 이름은 bpf()이다.
• eBPF에서 제공하는 헬퍼 함수들은 bpf_라는 접두사를 사용한다.
• eBPF 프로그램의 종류는 BPF_PROG_TYPE이라는 이름으로 정의된다.
즉, 커널 내부에서는 여전히 BPF라는 용어가 공식적인 기술 명칭처럼 사용된다.
반면 커널 커뮤니티 외부에서는 “eBPF”라는 이름이 더 널리 사용되는 경향이 있다.
대표적인 예는 다음과 같다.
• eBPF 관련 커뮤니티 사이트 ebpf.io
• 기술 생태계를 지원하는 eBPF Foundation
eBPF를 제대로 이해하려면 Linux에서 커널과 사용자 공간의 차이를 먼저 이해해야 한다.
따라서 애플리케이션이 어떤 작업을 수행하려면 커널에게 요청을 보내야 한다. 이때 사용되는 것이 시스템 콜 인터페이스다.
애플리케이션은 시스템 콜을 통해 커널에게 다음과 같은 작업을 요청한다.
즉, 하드웨어와 직접 상호작용하는 실제 작업은 모두 커널이 대신 수행한다.
개발자는 커널을 직접 다루지 않는 경우가 많다. 대부분의 프로그래밍 언어는 표준 라이브러리와 고수준 추상화를 제공하기 때문이다. 예를 들어 파일을 읽거나 네트워크 요청을 보내는 작업도 실제로는 내부적으로 시스템 콜이 호출되어 커널이 처리한다. 하지만 개발자는 이러한 복잡한 과정을 라이브러리 함수 형태로 간단하게 사용하게 된다.
strace는 프로그램을 사용하여 어플리케이션이 실행되는 동안 어떤 시스템 콜을 호출하는지 확인할 수 있다.

어플리케이션은 커널에 크게 의존하기 때문에 eBPF 를 사용하면 어플리케이션의 동작 및 사용 기록을 확인할 수 있다.
Linux 커널은 매우 거대한 소프트웨어다. 이 글을 작성하는 시점 기준으로 약 3천만 줄 이상의 코드로 이루어져 있다.이미 커널 개발 경험이 있는 사람이 아니라면 커널에 새로운 기능을 추가하는 것은 상당히 어려운 작업이 된다.
하지만 어려움은 단순히 기술적인 부분에만 있는 것이 아니다. 코드 변경 사항이 전체 Linux 생태계에 도움이 되는지에 대해 커뮤니티의 동의를 얻어야 한다.
또한 변경사항이 리눅스 커널에 공식적으로 포함되었다 하더라도 이후 리눅스 배포판(Debian, Red hat, Alpine, Ubuntu 등)에 적용되기까지 오래걸릴 수 있다.
즉, 새로운 커널 기능이 개발되더라도 실제 운영 환경에서 널리 사용되기까지는 상당한 시간이 걸릴 수 있다.
만약 커널에 변경 사항이 적용되기까지 몇 년을 기다리고 싶지 않다면 다른 방법도 있다. 바로 커널 모듈을 사용하는 것이다. Linux 커널은 커널 모듈을 동적으로 로드하거나 제거할 수 있도록 설계되어 있다. 따라서 커널의 동작을 수정하거나 기능을 확장하고 싶다면 커널 모듈을 작성하는 방식으로 이를 구현할 수 있다.
커널 모듈의 장점 중 하나는 공식 Linux 커널 릴리스와 별도로 배포할 수 있다는 점이다. 즉, 자신이 만든 기능이 커널 메인 코드에 포함되지 않더라도 다른 사용자들이 사용할 수 있다.
하지만 이 방식에는 큰 문제가 있다.
위와 같은 이유로 리눅스 배포판에서도 커널 업데이트에 신중한 편이다.
eBPF 프로그램의 중요한 특징 중 하나는 커널에 동적으로 로드하고 제거할 수 있다는 점이다. 즉, 시스템을 재부팅하거나 커널을 새로 빌드하지 않아도 필요할 때 프로그램을 커널에 추가하고 실행할 수 있다.
eBPF 프로그램은 특정 이벤트에 연결되어 동작한다. 한번 이벤트에 연결되면 그 이벤트가 발생할 때마다 자동으로 실행된다. 예를 들어 파일을 열 때 호출되는 시스템 콜에 eBPF 프로그램을 연결했다고 가정해보자. 이 경우 시스템에서 어떤 프로세스가 파일을 열려고 할 때마다 해당 eBPF 프로그램이 실행된다. 여기서 중요한 점은 프로그램이 언제 시작된 프로세스인지와 관계없이 동작한다는 것이다. 즉, 이미 실행 중이던 프로세스든 이후에 새로 시작된 프로세스든 모두 eBPF 프로그램의 영향을 받는다.
이는 기존 방식과 비교했을 때 매우 큰 장점이다. 전통적으로 커널 기능을 변경하려면 커널을 수정하고 다시 빌드한 뒤 시스템을 재부팅해야 했다. 하지만 eBPF는 재부팅 없이도 새로운 기능을 즉시 적용할 수 있다. eBPF 프로그램이 커널에 로드되는 순간 시스템에서 발생하는 모든 관련 이벤트(호스트 머신에서 실행되는 프로세스뿐만 아니라 컨테이너 내부에서 실행되는 프로세스까지)를 관찰할 수 있다. 이러한 특성 덕분에 eBPF 기반 관측 및 보안 도구는 매우 강력한 가시성을 제공한다.
eBPF 프로그램은 커널에 로드된 뒤 JIT(Just-In-Time) 컴파일 과정을 거쳐 CPU에서 직접 실행되는 네이티브 머신 코드로 변환된다. 즉, 일반적인 인터프리터 방식이 아니라 CPU가 바로 실행할 수 있는 명령어 형태로 동작하기 때문에 매우 높은 성능을 낼 수 있다.
또한 eBPF는 커널 공간과 사용자 공간 사이를 오가는 비용을 줄일 수 있다는 장점이 있다. 일반적으로 커널과 사용자 공간 사이의 전환은 비용이 큰 작업이다. 하지만 eBPF는 커널 내부에서 직접 이벤트를 처리할 수 있기 때문에 이러한 비용을 최소화할 수 있다.
eBPF 를 통해서 성능 향상한 사례가 몇몇 있다.
eBPF의 또 다른 중요한 장점은 이벤트를 커널 내부에서 먼저 필터링할 수 있다는 점이다. 예를 들어 성능 추적이나 보안 모니터링을 수행할 때 시스템에서는 매우 많은 이벤트가 발생한다. 만약 모든 이벤트를 사용자 공간으로 전달한다면 불필요한 데이터 전송 비용이 발생하게 된다. 하지만 eBPF 프로그램은 커널 내부에서 조건을 검사하고 필요한 이벤트만 선택적으로 전달할 수 있다.
오늘날 많은 조직들은 애플리케이션을 서버에서 직접 실행하는 방식 대신 클라우드 네이티브 환경에서 운영한다. 대표적인 예는 다음과 같다.
각 서버(가상 머신이든 물리 머신이든)는 Linux 커널을 실행하고 있다. 또한 컨테이너 기반 환경에서는 같은 머신에서 실행되는 여러 컨테이너가 동일한 커널을 공유한다. 예를 들어, Kubernetes 환경에서 하나의 노드 위에서 실행되는 모든 Pod와 그 안의 컨테이너들은 동일한 Linux 커널을 사용한다.
즉, 하나의 서버에 eBPF 프로그램을 올리면 해당 호스트 머신과 내부 컨테이너에 있는 모든 프로세스를 탐지할 수 있는 것이다.
위에서 언급한 특성과 동적으로 eBPF 프로그램을 로드할 수 있는 기능이 결합되면서, 클라우드 네이티브 환경에서 eBPF 기반 도구는 매우 강력한 장점을 갖게 된다.
이러한 특성은 기존의 사이드카 모델과 비교하면 더욱 분명해진다.
Kubernetes 환경에서는 로깅, 트레이싱, 보안 기능, 서비스 메시 등을 추가하기 위해 사이드카 모델이 널리 사용되어 왔다. 사이드카 모델에서는 애플리케이션 Pod 안에 추가 컨테이너를 함께 실행하여 계측이나 네트워크 기능을 제공한다. 이 과정은 보통 Pod 정의 YAML 파일을 수정하여 사이드카 컨테이너를 추가하는 방식으로 이루어진다. 이 방식은 과거처럼 애플리케이션 코드에 직접 기능을 추가하는 방식보다 훨씬 편리하다.
사이드카는 이러한 부담을 줄여주었지만, 여전히 몇 가지 문제점이 존재한다.
사이드카를 추가하려면 애플리케이션 Pod를 다시 시작해야 한다. 이는 운영 환경에서 불편함을 초래할 수 있다.
사이드카는 Pod YAML을 수정하는 과정을 필요로 한다. 이 작업은 보통 자동화되어 있지만, 설정이 잘못되면 사이드카가 제대로 추가되지 않을 수 있다.
Pod 안에 여러 컨테이너가 있을 경우 각 컨테이너의 준비 상태가 서로 다른 시점에 도달할 수 있다. 이 순서는 항상 예측 가능한 것이 아니다. 사이드카 주입은 Pod 시작 시간을 지연시키거나, 심한 경우 race condition이나 시스템 불안정성을 유발할 수도 있다. 예를 들어 일부 서비스 메시 구현에서는 Envoy 프록시가 준비되기 전까지 모든 트래픽이 차단될 수 있다.
서비스 메시와 같은 네트워크 기능이 사이드카로 구현될 경우 모든 네트워크 트래픽이 프록시 컨테이너를 거쳐야 한다. 애플리케이션 컨테이너 → 커널 네트워크 스택 → 프록시 컨테이너 → 다시 네트워크 스택와 같이 거쳐서 트래픽이 전달되므로 추가적인 네트워크 지연을 발생시킨다. (아래 이미지 참고)

하지만 eBPF라는 플랫폼이 등장하면서 새로운 접근 방식이 가능해졌다.