Cilium Study의 긴 여정이 드디어 마지막 주제에 도달했습니다. 우리는 CNI의 기본부터 서비스 메시, 성능 튜닝에 이르기까지 Cilium이 제공하는 강력한 기능들을 단계별로 학습했습니다. 이 모든 기술의 정점에는 바로 '보안' 이 있습니다. 동적이고 예측 불가능한 클라우드 네이티브 환경에서, 어떻게 우리의 애플리케이션과 데이터를 안전하게 보호할 수 있을까요?

이번 마지막 포스팅에서는 기존의 경계 기반 보안 모델의 한계를 짚어보고, Cilium이 제로 트러스트(Zero Trust) 원칙을 어떻게 네트워크 정책에 구현하는지 살펴봅니다. 또한, 네트워크를 넘어 커널 레벨의 행위까지 감지하고 차단하는 eBPF 기반의 혁신적인 런타임 보안 솔루션, Tetragon의 세계로 깊이 들어가 보겠습니다.

1. 경계의 붕괴, 새로운 보안 패러다임의 서막

과거의 데이터 센터 보안은 '성벽과 해자(Castle-and-moat)' 모델에 비유할 수 있었습니다. 외부의 침입을 막기 위해 강력한 방화벽(경계)을 구축하고, 일단 내부망에 들어온 트래픽은 비교적 신뢰하는 방식이었습니다. 하지만 마이크로서비스 아키텍처와 컨테이너 환경은 이러한 경계를 무의미하게 만들었습니다.

  • 동적인 워크로드: 파드는 수시로 생성되고 삭제되며, 노드를 넘나들며 이동합니다. IP 주소는 더 이상 신뢰할 수 있는 식별자가 아닙니다.
  • 내부 트래픽(East-West)의 증가: 대부분의 통신이 클러스터 내부에서 서비스 간에 발생합니다. 만약 하나의 서비스가 침해당하면, 내부망 전체가 위험에 노출될 수 있습니다.

이러한 변화는 제로 트러스트(Zero Trust) 라는 새로운 보안 패러다임을 요구합니다. "Never Trust, Always Verify." 즉, 네트워크의 위치(내부/외부)와 관계없이 모든 통신 요청을 신뢰하지 않고, 반드시 검증해야 한다는 원칙입니다. Cilium은 바로 이 제로 트러스트 원칙을 쿠버네티스 환경에 가장 효과적으로 구현하는 도구 중 하나입니다.

2. Cilium 네트워크 정책, 신원 기반 보안을 구축하다

Cilium은 쿠버네티스 표준 NetworkPolicy를 완벽하게 지원하며, 한 걸음 더 나아가 CiliumNetworkPolicy(CNP)와 CiliumClusterwideNetworkPolicy(CCNP)라는 강력한 자체 CRD를 제공합니다. Cilium 정책의 핵심은 IP 주소가 아닌 신원(Identity)을 기반으로 통신을 제어한다는 점입니다.

신원(Identity)이란 무엇인가?

Cilium은 파드가 가진 레이블(Label) 의 조합을 기반으로 고유한 숫자 형태의 Security Identity를 부여합니다. 예를 들어, app=frontend, env=prod 레이블을 가진 모든 파드는 동일한 ID를 공유하게 됩니다. 이 ID는 파드의 IP 주소와 매핑되어 eBPF Map에 저장되며, 파드의 IP가 바뀌더라도 레이블이 동일하다면 같은 ID를 유지합니다.

이 신원 기반 접근 방식은 다음과 같은 강력한 이점을 제공합니다.

  • IP에 대한 비의존성: 파드의 생명주기와 관계없이 일관된 정책 적용이 가능합니다.
  • 뛰어난 확장성: 수만 개의 IP 규칙을 iptables 체인으로 관리하는 대신, 수천 개의 신원 ID만으로 정책을 효율적으로 관리할 수 있습니다.
  • 풍부한 표현력: L3(IP), L4(Port)뿐만 아니라, L7(HTTP API 경로/메서드, Kafka 토픽 등)과 DNS 이름까지 조합하여 매우 정교하고 구체적인 정책을 만들 수 있습니다.

CiliumNetworkPolicy의 다층적 방어

CiliumNetworkPolicy를 사용하면 다층적인 보안 정책을 구현할 수 있습니다.

  • L3/L4 정책: 특정 신원(예: role=frontend)을 가진 파드가 다른 신원(role=backend)의 8080 포트로만 통신하도록 제한할 수 있습니다.
  • L7 정책: 한발 더 나아가, role=frontend 파드가 role=backend/api/v1/data 엔드포인트에 GET 요청만 보내도록 제한할 수 있습니다. 만약 허용되지 않은 POST 요청이 발생하면, Cilium의 내장 Envoy 프록시는 eBPF 레벨에서 해당 요청을 즉시 차단하고, Hubble을 통해 이벤트를 실시간으로 관측할 수 있습니다.
  • DNS 기반 정책: toFQDNs 필드를 사용하면 파드가 특정 외부 도메인(예: api.github.com)으로만 나가는(egress) 통신을 하도록 제한할 수 있습니다. Cilium은 DNS 응답을 감지하여 해당 도메인에 매핑되는 IP 목록을 동적으로 eBPF Map에 업데이트함으로써 이 기능을 구현합니다. 이는 외부 서비스와의 통신을 최소한으로 제한하는 데 매우 효과적입니다.

3. Tetragon, 커널 레벨의 런타임 보안을 열다

네트워크 정책이 아무리 강력하더라도, 컨테이너 내부에서 발생하는 악의적인 행위까지 막을 수는 없습니다. 예를 들어, 침해된 파드 내에서 악성코드가 실행되거나, 민감한 파일에 접근하거나, 외부로 몰래 데이터를 유출하려는 시도 등은 네트워크 정책만으로는 감지하기 어렵습니다.

Tetragon은 바로 이 런타임 보안(Runtime Security) 문제를 해결하기 위해 등장한 Cilium의 자매 프로젝트입니다. Tetragon은 eBPF를 사용하여 커널과 애플리케이션 사이에서 발생하는 모든 시스템 콜(System Call)과 다른 커널 이벤트를 실시간으로 관찰하고, 미리 정의된 정책에 따라 의심스러운 행위를 감지하거나 차단할 수 있습니다.

Tetragon의 작동 원리

Tetragon은 데몬셋(DaemonSet)으로 각 노드에 배포되며, eBPF 프로그램을 커널의 다양한 훅 포인트(kprobes, tracepoints 등)에 부착합니다. 이를 통해 기존의 보안 에이전트들이 유발했던 성능 저하 없이, 커널 수준에서 직접 다음과 같은 다양한 활동을 감지할 수 있습니다.

  • 프로세스 실행 감지: 파드 내에서 어떤 프로세스(execve 시스템 콜)가 실행되었는지, 어떤 인자(argument)를 사용했는지 추적합니다. "내 nginx 파드에서 bash 셸이 실행되었다"와 같은 의심스러운 활동을 즉시 포착할 수 있습니다.
  • 파일 접근 모니터링: 민감한 파일(예: /etc/shadow, /var/run/secrets/...)에 대한 읽기/쓰기 시도를 감지합니다.
  • 네트워크 소켓 활동: 어떤 프로세스가 어떤 IP와 포트로 네트워크 연결을 시도하는지 감지합니다. 이는 네트워크 정책에서 놓칠 수 있는 내부 프로세스의 악의적인 통신 시도를 잡아낼 수 있습니다.
  • 권한 상승 시도: CAP_SYS_ADMIN과 같은 위험한 리눅스 캐퍼빌리티(Capability) 사용을 감지하여 컨테이너 탈출 시도를 막습니다.

TracingPolicy를 통한 실시간 탐지

Tetragon의 모든 정책은 TracingPolicy라는 CRD를 통해 정의됩니다. 예를 들어, privileged-pod-exec 정책은 권한이 있는(privileged) 파드 내부에서 셸이 실행되는 것을 감지하도록 설정할 수 있습니다.

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "detect-shell-in-privileged-pod"
spec:
  kprobes:
  - call: "security_bprm_check"
    # ... (생략) ...
    selectors:
    - matchPIDs:
      - operator: "In"
        followForks: true
        isNamespacePID: true
        values:
        - 1
      matchArgs:
      - index: 0
        operator: "Equal"
        values:
        - "/bin/bash"
        # ... (생략) ...

4. Cilium과 Tetragon이 완성하는 통합 보안

Cilium Study의 마지막 여정을 통해, 우리는 eBPF가 단순한 네트워킹 기술을 넘어 클라우드 네이티브 보안의 지형을 어떻게 바꾸고 있는지를 목격했습니다.

  • Cilium네트워크 계층(L3-L7) 에서 신원 기반의 제로 트러스트 보안을 구현하여 서비스 간의 통신을 안전하게 보호합니다.
  • Tetragon커널과 런타임 계층에서 발생하는 모든 행위를 감시하여 네트워크 정책을 우회하는 내부 위협까지 차단합니다.

이 두 가지 강력한 도구가 결합될 때, 우리는 비로소 애플리케이션의 전체 생명주기에 걸쳐 일관되고 심층적인 보안 가시성과 제어 능력을 확보할 수 있습니다. 복잡하고 동적인 쿠버네티스 환경에서, Cilium과 Tetragon은 가장 신뢰할 수 있는 보안 파트너가 되어줄 것입니다.

지금까지 우리는 Cilium의 다양한 네트워킹 및 보안 기능을 학습하며 강력한 쿠버네티스 클러스터를 구축하는 방법을 익혔습니다. 하지만 클러스터의 규모가 수백, 수천 개의 노드로 확장되고 수만 개의 파드가 동작하는 대규모 환경이 되면, 우리는 새로운 도전에 직면하게 됩니다. 바로 '성능' 입니다. 응답이 느려지는 API 서버, 원인 모를 파드 생성 실패, 간헐적인 네트워크 드롭 등은 클러스터의 안정성을 심각하게 위협할 수 있습니다.

이번 7주차 스터디에서는 대규모 클러스터에서 발생할 수 있는 다양한 성능 병목 지점을 진단하고, 이를 해결하기 위한 구체적인 튜닝 전략을 심층적으로 다루고자 합니다. Kubernetes 컨트롤 플레인의 심장인 kube-apiserveretcd의 동작 원리부터 Cilium 데이터 플레인의 핵심인 eBPF Map 관리까지, 성능 최적화의 여정을 함께 떠나보겠습니다.

이미지 출처: https://cilium.io/use-cases/host-firewall/

1. Kubernetes 성능 병목 현상 탐구: API 서버는 왜 느려지는가?

클러스터의 모든 상태 변경은 kube-apiserver를 통해 etcd에 기록됩니다. 따라서 클러스터의 규모가 커질수록 컨트롤 플레인의 부하는 기하급수적으로 증가하며, 이곳에서 가장 먼저 성능 문제가 발생합니다. 이러한 문제를 재현하고 분석하기 위해 kube-burner와 같은 부하 테스트 도구를 활용할 수 있습니다.

부하 테스트를 통해 발견된 병목 지점

kube-burner를 사용하여 수백 개의 파드를 동시에 생성하는 시나리오를 시뮬레이션하면 다음과 같은 대표적인 문제들을 마주하게 됩니다.

  1. Too many pods (노드 당 파드 개수 제한): Kubelet은 maxPods 설정값에 따라 노드 당 생성 가능한 파드의 최대 개수를 제한합니다. 이 한계를 초과하면 스케줄러는 더 이상 해당 노드에 파드를 할당하지 못하고 FailedScheduling 이벤트를 발생시킵니다.
  2. No IP addresses available (PodCIDR 고갈): 각 노드는 PodCIDR이라는 고유한 IP 대역을 할당받아 자신이 관리하는 파드들에게 IP를 순차적으로 부여합니다. 만약 maxPods를 늘리더라도 할당된 PodCIDR 대역 내의 IP를 모두 소진하면, CNI 플러그인은 FailedCreatePodSandBox 에러를 내며 파드 생성을 실패시킵니다.

이러한 현상들은 표면적인 문제일 뿐, 그 근본 원인은 대량의 리소스 생성 요청이 컨트롤 플레인에 집중될 때 발생하는 부하와 관련이 깊습니다.

근본 원인: 대규모 LIST 요청과 메모리 폭증

쿠버네티스 컨트롤러(kubelet, CNI 에이전트 등)들은 클러스터의 최신 상태를 유지하기 위해 API 서버에 주기적으로 리소스 목록을 요청(LIST API Call)합니다. 문제는 클러스터에 수만 개의 파드가 존재할 때 발생합니다.

  • 동작 방식: 클라이언트가 kubectl get pods --all-namespaces와 같은 명령을 실행하면, kube-apiserveretcd로부터 모든 파드 정보를 가져와야 합니다. 이때 etcd는 트랜잭션의 일관성을 보장하기 위해 특정 시점의 데이터를 메모리에 복제한 후 응답합니다. kube-apiserver는 이 데이터를 받아 다시 Go 구조체로 변환(deserialization)하고, 최종적으로 클라이언트가 요청한 형식(JSON, YAML 등)으로 변환(serialization)하여 전달합니다.
  • 결과: 이 과정에서 etcdkube-apiserver 양쪽 모두에서 응답 데이터 크기의 몇 배에 달하는 막대한 메모리가 일시적으로 할당됩니다. 만약 이러한 대규모 LIST 요청이 여러 클라이언트로부터 동시에 발생하면, 컨트롤 플레인 컴포넌트는 메모리 사용량이 급증하여 OOM(Out of Memory)으로 종료될 수 있습니다. 이는 클러스터 전체의 가용성을 위협하는 심각한 장애로 이어집니다.

2. Kubernetes 성능 튜닝 전략

컨트롤 플레인의 안정성을 확보하기 위해서는 API 요청을 효율적으로 관리하고 인프라 전반의 설정을 최적화하는 다각적인 접근이 필요합니다.

API 요청 관리 기법

  1. 페이지네이션 (Limit & Continue): 대규모 리소스를 조회하는 클라이언트는 반드시 limitcontinue 파라미터를 사용하여 응답을 여러 페이지로 나누어 요청해야 합니다. 이는 한 번의 요청으로 인한 메모리 부담을 줄이는 가장 기본적인 방법입니다. kubectl을 포함한 대부분의 공식 클라이언트는 이 방식을 기본적으로 사용합니다.
  2. API 서버 캐시 활용 (ResourceVersion="0"): LIST 요청 시 resourceVersion="0" 파라미터를 사용하면, kube-apiserveretcd에 직접 요청하는 대신 자신의 내부 캐시에서 데이터를 반환합니다. 이는 etcd의 부하를 극적으로 줄여주지만, 약간의 지연이 있는 데이터(eventual consistency)를 받을 수 있다는 점을 감안해야 합니다. 하지만 대부분의 컨트롤러 동기화 작업에는 이 방식으로 충분합니다.
  3. API 우선순위 및 공정성 (APF - API Priority and Fairness): Kubernetes 1.20부터 도입된 APF는 단순한 요청 속도 제한을 넘어, 요청의 중요도에 따라 우선순위를 부여하고 큐를 분리하여 관리합니다.
    • FlowSchema: 들어오는 요청을 '누가(Subject)', '무엇을(Resource)' 요청하는지에 따라 분류합니다.
    • PriorityLevelConfiguration: 각 요청 흐름에 'system-critical', 'workload-high' 등과 같은 우선순위 레벨과 동시 처리 한도를 할당합니다. 이를 통해 중요도가 낮은 컨트롤러의 과도한 요청이 노드의 keepalive와 같은 핵심 시스템 동작을 방해하는 것을 방지하고, 클러스터의 안정성을 보장합니다.

인프라 및 커널 튜닝

  • 컨트롤 플레인 고가용성(HA): 운영 환경에서는 최소 3대 이상의 컨트롤 플레인 노드를 구성하고, etcd 클러스터는 API 서버와 분리된 전용 노드에 배치하는 것이 권장됩니다. 특히, 생성과 삭제가 빈번한 Event 리소스는 별도의 etcd 클러스터에 저장하여 메인 etcd의 부하를 줄일 수 있습니다.
  • ARP 캐시 튜닝: 대규모 L2 네트워크 환경에서는 노드 간 ARP 요청이 빈번해져 커널의 ARP 캐시가 가득 차는 neighbor table overflow 문제가 발생할 수 있습니다. sysctl을 통해 net.ipv4.neigh.default.gc_thresh 관련 파라미터들을 상향 조정하여 캐시 크기를 늘려야 합니다.
  • Kubelet 동시성 제어: serializeImagePulls 플래그를 false로 설정하면 노드에서 여러 컨테이너 이미지를 병렬로 다운로드하여 파드 생성 속도를 높일 수 있습니다. 또한, Kubelet이 API 서버에 가하는 부하를 제어하기 위해 kubeAPIQPSkubeAPIBurst 값을 적절히 조정해야 합니다(1.27부터 기본값이 상향됨).

핵심 성능 지표 모니터링

Prometheus와 Grafana를 활용하여 다음과 같은 핵심 지표를 지속적으로 모니터링해야 병목 지점을 조기에 발견할 수 있습니다.

  • apiserver_request_total: API 서버의 QPS와 에러율
  • apiserver_request_duration_seconds_bucket: API 요청의 99%ile 지연 시간
  • etcd_request_duration_seconds_bucket: etcd 요청 지연 시간
  • workqueue_depth: 컨트롤러의 작업 큐 깊이 (큐가 계속 쌓이면 병목)
  • workqueue_queue_duration_seconds_bucket: 작업이 큐에서 대기한 시간

3. Cilium 성능 분석 및 최적화

Cilium은 eBPF를 통해 고성능 데이터 플레인을 제공하지만, 대규모 환경에서는 Cilium 자체의 내부 상태 관리 메커니즘이 성능에 영향을 미칠 수 있습니다.

BPF Map Pressure와 StateDB

  • BPF Map: Cilium은 서비스, 엔드포인트, 정책 등의 정보를 커널 공간의 BPF Map이라는 특수한 자료구조에 저장하여 빠르게 조회합니다. 하지만 이 Map들은 생성 시 최대 크기가 정해져 있어, 서비스나 엔드포인트의 수가 이 한계를 초과하면 더 이상 새로운 정보를 저장할 수 없습니다.
  • 장애 현상: BPF Map이 가득 차면(BPF map pressure 메트릭 증가), Cilium 에이전트는 새로운 서비스 정보를 Map에 업데이트하지 못하고 Service backend not found와 같은 사유로 패킷을 드롭(drop)하기 시작합니다.
  • Cilium의 회복성 설계: Cilium은 이러한 일시적인 실패에 대응하기 위해 StateDB라는 인메모리 데이터베이스와 Reconciler 패턴을 사용합니다. API 서버로부터 받은 모든 desired state는 먼저 StateDB에 저장됩니다. Reconciler는 주기적으로 StateDB의 상태와 실제 BPF Map의 상태를 비교하여, 실패했거나 누락된 항목을 재시도하여 최종적 일관성을 보장합니다.

Cilium 튜닝 파라미터

  • bpf.mapDynamicSizeRatio: 노드의 전체 메모리 대비 BPF Map에 할당할 메모리의 비율을 조정합니다. 서비스나 엔드포인트가 매우 많은 환경에서는 이 비율을 기본값(0.0025)보다 높여 BPF Map의 크기를 늘려야 합니다.
  • k8s.client-qps / k8s.client-burst: Cilium 에이전트가 kube-apiserver에 요청을 보내는 속도를 제어합니다. 대규모 클러스터에서는 이 값을 적절히 상향 조정하여 Cilium이 클러스터 변경 사항을 더 빠르게 동기화하도록 할 수 있습니다.

결론

대규모 쿠버네티스 클러스터의 성능 튜닝은 단일 파라미터를 조정하는 것을 넘어, 시스템 전반의 동작 원리를 깊이 이해하는 것에서 시작됩니다. 특히 컨트롤 플레인의 심장인 API 서버와 etcd의 상호작용, 그리고 각 컨트롤러의 동기화 메커니즘을 파악하는 것이 중요합니다. kube-burner와 같은 도구로 한계 상황을 시뮬레이션하고, Prometheus 메트릭을 통해 병목 지점을 정량적으로 분석하는 체계적인 접근이 필요합니다.

Cilium 환경에서는 여기에 더해, eBPF 데이터 플레인의 핵심인 BPF Map의 동작 방식과 StateDB를 통한 회복성 메커니즘을 이해하고, 관련 파라미터를 클러스터의 특성에 맞게 최적화해야 합니다. 성능 문제는 복잡하고 다층적이지만, 그 원리를 파고들면 반드시 명확한 해결책을 찾을 수 있습니다.

지난 5주차 스터디에서는 BGP와 ClusterMesh를 통해 클러스터의 경계를 넘어 외부 네트워크, 그리고 다른 클러스터와 통신하는 방법을 다루었습니다. 클러스터의 '남-북(North-South)' 트래픽과 클러스터 간 통신을 마스터한 셈이죠. 이제 우리의 시선은 다시 클러스터 내부로, 마이크로서비스 간의 복잡하고 동적인 '동-서(East-West)' 트래픽으로 향합니다. 바로 서비스 메시(Service Mesh) 의 영역입니다.

이번 포스팅에서는 서비스 메시의 기본 개념부터 시작하여, 기존의 Sidecar 모델이 가졌던 한계와 이를 eBPF로 극복하는 Cilium의 혁신적인 접근법을 심층적으로 분석합니다. 또한, Ingress의 차세대 표준인 Gateway API와 강력한 워크로드 신원 증명 프레임워크인 SPIFFE까지, Cilium이 제공하는 차세대 서비스 메시의 구성 요소들을 자세히 살펴보겠습니다.

이미지 출처: https://cilium.io/blog/2019/08/20/cilium-16/

1. 서비스 메시(Service Mesh)란 무엇인가?

마이크로서비스 아키텍처(MSA)는 애플리케이션을 작고 독립적인 서비스 단위로 분리하여 개발과 배포의 민첩성을 높였습니다. 하지만 이는 곧 서비스 간의 네트워크 통신이 폭발적으로 증가함을 의미했고, 다음과 같은 새로운 운영상의 과제들을 낳았습니다.

  • 관측 가능성(Observability): 수많은 서비스 중 어디에서 병목이나 에러가 발생하는지 추적하기 어렵습니다. 특정 사용자 요청이 여러 서비스를 거칠 때, 어느 구간에서 지연이 발생하는지, 어떤 서비스의 실패가 연쇄적인 장애를 유발하는지 파악하기가 매우 힘들어집니다.
  • 트래픽 관리(Traffic Management): A/B 테스팅, 카나리 배포, 서킷 브레이킹과 같은 정교한 트래픽 제어를 어떻게 구현할 것인가? 예를 들어, 신규 버전의 서비스에 전체 트래픽의 1%만 보내거나, 특정 HTTP 헤더를 가진 요청만 신규 버전으로 라우팅하는 등의 고급 전략을 애플리케이션 코드 수정 없이 적용하기 어렵습니다.
  • 보안(Security): 모든 서비스 간 통신을 어떻게 안전하게 암호화하고, 허가된 서비스끼리만 통신하도록 제어할 것인가? 제로 트러스트(Zero Trust) 보안 모델을 구현하기 위해 서비스 간 상호 TLS(mTLS) 인증을 적용하고 관리하는 것은 매우 복잡한 작업입니다.

이러한 공통의 관심사들을 각 애플리케이션 개발팀이 개별적으로 구현하는 것은 비효율적입니다. 서비스 메시는 바로 이 문제들을 해결하기 위해 등장한 전용 인프라 계층입니다. 애플리케이션 코드 변경 없이, 서비스 간의 모든 네트워크 통신을 가로채어 신뢰성, 보안, 관측 가능성을 제공하는 것이 핵심입니다.

전통적인 접근: 사이드카(Sidecar) 패턴

Istio, Linkerd와 같은 1세대 서비스 메시들은 이 기능을 사이드카 패턴으로 구현했습니다. 각 애플리케이션 파드에 Envoy와 같은 고성능 프록시 컨테이너를 '사이드카'로 함께 주입하는 방식입니다.

  • 동작 방식: 파드가 시작될 때, 초기화 컨테이너(init container)가 파드 내부의 iptables 규칙을 수정하여 모든 네트워크 트래픽이 이 사이드카 프록시를 거치도록 강제합니다. 이 프록시가 트래픽 라우팅, mTLS 암호화, 메트릭 수집과 같은 서비스 메시의 모든 기능을 대신 처리하며, 중앙의 컨트롤 플레인으로부터 정책을 받아 동적으로 적용합니다.
  • 한계점: 이 방식은 애플리케이션에 투명성을 제공했지만, 모든 파드마다 프록시를 띄워야 하므로 상당한 리소스 오버헤드를 유발했습니다. 또한, App -> Sidecar -> Node로 이어지는 과정에서 패킷이 커널의 TCP/IP 스택을 여러 번 거치게 되어 네트워크 지연 시간(Latency)이 증가하는 문제도 있었습니다.

2. Cilium의 새로운 접근: Sidecar-less 서비스 메시

Cilium은 eBPF를 활용하여 사이드카 모델의 근본적인 한계를 극복하는 Sidecar-less 서비스 메시라는 새로운 패러다임을 제시합니다.

  • 동작 방식: 파드마다 프록시를 두는 대신, 노드(Node)당 하나의 Envoy 프록시만 데몬셋으로 배포합니다. 그리고 eBPF를 사용하여 파드의 소켓(Socket) 레벨에서 나가는 트래픽을 가로채, 커널의 복잡한 네트워크 스택을 건너뛰고 바로 이 노드-로컬 Envoy 프록시로 전달합니다. 애플리케이션이 connect()나 send() 같은 시스템 콜을 호출하는 순간, eBPF가 이를 감지하여 패킷의 경로를 효율적으로 변경하는 것입니다.
  • 핵심 기술 (TPROXY): 이 투명한 리다이렉션은 커널의 TPROXY 기능을 통해 이루어집니다. 일반적인 NAT(REDIRECT)와 달리, TPROXY는 패킷의 원본 출발지/목적지 IP와 포트 정보를 그대로 유지한 채 트래픽을 프록시로 전달할 수 있게 해줍니다. 덕분에 Envoy 프록시는 자신이 실제 목적지인 것처럼 트래픽을 처리하면서도, 클라이언트의 원본 IP를 유실하지 않아 완전한 투명성을 보장합니다.
  • 장점: 리소스 사용량이 획기적으로 줄어들고, 커널 스택을 우회하여 네트워크 지연 시간이 단축됩니다. 또한, 모든 파드를 재시작하지 않고도 서비스 메시 기능을 전체 클러스터에 적용하거나 업그레이드할 수 있어 운영 편의성이 크게 향상됩니다.

3. Ingress를 넘어 Gateway API로

서비스 메시는 클러스터 내부 통신뿐만 아니라, 외부 트래픽이 클러스터로 들어오는 관문, 즉 인그레스(Ingress) 와도 밀접한 관련이 있습니다. Cilium은 표준 쿠버네티스 Ingress를 완벽하게 지원하며, 한 걸음 더 나아가 그 차세대 표준인 Gateway API를 핵심 기능으로 채택했습니다.

Gateway API는 기존 Ingress가 가진 표현력의 한계와 역할 불분명성을 해결하기 위해 등장했습니다.

  • 역할 지향(Role-Oriented) 아키텍처:
  • GatewayClass: 클러스터 관리자가 "우리 회사에서는 Cilium을 Gateway 구현체로 사용한다"고 정의하는 템플릿입니다.
  • Gateway: 인프라 운영자가 "80번, 443번 포트를 사용하는 my-gateway라는 LoadBalancer를 생성하겠다"고 선언하는 관문입니다.
  • HTTPRoute: 애플리케이션 개발자가 "my-gateway로 들어오는 /foo 경로는 foo-service로 보내달라"고 라우팅 규칙을 정의합니다.
    이처럼 역할이 명확히 분리되어, 각 팀은 자신의 책임 영역에 맞는 리소스만 관리하면 되므로 협업이 용이해지고 실수를 줄일 수 있습니다.
  • 표현력과 확장성: Ingress가 HTTP(S)에 국한되었던 것과 달리, Gateway API는 TCP, UDP, gRPC, TLS 라우팅을 표준 리소스로 지원합니다. 또한, 헤더 기반 라우팅, 트래픽 가중치 분산, 트래픽 미러링과 같은 고급 기능들을 어노테이션이 아닌 정식 API 필드로 정의하여 표준화된 방식으로 사용할 수 있습니다.

Cilium은 Gateway API를 네이티브로 구현하여, eBPF 기반의 고성능 데이터 플레인 위에서 이러한 정교한 L7 트래픽 관리 기능을 완벽하게 제공합니다.

4. 신뢰의 기반, 상호 인증(mTLS)과 SPIFFE

동적인 마이크로서비스 환경에서는 IP 주소가 더 이상 신뢰할 수 있는 식별자가 아닙니다. 따라서 서비스 간 통신을 보호하기 위해서는 IP 주소가 아닌, 암호학적으로 검증 가능한 워크로드 신원(Workload Identity) 이 필요합니다.

SPIFFE(Secure Production Identity Framework for Everyone) 는 이러한 강력한 신원을 모든 워크로드에 보편적으로 제공하기 위한 오픈소스 표준입니다.

  • 핵심 개념:
  • SPIFFE ID: spiffe://trust-domain/ns/default/sa/myapp과 같은 URI 형식의 고유한 워크로드 식별자입니다. 여기서 trust-domain은 신뢰의 루트(예: cluster.local)를, path는 네임스페이스와 서비스 어카운트 등을 조합하여 해당 워크로드를 고유하게 식별합니다.
  • SVID (SPIFFE Verifiable Identity Document): 이 SPIFFE ID가 담겨있는 검증 가능한 문서로, 일반적으로 X.509 인증서 형태로 제공됩니다.
  • 신원 발급 과정: 파드가 생성되면, 쿠버네티스는 해당 파드의 서비스 어카운트(Service Account) 정보를 담은 JWT 토큰을 주입합니다. 워크로드는 이 토큰을 증거로 SPIFFE 런타임 환경(예: SPIRE)에 자신의 신원을 증명하고, 해당 신원이 담긴 SVID(인증서)를 발급받습니다. 이 과정은 주기적으로 자동 갱신되어 신원의 유효성을 보장합니다.

Cilium은 이 SPIFFE 표준을 채택하여 클러스터 내 모든 서비스에 대한 상호 TLS(mTLS) 인증을 자동화합니다. 두 서비스가 통신을 시작할 때, 각자는 자신의 SVID를 상대방에게 제시하여 신원을 증명하고, 이 과정을 통과해야만 암호화된 통신 채널이 수립됩니다. 이 모든 과정이 애플리케이션에 투명하게, 인프라 레벨에서 자동으로 처리됩니다.

결론

이번 스터디를 통해 우리는 Cilium이 단순한 CNI를 넘어, eBPF라는 강력한 무기를 통해 어떻게 차세대 서비스 메시로 진화하고 있는지를 확인했습니다. 리소스 효율성과 성능을 극대화한 Sidecar-less 아키텍처, Ingress의 한계를 뛰어넘는 Gateway API 지원, 그리고 SPIFFE 기반의 강력한 mTLS 보안까지, Cilium은 현대적인 마이크로서비스 환경이 요구하는 복잡한 과제들을 하나의 통합된 솔루션으로 해결하고 있습니다.

Cilium과 함께라면, 더 이상 서비스 메시 도입을 위해 복잡한 사이드카 주입이나 별도의 솔루션을 고민할 필요 없이, 네트워킹 스택의 가장 낮은 레벨에서부터 가장 높은 애플리케이션 레벨까지 일관되고 효율적으로 관리할 수 있게 될 것입니다.

 지난 4주차 스터디에서는 Cilium의 LB-IPAM과 L2 Announcement 기능을 통해 온프레미스 환경에서도 LoadBalancer 타입의 서비스를 외부에 노출하는 방법을 알아보았습니다. L2 통신을 통해 외부 트래픽을 클러스터로 인입시키는 편리한 방법이었죠. 하지만 L2 통신은 동일 네트워크 대역이라는 한계를 가집니다. 만약 우리 클러스터가 여러 네트워크 대역에 걸쳐 있거나, 외부 라우터와 더 지능적으로 경로를 교환해야 한다면 어떻게 해야 할까요?

이번 포스팅에서는 L3 라우팅 프로토콜의 표준인 BGP(Border Gateway Protocol) 를 Cilium과 연동하여 동적으로 라우팅 정보를 교환하는 방법을 알아보고, 한 걸음 더 나아가 여러 쿠버네티스 클러스터를 하나의 거대한 클러스터처럼 연결하는 ClusterMesh 기능까지 깊이 있게 다뤄보겠습니다.

1. Cilium BGP Control Plane: 동적 라우팅으로 똑똑하게 길 찾기

지난 시간에 다룬 L2 Announcement는 동일 L2 네트워크 안에서는 훌륭하게 동작하지만, 라우터를 거쳐야 하는 다른 네트워크 대역에서는 외부 IP를 노출할 수 없습니다. 또한, 노드가 추가되거나 Pod CIDR이 변경될 때마다 외부 라우터에 수동으로 Static Route를 추가하는 것은 대규모 환경에서는 거의 불가능에 가까운 운영 부담을 야기합니다.

Cilium BGP Control Plane은 바로 이 문제를 해결하기 위한 솔루션입니다. 이는 인터넷을 지탱하는 핵심 라우팅 프로토콜인 BGP를 쿠버네티스 클러스터에 통합한 기능입니다. 각 쿠버네티스 노드가 BGP 스피커(Speaker)가 되어, 자신이 담당하는 파드 IP 대역(Pod CIDR)과 LoadBalancer 서비스의 외부 IP(ExternalIP)를 외부 라우터에게 동적으로 "광고(Advertise)"합니다.

BGP 연동의 핵심 흐름

  1. BGP Peering: Cilium이 설치된 각 노드는 외부 라우터(실습에서는 FRR 사용)와 BGP Peer 관계를 맺습니다. 이는 각자의 라우팅 정보를 교환하기 위한 일종의 '친구 맺기' 과정으로, "우리 이제부터 서로 아는 길을 공유하자"고 약속하는 것과 같습니다.
  2. Route Advertisement (경로 광고): Peer 관계가 수립되면, 각 노드는 자신이 알고 있는 경로 정보, 즉 "172.20.1.0/24 대역으로 가려면 나에게 와!" 와 같은 Pod CIDR 정보를 외부 라우터에게 알려줍니다. LoadBalancer 서비스 IP 또한 동일한 방식으로 광고하여 외부에서 직접 접근할 수 있는 경로를 제공합니다.
  3. Dynamic Routing: 외부 라우터는 여러 노드로부터 BGP를 통해 경로 정보를 수신하고, 이를 자신의 라우팅 테이블에 자동으로 업데이트합니다. 이제 라우터는 클러스터 내부의 모든 파드 IP 대역으로 가는 최적의 경로를 동적으로 학습하게 되며, 노드가 추가되거나 파드가 다른 노드로 이동하더라도 이 정보는 자동으로 갱신됩니다.

이 모든 복잡한 과정이 Cilium의 CRD (CiliumBGPClusterConfig, CiliumBGPPeerConfig, CiliumBGPAdvertisement)를 통해 쿠버네티스 네이티브 방식으로 선언적으로 관리됩니다. 즉, 우리는 YAML 파일을 통해 "어떤 경로를, 어떤 조건으로 광고하겠다"고 정의만 하면, Cilium이 알아서 BGP 연동을 처리해줍니다. 더 이상 노드나 라우터에 하나하나 접속하여 명령어를 입력할 필요가 없는 것이죠.

ExternalTrafficPolicy와 ECMP

BGP를 사용하면 여러 노드가 동일한 서비스 IP(172.16.1.1/32)를 동시에 광고할 수 있습니다. 이때 라우터는 ECMP(Equal-Cost Multi-Path) 기능을 통해 들어온 트래픽을 여러 노드로 효과적으로 분산시킬 수 있습니다. 여기서 서비스의 externalTrafficPolicy 설정이 매우 중요해집니다.

  • externalTrafficPolicy: Cluster (기본값): 트래픽이 어떤 노드로 들어오든, 해당 서비스의 파드가 없는 노드라 할지라도 일단 받아서 클러스터 내부에서 실제 파드가 있는 노드로 한 번 더 전달합니다. 이 방식은 구현이 간단하지만, 클라이언트의 원본 IP가 소실(SNAT)되고 클러스터 내부에 불필요한 트래픽(Hairpinning)이 발생하는 단점이 있습니다.
  • externalTrafficPolicy: Local (권장): BGP 광고 단계에서부터 실제 서비스 파드가 실행 중인 노드만 해당 서비스 IP를 광고하도록 지능적으로 제어합니다. 트래픽은 처음부터 올바른 노드로만 전달되므로 불필요한 내부 홉이 사라집니다. 무엇보다 클라이언트의 원본 IP 주소(Source IP)가 백엔드 파드까지 그대로 보존되므로, 로깅이나 IP 기반 보안 정책 적용에 매우 중요합니다.

2. Kind: 내 PC 안에 작은 데이터센터 만들기

ClusterMesh와 같이 여러 쿠버네티스 클러스터를 연동하는 복잡한 시나리오를 실습하려면, 실제로 여러 개의 클러스터가 필요합니다. 이때 매우 유용한 도구가 바로 Kind(Kubernetes in Docker) 입니다.

Kind는 이름 그대로 도커 컨테이너를 하나의 쿠버네티스 '노드'로 사용하여, 내 로컬 PC에서 가볍고 빠르게 멀티 노드, 멀티 클러스터 환경을 구축할 수 있게 해줍니다. 가상 머신을 여러 개 띄우는 것보다 훨씬 자원을 적게 사용하면서도 실제와 거의 동일한 클러스터 환경을 제공합니다. 또한, 모든 클러스터 구성을 YAML 파일 하나로 선언적으로 관리할 수 있어, 동일한 테스트 환경을 반복적으로 생성하고 파괴해야 하는 CI/CD 파이프라인에 통합하기에도 매우 용이합니다.

3. ClusterMesh: 클러스터의 경계를 허물다

애플리케이션의 규모가 커지면 고가용성(High Availability) 확보, 지역별 지연 시간 감소, 기능별 클러스터 분리 등의 이유로 여러 개의 쿠버네티스 클러스터를 운영하게 됩니다. 하지만 이렇게 분리된 클러스터들은 기본적으로 서로를 인식하지 못하는 별개의 섬과 같습니다.

Cilium ClusterMesh는 이 섬들을 다리로 연결하여, 여러 개의 클러스터를 마치 하나의 거대한 클러스터처럼 동작하게 만드는 강력한 기능입니다. 마치 바다로 단절된 여러 개의 섬(클러스터)들을 거대한 다리(ClusterMesh)로 연결하여, 주민들(파드)이 자유롭게 오가고, 각 섬의 특산물(서비스)을 함께 나눌 수 있는 하나의 대도시권으로 만드는 것과 같습니다.

ClusterMesh의 마법

ClusterMesh가 활성화되면 다음과 같은 놀라운 일들이 가능해집니다.

  • Global Service Discovery: cluster-west에 있는 파드가 cluster-east에 있는 서비스를 별도의 설정 없이 my-service.default.svc.cluster.local이라는 익숙한 이름으로 바로 찾아 접속할 수 있습니다. Cilium이 클러스터 간 서비스 정보를 동기화하고 DNS 요청을 가로채 다른 클러스터의 서비스 IP로 응답해주기 때문에, 개발자 입장에서는 서비스가 어느 클러스터에 있는지 전혀 신경 쓸 필요가 없습니다.
  • Cross-Cluster Pod-to-Pod Communication: 클러스터 간에 파드 IP 대역에 대한 라우팅이 자동으로 설정되어, 서로 다른 클러스터에 있는 파드들이 마치 같은 클러스터에 있는 것처럼 직접 통신할 수 있습니다. 이 클러스터 간의 모든 통신은 상호 TLS(mTLS)를 통해 암호화되므로, 민감한 데이터가 클러스터 경계를 넘나들더라도 안전하게 보호됩니다.
  • Shared Services & High Availability: 여러 클러스터에 동일한 서비스를 배포하고 이를 하나의 Global Service로 묶어, 한쪽 클러스터에 장애가 발생하더라도 다른 클러스터의 서비스로 트래픽이 자동으로 Failover 되도록 구성하여 높은 수준의 가용성을 확보할 수 있습니다.

이 모든 복잡한 과정이 cilium clustermesh CLI 명령 몇 줄로 간단하게 처리됩니다. 클러스터 간 인증서를 교환하고, 서로의 API 서버 정보를 등록하면, Cilium 에이전트들이 알아서 필요한 설정들을 구성하여 클러스터들을 안전하게 연결합니다.

결론

이번 스터디를 통해 우리는 단일 클러스터의 경계를 넘어 외부 네트워크, 그리고 다른 클러스터와 소통하는 고급 네트워킹 기술을 살펴보았습니다. BGP Control Plane은 온프레미스 환경에서도 동적 라우팅을 통해 대규모 클러스터의 외부 연결을 자동화하고 최적화하는 길을 열어주었고, ClusterMesh는 물리적으로 분리된 클러스터들을 논리적으로 하나의 네트워크로 묶어줌으로써 진정한 의미의 멀티 클러스터 아키텍처를 구현할 수 있게 했습니다.

Cilium과 함께라면, 복잡하고 거대해지는 인프라 환경 속에서도 네트워킹을 단순하고, 선언적이며, 안전하게 관리할 수 있다는 확신을 다시 한번 얻게 됩니다.

 추가로, 예전에 가이드를 작성하였던 Cilium + BGP 관련 링크도 함께 남겨봅니다.

 

On-Premise 환경에서 Kubernetes LoadBalancer 구현

CSP에서 제공하는 Kubernetes 서비스는 클라우드 인프라에 잘 통합되어 있어 있습니다. 그래서 간단한 명령어나 웹 UI를 통해 쉽게 클러스터를 생성할 수 있고 사용할 수 있습니다. 특히 Load Balancer

tech-recipe.tistory.com

 

 

Cilium BGP와 ECMP

이전 포스팅에서 이어집니다. 'On-Premise 환경에서 Kubernetes LoadBalancer 구현'을 읽고 오시길 권장드립니다. On-Premise 환경에서 Kubernetes LoadBalancer 구현CSP에서 제공하는 Kubernetes 서비스는 클라우드 인

tech-recipe.tistory.com

 



지난 3주차 스터디에서는 IPAM, 라우팅, 마스커레이딩 등 파드 간 통신을 가능하게 하는 쿠버네티스 네트워킹의 내부 동작 원리를 깊이 있게 살펴보았습니다. 이제 클러스터라는 우리만의 세상 안에서 통신하는 법을 익혔으니, 드디어 클러스터 외부의 사용자들이 우리 애플리케이션을 만날 수 있도록 문을 열어줄 시간입니다.

이번 포스팅에서는 쿠버네티스에서 외부 트래픽을 처리하는 핵심 관문인 Service의 동작 원리를 알아보고, Cilium이 제공하는 강력한 LoadBalancer 기능을 통해 온프레미스 환경에서도 클라우드처럼 서비스를 외부에 노출하는 방법을 자세히 다뤄보겠습니다.

이미지 출처: https://cilium.io/use-cases/load-balancer/


1. 쿠버네티스 Service 다시 보기: 왜 필요할까?

쿠버네티스에서 파드는 언제든지 사라지고 다시 생성될 수 있는 '임시적인' 존재입니다. 파드가 재시작되면 IP 주소도 바뀌어 버리죠. 만약 우리가 이 파드의 IP 주소를 코드에 직접 기록해두었다면, 파드가 재시작될 때마다 코드를 수정해야 하는 끔찍한 상황이 발생할 겁니다.

바로 이 문제를 해결하기 위해 서비스(Service) 가 등장했습니다. 서비스는 여러 개의 파드에 대한 고정된 단일 진입점(Single, Stable Entrypoint) 을 제공합니다. 우리는 변하기 쉬운 파드의 IP 대신, 변하지 않는 서비스의 고유한 IP(ClusterIP)나 도메인 이름을 바라보면 됩니다. 서비스는 마치 똑똑한 중간 관리자처럼, 자신에게 온 요청을 현재 실행 중인 건강한 파드들에게 알아서 분배(로드 밸런싱)해주는 역할을 합니다.

kube-proxy: 서비스의 마법을 현실로 만드는 일꾼

이러한 서비스의 마법은 각 노드에서 실행되는 kube-proxy라는 컴포넌트 덕분에 가능합니다. kube-proxy는 API 서버를 지켜보다가 서비스나 엔드포인트(서비스에 연결된 파드들)에 변경이 생기면, 각 노드의 네트워크 규칙을 업데이트하여 서비스로 가는 트래픽이 실제 파드로 전달되도록 설정합니다. 이 kube-proxy가 동작하는 방식은 역사적으로 발전해 왔습니다.

  1. Userspace 모드 (초기 방식): 모든 서비스 트래픽이 커널 공간과 사용자 공간을 넘나들며 kube-proxy 프로세스를 직접 거쳐 파드로 전달되었습니다. 구조는 간단했지만, 잦은 컨텍스트 스위칭으로 인한 성능 저하가 심해 현재는 거의 사용되지 않습니다.
  2. iptables 모드 (오랜 기간 기본값): kube-proxy가 트래픽을 직접 처리하지 않고, 커널의 netfilter 모듈(iptables)에 서비스 IP를 실제 파드 IP로 변환(DNAT)하는 규칙을 기록해둡니다. 트래픽은 커널 공간에서 바로 처리되므로 성능이 크게 향상되었습니다. 하지만 서비스와 파드가 수천, 수만 개로 늘어나면 iptables 규칙의 양이 방대해져 규칙 업데이트와 패킷 처리에 지연이 발생하는 확장성 문제가 있었습니다.
  3. IPVS 모드: iptables의 확장성 문제를 해결하기 위해 등장했습니다. IPVS(IP Virtual Server)는 해시 테이블을 사용하는 고성능 L4 로드 밸런서로, 훨씬 더 많은 수의 서비스를 효율적으로 처리할 수 있습니다. 현재 많은 환경에서 권장되는 방식입니다.
  4. eBPF (Cilium의 방식): Cilium은 kube-proxy를 완전히 대체하고 eBPF를 사용하여 서비스 로드 밸런싱을 구현합니다. 패킷이 커널의 네트워크 스택을 거치는 복잡한 과정을 우회하고, 네트워크 인터페이스(NIC)에 도착하는 시점에서 eBPF 프로그램이 직접 패킷을 처리하여 목적지 파드로 전달합니다. 이는 가장 높은 성능과 유연성을 제공하는 최신 방식입니다.

2. 서비스를 외부에 노출하는 방법들

ClusterIP 타입의 서비스는 클러스터 내부에서만 유효합니다. 외부 사용자가 서비스에 접근하게 하려면 NodePortLoadBalancer 타입을 사용해야 합니다.

  • NodePort: 모든 노드에 특정 포트(기본 30000-32767)를 열고, http://<아무노드IP>:<NodePort>로 들어온 요청을 해당 서비스로 전달합니다. 간단하지만, 노드 IP가 변경될 수 있고 3만번대 포트를 사용해야 하는 등의 제약이 있습니다.
  • LoadBalancer: 클라우드 환경에서 가장 이상적인 방식입니다. 이 타입으로 서비스를 생성하면, AWS의 ELB나 GCP의 Cloud Load Balancer와 같은 클라우드 제공사의 로드 밸런서가 자동으로 생성되고, 이 로드 밸런서의 고유한 외부 IP 주소를 통해 서비스에 접근할 수 있게 됩니다.

하지만 온프레미스(On-premise) 환경에서는 이런 외부 로드 밸런서를 자동으로 생성해주는 주체가 없습니다. 과거에는 MetalLB와 같은 별도의 솔루션을 설치해야만 LoadBalancer 타입의 서비스를 사용할 수 있었죠. 하지만 이제 Cilium이 이 기능을 자체적으로 품게 되었습니다.


3. Cilium의 LoadBalancer IPAM과 L2 Announcement

Cilium은 온프레미스 환경에서도 클라우드와 유사한 경험을 제공하기 위해 LoadBalancer IPAM(LB-IPAM)L2 Announcement 기능을 제공합니다.

LoadBalancer IPAM (LB-IPAM)

LB-IPAM은 LoadBalancer 타입의 서비스에 할당할 외부 IP 주소 풀(Pool)을 사용자가 직접 정의하고 관리할 수 있게 해주는 기능입니다.

# LoadBalancer 서비스에 할당할 IP 주소 풀을 정의하는 CRD
apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-lb-ippool"
spec:
  blocks:
    - start: "192.168.10.211"
      stop: "192.168.10.215"

위와 같이 IP 풀을 생성해두고, webpod 서비스를 LoadBalancer 타입으로 변경하면, Cilium은 정의된 풀에서 사용 가능한 IP(192.168.10.211)를 자동으로 할당하여 서비스의 EXTERNAL-IP로 설정해줍니다.

# webpod 서비스를 LoadBalancer 타입으로 변경
kubectl patch svc webpod -p '{"spec":{"type": "LoadBalancer"}}'

# 할당된 EXTERNAL-IP 확인
kubectl get svc webpod
# NAME     TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
# webpod   LoadBalancer   10.96.32.212   192.168.10.211   80:32039/TCP   150m

L2 Announcement: "192.168.10.211은 바로 나야!"

이제 서비스에 외부 IP가 할당되었지만, 아직 한 가지 문제가 남았습니다. 같은 네트워크 대역에 있는 다른 장비들(예: router)은 192.168.10.211이라는 IP가 실제로 어디에 있는지 알지 못합니다. 이 IP 주소에 대한 MAC 주소를 모르기 때문에 ARP 요청을 보내도 아무도 응답하지 않아 통신이 불가능하죠.

이때 필요한 것이 L2 Announcement 기능입니다. 이 기능이 활성화되면, LoadBalancer 서비스의 트래픽을 처리하게 될 노드(일반적으로 서비스의 엔드포인트 파드가 실행 중인 노드 중 하나)가 "192.168.10.211 주소의 주인은 바로 나(의 MAC 주소)야!" 라고 네트워크 전체에 알리기 위해 Gratuitous ARP (GARP) 패킷을 주기적으로 브로드캐스트합니다.

이 GARP 응답을 받은 스위치나 라우터는 자신의 ARP 테이블에 192.168.10.211 IP와 해당 노드의 MAC 주소를 매핑하여 기록합니다. 그 결과, 외부 클라이언트가 192.168.10.211로 보내는 트래픽은 L2 레벨에서 정확히 해당 노드로 전달될 수 있게 됩니다. 이는 MetalLB의 L2 모드와 동일한 원리로 동작합니다.

결론

쿠버네티스 서비스는 변덕스러운 파드들의 세상에 안정성이라는 질서를 부여하는 핵심 개념입니다. 그리고 kube-proxy의 발전사와 Cilium의 eBPF 기반 서비스 구현은 쿠버네티스 네트워킹이 어떻게 성능과 확장성을 향해 진화해왔는지를 보여주는 좋은 예시입니다.

특히 Cilium의 LB-IPAM과 L2 Announcement 기능은 온프레미스 환경의 제약을 뛰어넘어, 클라우드 환경과 거의 동일한 수준의 편리한 서비스 노출을 가능하게 합니다. 더 이상 외부 노출을 위해 복잡한 솔루션을 고민할 필요 없이, Cilium 하나로 클러스터 내부와 외부 네트워킹을 모두 해결할 수 있게 된 것입니다. 다음 스터디에서는 BGP를 연동하여 더욱 정교하고 확장성 있는 외부 라우팅을 구성하는 방법을 알아보겠습니다.

지난 스터디에서는 Hubble과 Grafana를 통해 클러스터의 네트워크 흐름과 시스템 상태를 관찰하는 방법을 알아보았습니다. 이제는 한 걸음 더 나아가, 그 내부에서 실제로 어떤 일들이 벌어지고 있는지 심층적으로 파헤쳐 볼 시간입니다. "파드들은 어떻게 IP 주소를 할당받을까?", "다른 노드에 있는 파드와는 어떻게 통신할까?", "클러스터 외부로는 어떻게 나갈까?"와 같은 근본적인 질문들에 답을 찾아보겠습니다.

이번 포스팅에서는 Cilium이 쿠버네티스 네트워킹의 핵심 요소들을 어떻게 처리하는지, 그 세부 동작 원리를 중심으로 정리해 보겠습니다.


1. IPAM: 파드에게 IP 주소를 나눠주는 방법

IPAM(IP Address Management)은 새로 생성되는 파드에 IP 주소를 할당하고 관리하는 중요한 메커니즘입니다. 어떤 파드가 어떤 IP를 가질지 결정하는 규칙인 셈이죠. Cilium은 여러 IPAM 모드를 제공하지만, 여기서는 대표적인 두 가지 모드를 중심으로 그 동작 방식과 특징을 자세히 살펴보겠습니다.

Kubernetes Host-Scope (기본값)

  • 동작 방식: 이 모드는 쿠버네티스의 기본 동작 방식을 가장 충실히 따릅니다. 클러스터의 kube-controller-manager가 전체 Pod CIDR 대역(예: 10.244.0.0/16)을 각 노드에 맞게 작은 podCIDR(예: 10.244.0.0/24, 10.244.1.0/24)로 잘라서 분배합니다. 그러면 각 노드에 배포된 Cilium 에이전트는 자신이 속한 노드에 할당된 podCIDR 범위 내에서만 파드 IP를 할당하고 관리하는 역할을 맡습니다. 즉, IP 주소의 큰 그림은 쿠버네티스가 그리고, 세부적인 할당만 Cilium이 담당하는 구조입니다.
  • 특징: 이 방식은 쿠버네티스 컨트롤 플레인의 kube-controller-manager--allocate-node-cidrs=true 옵션으로 실행되어야 한다는 전제 조건이 있습니다. 구조가 직관적이고 쿠버네티스 표준에 가깝다는 장점이 있습니다.
# 각 노드에 할당된 PodCIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
# k8s-ctr   10.244.0.0/24
# k8s-w1    10.244.1.0/24

Cluster-Pool

  • 동작 방식: 이 모드에서는 IP 주소 할당의 주체가 쿠버네티스 컨트롤 플레인이 아닌, Cilium 오퍼레이터로 변경됩니다. Cilium 오퍼레이터가 직접 전체 IP 주소 풀을 관리하며 각 노드에 필요한 podCIDR를 할당해 줍니다. 이렇게 할당된 정보는 쿠버네티스 v1.Node 리소스가 아닌, Cilium의 고유 리소스(CRD)인 v2.CiliumNode에 기록되어 관리됩니다.
  • 특징: 쿠버네티스 컨트롤 플레인의 설정을 직접 변경하기 어려운 관리형 쿠버네티스(Managed Kubernetes)나 클라우드 환경에서 매우 유용합니다. 쿠버네티스 자체의 설정에 의존하지 않으므로, 더 유연하고 독립적인 IP 관리가 가능해집니다.

운영 중인 클러스터에서 IPAM 모드를 변경하는 것은 매우 신중해야 합니다. 모드 변경 시 기존에 실행 중이던 파드들의 IP가 변경되거나 통신이 단절될 수 있으므로, IPAM 모드는 클러스터 구축 시점에 아키텍처를 충분히 고려하여 결정하는 것이 바람직합니다.


2. Routing: 파드 트래픽의 길을 찾는 방법

다른 노드에 있는 파드와 통신하기 위해서는 패킷이 올바른 목적지를 찾아갈 수 있도록 '길'을 알려주는 라우팅이 필수적입니다. Cilium은 크게 두 가지 라우팅 방식을 제공합니다.

Encapsulation (VXLAN, Geneve)

  • 동작 방식: 노드 간에 전송되는 파드의 패킷을 VXLAN이나 Geneve 같은 터널링 프로토콜을 이용해 한 번 더 '포장(캡슐화)'해서 보냅니다. 이 포장된 패킷의 출발지와 목적지는 각 노드의 IP가 되며, 패킷을 수신한 노드는 포장을 풀어 내부의 원본 패킷을 목적지 파드로 전달합니다. 마치 택배 상자 안에 물건을 넣어 보내는 것과 같습니다.
  • 장점: 클러스터 노드들을 연결하는 물리 네트워크(Underlay Network)가 파드들의 IP 대역을 전혀 알 필요가 없다는 점에서 구성이 매우 간단합니다. 노드 간에 IP 통신만 가능하다면 어떤 네트워크 환경에서든 손쉽게 적용할 수 있습니다.
  • 단점: 패킷을 한 번 더 포장하는 과정에서 캡슐화 헤더(약 50바이트)가 추가됩니다. 이로 인해 한 번에 보낼 수 있는 실제 데이터의 크기(MTU)가 줄어드는 오버헤드가 발생하며, 이는 아주 높은 처리량이 요구되는 환경에서는 성능 저하의 요인이 될 수 있습니다.

Native-Routing

이미지 출처: https://docs.cilium.io/en/stable/network/concepts/routing/#encapsulation

  • 동작 방식: 캡슐화라는 포장 과정 없이, 파드의 패킷을 노드에서 발생하는 일반 트래픽처럼 네트워크에 직접 전송합니다. 이 방식이 가능하려면, 물리 네트워크 장비들이 파드 IP 대역으로 가는 길을 알고 있어야 합니다. 즉, "10.244.1.0/24 대역으로 가려면 k8s-w1 노드로 보내라"와 같은 라우팅 정보가 네트워크 장비에 설정되어 있어야 합니다.
  • 장점: 캡슐화 오버헤드가 전혀 없어 최고의 네트워크 성능을 제공합니다.
  • 단점: 물리 네트워크에 대한 추가 설정이 필요하다는 점이 가장 큰 허들입니다. 모든 노드가 동일한 L2 네트워크에 있다면 Cilium의 auto-direct-node-routes: true 옵션으로 노드 간 라우팅을 자동으로 처리할 수 있지만, 그렇지 않은 복잡한 환경에서는 BGP와 같은 동적 라우팅 프로토콜을 연동해야 하므로 구성 난이도가 크게 올라갑니다.

이러한 특징 때문에, 구성의 복잡성을 피하고 싶은 온프레미스 환경에서는 VXLAN 기반의 캡슐화 모드를 사용하는 경우가 많습니다. 반면, 클라우드 환경에서는 클라우드 제공사가 제공하는 가상 네트워크(VPC)의 네이티브 라우팅 기능과 통합하여 최적의 성능을 내는 구성이 선호됩니다.


3. Masquerading: 클러스터 밖으로 나갈 때의 신분 위장

파드가 사용하는 10.244.x.x와 같은 내부 IP는 클러스터 외부에서는 인식할 수 없는 사설 IP입니다. 따라서 파드가 외부 인터넷(예: google.com)과 통신하려면, 자신의 사설 IP를 외부와 통신 가능한 공인 IP(주로 노드의 IP)로 '변환'해야 합니다. 이 과정을 Masquerading(마스커레이딩) 또는 SNAT(Source Network Address Translation)이라고 합니다.

eBPF-based (기본값)

  • 동작 방식: Cilium은 이 마스커레이딩 과정을 eBPF를 통해 커널 레벨에서 매우 효율적으로 처리합니다. 파드에서 외부로 나가는 패킷이 노드의 네트워크 인터페이스를 통과하는 순간, eBPF 프로그램이 패킷의 출발지 IP 주소를 노드의 IP 주소로 신속하게 바꿔치기합니다.
  • 장점: 기존의 iptables 방식보다 훨씬 빠르고 확장성이 뛰어납니다. 서비스와 파드의 수가 늘어나도 성능 저하가 거의 없어 대규모 클러스터 환경에 매우 적합합니다.

iptables-based (레거시)

  • 동작 방식: 전통적인 리눅스 방화벽 기능인 iptables의 SNAT 규칙을 사용하여 마스커레이딩을 수행합니다.
  • 단점: 서비스 및 파드의 수가 많아질수록 관리해야 할 iptables 규칙의 수가 선형적으로 증가하며, 이는 커널에 부하를 주어 성능 저하의 원인이 될 수 있습니다.

참고로, ip-masq-agent와 같은 설정을 활용하면 마스커레이딩 동작을 더 세밀하게 제어할 수 있습니다. 예를 들어, 외부 인터넷으로 나가는 트래픽은 마스커레이딩을 적용하되, 사내망의 특정 IP 대역으로 향하는 트래픽은 예외 처리하여 파드의 IP 그대로 통신하게 만들 수 있습니다. 이를 통해 파드가 내부 시스템과 NAT 없이 직접 통신하는 유연한 구성이 가능해집니다.


4. CoreDNS: 클러스터의 길잡이

쿠버네티스에서 DNS는 파드들이 서로의 서비스를 이름으로 찾을 수 있게 해주는 핵심적인 서비스 디스커버리 메커니즘입니다. 파드는 my-svc.default.svc.cluster.local과 같은 정해진 도메인 이름으로 다른 서비스를 찾으며, 이 '이름 풀이'를 담당하는 것이 바로 CoreDNS입니다.

  • Corefile: CoreDNS의 모든 동작은 Corefile이라는 설정 파일을 통해 정의됩니다. 이 파일에는 Kubernetes 플러그인을 통해 클러스터 내부 도메인(cluster.local)에 대한 요청을 어떻게 처리할지, 여기에 없는 외부 도메인에 대한 요청은 어떤 상위 DNS 서버로 전달(forward)할지 등이 명시되어 있습니다.
  • NodeLocal DNSCache: 클러스터 규모가 커지고 DNS 질의가 많아지면, 중앙화된 CoreDNS 파드에 부하가 집중되거나 커널의 conntrack 테이블 고갈과 같은 이슈로 DNS 통신이 불안정해질 수 있습니다. 이를 방지하기 위해 각 노드에 DNS 캐싱 에이전트를 데몬셋 형태로 배포하는 NodeLocal DNSCache 아키텍처를 도입할 수 있습니다. 이를 통해 DNS 요청이 노드 내부에서 빠르게 처리되므로 성능과 안정성을 크게 향상시킬 수 있습니다.

결론

이번 포스팅을 통해 파드에 IP가 할당되고(IPAM), 다른 파드와 통신하며(Routing), 외부 세계와 연결되는(Masquerading) 과정의 내부 동작을 Cilium 관점에서 자세히 살펴보았습니다. 각 기능들이 어떤 옵션들을 가지고 있으며, 환경에 따라 어떤 장단점이 있는지 이해하는 것은 안정적이고 성능 좋은 클러스터를 구축하는 데 매우 중요한 밑거름이 됩니다.

특히 Cilium이 eBPF를 활용하여 기존의 iptables 기반 네트워킹이 가졌던 성능과 확장성의 한계를 어떻게 극복하는지 확인하는 과정은 eBPF의 강력함을 다시 한번 체감하는 계기가 되었습니다. 다음 스터디에서는 또 다른 네트워킹 주제로 더 깊이 들어가 보겠습니다.

 

 

[Cilium Study] 2주차 - 1. Cilium Hubble을 통한 네트워크 관측

지난 1주 차 포스팅에서는 Cilium과 eBPF의 기본 개념을 맛보고, 복잡한 iptables의 세계에서 벗어날 수 있다는 희망을 보았습니다. 하지만 CNI를 설치하고 클러스터가 잘 동작한다고 해서 모든 것을

tech-recipe.tistory.com

 이전 Hubble 포스팅에서는 네트워크 흐름을 실시간으로 관찰하는 방법에 대해 알아보았습니다. 하지만 시스템의 상태를 지속적으로 추적하고 잠재적인 문제를 감지하기 위해서는 수치화된 데이터를 수집하고 분석하는 과정이 필수적입니다. 이것이 바로 모니터링(Monitoring) 의 영역입니다.

 이번 포스팅에서는 Cilium 환경에서 모니터링 시스템의 표준으로 자리 잡은 Prometheus(프로메테우스)Grafana(그라파나) 를 설치하고, 이를 활용하여 클러스터의 주요 메트릭을 수집하고 시각화하는 방법을 다루겠습니다.


1. 모니터링과 관측 가능성(Observability)

이미지 출처: https://cilium.io/use-cases/metrics-export/

 

시스템을 이해하는 접근 방식에는 모니터링과 관측 가능성, 두 가지 주요 개념이 있습니다.

  • 모니터링(Monitoring): CPU, 메모리 사용량과 같이 사전에 정의된 메트릭을 추적하여 시스템의 상태를 감시하고, 문제가 발생했을 때 경고를 발생시키는 활동입니다. 주로 "시스템이 다운되었는가?"와 같이 알려진 문제(Known-Unknowns) 를 감지하는 데 중점을 둡니다.
  • 관측 가능성(Observability): 로그, 메트릭, 트레이스 등 시스템이 외부로 출력하는 데이터를 통해 그 내부 상태를 추론하고, 예상치 못한 알려지지 않은 문제(Unknown-Unknowns) 의 원인을 파악하는 능력입니다. "왜 특정 요청의 지연 시간이 급증했는가?"와 같은 질문에 답할 수 있게 해줍니다.

 관측 가능성은 일반적으로 메트릭, 로그, 추적(Tracing) 이라는 세 가지 데이터를 통해 구현됩니다.

관측 가능성의 3대 요소

비교 항목 메트릭 (Metrics) 로그 (Logs) 추적 (Tracing)
정의 수치로 표현된 시계열 데이터 시스템 이벤트의 기록 단일 요청의 전체 처리 과정 추적
목적 시스템 성능 모니터링 및 경고 이벤트 분석 및 디버깅 서비스 간 호출 경로 및 병목 분석
대표 도구 Prometheus, Grafana ELK Stack, Loki Jaeger, Zipkin

2. Prometheus & Grafana 설치

 실습 환경에서는 사전 구성된 YAML 파일을 통해 Prometheus와 Grafana 스택을 간편하게 배포합니다.

Prometheus & Grafana 스택 배포

 monitoring-example.yaml 파일은 Prometheus, Grafana 및 관련 설정을 한 번에 배포합니다. 이 설정에는 Cilium 및 Hubble 대시보드가 Grafana에 자동으로 포함되는 내용이 정의되어 있습니다.

kubectl apply -f [https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/kubernetes/addons/prometheus/monitoring-example.yaml](https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/kubernetes/addons/prometheus/monitoring-example.yaml)

Cilium 및 Hubble 메트릭 활성화

 Cilium 에이전트, Operator, Hubble이 메트릭을 외부에 노출하도록 Helm 설정을 통해 활성화해야 합니다. 이 설정은 이전 실습에서 이미 완료되었습니다.

  • prometheus.enabled=true: Cilium 에이전트의 메트릭을 활성화합니다.
  • operator.prometheus.enabled=true: Cilium Operator의 메트릭을 활성화합니다.
  • hubble.metrics.enabled: Hubble의 메트릭 목록을 활성화합니다.

외부 접속을 위한 NodePort 설정

 배포된 Prometheus와 Grafana 서비스는 기본적으로 ClusterIP 타입이므로, 외부 웹 UI에서 접근하기 위해 NodePort 타입으로 변경합니다.

# Prometheus 서비스 타입을 NodePort로 변경
kubectl patch svc -n cilium-monitoring prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "nodePort": 30001}]}}'

# Grafana 서비스 타입을 NodePort로 변경
kubectl patch svc -n cilium-monitoring grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "nodePort": 30002}]}}'

 설정 후 노드 IP와 지정된 포트(예: Prometheus - 30001, Grafana - 30002)를 통해 접속할 수 있습니다.


3. Prometheus & Grafana 사용법

Prometheus UI

 Prometheus UI는 데이터 수집 상태를 확인하고 PromQL을 테스트하는 데 유용합니다.

  • Status → Targets: Prometheus가 현재 메트릭을 수집하고 있는 대상(endpoint)들의 상태를 확인할 수 있습니다.
  • Status → Configuration: 현재 적용된 Prometheus의 설정 파일을 조회할 수 있습니다.
  • Graph: PromQL(Prometheus Query Language)을 사용해 메트릭을 직접 쿼리하고 그래프로 시각화할 수 있습니다.

Grafana UI

 Grafana는 수집된 데이터를 시각적으로 표현하는 데 사용됩니다.

  • Data Sources: Grafana가 데이터를 가져올 소스를 설정합니다. 배포된 스택에는 Prometheus가 이미 등록되어 있습니다.
  • Dashboards: Prometheus로 수집한 데이터를 시각화하는 공간입니다. monitoring-example.yaml을 통해 Cilium, Hubble 관련 대시보드가 미리 생성되어 있습니다.

4. PromQL 쿼리 예제 분석

 Grafana의 "Cilium Metrics" 대시보드에 있는 map ops 패널의 쿼리를 통해 PromQL의 동작을 이해할 수 있습니다.

예제 쿼리

topk(5, avg(rate(cilium_bpf_map_ops_total{k8s_app="cilium", pod=~"$pod"}[5m])) by (pod, map_name, operation))

단계별 분석

  1. cilium_bpf_map_ops_total{...}: cilium_bpf_map_ops_total이라는 Counter 타입 메트릭을 선택합니다.
  2. rate(...[5m]): 위 메트릭의 최근 5분간 초당 평균 증가율을 계산합니다.
  3. avg(...) by (pod, map_name, operation): 계산된 증가율을 pod, map_name, operation 레이블 기준으로 그룹화하여 평균을 계산합니다.
  4. topk(5, ...): 그룹화된 결과 중 값이 가장 큰 상위 5개를 선택하여 보여줍니다.

5. 커스텀 Grafana 대시보드 만들기

 Grafana에서는 PromQL을 사용하여 다양한 시각화 패널로 구성된 맞춤형 대시보드를 만들 수 있습니다.

패널 타입별 활용법

패널 타입 설명 예시 PromQL 쿼리
Gauge 단일 메트릭이 임계값 대비 어느 정도인지를 보여줍니다. 1 - (avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) (노드별 CPU 사용률)
Bar chart 범주형 데이터를 막대그래프로 보여줍니다. count(kube_deployment_status_replicas_available) by (namespace) (네임스페이스별 디플로이먼트 개수)
Stat 중요한 단일 수치를 크게 표시합니다. kube_deployment_spec_replicas{deployment="nginx"} (Nginx 파드 개수)
Time series 시간에 따른 데이터 변화를 그래프로 보여줍니다. sum(rate(node_cpu_seconds_total[5m])) by (instance) (노드별 5분간 CPU 사용 변화율)
Table 데이터를 테이블 형태로 보여줍니다. node_os_info (노드 OS 정보)

6. Grafana Alerting

 Grafana를 사용하여 특정 조건이 충족되었을 때 알림을 보내도록 구성할 수 있습니다.

알림 설정 구성 요소

  • Contact points: 슬랙(Slack), 이메일 등 알림을 수신할 채널을 설정합니다.
  • Notification policy: 어떤 알림을 어떤 Contact point로 보낼지 정책을 정의합니다.
  • Alert rule: 특정 PromQL 쿼리 결과가 임계값을 넘는 등 알림을 발생시킬 조건을 생성합니다.

알림 설정 순서

  1. Contact points 설정: 알림을 받을 채널(이메일, 슬랙 등)을 구성합니다.
  2. Notification policy 정의: 알림 라우팅 규칙을 설정합니다.
  3. Alert rule 생성: 알림 발생 조건 및 임계값을 설정합니다.
  4. 테스트 및 모니터링: 설정된 알림이 정상적으로 동작하는지 확인합니다.

결론

 Prometheus와 Grafana를 통한 모니터링 시스템은 Cilium 환경에서 네트워크 성능과 보안을 효과적으로 관찰할 수 있게 해줍니다. PromQL을 활용한 메트릭 쿼리와 Grafana의 다양한 시각화 옵션을 통해 시스템의 상태를 실시간으로 파악하고, 알림 기능을 통해 문제 상황에 신속하게 대응할 수 있습니다.

 지난 포스팅에서는 Cilium과 eBPF의 기본 개념에 대해 다루었습니다. CNI가 정상적으로 배포되어 클러스터가 동작하더라도, 파드 간의 네트워크 흐름이나 적용된 정책을 직관적으로 파악하기는 어렵습니다. 이러한 내부 동작을 시각적으로 확인하고 분석하기 위해서는 별도의 관측 도구가 필요합니다.

 Cilium 환경에서 이 역할을 수행하는 것이 바로 Hubble(허블) 입니다. Hubble은 Cilium에 특화된 관측 가능성(Observability) 플랫폼으로, 이번 포스팅에서는 Hubble을 설치하고 활용하여 쿠버네티스 네트워크를 관찰하는 방법에 대해 알아보겠습니다.


1. Hubble 소개

이미지 출처: https://github.com/cilium/hubble

 Hubble은 Cilium과 eBPF를 기반으로 구축된 분산 네트워킹 및 보안 관측 플랫폼입니다. eBPF를 직접 활용하여 낮은 오버헤드로 서비스 통신, 네트워크 인프라에 대한 깊은 가시성을 제공하는 것이 핵심적인 특징입니다.

Hubble의 주요 기능

 Hubble을 통해 얻을 수 있는 주요 기능은 다음과 같습니다.

  • 서비스 의존성 및 통신 맵: 서비스 간의 통신 흐름과 의존성 그래프를 시각화하고, HTTP 호출과 같은 L7 트래픽을 확인할 수 있습니다.
  • 네트워크 모니터링 및 알림: DNS 조회 실패, TCP 연결 중단 등 네트워크 통신의 실패 여부와 원인을 파악합니다.
  • 애플리케이션 모니터링: 특정 서비스의 HTTP 4xx/5xx 응답 코드 비율이나 95/99번째 백분위수 지연 시간 등을 측정합니다.
  • 보안 관측: 네트워크 정책에 의해 차단된 연결을 확인하고, 클러스터 외부에서의 접근이나 특정 DNS 조회 이력을 추적합니다.

Hubble 구성 요소

 Hubble은 다음과 같은 주요 구성 요소로 이루어져 있습니다.

  • Hubble API: 각 노드의 Cilium 에이전트 내에서 관찰된 트래픽에 대한 인사이트를 제공합니다.
  • Hubble CLI: 로컬 유닉스 도메인 소켓을 통해 Hubble API를 쿼리하는 데 사용되는 CLI 도구입니다.
  • Hubble Relay: 클러스터 전체의 Hubble API 데이터를 집계하여 중앙화된 가시성을 제공하는 서비스입니다.
  • Hubble UI: Hubble Relay를 통해 수집된 데이터를 사용자 친화적인 서비스 맵과 필터링 기능을 갖춘 웹 인터페이스로 시각화합니다.

2. Hubble 설치 및 설정

 Cilium 설치 후, helm upgrade 또는 cilium CLI 명령을 사용하여 Hubble을 활성화할 수 있습니다.

Hubble 활성화

방법 1: helm upgrade 사용

 Hubble UI 활성화, NodePort 타입으로 서비스 노출, Prometheus 메트릭 활성화를 동시에 진행할 수 있습니다.

helm upgrade cilium cilium/cilium --namespace kube-system --reuse-values \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true \
  --set hubble.ui.service.type=NodePort \
  --set hubble.ui.service.nodePort=31234 \
  --set prometheus.enabled=true \
  --set operator.prometheus.enabled=true \
  --set hubble.metrics.enableOpenMetrics=true \
  --set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload}"

방법 2: cilium CLI 사용

 간단하게 Hubble과 UI를 활성화하는 경우 사용합니다.

cilium hubble enable --ui

Hubble UI 접속

 UI 서비스가 NodePort로 설정되면 아래 명령으로 접속 주소를 확인할 수 있습니다.

NODEIP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo -e "http://$NODEIP:31234"

웹 브라우저로 해당 주소에 접속한 후, kube-system 네임스페이스를 선택하여 서비스 맵을 확인합니다.

Hubble CLI 사용 및 API 접근

로컬 PC에 Hubble CLI 설치

 로컬 환경에 맞는 Hubble CLI 바이너리를 다운로드하여 설치합니다.

포트 포워딩을 통한 API 접속

 로컬 머신에서 클러스터의 Hubble Relay 서비스로 포트 포워딩을 설정하여 Hubble API에 접근할 수 있습니다.

cilium hubble port-forward &

포트 포워딩 후 hubble status 명령으로 API 접근을 확인합니다.

hubble status
# Healthcheck (via localhost:4245): Ok
# Current/Max Flows: 12,285/12,285 (100.00%)
# Flows/s: 41.28

3. Hubble 활용 실습 (Star Wars 데모)

데모 애플리케이션 배포

 Star Wars 예제 애플리케이션(deathstar, tiefighter, xwing)을 배포하여 트래픽을 생성합니다.

kubectl apply -f [https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/minikube/http-sw-app.yaml](https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/minikube/http-sw-app.yaml)

L3/L4 정책 적용 및 관찰

정책 미적용 상태 확인

 초기에는 네트워크 정책이 없으므로 xwingtiefighter 모두 deathstar 서비스에 접근할 수 있습니다. hubble observe 명령으로 트래픽을 모니터링할 수 있습니다.

# xwing에서 deathstar로 호출
kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing

# tiefighter에서 deathstar로 호출
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing

Hubble UI에서도 xwingtiefighterdeathstar로 연결되는 것을 확인할 수 있습니다.

L3/L4 정책 적용

 org=empire 레이블을 가진 파드만 deathstar에 접근하도록 L3/L4 CiliumNetworkPolicy를 적용합니다. 이 정책은 tiefighter의 접근은 허용하지만, xwing의 접근은 차단합니다.

# sw_l3_l4_policy.yaml
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L3-L4 policy to restrict deathstar access to empire ships only"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: "TCP"
```bash
# 정책 적용
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/minikube/sw_l3_l4_policy.yaml

정책 적용 후 관찰

 다시 xwing에서 deathstar로의 접근을 시도하면 타임아웃이 발생합니다.

kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing --connect-timeout 2

 Hubble CLI로 차단된(DROPPED) 패킷을 확인할 수 있습니다.

hubble observe -f --type drop

L7 정책 적용 및 관찰

L7 정책의 필요성

 L3/L4 정책만으로는 같은 org=empire 레이블을 가진 tiefighter가 허용되지 않은 API(예: PUT /v1/exhaust-port)를 호출하는 것을 막을 수 없습니다.

L7 (HTTP) 정책 적용

 기존 정책(rule1)을 수정하여 tiefighterPOST /v1/request-landing API만 호출하도록 제한하는 L7 정책을 추가합니다.

# sw_l3_l4_l7_policy.yaml
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "POST"
          path: "/v1/request-landing"
```bash
kubectl apply -f [https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/minikube/sw_l3_l4_l7_policy.yaml](https://raw.githubusercontent.com/cilium/cilium/1.17.6/examples/minikube/sw_l3_l4_l7_policy.yaml)

정책 적용 후 관찰

 tiefighter에서 허용되지 않은 exhaust-port로의 PUT 요청을 보내면 "Access denied" 응답을 받습니다.

kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port

 Hubble에서 해당 요청이 정책에 의해 차단(DROPPED)되었음을 L7 수준에서 확인할 수 있습니다.

hubble observe -f --pod deathstar --verdict DROPPED
# ... http-request DROPPED (HTTP/1.1 PUT [http://deathstar.default.svc.cluster.local/v1/exhaust-port](http://deathstar.default.svc.cluster.local/v1/exhaust-port))

4. Hubble Exporter (흐름 로그)

 Hubble Exporter는 Hubble의 흐름(flow) 로그를 파일에 저장하는 기능입니다. 파일 로테이션, 크기 제한, 필터링을 지원합니다.

설정 및 확인

 Helm 업그레이드 시 관련 플래그를 설정하여 활성화할 수 있습니다.

# helm upgrade ...
  --set hubble.export.static.enabled=true \
  --set hubble.export.static.filePath=/var/run/cilium/hubble/events.log

 Cilium 파드 내에서 로그 파일이 정상적으로 생성되는지 확인할 수 있습니다.

kubectl -n kube-system exec ds/cilium -- tail -f /var/run/cilium/hubble/events.log | jq

성능 튜닝 및 동적 설정

 allowList, denyList, fieldMask 같은 옵션을 사용하여 저장되는 로그의 양을 조절하여 성능에 미치는 영향을 최소화할 수 있습니다. 또한, ConfigMap을 통해 Cilium 파드를 재시작하지 않고도 흐름 로그 설정을 동적으로 변경할 수 있습니다.

결론

 Hubble은 Cilium과 eBPF를 기반으로 하여 Kubernetes 클러스터의 네트워크를 효과적으로 관찰하고 정책을 적용할 수 있는 강력한 도구입니다. L3/L4부터 L7까지의 네트워크 정책을 세밀하게 제어할 수 있으며, 실시간 트래픽 모니터링과 보안 관측을 통해 클러스터의 네트워크 상태를 명확하게 파악할 수 있습니다.

+ Recent posts