Skip to content

필사 모드: eBPF가 관측성을 삼키고 있다

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

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

관측성(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의 힘은 "어디에 붙일 수 있는가"에서 나옵니다. 후크 지점의 종류가 곧 관측할 수 있는 대상의 범위입니다. 대표적인 네 가지를 봅시다.

  • kprobe (커널 프로브): 커널 함수의 진입 또는 반환 시점에 프로그램을 붙입니다. 예를 들어 tcp_connect가 호출될 때마다 실행되도록 걸 수 있습니다. 사실상 커널의 어떤 함수든 후크할 수 있어 가장 유연하지만, 커널 내부 함수 이름에 의존하므로 커널 버전이 바뀌면 깨질 수 있습니다.
  • uprobe (사용자 프로브): 사용자 공간 프로그램의 함수에 붙입니다. 애플리케이션 바이너리의 특정 함수가 호출될 때 실행됩니다. 이것이 "코드를 안 고치고" 애플리케이션 내부를 들여다보는 핵심 수단입니다. 예를 들어 SSL 라이브러리의 암호화 함수에 uprobe를 걸어 평문 트래픽을 관측하는 식입니다.
  • tracepoint (트레이스포인트): 커널 개발자가 미리 심어 둔 안정적인 계측 지점입니다. kprobe와 달리 커널이 공식적으로 유지하는 API라서, 커널 버전이 올라가도 잘 깨지지 않습니다. 안정성이 중요하면 kprobe보다 tracepoint를 선호합니다.
  • XDP (eXpress Data Path): 네트워크 스택의 가장 앞단, 즉 패킷이 드라이버에 도착하자마자 실행됩니다. 커널 네트워크 스택을 거치기도 전이므로 극도로 빠릅니다. 초당 수백만 패킷을 처리하는 DDoS 방어나 로드밸런싱이 여기서 이루어집니다.

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

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

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

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

  • 종료 보장: 프로그램이 반드시 끝나야 합니다. 전통적으로 무제한 반복문은 금지되었고, 지금은 검증기가 상한을 증명할 수 있는 유계 루프만 허용합니다. 절대 멈추지 않는 프로그램은 로드조차 되지 않습니다.
  • 메모리 안전: 프로그램은 허용된 메모리 영역만 읽고 쓸 수 있습니다. 포인터를 역참조하기 전에 반드시 널 검사를 거쳤는지, 배열 접근이 경계를 넘지 않는지 검증기가 추적합니다.
  • 제한된 명령 수: 프로그램의 복잡도에 상한이 있어, 검증기가 현실적인 시간 안에 전체를 분석할 수 있어야 합니다.

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

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

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

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

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

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

  • 언어 무관: Go든 Python이든 Rust든 Java든, 커널에서 보면 다 똑같은 시스템 콜과 네트워크 이벤트입니다. 언어별 SDK를 관리할 필요가 없습니다.
  • 레거시 포함: 소스 코드가 없거나 수정할 수 없는 서드파티 바이너리도 관측됩니다. 코드를 바꿀 수 없는 상황에서 특히 강력합니다.
  • 성능 오버헤드 최소: 계측 코드가 애플리케이션 핫패스에 들어가지 않고, 커널에서 JIT로 컴파일되어 돌기 때문에 오버헤드가 작습니다.

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

도구들 — Cilium, Falco, Pixie, bpftrace

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

  • Cilium: 쿠버네티스 네트워킹과 보안을 eBPF로 구현합니다. 전통적인 쿠버네티스 네트워킹은 iptables 규칙에 의존했는데, 파드가 수천 개로 늘면 iptables 규칙도 폭발적으로 늘어 성능이 무너졌습니다. Cilium은 이를 eBPF 프로그램으로 대체해 훨씬 효율적으로 라우팅·정책 적용·부하 분산을 처리합니다. 서비스 메시 기능도 사이드카 프록시 없이 eBPF로 구현하는 방향으로 나아가고 있습니다.
  • Falco: 런타임 보안 관측 도구입니다. 시스템 콜을 실시간으로 감시하다가 수상한 행동(예: 컨테이너 안에서 셸이 뜨거나, 민감한 파일을 읽거나, 예상치 못한 네트워크 연결이 생기는 것)을 탐지해 경보를 울립니다. 커널에서 직접 관측하므로 공격자가 우회하기 어렵습니다.
  • Pixie: 쿠버네티스 관측성 플랫폼으로, eBPF의 제로 계측을 전면에 내세웁니다. 아무 계측 코드 없이도 클러스터의 HTTP·gRPC·데이터베이스 트래픽을 자동으로 캡처하고, 서비스 간 지연과 에러율을 보여줍니다. "설치하면 바로 보인다"는 경험이 eBPF 덕분에 가능해진 대표적 사례입니다.
  • bpftrace: eBPF의 스위스 군용칼입니다. 한 줄짜리 스크립트로 즉석에서 커널을 관측할 수 있는 고수준 추적 언어입니다. 프로덕션에서 "지금 무슨 일이 벌어지는지" 즉시 파고들 때 쓰입니다.

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

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

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

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

왜 사이드카를 이기는가

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

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

  • 자원 배증: 파드마다 프록시가 하나씩 붙으니, 파드가 1000개면 프록시도 1000개입니다. 각 프록시가 CPU와 메모리를 먹고, 그 합은 무시할 수 없는 규모가 됩니다.
  • 지연 추가: 모든 요청이 애플리케이션 → 사이드카 → 네트워크 → 상대 사이드카 → 상대 애플리케이션을 거칩니다. 매 홉마다 지연이 쌓입니다.
  • 운영 복잡도: 사이드카를 주입하고, 버전을 맞추고, 업그레이드하는 일 자체가 관리 부담입니다.

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

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

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

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

  • 리눅스 중심: eBPF는 본질적으로 리눅스 커널 기술입니다. Windows용 eBPF 이식이 진행되고 있지만 성숙도와 생태계는 리눅스에 크게 뒤처집니다. 리눅스가 아닌 환경에서는 이야기가 완전히 달라집니다.
  • 커널 버전 의존성: kprobe처럼 커널 내부에 의존하는 후크는 커널 버전이 바뀌면 깨질 수 있습니다. CO-RE(Compile Once, Run Everywhere) 같은 기술이 이 문제를 크게 완화했지만, 여전히 낮은 커널 버전에서는 특정 기능을 못 쓰는 제약이 있습니다.
  • 검증기의 벽: 앞서 말했듯 검증기는 안전하지만 까다롭습니다. 복잡한 로직을 구현하려다 검증기에 막히는 일이 잦고, 이를 우회하느라 코드가 부자연스러워집니다. 프로그램 크기와 복잡도의 상한도 실질적 제약입니다.
  • 높은 권한 필요: eBPF 프로그램을 로드하려면 대개 강력한 권한(CAP_BPF 등)이 필요합니다. 이는 곧 eBPF 자체가 공격 표면이 될 수 있다는 뜻입니다. 악의적이거나 결함 있는 eBPF 프로그램을 로드할 권한을 얻은 공격자는 시스템을 깊이 관측하거나 조작할 수 있습니다.
  • 의미론의 한계: 앞서 강조했듯, 커널은 저수준 이벤트만 봅니다. 애플리케이션의 비즈니스 의미(이 요청이 어떤 사용자의 어떤 주문인가)는 커널이 알 수 없습니다. 고수준 문맥이 필요한 관측은 여전히 애플리케이션의 협조가 필요합니다.

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

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

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

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

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

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

참고 자료

현재 단락 (1/82)

관측성(observability) 도구를 깔아 본 사람이라면 익숙한 피로가 있습니다. 애플리케이션마다 에이전트를 넣고, 언어마다 계측 라이브러리를 붙이고, 파드마다 사이드카를 띄우...

작성 글자: 0원문 글자: 6,677작성 단락: 0/82