[구축사례] IPFIX와 Goflow2로 구현한 kt cloud Network 미터링 내재화

Tech Story/Cloud Architecture

[구축사례] IPFIX와 Goflow2로 구현한 kt cloud Network 미터링 내재화

 
Tech-Frontier.md kt cloud
$ whoami --team
kt cloud Cloud플랫폼팀 윤찬열
📋 요약 TL;DR
이 글에서는 IPFIX와 Goflow2를 활용해 OVN 환경의 네트워크 과금 미터링 체계를 구축한 과정을 다룹니다.
정확한 트래픽 측정은 클라우드 운영 신뢰성과 과금 리스크 관리에 중요한 기준이 됨을 정리합니다.

#IPFIX #Goflow2 #OVN #NetworkMetering #OpenStack

iptables가 사라진 자리, 과금은 어디서 측정하나

[구축사례] IPFIX와 Goflow2로 구현한 KCP Network 미터링 내재화


클라우드 사업에서 네트워크 과금은 민감한 영역입니다. 테넌트별로 외부와 주고받는 North-South 트래픽을 정확히 측정해야 청구서가 나가는데, 여기서 1바이트라도 어긋나면 그대로 비즈니스 신뢰도 문제로 번지기 때문입니다.

 

ML2/OVS 시절에는 이걸 Neutron-metering-agent가 처리했습니다. iptables chain injection 방식으로 Netfilter 카운터를 읽어 L3 트래픽을 집계하는 구조였죠. 리눅스 IP 스택에 카운터가 붙어 있으니, 그 숫자를 그대로 긁어오면 됐습니다.

 

그런데 아키텍처가 ML2/OVN으로 넘어가면서 이 전제가 통째로 깨졌습니다. OVN은 라우팅을 OVS Flow로 처리합니다. 패킷이 리눅스 IP 스택(iptables 등)을 더 이상 타지 않는다는 뜻입니다. 카운터를 읽을 자리 자체가 없어진 겁니다. 기존 에이전트로는 측정이 불가능해졌고, 우리는 미터링 파이프라인을 처음부터 다시 설계해야 했습니다.

 

새 구조에 걸린 요건은 세 가지였습니다.

  • OVS Bridge 안에서 여러 테넌트 트래픽이 섞여 흐를 때, External IP를 기준으로 고객(테넌트)을 식별할 수 있을 것
  • 과금 데이터인 만큼 데이터 정확도를 최대한 보장할 것
  • 컴퓨트 노드(Cnode)에 성능 오버헤드를 주지 않을 것

sFlow를 버리고 IPFIX를 고른 이유

OVS Bridge에서 트래픽을 뽑아낼 방법으로 Traffic Mirroring, sFlow, IPFIX를 두고 검토했습니다. 최종 후보는 sFlow와 IPFIX였는데, 과금이라는 목적을 놓고 보면 둘의 구조적 차이가 결정적이었습니다.

 

sFlow는 상태를 저장하지 않습니다. 패킷이 스위치를 지날 때 1/N 확률(예: 1000개 중 1개)로 헤더만 복사해서 즉시 내보냅니다. 스위치 리소스를 거의 안 쓰니 오버헤드는 낮지만, 수집된 샘플로 전체 트래픽을 역산해야 합니다. "대략 이 정도 썼겠다"는 통계적 추정이라, "정확히 몇 바이트인가"를 따져야 하는 과금에는 정합성이 부족합니다.

 

IPFIX(NetFlow v9 기반 표준)는 OVS 내부에서 Flow 단위로 상태를 유지합니다. 동작은 이렇습니다.

  • 5-Tuple로 분류: 패킷이 들어오면 출발지 IP, 목적지 IP, 출발지 포트, 목적지 포트, 프로토콜 — 이 5가지를 기준으로 묶습니다.
  • In-Memory 집계: 같은 5-Tuple 패킷은 매번 내보내지 않고 OVS Flow Cache에 쌓아두면서 패킷 수(n_packets)와 바이트 수(n_bytes)를 카운터에 누적합니다.
  • Timeout 시 Export: 통신이 끝나거나(Inactive Timeout), 너무 오래 지속되거나(Active Timeout), Cache가 가득 차면 그제서야 최종 카운터를 묶어서 수집기로 보냅니다.

핵심은 수집기에 도달하기 전에 이미 OVS 단에서 바이트가 합산돼 넘어온다는 점입니다. 그래서 정합성이 100%에 가깝습니다. 과금 로스를 감수할 수 없는 상황이라 IPFIX로 결정했습니다.

 

구분 sFlow IPFIX
동작 방식 Stateless 샘플링 Stateful 집계
데이터 수집 1/N 확률로 헤더 복사 Flow 단위 카운터 누적
정확도 통계적 추정(역산 필요) 실측값에 가까움
오버헤드 매우 낮음 상대적으로 높음(튜닝 필요)
과금 적합성 정합성 이슈 적합

가장 큰 산: "이 IP, 대체 누구 건가?"

각 Cnode의 OVS에 IPFIX 설정을 마치고, 오픈소스 수집기인 Goflow2를 노드에 데몬셋으로 띄웠습니다. 그리고 여기서 이번 프로젝트의 진짜 난관에 부딪혔습니다.

 

IPFIX Raw 데이터에는 Source IP와 Destination IP만 있습니다. 그런데 청구서를 발행하려면 이 트래픽을 일으킨 주체, 즉 OpenStack Project ID(Tenant ID)를 알아야 합니다. IP만 가지고는 "누구한테 돈을 받아야 하는지"를 알 수 없는 거죠.

 

가장 단순한 방법은 패킷 로그가 들어올 때마다 Neutron API에 "이 IP 누구 거야?"라고 물어보는 겁니다. 그런데 초당 수만 건씩 쏟아지는 IPFIX 레코드마다 API나 DB 조회를 때린다면(매번 Network RTT 발생), 컨트롤 플레인은 그 즉시 주저앉습니다. 이 방식은 처음부터 선택지가 아니었습니다.

 

대신 외부 호출(네트워크 I/O)을 0으로 만드는 방향으로 갔습니다. Goflow2 소스코드를 직접 수정해서, 로컬 메모리 캐시 기반으로 O(1) 매핑을 하는 Enrichment 모듈을 붙였습니다. 파이프라인은 3단계로 동작합니다.

 

1단계 — 사전 캐싱(Initial Sync). Goflow2 프로세스가 뜨면 가장 먼저 해당 Cnode의 로컬 OVSDB(또는 OVN SB DB)와 소켓 커넥션을 맺습니다. 그리고 클라우드 안의 전체 FIP 및 NAT 자원 정보(IP ↔ Project ID 매핑)를 읽어와 프로세스 메모리에 Hash Map으로 올려둡니다. 이걸 가능하게 하려고, 사전에 Neutron NAT 리소스를 만들 때 External IDs 필드에 Project ID를 기록하도록 커스터마이징을 해뒀습니다.

 

2단계 — 실시간 동기화(Watch & Update). 클라우드는 살아 움직입니다. VM이 지워지거나 IP가 새로 할당되면 매핑이 바뀌죠. 그래서 Goflow2 내부 OVSDB Client가 DB 변경을 구독(Monitor RPC)합니다. NAT/FIP 정보가 추가·변경·삭제될 때마다 이벤트를 받아 로컬 Hash Map을 최신 상태로 갱신합니다.

 

3단계 — Zero-Latency 매핑(Enrichment). 실제 IPFIX 데이터가 Goflow2로 들어오면, 파서가 출발지·목적지 IP를 추출하고 Enrichment 모듈이 가동됩니다. 외부 통신은 전혀 하지 않고 프로세스 내부 Hash Map만 조회해서 매칭되는 Project ID를 찾습니다. 메모리 서치라 매핑 시간은 사실상 0에 수렴합니다.

 

이 캐시 구조 덕분에 인프라에 부하를 주지 않으면서도 openstack_src_project_id, openstack_dst_project_id가 깔끔하게 태깅된 과금용 로그를 Kafka로 흘려보낼 수 있었습니다.


왜 Sampling Rate를 1로 두고, 대신 캐시를 튜닝했나

여기서부터는 수치 얘기인데, 먼저 한 가지 분명히 해둘 게 있습니다. 아래 결과는 개발 환경에서 100MB 파일을 500회 다운로드하는 인위적인 시나리오로 뽑은 값입니다. 실제 운영 트래픽은 flow 분포, 패킷 크기, 동시 세션 수가 전혀 다르기 때문에, 같은 설정이라도 CPU 부하와 정확도 수치는 환경마다 달라집니다. 그래서 아래 숫자는 "이 설정이면 항상 이렇게 나온다"는 절대값이 아니라, 튜닝 방향을 잡기 위한 상대 비교로 읽는 게 맞습니다. 실제 적용 전에는 각자 환경의 대표 트래픽으로 재측정하는 걸 권장합니다.

 

그 위에서, 우리가 왜 Sampling Rate를 1로 고정했는지부터 짚겠습니다.

 

과금이라는 목적이 Sampling Rate를 1로 못박습니다. Sampling Rate를 N으로 두면 패킷을 1/N 확률로만 집계하고, 나머지는 통계적으로 역산합니다. 일반적인 트래픽 가시성·이상 탐지 용도라면 이 정도 오차는 충분히 허용됩니다. 실제로 많은 ISP가 1/1,000 수준의 샘플링으로 트래픽을 관측하고, “한 대형 ISP를 대상으로 1/8,000 샘플링 IPFIX와 SNMP 바이트 카운터를 비교했을 때 두 지표의 비율은 평균·중앙값 모두 약 0.98이었으며, 샘플링 레이트를 높일수록 SNMP 대비 정확도는 더 개선된다”고 보고됩니다. 즉 샘플링은 본질적으로 근사값입니다.이상적으로는 “모든 flow 모니터링 기술이 100% 정확도를 제공해야 하지만, 현실에서는 확장성 문제를 풀기 위한 데이터 축소 과정에서 정확도가 떨어진다”는 것이 일반적인 결론이고요. 청구서에 들어갈 바이트를 다루는 입장에서는 이 근사 오차 자체를 받아들일 수 없어서, Rate=1(전수 집계)이 사실상 강제됩니다.

 

문제는 "Rate를 1로 두면 CPU가 터지지 않나?"입니다. 실제로 튜닝 전에는 CPU 부하가 115%까지 치솟으며 패킷 드랍이 발생했고, 역설적으로 정확도가 94.9%로 떨어졌습니다. 다 잡으려다 오히려 흘린 셈이죠.

 

해법은 샘플링 비율(무엇을 볼지)과 export 동작(언제 묶어 내보낼지)을 분리해서 생각하는 것이었습니다. OVS IPFIX에서 패킷 선택 확률(sampling)과, 집계된 flow를 언제 수집기로 내보낼지 정하는 캐시 파라미터(cache_active_timeout, cache_max_flows)는 별개의 손잡이입니다. 패킷은 Rate=1로 전부 보되, 같은 5-Tuple은 캐시 안에서 카운터로 충분히 누적시킨 다음 한 번에 묶어 내보내면, 봐야 할 바이트는 그대로 다 세면서도 export 레코드 수와 그에 따른 유저스페이스 처리 부하는 크게 줄어듭니다. 그래서 "Rate=1인데 CPU가 낮다"는 게 모순이 아닙니다.

 

이 방향으로 Cache Active Timeout을 30초, Cache Max Flows를 1000으로 잡았더니, OVS 레벨 CPU 부하가 4% 미만으로 안정화되면서 데이터 정확도는 100.4%(헤더 오버헤드 포함 기준)까지 맞춰졌습니다. (100%를 살짝 넘는 건 IPFIX 헤더·패딩이 페이로드 바이트에 더해져 계측되기 때문으로, 과금 정합성 관점에서는 별도 보정 대상입니다.)

 

설정 CPU 부하 정확도 비고
Sampling Rate 1 (튜닝 전) ~115% 94.9% 캐시 미조정 → export 폭주 → 패킷 드랍
Sampling Rate 1 + Cache 튜닝 < 4% 100.4% Active Timeout 30s / Max Flows 1000

⚠️ 다시 강조하면, 위 수치는 100MB×500회 다운로드라는 테스트 시나리오 기준입니다. Cache Timeout을 길게 잡을수록 export는 줄지만 과금 데이터의 반영 지연이 커지고, Max Flows가 실제 동시 flow 수보다 작으면 캐시가 조기에 차서 export가 잦아집니다. 두 값 모두 환경의 동시 flow 규모와 과금 정산 주기에 맞춰 다시 잡아야 합니다.

 

하지만 튜닝으로 끝날 문제가 아니었습니다. Goflow2 수집기가 Cnode 안에서 돈다는 건, 결국 고객의 실제 서비스 VM과 물리 CPU를 공유한다는 의미입니다. 프로세스를 아무리 가볍게 깎아도, 트래픽 스파이크로 미터링 프로세스에 과부하가 걸려 고객 VM 성능을 갉아먹는 Noisy Neighbor가 생기면 안 됩니다.

 

그래서 두 가지 방향을 잡았습니다.

  • CPU Pinning & Cgroups 격리 (현재 적용): 미터링 프로세스(Goflow2 등)가 리소스를 멋대로 점유하지 못하도록 OS 레벨 Cgroups로 CPU/Memory Limit을 엄격하게 걸었습니다. 더 나아가 고객 VM이 쓰는 코어와 에이전트가 쓰는 코어를 물리적으로 분리(CPU Pinning / cpuset)해서 리소스 경합을 원천 차단했습니다.
  • 외부 수집기 클러스터 분리 (향후 대비): 지금은 Zero-Latency 매핑을 위한 로컬 데몬셋 구조가 최적입니다. 다만 트래픽 규모가 폭발적으로 커져서 로컬 튜닝·격리만으로 감당이 안 되는 임계점이 오면, Goflow2 Collector를 Cnode에서 떼어내 외부 전용 수집 서버 클러스터로 오프로딩하는 구조로 전환할 수 있게 설계 여지를 열어뒀습니다.

전체 아키텍처와 로드맵

완성된 데이터 플로우는 이렇습니다.

[구축사례] IPFIX와 Goflow2로 구현한 KCP Network 미터링 내재화

 

여담으로, IETF RFC 5472는 IPFIX를 과금에 쓸 때 UDP 전송 손실과 중복 레코드 문제가 생길 수 있다고 짚습니다. 우리는 이걸 두 갈래로 대응했습니다. 앞서 말한 대로 Goflow2를 Cnode에 로컬 배포해 장애 허용성(Fault Tolerance)을 먼저 확보하고, 후단에서 sequence_num으로 중복을 제거하는 로직을 추가했습니다.

 

OVN 전환은 미터링 입장에서 꽤 큰 숙제였습니다. 하지만 Goflow2 커스터마이징과 캐시 튜닝, 자원 격리 전략을 엮으면서, 결과적으로는 예전보다 더 견고하고 성능 좋은 미터링 시스템을 직접 손으로 만들어낼 수 있었습니다. 앞으로 고도화될 네트워크 서비스에도 많은 관심 부탁드립니다.

 

kt cloud 클라우드 플랫폼 서비스 바로가기 배너

?› 자주 묻는 질문 FAQ

Q. ML2/OVN에서는 왜 기존 Neutron-metering-agent를 못 쓰나요?
A. 기존 에이전트는 iptables chain injection으로 Netfilter 카운터를 읽는 방식입니다. ML2/OVS 시절엔 패킷이 리눅스 IP 스택을 타니 거기 붙은 카운터를 긁으면 됐지만, OVN은 라우팅을 OVS Flow로 처리하기 때문에 패킷이 iptables를 더 이상 거치지 않습니다. 읽을 카운터 자체가 없어지는 거라, 측정 지점을 OVS Flow 레벨(IPFIX)로 옮겨야 합니다.
Q. 정확도가 중요하면 그냥 sFlow 말고 IPFIX 쓰면 끝 아닌가요?
A. 방향은 맞지만 그게 끝이 아닙니다. IPFIX도 Sampling Rate를 1로 두고 무작정 다 캡처하려 들면 CPU 부하가 100%를 넘기면서 패킷 드랍이 나고, 오히려 정확도가 떨어집니다(저희 환경에선 94.9%까지 하락). Sampling Rate는 1로 유지하되 OVS Cache Timeout과 Max Flows를 함께 튜닝해야 CPU를 4% 미만으로 누르면서 정합성을 확보할 수 있습니다.
Q. 수집기가 컴퓨트 노드 안에서 돌면 고객 VM에 영향을 주지 않나요?
A. 그 우려 때문에 자원 격리를 따로 챙겼습니다. Cgroups로 미터링 프로세스의 CPU/Memory Limit을 엄격히 걸고, CPU Pinning(cpuset)으로 고객 VM 코어와 에이전트 코어를 물리적으로 분리해 경합을 차단했습니다. 트래픽 규모가 더 커지면 수집기를 외부 전용 클러스터로 오프로딩하는 전환도 설계상 열어둔 상태입니다.

📚 관련 / 출처REFERENCES