IT/Kubernets

Cilium Hubble로 Network Policy 디버깅하기

gfrog 2026. 4. 30. 18:12
반응형

 

NetworkPolicy를 처음 도입할 때 가장 답답한 게 뭐냐고 물으면, 나는 망설임 없이 "왜 막혔는지 모르겠다"고 답한다. kubectl logs를 봐도 connection timeout만 보이고, 정작 어느 정책이 그 트래픽을 끊었는지는 알려주지 않는다. 예전엔 cilium monitor를 노드 단위로 띄워서 한참을 들여다봤는데, Hubble이 들어오고 나서는 이 과정이 꽤 깔끔해졌다.

이 글은 Cilium 1.19.x 환경에서 Hubble을 가지고 네트워크 정책을 디버깅하는 워크플로우를 정리한 거다. 우리 팀에서 정책을 새로 추가할 때마다 반복적으로 쓰는 절차이고, 신규 입사자한테도 이 순서대로 보여준다.

Hubble flow를 보기 전에 확인할 것

먼저 Hubble이 활성화돼 있어야 한다. 의외로 클러스터 도입 초기에 metrics만 켜고 flow export는 끄는 경우가 많다. 그러면 hubble observe가 빈 결과를 뱉는다.

cilium status | grep -i hubble
# Hubble:                  Ok   Current/Max Flows: 4095/4095 (100.00%)
# Metrics:                 Ok   dns,drop,tcp,flow,...

Current/Max Flows가 0/0이면 Hubble은 떠 있어도 ring buffer가 비활성화된 거다. cilium-config ConfigMap에서 hubble-buffer-max-flows를 확인하고, 운영 클러스터라면 4095보다 크게 잡아두는 걸 권장한다. 노드별 메모리 부담이 있긴 한데, 디버깅 시점에 flow가 남아있느냐가 훨씬 중요하다.

CLI는 cilium hubble port-forward &로 띄우거나, cilium-cli가 있다면 cilium hubble enable --ui 한 줄로 UI까지 켤 수 있다. 나는 보통 CLI를 더 쓴다. UI가 보기는 좋은데 grep이 안 된다.

첫 번째 시나리오: 막혔는데 어디서 막혔는지 모름

가장 흔한 케이스. 서비스 A에서 서비스 B로 호출이 안 된다는 신고가 들어왔다고 하자.

hubble observe --verdict DROPPED --since 5m -o compact

--verdict DROPPED만 걸면 노이즈가 확 줄어든다. 출력은 이런 식이다:

Apr 30 08:42:11.231: default/svc-a-7f9d6b-x2k4l (ID:12453) <> default/svc-b-6c8c9-q9p2m (ID:8821) policy-verdict:none EGRESS DENIED (TCP Flags: SYN)

여기서 핵심은 policy-verdict:none이다. "어떤 policy도 허용하지 않아서 default deny에 걸렸다"는 뜻이다. Cilium의 default deny는 한 번이라도 해당 endpoint에 정책이 붙으면 자동으로 발동되니까, "정책 추가했더니 갑자기 다 막혔어요"의 90%가 이 케이스다.

반대로 어떤 정책이 명시적으로 거부했다면 policy-verdict:l3-l4처럼 어느 레이어에서 차단됐는지가 찍힌다. 이걸 보고 정책 ID를 확인할 수 있다.

두 번째 시나리오: 어떤 정책이 허용했는지 추적

trace ID로 거꾸로 따라가는 방법도 알아두면 좋다. 이번에는 --verdict FORWARDED로 보면 된다:

hubble observe --pod default/svc-a --to-pod default/svc-b \
  --verdict FORWARDED -o jsonpb | jq '.flow.policy_match_type, .flow.egress_allowed_by'

egress_allowed_by 배열에는 매칭된 CiliumNetworkPolicy의 namespace/name이 들어있다. 만약 의도하지 않은 정책이 트래픽을 흘리고 있다면 여기서 잡힌다. 이게 GitHub 이슈 #41357에서 한참 논의되던 "어느 정책 룰이 매칭됐는지 정확히 보여달라"는 요구사항인데, 1.19에서 jsonpb 출력에 꽤 자세히 들어왔다.

세 번째 시나리오: DNS는 되는데 HTTP만 막힘

L7 정책이 들어가면 트러블슈팅이 한 단계 어려워진다. 예전에 우리 팀에서 KubeCon 끝나고 L7 policy를 적극 도입했다가, 의도한 path만 막히는 게 아니라 health check까지 같이 막혀서 새벽에 롤백한 적이 있다.

L7 매치는 --type l7로 필터링한다:

hubble observe --type l7 --verdict DROPPED --since 10m

L7 drop은 http-status-code: 403 같은 형태로도 보이는데, 이건 진짜 애플리케이션 403이 아니라 Cilium proxy(Envoy)가 만든 가짜 403이다. 처음 보면 헷갈리니까 주의. 이걸 진짜 앱 응답인 줄 알고 백엔드 코드를 한참 뒤진 동료가 있었다.

디버깅을 빠르게 하는 자잘한 팁

hubble observe --follow는 좋지만, 실제 트러블슈팅에선 --last 10000 -o jsonpb | jq 조합이 훨씬 강력하다. 시간순으로 묶어서 보고, 나중에 grep도 가능하기 때문이다. ring buffer가 작으면 최근 수 초만 남으므로, 재현이 어려운 이슈는 buffer를 크게 잡고 재현시키는 게 첫걸음이다.

특정 트래픽만 빨리 보고 싶으면 --protocol tcp --port 5432 같은 식으로 좁힌다. Postgres 트래픽만 보고 싶을 때 노이즈를 99% 줄인다.

cilium policy get 결과를 cache해서 보는 것도 의외로 도움된다. 정책 적용은 됐는데 endpoint가 selector에 잡히질 않는 경우, label 한두 글자 오타가 원인인 경우가 많다.

결론

NetworkPolicy 디버깅은 결국 "어떤 트래픽이, 어떤 정책 때문에, 어떻게 됐나"를 빨리 보는 게 전부다. Hubble은 이 세 가지에 대한 답을 한 줄로 보여준다는 점에서 처음부터 켜두는 게 정답이라고 생각한다. 우리 팀에선 신규 클러스터 부트스트랩 단계에 Hubble과 buffer 설정을 차트화해서 항상 같이 깔리게 해뒀다.

물론 만능은 아니다. eBPF 영역에서 일어나는 더 깊은 동작 — 예를 들어 conntrack 만료나 NAT 충돌 같은 건 여전히 cilium monitorbpftool로 내려가야 한다. 다음 글에서는 그 영역도 정리해보려고 한다.

혹시 Cilium 외에 다른 CNI에서 비슷한 워크플로우를 쓰시는 분 있으면 어떻게 푸시는지 댓글로 알려주시면 좋겠다. Calico의 calicoctl get policy도 비슷한 방향이라고 들었는데, 직접 운영해보진 못했다.

반응형