지난 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를 연동하여 더욱 정교하고 확장성 있는 외부 라우팅을 구성하는 방법을 알아보겠습니다.

 

 

[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의 다양한 시각화 옵션을 통해 시스템의 상태를 실시간으로 파악하고, 알림 기능을 통해 문제 상황에 신속하게 대응할 수 있습니다.

+ Recent posts