LabHub

블로그

eBPF가 관측성을 삼키고 있다

한국어English日本語中文

들어가며 — 커널 안에서 코드를 돌린다는 발상

관측성(observability) 도구를 깔아 본 사람이라면 익숙한 피로가 있습니다. 애플리케이션마다 에이전트를 넣고, 언어마다 계측 라이브러리를 붙이고, 파드마다 사이드카를 띄우고, 그렇게 붙인 것들이 다시 CPU와 메모리를 갉아먹습니다. 무언가를 관측하려고 시스템을 점점 더 무겁고 복잡하게 만드는 역설입니다.

eBPF는 이 문제를 전혀 다른 각도에서 공격합니다. 애플리케이션에 손을 대는 대신, 커널 안으로 내려가 그 밑에서 모든 것을 지켜봅니다. 네트워크 패킷, 시스템 콜, 함수 호출, 디스크 I/O가 전부 커널을 거치기 때문에, 커널에서 한 번 관측하면 그 위에 무엇이 돌든 계측할 수 있습니다. 애플리케이션은 자기가 관측되고 있다는 사실조차 모릅니다.

이 글은 eBPF가 정확히 무엇이고, 어떻게 커널 안에서 임의의 코드를 안전하게 돌릴 수 있는지, 그리고 왜 그것이 관측성 도구들을 하나씩 삼키고 있는지를 다룹니다. 동시에 eBPF가 만능이 아니라는 점, 그 한계가 어디인지도 솔직하게 짚습니다.

eBPF란 무엇인가 — 커널 안의 작은 가상 머신

eBPF(extended Berkeley Packet Filter)는 이름이 무색하게 더 이상 패킷 필터에 국한되지 않습니다. 지금의 eBPF는 리눅스 커널 안에 내장된 작은 가상 머신에 가깝습니다. 여러분이 작은 프로그램을 작성하면, 커널이 그것을 특정 이벤트에 붙여 두었다가, 그 이벤트가 발생할 때마다 커널 문맥 안에서 실행합니다.

핵심은 이렇습니다. 전통적으로 커널의 동작을 바꾸거나 들여다보려면 커널 모듈을 작성해야 했습니다. 커널 모듈은 강력하지만 위험합니다. 버그 하나가 커널 전체를 패닉으로 몰아넣고, 잘못 짜면 시스템을 멈추게 합니다. 그래서 프로덕션 커널에 함부로 모듈을 올리는 것은 큰 결심이 필요한 일이었습니다.

eBPF는 이 위험을 제거합니다. eBPF 프로그램은 커널 안에서 돌지만, 커널을 무너뜨릴 수 없도록 엄격하게 제약됩니다. 이 제약을 강제하는 것이 뒤에서 자세히 다룰 검증기(verifier)입니다. 덕분에 우리는 커널 모듈의 힘에 근접하면서도, 시스템을 멈추게 할 위험 없이 프로덕션에서 프로그램을 붙였다 뗐다 할 수 있습니다.

동작의 큰 그림은 다음과 같습니다.

  사용자 공간
  +------------------------------+
  | 1. C/Rust 등으로 eBPF 작성   |
  | 2. LLVM이 eBPF 바이트코드로  |
  |    컴파일                    |
  +--------------+---------------+
                 | bpf() 시스템 콜로 로드
                 v
  커널 공간
  +------------------------------+
  | 3. 검증기가 안전성 검사      |
  | 4. JIT가 네이티브 코드로 변환|
  | 5. 후크 지점에 부착          |
  +--------------+---------------+
                 | 이벤트 발생 시 실행
                 v
  +------------------------------+
  | 6. 맵(map)에 결과 기록       |
  |    사용자 공간이 맵을 읽음   |
  +------------------------------+

프로그램은 사용자 공간에서 작성·컴파일되어 bpf() 시스템 콜로 커널에 로드됩니다. 검증기를 통과하면 JIT 컴파일러가 이를 네이티브 기계어로 바꾸고, 지정한 후크 지점에 붙습니다. 프로그램이 수집한 데이터는 맵이라는 공유 자료구조를 통해 사용자 공간으로 전달됩니다.

어디에 후크를 걸 수 있나 — kprobe, uprobe, tracepoint, XDP

eBPF의 힘은 "어디에 붙일 수 있는가"에서 나옵니다. 후크 지점의 종류가 곧 관측할 수 있는 대상의 범위입니다. 대표적인 네 가지를 봅시다.

이 목록을 관통하는 통찰이 하나 있습니다. 시스템에서 흥미로운 일은 거의 다 커널을 거친다는 것입니다. 파일을 열든, 소켓을 만들든, 프로세스를 포크하든, 패킷을 보내든, 그 순간 커널의 어떤 함수나 트레이스포인트가 실행됩니다. eBPF는 바로 그 지점들에 앉아서, 시스템 전체의 활동을 한자리에서 지켜봅니다.

검증기 — 왜 커널이 죽지 않는가

여기서 자연스러운 의문이 듭니다. 임의의 코드를 커널 안에서 돌린다면, 무한 루프에 빠지거나 잘못된 메모리를 건드려 커널을 무너뜨릴 수 있지 않은가? eBPF가 프로덕션에서 신뢰받는 이유가 바로 이 질문에 대한 답, 검증기에 있습니다.

검증기는 프로그램이 커널에 로드되는 순간, 실행되기 전에 정적으로 분석합니다. 프로그램이 가질 수 있는 모든 실행 경로를 따라가며 다음을 증명하려 합니다.

이 과정을 통과하지 못하면 커널은 프로그램 로드를 거부합니다. 즉 안전하지 않은 eBPF 프로그램은 애초에 실행될 기회를 얻지 못합니다. 이것이 커널 모듈과의 결정적 차이입니다. 커널 모듈은 "믿고 올리는" 것이지만, eBPF는 "증명되어야 올라가는" 것입니다.

물론 검증기는 완벽하지 않습니다. 때로는 실제로 안전한 프로그램인데도 검증기가 안전성을 증명하지 못해 거부하는 경우가 있습니다. eBPF 개발자들이 "검증기와 싸운다"고 농담하는 이유가 여기 있습니다. 검증기를 만족시키려고 멀쩡한 코드를 이리저리 비트는 경험은 eBPF를 진지하게 다뤄 본 사람이라면 누구나 겪습니다. 하지만 이 엄격함이야말로 커널이 죽지 않는 대가입니다.

제로 계측 추적 — 코드를 고치지 않는다

eBPF가 관측성에서 혁명적인 이유를 한마디로 요약하면 "제로 계측(zero-instrumentation)"입니다. 전통적인 관측성은 애플리케이션 코드에 계측을 심는 데서 출발합니다. 트레이싱을 하려면 각 요청에 스팬을 여는 코드를 넣고, 메트릭을 뽑으려면 카운터를 증가시키는 코드를 넣습니다. 언어마다 SDK가 다르고, 라이브러리를 업데이트할 때마다 다시 손봐야 합니다.

eBPF는 이 전제를 뒤집습니다. 애플리케이션은 그대로 두고, 커널 후크에서 필요한 정보를 관측합니다. HTTP 요청을 추적하고 싶다면 소켓 관련 커널 함수에, gRPC 지연을 재고 싶다면 관련 uprobe에 후크를 걸면 됩니다. 애플리케이션은 다시 컴파일할 필요도, 재시작할 필요도, 심지어 자기가 관측되고 있다는 사실을 알 필요도 없습니다.

이것이 실무에서 갖는 의미는 큽니다.

다만 "제로 계측"이라는 말은 오해를 부를 수 있으니 정확히 해둡시다. eBPF가 커널 레벨에서 자동으로 보는 것은 시스템 콜, 네트워크 흐름, 함수 호출 같은 저수준 신호입니다. "이 요청이 어느 비즈니스 트랜잭션에 속하는가" 같은 애플리케이션 고유의 의미론은 커널이 알 수 없습니다. 그래서 고수준 분산 트레이싱의 문맥 전파 같은 것은 여전히 애플리케이션의 협조가 필요할 때가 많습니다. eBPF는 계측의 상당 부분을 없애 주지만, 모든 것을 없애 주지는 않습니다.

도구들 — Cilium, Falco, Pixie, bpftrace

이론을 넘어, eBPF는 이미 프로덕션에서 널리 쓰이는 도구들의 엔진이 되었습니다. 대표적인 넷을 봅시다.

예를 들어 bpftrace로 어떤 프로세스가 어떤 시스템 콜을 얼마나 호출하는지 세어 보는 것은 이렇게 간단합니다.

# 프로세스별 시스템 콜 호출 횟수를 집계
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

이 한 줄이 커널 트레이스포인트에 후크를 걸고, 시스템 콜이 발생할 때마다 프로세스 이름(comm)별로 카운터를 증가시킨 뒤, 종료 시 집계를 출력합니다. 별도의 에이전트도, 애플리케이션 수정도 없습니다. 이 즉시성이 bpftrace가 프로덕션 디버깅에서 사랑받는 이유입니다.

이 도구들을 브라우저에서 개념적으로 실습해 보고 싶다면 eBPF 놀이터에서 eBPF 명령어 세트와 검증기의 동작을 시뮬레이션으로 익힐 수 있고, 쿠버네티스 네트워킹 맥락은 쿠버네티스 네트워크 실습실에서 살펴볼 수 있습니다.

왜 사이드카를 이기는가

최근 몇 년간 클라우드 네이티브 세계의 기본 패턴은 사이드카였습니다. 서비스 메시는 모든 파드 옆에 프록시 컨테이너를 붙여 트래픽을 가로채고 관측했습니다. 이 모델은 잘 동작했지만 대가가 컸습니다.

사이드카의 문제를 짚어 봅시다.

eBPF는 이 구조를 근본적으로 바꿉니다. 프록시를 각 파드 옆에 두는 대신, 커널 안에서 한 번 관측하고 정책을 적용합니다. 노드마다 프록시 1000개가 아니라, 커널이라는 공유 지점 하나에서 처리하는 것입니다. 트래픽이 사용자 공간의 프록시로 우회했다가 돌아올 필요가 없으니 지연도 줄어듭니다.

물론 이것이 "사이드카는 죽었다"는 뜻은 아닙니다. 사이드카는 여전히 애플리케이션 레벨의 복잡한 로직(고급 라우팅 규칙, 프로토콜별 정교한 조작)을 다루기에 유리합니다. eBPF는 저수준의 빠른 경로에서, 사이드카는 고수준의 유연한 경로에서 각자의 강점이 있습니다. 실제로 업계는 두 접근을 조합하는 "사이드카 없는 데이터 플레인 + 필요한 곳에만 프록시"라는 하이브리드로 수렴하는 중입니다.

eBPF가 못 하는 것 — 한계와 함정

eBPF는 강력하지만 만능이 아닙니다. 도구를 도입하기 전에 알아 둘 한계가 분명히 있습니다.

이 한계들을 종합하면, eBPF는 "리눅스 위에서, 저수준·고성능·시스템 전반의 관측과 네트워킹"에 최적입니다. 크로스 플랫폼이 필요하거나, 깊은 애플리케이션 의미론이 핵심이거나, 검증기가 감당 못 할 만큼 복잡한 로직이 필요한 경우에는 다른 접근이 낫습니다.

마치며 — 관측성의 무게중심이 내려간다

eBPF가 일으키는 변화의 본질은 관측성의 무게중심이 애플리케이션에서 커널로 내려간다는 것입니다. 예전에는 관측을 위해 애플리케이션마다 계측을 심고 파드마다 사이드카를 붙였습니다. 이제는 커널이라는 공유 지점에 한 번 후크를 걸어, 그 위에 무엇이 돌든 지켜봅니다.

이 이동이 매력적인 이유는 명확합니다. 언어와 무관하고, 코드 수정이 필요 없으며, 오버헤드가 작고, 우회하기 어렵습니다. Cilium이 서비스 메시를, Falco가 런타임 보안을, Pixie가 자동 관측을, bpftrace가 즉석 디버깅을 각자의 방식으로 재정의하고 있는 것도 이 때문입니다.

동시에 냉정함도 필요합니다. eBPF는 리눅스에 묶여 있고, 검증기는 까다로우며, 커널이 볼 수 있는 것에는 의미론적 한계가 있습니다. "eBPF가 관측성을 삼키고 있다"는 말은 과장이 아니지만, 그것이 모든 것을 삼킨다는 뜻은 아닙니다. 커널이 잘 볼 수 있는 저수준 세계에서 eBPF는 압도적이고, 그 위의 고수준 세계에서는 여전히 다른 도구들과 공존합니다.

다음에 관측성 스택을 설계할 일이 있다면, 애플리케이션에 무언가를 더 붙이기 전에 이렇게 물어보시기 바랍니다. "이건 커널에서 이미 볼 수 있는 것 아닐까?" 의외로 많은 경우, 답은 "그렇다"입니다.

참고 자료

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다