Skip to content

Latest commit

 

History

History
106 lines (79 loc) · 13.9 KB

File metadata and controls

106 lines (79 loc) · 13.9 KB

v1.20 Cilium Datapath Plugins

메타데이터

항목 내용
프로젝트 Cilium
관련 릴리스 v1.20.0 (Beta 기능)
관련 이슈/PR cilium/cilium#41634 (원 이슈), design-cfps CFP-41634, cilium/cilium#45028 (Part 1: API) (2026-04-16 머지), #45429 (Part 2: Registry And Orchestration) (2026-04-23 머지), #45558 (Part 3: Collection Instrumentation) (2026-06-22 머지), #46673 (Part 4: Plugin Coordination) (2026-07-02 머지), #46872 (Part 5: Documentation And Example) (2026-07-21 머지)
발견 출처 Cilium v1.20.0 릴리스 노트의 "Extensible Datapath: Datapath plugins let cloud providers extend or instrument Cilium's eBPF datapath with independently versioned programs, without maintaining a Cilium fork"
검증 근거 CFP-41634 설계 문서 원문(대안 비교 포함), 5개 구현 PR의 실제 diff(pkg/datapath/loader/plugins.go, api/v1/datapathplugins/datapathplugins.proto, pkg/k8s/apis/cilium.io/v2alpha1/cdpp_types.go 등), 최종 문서 Documentation/contributing/development/plugins.rst

개요

Cilium은 지금까지 자신이 커널에 붙이는 모든 eBPF 프로그램을 스스로 관리했고, 클라우드 사업자나 파워유저가 커스텀 관측/프록시/인캡슐레이션용 BPF 프로그램을 같은 attach point에 안전하게 끼워 넣을 방법이 없었다. v1.20은 datapath plugin이라는 별도 프로세스가 gRPC로 Cilium 에이전트와 통신하며, Cilium이 만드는 BPF collection의 attach 지점 앞뒤에 자신의 프로그램을 붙이도록 요청할 수 있는 2단계 코디네이션 프로토콜을 Beta로 도입했다. 이를 통해 Cilium을 포크하지 않고도 datapath를 확장할 수 있게 됐다.

사전 지식

Cilium이 BPF collection을 로드하고 attach하는 방식

Cilium 에이전트는 엔드포인트(파드) 하나가 생성되거나 설정이 바뀔 때마다 해당 엔드포인트의 데이터패스를 재생성한다. 이 과정에서 cil_from_container처럼 이름이 정해진 entrypoint 프로그램들을 포함한 BPF collection을 컴파일하고, cilium/ebpf 라이브러리를 통해 커널에 로드한 뒤 TC(TCX), XDP, cgroup(SOCK_ADDR/SOCKOPS) 같은 attach point에 붙인다. TC/TCX는 하나의 attach point에 여러 프로그램을 순서대로 실행할 수 있는 mprog 리스트 구조를 커널이 제공하지만, 지금까지 이 리스트를 채우는 주체는 오직 Cilium 자신뿐이었다.

지금까지 서드파티 BPF 프로그램을 끼워 넣기 어려웠던 이유

CFP-41634 문서는 이 문제를 대규모 클러스터 운영자의 관점에서 설명한다. 자체 관측 도구나 투명 프록시, 커스텀 인캡슐레이션/라우팅을 위해 BPF를 쓰고 싶은 팀이 있어도, Cilium이 attach point를 독점 관리하는 한 그 프로그램을 Cilium과 조율 없이 같은 인터페이스에 붙이면 순서가 꼬이거나 서로 충돌할 위험이 있었다. 결과적으로 운영자는 BPF를 아예 포기하거나 Cilium 자체를 포크해서 필요한 로직을 심는 수밖에 없었고, Cilium 팀은 이런 요구를 매번 개별적으로 검토해야 했다.

변경 분석

변경 전: attach point를 Cilium이 전적으로 소유하는 구조

[변경 전] TC/TCX attach point 하나 = Cilium 소유의 mprog 리스트
┌─────────────────────────────────────────────────┐
│ 네트워크 인터페이스의 TCX attach point            │
│  ┌─────────────────────────────────────────┐    │
│  │ cil_from_container (Cilium entrypoint)   │    │
│  └─────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘
        ↑ 서드파티 BPF 프로그램이 끼어들 공식 경로 없음
        (별도로 attach하면 순서/생명주기를 Cilium이 모른 채 공존)

Cilium은 엔드포인트 재생성마다 이 mprog 리스트를 자신의 프로그램으로 다시 채운다. 외부 프로그램이 같은 리스트에 들어가려면 Cilium의 재생성 타이밍과 무관하게 스스로 붙어야 했고, Cilium은 그 존재를 알지 못하므로 재생성 시 순서가 뒤바뀌거나 의도치 않게 덮어써질 수 있었다.

변경 후: PrepareCollection/InstrumentCollection 2단계 gRPC 코디네이션과 디스패처 프로그램

datapath plugin은 DaemonSet으로 각 노드에 배포되고, CiliumDatapathPlugin CRD로 등록된다. Cilium은 collection을 로드하기 전, 등록된 각 플러그인에 gRPC로 다음 두 단계를 거쳐 조율한다.

  1. PrepareCollection: Cilium이 collection의 프로그램/맵 정보와 attachment context(호스트/LXC/오버레이/소켓/와이어가드/XDP 중 어떤 attach 지점인지)를 전달하면, 플러그인은 어떤 entrypoint 앞(pre)이나 뒤(post)에 훅을 걸고 싶은지, 그리고 다른 플러그인 훅과의 상대적 순서 제약(BEFORE/AFTER)을 응답으로 알려준다.
  2. Cilium은 이 응답들을 모아 훅 순서 제약을 위상 정렬(topological sort)로 풀고, 원래 entrypoint 프로그램을 서브프로그램으로 이름을 바꿔 보존한 채 pre 훅 → 원본 → post 훅 순으로 호출하는 디스패처 프로그램을 생성해 collection spec을 수정한 뒤 커널에 로드한다.
  3. InstrumentCollection: Cilium이 각 훅 자리에 대해 BPFFS의 임시 디렉터리(예: /sys/fs/bpf/cilium/operations/<uuid>/) 안에 고유 pin 경로를 만들어 플러그인에 알려주면, 플러그인은 BPF_PROG_TYPE_EXT 타입의 훅 프로그램을 로드해 그 경로에 pin한다.
  4. Cilium이 pin된 프로그램을 열어 freplace 링크로 디스패처의 자리에 연결하고, 최종적으로 완성된 entrypoint(디스패처 포함)를 실제 attach point에 붙인다.

TC 계열 attach point는 mprog 체인이 TC_ACT_UNSPEC/TCX_NEXT가 아닌 값을 반환하는 순간 실행을 멈추는 구조이기 때문에, post 훅이 Cilium의 최종 판정과 무관하게 항상 실행되도록 만드는 것이 별도 문제였다. 이를 위해 로더는 entrypoint의 모든 반환 지점(테일콜로 도달 가능한 지점 포함)을 자동으로 찾아 exit handler로 바꿔 끼운다. 이 핸들러는 원래 반환값을 per-CPU 맵에 저장하고 TCX_NEXT를 반환해 다음 훅으로 진행시키며, 이후 훅은 이 저장된 값을 읽어 그대로 넘기거나 덮어쓸 수 있다.

설계 결정 및 트레이드오프

CFP-41634 문서는 핵심 설계 지점마다 여러 대안을 검토했고, 실제 구현 과정에서 문서와 다르게 정리된 지점도 있다.

  • 다중 attach 메커니즘: fentry/fmod_ret 훅은 모든 attach 타입에서 동작하고 구현이 단순하지만, 순수 트레이싱 용도라 프로그램의 제어 흐름(판정 override, 실행 중단)을 바꿀 수 없어 기각됐다. TCX의 exit point만 자동 계측하는 방식은 TC 전용이라 XDP/cgroup으로 확장하기 어려웠다. 최종적으로 디스패처 프로그램 + BPF_PROG_TYPE_EXT(freplace) 방식을 택했는데, 구현은 더 복잡하지만 모든 attach 타입과 커널 버전에서 동일하게 동작하고 사전 준비(Prepare)와 로드(Instrument)를 분리할 수 있다는 점이 결정적이었다.
  • collection 단위 vs 프로그램 단위 RPC: 개별 프로그램 단위로 인터페이스를 좁히면 구현은 쉽지만 향후 entrypoint 외의 부분까지 계측을 확장하려면 gRPC 인터페이스 자체를 바꿔야 한다. Cilium은 collection 전체를 넘기는 방식을 택해, 지금은 entrypoint만 계측을 허용하도록 제한하되 향후 더 세밀한 계측으로 확장할 여지를 남겼다.
  • 프로그램 소유권 이전 방식: 문서는 소유권 이전 방법으로 6가지 옵션(단순 로드 후 ACK 대기, PROG_ARRAY에 삽입, Cilium이 만든 링크를 플러그인이 갱신, 플러그인이 BPFFS에 pin, 플러그인이 직접 교체, 플러그인이 생명주기 전체를 관장)을 비교했다. PROG_ARRAY 삽입은 커널이 BPF_PROG_TYPE_EXT 프로그램의 삽입을 막아 기술적으로 불가능했고, 링크 갱신(BPF_LINK_UPDATE)도 트레이싱 링크에는 적용되지 않아 제외됐다. 남은 옵션 중 플러그인이 BPFFS에 pin하는 방식을 택했는데, ACK 왕복이나 FD 회수 타이밍을 신경 쓸 필요가 없고 CAP_SYS_ADMIN 없이도 pin 경로만으로 상태를 주고받을 수 있다는 점이 이유였다. 대신 pin이 남는 실패 상황을 대비해 별도의 GC 루프(pkg/datapath/loader/plugins.go의 staging 디렉터리 정리)가 필요해졌다.
  • 별도 CNI 플러그인 vs 로더 직접 통합: Cilium과 완전히 분리된 별도 CNI 플러그인으로 구현하면 유지보수 부담은 줄지만, 재생성 이벤트에 반응하기 어렵고 post-Cilium 훅을 위한 계측(exit handler)을 어차피 Cilium 로더가 알아야 해서 완전한 분리가 불가능했다. 결국 로더에 직접 통합하는 방식을 택해, 로더가 짊어지는 유지보수 부담을 감수했다.
  • 문서와 실제 구현이 달라진 지점: PR #45429의 Deviations From CFP 절은, 애초 문서가 Cilium이 gRPC 서버를 열고 플러그인이 접속하는 방향을 제안했던 것과 달리 실제로는 각 플러그인이 자신의 Unix 소켓 위에서 gRPC 서버를 열고 Cilium이 그 소켓에 접속하는 방향으로 뒤집혔다고 명시한다. 또한 최종 CRD(CiliumDatapathPluginSpec)는 문서가 제안했던 Always/BestEffort/Eventually 세 가지 attachmentPolicyEventually(플러그인 재연결 시 자동 재초기화)를 구현하지 않았고, 대신 Version이라는 문자열 필드를 추가해 사용자가 플러그인 버전을 올릴 때마다 명시적으로 데이터패스 재초기화를 트리거하는 방식을 택했다(정확한 사유는 PR 설명에 명시돼 있지 않아 확인 필요).
[변경 후] entrypoint 자리에 생성된 디스패처 프로그램 (TC 기준)
┌───────────────────────────────────────────────────────────┐
│ cil_from_container_dispatch (Cilium이 생성)                 │
│                                                             │
│  pre 훅(plugin A) → pre 훅(plugin B) → ...                  │
│         │ (TC_ACT_UNSPEC 아니면 즉시 반환)                    │
│         ▼                                                  │
│  cil_from_container_orig (원래 Cilium 프로그램, 서브프로그램화)│
│         │ 반환값을 exit handler가 per-CPU 맵에 저장           │
│         ▼                                                  │
│  post 훅(plugin B) → post 훅(plugin A) → ...                │
│         │ (override 없으면 저장된 반환값 그대로 전달)          │
│         ▼                                                  │
│  최종 반환                                                  │
└───────────────────────────────────────────────────────────┘
  각 훅 프로그램은 BPF_PROG_TYPE_EXT + freplace로 연결되며
  플러그인이 BPFFS pin 경로에 올려두면 Cilium이 가져가 붙인다

더 살펴볼 점

  • 여러 플러그인의 순서 제약(BEFORE/AFTER)이 서로 사이클을 이뤄 위상 정렬이 불가능해지면 Cilium은 로드 자체를 실패시키는지, 아니면 일부 제약을 무시하는지?
  • 현재 문서와 코드는 TC 계열 exit handler 계측을 중심으로 서술하는데, XDP/cgroup(SOCK_ADDR, SOCKOPS) attach에도 동일한 pre/post 훅이 실제로 지원되는지, 아니면 아직 TC 전용인지?
  • attachmentPolicy: Always인 플러그인 파드가 재시작되는 동안, 이미 붙어 있던 기존 엔드포인트들의 데이터패스는 그대로 유지되는지 아니면 다음 재생성 때까지 훅이 빠진 채로 동작하는지?

참고 자료

참고 외 별도 확인 링크

본문 참고 자료로 충분했다.