왜 eBPF인가? iptables와의 차이

배경: 리눅스 네트워킹의 전통적인 방식

리눅스에서 네트워크 패킷 필터링은 오랫동안 iptables가 담당해왔다. Netfilter 프레임워크 위에서 동작하는 iptables는 방화벽 규칙, NAT, 패킷 조작 등 사실상 모든 네트워크 정책을 처리해왔다.

쿠버네티스도 초기부터 iptables를 활용했다. kube-proxy가 Service IP를 각 Pod IP로 DNAT하는 방식으로 동작했고, 클러스터가 작을 때는 아무런 문제가 없었다.

iptables 패킷 처리 흐름

iptables는 커널의 Netfilter 프레임워크에 5개의 훅(hook) 포인트를 등록해 패킷을 처리한다.

flowchart LR
    NIC_IN(["`**NIC**
    패킷 수신`"])
    PREROUTING["`**PREROUTING**
    ─────────────
    *raw* → mangle → nat
    ─────────────
    DNAT 처리 (예: Service IP → Pod IP)`"]
    ROUTE{라우팅 결정}
    INPUT["`**INPUT**
    ─────────────
    mangle → filter
    ─────────────
    로컬 프로세스 수신 필터링`"]
    FORWARD["`**FORWARD**
    ─────────────
    mangle → filter
    ─────────────
    포워딩 패킷 필터링`"]
    LOCAL(["`**로컬 프로세스**
    (애플리케이션)`"])
    OUTPUT["`**OUTPUT**
    ─────────────
    raw → mangle → nat → filter
    ─────────────
    로컬 발신 패킷 처리`"]
    POSTROUTING["`**POSTROUTING**
    ─────────────
    mangle → nat
    ─────────────
    SNAT 처리 (예: Masquerade)`"]
    NIC_OUT(["`**NIC**
    패킷 송신`"])

    NIC_IN --> PREROUTING
    PREROUTING --> ROUTE
    ROUTE -->|"목적지 = 로컬"| INPUT
    ROUTE -->|"목적지 = 외부"| FORWARD
    INPUT --> LOCAL
    LOCAL --> OUTPUT
    OUTPUT --> POSTROUTING
    FORWARD --> POSTROUTING
    POSTROUTING --> NIC_OUT

    style PREROUTING fill:#fff3cd,stroke:#ffc107,color:#000
    style INPUT fill:#d1ecf1,stroke:#17a2b8,color:#000
    style FORWARD fill:#d4edda,stroke:#28a745,color:#000
    style OUTPUT fill:#d1ecf1,stroke:#17a2b8,color:#000
    style POSTROUTING fill:#fff3cd,stroke:#ffc107,color:#000
    style ROUTE fill:#f8d7da,stroke:#dc3545,color:#000
    style NIC_IN fill:#6c757d,stroke:#495057,color:#fff
    style NIC_OUT fill:#6c757d,stroke:#495057,color:#fff
    style LOCAL fill:#e2d9f3,stroke:#6f42c1,color:#000

각 훅에서 해당 테이블의 chain을 순서대로 순회하며 매칭되는 규칙을 찾는다.
서비스가 많아질수록 규칙 수가 선형으로 증가하고, 패킷마다 이 탐색을 반복한다.

chain 순회 방식

flowchart LR
    PKT([패킷]) --> R1
    R1["규칙 1\n(match?)"] -->|no| R2
    R2["규칙 2\n(match?)"] -->|no| R3
    R3["규칙 3\n(match?)"] -->|no| RN["...규칙 N"]
    R1 -->|yes| A1([ACCEPT / DROP / DNAT])
    R2 -->|yes| A2([ACCEPT / DROP / DNAT])
    R3 -->|yes| A3([ACCEPT / DROP / DNAT])
    RN --> DEF([기본 정책])

    style PKT fill:#6c757d,stroke:#495057,color:#fff
    style A1 fill:#d4edda,stroke:#28a745,color:#000
    style A2 fill:#d4edda,stroke:#28a745,color:#000
    style A3 fill:#d4edda,stroke:#28a745,color:#000
    style DEF fill:#f8d7da,stroke:#dc3545,color:#000

규칙이 N개면 최악의 경우 N번 비교 — O(n) 탐색.

iptables CLI 예시

규칙 조회

# 모든 체인의 규칙 목록 (패킷/바이트 카운터 포함)
iptables -L -v -n
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
 125K   18M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
  203 12180 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            state NEW tcp dpt:22
 3472  208K DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 84 packets, 8520 bytes)
 pkts bytes target     prot opt in     out     source               destination
# 규칙 번호 포함 출력
iptables -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
2    ACCEPT     icmp --  anywhere             anywhere
3    ACCEPT     all  --  anywhere             anywhere
4    ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
5    DROP       all  --  anywhere             anywhere
# 실제 적용된 규칙을 iptables-save 형식으로 출력
iptables-save
# Generated by iptables-save v1.8.7 on Wed Apr 29 10:00:00 2026
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [84:8520]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j DROP
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT

규칙 추가

# 특정 IP에서 오는 패킷 차단 (INPUT 체인 끝에 추가)
iptables -A INPUT -s 192.168.1.100 -j DROP
 
# 80 포트 허용 (체인 앞에 삽입, -I)
iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT
 
# DNAT: 외부 8080 → 내부 80으로 포워딩 (kube-proxy가 하는 일)
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.5:80
 
# SNAT: 나가는 패킷의 출발지 IP를 eth0의 IP로 변경 (Masquerade)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

규칙 삭제

# 규칙 번호로 삭제
iptables -D INPUT 3
 
# 규칙 내용으로 삭제 (-A와 동일한 형식, -D로 변경)
iptables -D INPUT -s 192.168.1.100 -j DROP
 
# 특정 체인의 규칙 전체 삭제
iptables -F INPUT
 
# 모든 테이블 규칙 초기화
iptables -F && iptables -t nat -F && iptables -t mangle -F

쿠버네티스에서 실제 생성되는 규칙 확인

# kube-proxy가 생성한 nat 규칙만 필터링
iptables -t nat -L -n | grep -E 'KUBE|Chain'
Chain PREROUTING (policy ACCEPT)
Chain INPUT (policy ACCEPT)
Chain OUTPUT (policy ACCEPT)
Chain POSTROUTING (policy ACCEPT)
Chain KUBE-MARK-DROP (1 references)
Chain KUBE-MARK-MASQ (20 references)
Chain KUBE-NODEPORTS (1 references)
Chain KUBE-POSTROUTING (1 references)
Chain KUBE-SEP-2FHBVKZOLXDXMTVN (1 references)   # 개별 Pod endpoint
Chain KUBE-SEP-4VBLKZOLXDXM7KQN (1 references)
Chain KUBE-SEP-RVXJZ7OLXDXMABCD (1 references)
Chain KUBE-SERVICES (2 references)
Chain KUBE-SVC-ERIFXISQEP7F7OF4 (2 references)   # Service 1개당 체인 1개
Chain KUBE-SVC-NPX46M4PTMTKRN6Y (2 references)
Chain KUBE-SVC-TCOU7JCQXEZGVUNU (2 references)
...

서비스가 많은 클러스터에서는 KUBE-SVC-*, KUBE-SEP-* 체인이 서비스·엔드포인트 수에 비례해 수천 개 생성된다.


iptables의 한계

1. O(n) 규칙 순회

iptables는 규칙을 연결 리스트(chain) 형태로 저장한다. 패킷이 들어오면 해당되는 규칙을 찾을 때까지 순서대로 탐색한다.

  • 서비스 100개 → 규칙 수백 개
  • 서비스 10,000개 → 규칙 수만 개
  • 패킷마다 규칙을 처음부터 순회 → 레이턴시와 CPU 사용량이 서비스 수에 비례해서 증가

실제로 쿠버네티스 대규모 클러스터에서는 iptables 규칙이 10만 개를 넘기도 하며, 규칙 업데이트 자체도 lock을 잡고 전체를 재작성하기 때문에 수 초의 중단이 발생할 수 있다.

2. 상태 저장의 부재

iptables는 기본적으로 stateless하다. conntrack(연결 추적)으로 상태를 보완하지만, 이 또한 별도의 커널 테이블로 관리되며 대규모 트래픽에서는 conntrack 테이블 고갈이 심각한 문제가 된다.

3. 가시성의 부재

iptables는 규칙에 맞는 패킷이 카운트되는 것 외에 무슨 일이 일어나고 있는지 거의 알 수 없다. 특정 연결이 왜 드롭되었는지, 어느 규칙에서 막혔는지 추적하기 어렵다.

4. 유연성의 한계

커널 코드를 수정하지 않고는 새로운 네트워크 기능을 추가하기 어렵다. Netfilter 훅에 맞는 방식으로만 동작해야 한다.


eBPF란 무엇인가

eBPF(extended Berkeley Packet Filter) 는 커널을 수정하거나 모듈을 로드하지 않고도, 커널 내부에서 안전하게 프로그램을 실행할 수 있는 기술이다.

원래 BPF는 패킷 필터링을 위한 단순한 VM이었지만, eBPF로 확장되면서:

  • 더 큰 레지스터(64비트)와 풍부한 명령어 세트
  • 커널의 다양한 이벤트에 attach 가능 (XDP, TC, kprobe, tracepoint 등)
  • verifier를 통한 안전성 보장 — 무한 루프, 메모리 경계 위반 차단
  • 커널-유저 공간 간 데이터 공유를 위한 BPF Maps

eBPF 프로그램이 동작하는 방식

사용자 코드 (C) → clang/LLVM → BPF 바이트코드 → verifier → JIT 컴파일 → 커널 실행

검증을 통과한 프로그램은 JIT 컴파일되어 네이티브 코드 수준으로 실행된다.


eBPF가 iptables보다 나은 이유

1. XDP: 패킷을 가장 빠른 시점에 처리

XDP(eXpress Data Path) 는 NIC 드라이버 레벨, 즉 커널 네트워크 스택에 진입하기 전에 패킷을 처리한다.

NIC → XDP hook (eBPF) → (pass/drop/redirect) → 커널 네트워크 스택 → iptables → 애플리케이션

불필요한 패킷은 스택에 올라오기 전에 버릴 수 있어 DDoS 방어, 로드밸런싱 등에서 극적인 성능 향상이 가능하다.

2. O(1) 룩업: BPF Maps

iptables의 chain 순회 대신, eBPF는 해시맵이나 LPM 트리 형태의 BPF Maps를 사용한다.

  • 서비스 10만 개도 O(1)에 가까운 시간으로 룩업
  • 규칙 업데이트도 atomic하게 처리 가능 — 전체 재작성 불필요

Cilium은 이를 이용해 kube-proxy를 완전히 대체하고, Service IP 변환을 BPF 레벨에서 처리한다.

3. 커널 수정 없이 확장 가능

eBPF 프로그램은 런타임에 커널에 로드된다. 네트워크 정책, 모니터링, 암호화 등을 커널 업그레이드 없이 배포할 수 있다.

4. 풍부한 가시성 (Observability)

eBPF는 커널의 어느 지점에나 attach할 수 있기 때문에, 패킷의 생애주기 전반에 걸쳐 세밀한 데이터를 수집할 수 있다.

  • 어떤 프로세스(PID)가 어떤 소켓을 열었는가
  • 패킷이 어느 지점에서 드롭되었는가
  • 연결 레이턴시, 재전송 횟수 등 TCP 지표

Cilium의 Hubble이 이를 활용해 서비스 메시 없이도 L7 수준의 네트워크 가시성을 제공한다.


비교 요약

항목iptableseBPF
패킷 처리 위치Netfilter 훅 (커널 스택 내부)XDP (NIC 레벨) 또는 TC
규칙 탐색 복잡도O(n)O(1) (해시맵)
규칙 업데이트전체 테이블 lock & 재작성Atomic 업데이트
가시성카운터 수준패킷/흐름/프로세스 단위
확장성Netfilter 훅에 종속커널 전반에 attach 가능
대규모 클러스터 적합성낮음 (규칙 수 증가 시 성능 저하)높음

왜 Cilium은 eBPF를 선택했는가

Cilium은 처음부터 eBPF를 핵심으로 설계되었다. kube-proxy를 대체함으로써:

  • kube-proxy replacement: Service 트래픽을 BPF Maps로 처리, iptables 완전 제거
  • 네트워크 정책: L3/L4뿐 아니라 L7(HTTP, gRPC) 정책도 eBPF로 구현
  • Transparent encryption: WireGuard/IPsec을 eBPF로 통합
  • Hubble: eBPF 기반 분산 네트워크 관측 플랫폼

iptables는 수십 년 전 설계된 도구다. 단일 서버에서 방화벽 규칙 몇 개를 관리하기엔 충분하지만, 수천 개의 Pod가 동적으로 생성·삭제되는 쿠버네티스 환경에서는 근본적인 한계가 있다.

eBPF는 커널을 수정하지 않고도 커널 수준의 성능과 유연성을 제공한다. 이것이 바로 Cilium을 비롯한 현대적인 클라우드 네이티브 네트워킹 솔루션이 eBPF를 선택하는 이유다.


참고