Skip to content

Split View: Kubernetes Network Policy 완벽 가이드: Cilium·Calico로 구현하는 제로 트러스트 네트워크 보안

✨ Learn with Quiz
|

Kubernetes Network Policy 완벽 가이드: Cilium·Calico로 구현하는 제로 트러스트 네트워크 보안

Kubernetes Network Policy with Cilium and Calico

들어가며

Kubernetes 클러스터에서 Pod는 기본적으로 모든 다른 Pod와 자유롭게 통신할 수 있다. 이는 개발 초기에는 편리하지만, 프로덕션 환경에서는 심각한 보안 위협이 된다. 하나의 Pod가 침해되면 클러스터 내 모든 서비스로 lateral movement가 가능해지기 때문이다. 실제로 2024년 CNCF Security Audit에서 조사된 Kubernetes 보안 사고의 67%가 내부 네트워크 격리 미비로 인한 것이었다.

제로 트러스트 네트워크(Zero Trust Network) 아키텍처는 이 문제에 대한 해답으로, 네트워크 내부의 모든 트래픽도 신뢰하지 않고 명시적으로 허용된 통신만 허용하는 원칙이다. Kubernetes에서 이를 구현하는 핵심 도구가 바로 NetworkPolicy이다.

하지만 기본 Kubernetes NetworkPolicy는 L3/L4(IP, 포트) 수준의 제어만 지원하며, DNS 기반 정책이나 HTTP 경로 기반 필터링 같은 고급 기능은 제공하지 않는다. 이 한계를 극복하기 위해 CiliumCalico 같은 CNI 플러그인이 등장했다. Cilium은 eBPF 기반으로 L7까지의 정밀한 정책을 제공하며, Calico는 BGP 라우팅과 GlobalNetworkPolicy를 통해 엔터프라이즈급 네트워크 보안을 구현한다.

이 글에서는 Kubernetes NetworkPolicy의 기본 개념부터 시작하여, Cilium과 Calico의 고급 정책 구현, 비교 분석, 모니터링 및 트러블슈팅, 실제 장애 사례와 복구 절차, 그리고 프로덕션 배포 체크리스트까지 종합적으로 다룬다.

Kubernetes NetworkPolicy 아키텍처

NetworkPolicy 기본 구조

Kubernetes NetworkPolicy는 Pod 레벨에서 네트워크 트래픽을 제어하는 네임스페이스 범위의 리소스이다. CNI 플러그인이 실제 정책을 적용하며, NetworkPolicy를 지원하지 않는 CNI(예: Flannel)에서는 리소스를 생성해도 아무런 효과가 없다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-server-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              environment: production
          podSelector:
            matchLabels:
              role: frontend
        - ipBlock:
            cidr: 10.0.0.0/8
            except:
              - 10.0.1.0/24
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53

이 정책은 다음과 같이 동작한다.

  1. 대상 Pod 선택: podSelectorapp: api-server 레이블을 가진 Pod에 적용
  2. 인그레스 규칙: production 네임스페이스의 frontend Pod와 10.0.0.0/8 대역(10.0.1.0/24 제외)에서 TCP 8080 포트로의 접근만 허용
  3. 이그레스 규칙: database Pod로의 TCP 5432 접근과 DNS(UDP 53) 트래픽만 허용

Default Deny 전략

제로 트러스트의 기본은 모든 트래픽을 차단한 뒤, 필요한 것만 명시적으로 허용하는 것이다. Default Deny 정책은 네임스페이스 내 모든 Pod의 인그레스와 이그레스를 차단한다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

podSelector는 네임스페이스 내 모든 Pod를 선택한다. Ingress와 Egress 모두 policyTypes에 지정했지만 허용 규칙이 없으므로 모든 트래픽이 차단된다. DNS를 별도로 허용해야 서비스 디스커버리가 정상 동작한다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

네임스페이스 격리 패턴

대규모 클러스터에서는 네임스페이스 간 격리가 필수적이다. 다음은 동일 네임스페이스 내 통신만 허용하는 패턴이다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: namespace-isolation
  namespace: team-alpha
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector: {}

podSelector: {}namespaceSelector가 없으면 현재 네임스페이스의 모든 Pod만 매칭된다. 이를 통해 네임스페이스 간 격리를 간결하게 구현할 수 있다.

Cilium CiliumNetworkPolicy 심층 분석

Cilium 아키텍처와 eBPF

Cilium은 Linux 커널의 eBPF(extended Berkeley Packet Filter) 기술을 활용하여 네트워크 정책을 커널 수준에서 적용한다. 전통적인 iptables 기반 접근과 달리 eBPF는 프로그래머블한 데이터 플레인을 제공하므로 정책 수가 증가해도 성능 저하가 거의 없다.

Cilium의 핵심 구성 요소는 다음과 같다.

  • Cilium Agent: 각 노드에서 DaemonSet으로 실행되며, eBPF 프로그램을 컴파일하고 커널에 로드한다
  • Cilium Operator: 클러스터 전체의 리소스 관리를 담당한다
  • Hubble: 네트워크 플로우를 실시간으로 관찰하는 모니터링 도구이다
  • Envoy Proxy: L7 정책 적용 시 투명 프록시로 동작한다

L3-L7 필터링 구현

Cilium의 CiliumNetworkPolicy는 기본 NetworkPolicy의 모든 기능을 포함하면서 L7(HTTP, gRPC, Kafka) 수준의 정밀한 제어를 추가로 지원한다.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: l7-api-policy
  namespace: production
spec:
  endpointSelector:
    matchLabels:
      app: api-server
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: frontend
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
          rules:
            http:
              - method: GET
                path: '/api/v1/products'
              - method: POST
                path: '/api/v1/orders'
                headers:
                  - 'Content-Type: application/json'
              - method: GET
                path: '/healthz'

이 정책은 frontend Pod에서 api-server로의 트래픽 중, GET /api/v1/products, POST /api/v1/orders(JSON Content-Type 헤더 필수), GET /healthz 요청만 허용한다. PUT, DELETE 등 다른 HTTP 메서드는 차단된다.

DNS 기반 정책

Cilium은 FQDN(Fully Qualified Domain Name) 기반으로 이그레스 트래픽을 제어할 수 있다. 외부 API 호출을 특정 도메인으로 제한할 때 유용하다.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: dns-based-egress
  namespace: production
spec:
  endpointSelector:
    matchLabels:
      app: payment-service
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: '53'
              protocol: ANY
          rules:
            dns:
              - matchPattern: '*.stripe.com'
              - matchPattern: '*.amazonaws.com'
    - toFQDNs:
        - matchPattern: '*.stripe.com'
      toPorts:
        - ports:
            - port: '443'
              protocol: TCP
    - toFQDNs:
        - matchPattern: '*.amazonaws.com'
      toPorts:
        - ports:
            - port: '443'
              protocol: TCP

이 정책은 payment-service가 stripe.com과 amazonaws.com 도메인으로만 HTTPS 통신을 할 수 있도록 제한한다. DNS 쿼리 자체도 해당 도메인에 대해서만 허용된다.

CiliumClusterwideNetworkPolicy

클러스터 전체에 적용되는 정책은 CiliumClusterwideNetworkPolicy를 사용한다.

apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: block-metadata-access
spec:
  endpointSelector: {}
  egressDeny:
    - toCIDR:
        - 169.254.169.254/32
      toPorts:
        - ports:
            - port: '80'
              protocol: TCP

이 정책은 클러스터 내 모든 Pod가 클라우드 메타데이터 서비스(169.254.169.254)에 접근하는 것을 차단한다. IMDS를 통한 자격 증명 탈취 공격을 방지하는 중요한 보안 조치이다.

Calico GlobalNetworkPolicy 심층 분석

Calico 아키텍처

Calico는 Tigera가 개발한 네트워킹 솔루션으로, BGP(Border Gateway Protocol)를 기반으로 한 L3 라우팅과 정책 엔진을 제공한다. 주요 구성 요소는 다음과 같다.

  • Felix: 각 노드에서 실행되는 에이전트로, 라우팅 테이블과 iptables/eBPF 규칙을 관리한다
  • BIRD: BGP 클라이언트로, 노드 간 라우팅 정보를 교환한다
  • Typha: Felix와 Kubernetes API Server 사이의 프록시로, API Server 부하를 줄인다
  • calicoctl: Calico 리소스를 관리하는 CLI 도구이다

GlobalNetworkPolicy 구현

Calico의 GlobalNetworkPolicy는 네임스페이스에 종속되지 않는 클러스터 전체 범위의 정책이다. Kubernetes 표준 NetworkPolicy보다 우선하여 적용된다.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: deny-egress-to-internet
spec:
  selector: environment == 'production'
  types:
    - Egress
  egress:
    - action: Allow
      destination:
        nets:
          - 10.0.0.0/8
          - 172.16.0.0/12
          - 192.168.0.0/16
    - action: Allow
      protocol: UDP
      destination:
        ports:
          - 53
    - action: Deny
      destination:
        notNets:
          - 10.0.0.0/8
          - 172.16.0.0/12
          - 192.168.0.0/16

이 정책은 production 환경의 모든 Pod가 RFC 1918 사설 IP 대역과 DNS 트래픽만 허용하고, 인터넷으로의 직접 통신을 차단한다.

Calico 티어(Tier) 시스템

Calico Enterprise에서는 정책 티어를 사용하여 정책 적용 순서를 명확하게 관리할 수 있다.

apiVersion: projectcalico.org/v3
kind: Tier
metadata:
  name: security
spec:
  order: 100

---
apiVersion: projectcalico.org/v3
kind: Tier
metadata:
  name: platform
spec:
  order: 200

---
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: security.block-known-threats
spec:
  tier: security
  order: 10
  selector: all()
  types:
    - Ingress
    - Egress
  ingress:
    - action: Deny
      source:
        nets:
          - 198.51.100.0/24
    - action: Pass
  egress:
    - action: Deny
      destination:
        nets:
          - 198.51.100.0/24
    - action: Pass

보안 팀의 정책이 플랫폼 팀의 정책보다 먼저 평가되므로, 알려진 위협 IP를 플랫폼 정책과 무관하게 차단할 수 있다.

Cilium vs Calico vs 기본 NetworkPolicy 비교표

각 솔루션의 주요 기능과 특성을 비교한 표이다.

항목Kubernetes NetworkPolicyCiliumCalico
정책 범위네임스페이스네임스페이스 + 클러스터네임스페이스 + 클러스터
L3/L4 지원OOO
L7 지원XO (HTTP, gRPC, Kafka, DNS)부분 (Enterprise만)
DNS 기반 정책XO (FQDN 매칭)O (Calico Enterprise)
정책 엔진CNI 종속eBPF 네이티브iptables / eBPF 선택
성능 (정책 1000+)iptables 기반 성능 저하eBPF로 일정한 성능eBPF 모드 시 양호
모니터링별도 도구 필요Hubble 내장calicoctl + Prometheus
FQDN 이그레스XOO (Enterprise)
정책 티어XXO (Enterprise)
Host 방화벽XOO
암호화XWireGuard/IPsecWireGuard
멀티 클러스터XCluster MeshCalico Federation
CNCF 등급표준Graduated- (Tigera 상용)
GUI 관리XHubble UICalico Enterprise UI

구현 가이드

단계별 Default Deny 적용

프로덕션 환경에서 Default Deny를 한 번에 적용하면 대규모 장애가 발생할 수 있다. 다음은 안전한 단계별 적용 절차이다.

1단계: 감사 모드로 시작 (Cilium)

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: audit-default-deny
  namespace: staging
  annotations:
    policy.cilium.io/audit-mode: 'true'
spec:
  endpointSelector: {}
  ingress:
    - fromEndpoints:
        - matchLabels:
            reserved:host: ''
  egress:
    - toEndpoints:
        - matchLabels:
            reserved:host: ''

감사 모드에서는 정책이 실제로 트래픽을 차단하지 않고, 차단 대상이 될 트래픽을 Hubble을 통해 로그로 기록한다.

2단계: Hubble로 트래픽 분석

# Hubble CLI로 정책에 의해 감사(audit)된 트래픽 확인
hubble observe --namespace staging --verdict AUDIT --output json | \
  jq '.flow | {src: .source.labels, dst: .destination.labels, port: .l4.TCP.destination_port}'

# 네임스페이스 내 통신 패턴 파악
hubble observe --namespace staging --type trace:to-endpoint \
  --output compact --last 1000

3단계: 허용 정책 작성 후 Default Deny 활성화

감사 로그 분석 결과를 바탕으로 필요한 허용 정책을 모두 작성한 뒤, 감사 모드의 annotation을 제거하여 정책을 활성화한다.

마이크로서비스 정책 패턴

실제 마이크로서비스 환경에서 자주 사용되는 정책 패턴이다.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: microservice-pattern
  namespace: ecommerce
spec:
  endpointSelector:
    matchLabels:
      app: order-service
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: api-gateway
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
          rules:
            http:
              - method: POST
                path: '/orders'
              - method: GET
                path: '/orders/[0-9]+'
  egress:
    - toEndpoints:
        - matchLabels:
            app: inventory-service
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            app: payment-service
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: '53'
              protocol: ANY

이 정책은 order-service가 api-gateway에서만 주문 관련 HTTP 요청을 받고, inventory-service와 payment-service로만 통신할 수 있도록 제한한다.

모니터링과 트러블슈팅

Hubble을 활용한 Cilium 모니터링

Hubble은 Cilium에 내장된 네트워크 관찰 도구로, eBPF 기반으로 모든 네트워크 플로우를 실시간으로 수집한다.

# Hubble 활성화 (Cilium Helm 설치 시)
helm upgrade cilium cilium/cilium \
  --namespace kube-system \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true

# 특정 네임스페이스에서 차단된 트래픽 확인
hubble observe --namespace production --verdict DROPPED \
  --output json --last 100

# 특정 Pod 간 통신 추적
hubble observe --from-pod production/api-server-7d9f8b6c5d-x2k4m \
  --to-pod production/database-5f7b9c8d6e-m3n7p --output compact

# 정책 적용 상태 확인
cilium policy get --endpoint 12345

# Cilium 엔드포인트 상태 확인
cilium endpoint list -o json | \
  jq '.[] | select(.status.policy.realized.denied > 0) | {id: .id, labels: .status.labels}'

calicoctl을 활용한 Calico 트러블슈팅

# Calico 노드 상태 확인
calicoctl node status

# 적용된 정책 목록 확인
calicoctl get networkpolicy --all-namespaces -o wide
calicoctl get globalnetworkpolicy -o wide

# 특정 워크로드의 정책 평가 시뮬레이션
calicoctl get workloadendpoint --all-namespaces -o yaml | \
  grep -A 5 "api-server"

# Felix 로그에서 정책 적용 확인
kubectl logs -n calico-system -l k8s-app=calico-node -c calico-node \
  --tail=100 | grep -i "policy"

# BGP 피어 상태 확인
calicoctl node status | grep -A 10 "BGP"

Prometheus 메트릭 수집

Cilium과 Calico 모두 Prometheus 메트릭을 노출한다.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: cilium-metrics
  namespace: monitoring
spec:
  selector:
    matchLabels:
      k8s-app: cilium
  namespaceSelector:
    matchNames:
      - kube-system
  endpoints:
    - port: metrics
      interval: 15s
      path: /metrics

주요 모니터링 메트릭은 다음과 같다.

  • cilium_policy_verdict_total: 정책 판정 결과(FORWARDED, DROPPED, AUDIT) 카운터
  • cilium_drop_count_total: 정책에 의해 드랍된 패킷 수
  • cilium_policy_import_errors_total: 정책 파싱 에러 수
  • calico_felix_active_local_policies: Felix가 활성 관리 중인 정책 수
  • calico_felix_iptables_save_errors: iptables 규칙 저장 에러 수

장애 사례와 복구 절차

사례 1: Default Deny 일괄 적용으로 인한 전체 서비스 다운

상황: 운영팀이 프로덕션 네임스페이스에 Default Deny 정책을 테스트 없이 적용. DNS 허용 정책을 누락하여 모든 서비스의 서비스 디스커버리가 실패하고, 연쇄적으로 모든 마이크로서비스가 상호 통신 불가 상태에 빠졌다.

증상:

  • 모든 Pod의 readiness probe 실패
  • 서비스 간 HTTP 요청 타임아웃
  • DNS 쿼리 응답 없음

복구 절차:

# 1. 긴급 조치: 문제를 일으킨 Default Deny 정책 제거
kubectl delete networkpolicy default-deny-all -n production

# 2. 상태 확인
kubectl get pods -n production -o wide
kubectl get endpoints -n production

# 3. DNS 통신 확인
kubectl exec -n production deploy/api-server -- nslookup kubernetes.default

# 4. 근본 원인 분석 후 올바른 정책 세트 재적용
# - DNS 허용 정책을 먼저 적용
kubectl apply -f allow-dns-egress.yaml
# - 서비스 간 통신 정책 적용
kubectl apply -f service-communication-policies/
# - Default Deny를 마지막에 적용
kubectl apply -f default-deny-all.yaml

교훈: Default Deny는 반드시 허용 정책을 먼저 적용한 후 마지막에 적용해야 한다. 스테이징 환경에서 반드시 사전 검증을 수행해야 한다.

사례 2: Cilium DNS 정책과 CoreDNS 캐시 불일치

상황: Cilium의 FQDN 기반 이그레스 정책을 적용했으나, CoreDNS의 캐시된 DNS 응답이 Cilium의 DNS 프록시를 우회하여 정책이 적용되지 않는 문제가 발생했다.

증상:

  • 특정 FQDN 정책이 간헐적으로 동작하지 않음
  • Hubble에서 해당 FQDN에 대한 DNS 쿼리가 관측되지 않음
  • CoreDNS 캐시 TTL이 만료되면 정책이 정상 동작

복구 절차:

# 1. CoreDNS 캐시 확인
kubectl exec -n kube-system deploy/coredns -- \
  curl -s http://localhost:9153/metrics | grep 'coredns_cache'

# 2. Cilium DNS 프록시 로그 확인
cilium monitor --type drop --related-to fqdn

# 3. DNS 정책 재적용 및 Cilium Agent 재시작
kubectl rollout restart daemonset/cilium -n kube-system

# 4. CoreDNS 설정에서 Cilium DNS 프록시를 통한 포워딩 확인
kubectl get configmap coredns -n kube-system -o yaml

사례 3: Calico 정책 순서 충돌

상황: 두 팀이 독립적으로 GlobalNetworkPolicy를 작성하면서 order 값이 동일한 정책이 생성되었고, 의도하지 않은 Allow 규칙이 Deny보다 먼저 평가되어 보안 정책이 무력화되었다.

증상:

  • 차단해야 할 트래픽이 허용됨
  • calicoctl에서 정책 순서 확인 시 충돌 발견

복구 절차:

# 1. 모든 GlobalNetworkPolicy의 order 확인
calicoctl get globalnetworkpolicy -o yaml | grep -E "name:|order:"

# 2. 충돌 정책의 order 수정
calicoctl apply -f - <<EOF
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: security-block-external
spec:
  order: 50
  selector: all()
  types:
    - Egress
  egress:
    - action: Deny
      destination:
        notNets:
          - 10.0.0.0/8
EOF

# 3. 정책 적용 순서 검증
calicoctl get globalnetworkpolicy -o wide | sort -k3 -n

프로덕션 배포 체크리스트

사전 준비

  • CNI 플러그인(Cilium/Calico)이 설치되어 있고 정상 동작하는지 확인
  • 모든 네임스페이스에 적절한 레이블이 설정되어 있는지 확인
  • 모든 워크로드에 app, role 등 선택에 사용할 레이블이 있는지 확인
  • kube-system 네임스페이스의 핵심 서비스(CoreDNS, metrics-server)에 대한 허용 정책 준비

정책 적용 순서

  • 1단계: DNS 허용 정책을 모든 네임스페이스에 적용
  • 2단계: kube-system 네임스페이스와의 필수 통신 허용
  • 3단계: 서비스 간 통신 허용 정책 적용
  • 4단계: 외부 통신 허용 정책 적용
  • 5단계: Default Deny 정책을 스테이징에서 먼저 테스트
  • 6단계: 프로덕션에 Default Deny 적용 (트래픽 모니터링 병행)

모니터링 필수 항목

  • Hubble 또는 calicoctl로 드랍된 트래픽 실시간 모니터링 구성
  • Prometheus + Grafana 대시보드에 정책 관련 메트릭 추가
  • 정책 위반 시 PagerDuty/Slack 알림 설정
  • 주기적인 정책 감사(Policy Audit) 자동화 구성

운영 주의사항

  • 정책 변경 시 항상 --dry-run=client 옵션으로 사전 검증
  • CIDR 기반 정책은 IP 변경에 취약하므로 레이블 기반 정책 우선 사용
  • Headless Service의 경우 Pod IP가 직접 사용되므로 별도 정책 고려
  • NodePort, LoadBalancer 서비스의 인그레스 트래픽 경로 확인
  • Cilium Host Policy 사용 시 노드 간 통신(kubelet, etcd) 차단 주의
  • 정책 백업 및 Git 저장소 관리 (GitOps 연동 권장)

롤백 계획

  • 긴급 상황 시 Default Deny 정책을 즉시 제거할 수 있는 스크립트 준비
  • 정책 변경 전후 Hubble/calicoctl 스냅샷 비교 프로세스 마련
  • 롤백 대상 정책의 이전 버전을 Git에서 즉시 복원할 수 있는 파이프라인 구성

고급 패턴: 멀티 클러스터 네트워크 정책

Cilium Cluster Mesh

Cilium Cluster Mesh를 사용하면 여러 클러스터에 걸쳐 네트워크 정책을 적용할 수 있다.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: cross-cluster-policy
  namespace: shared-services
spec:
  endpointSelector:
    matchLabels:
      app: shared-database
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: backend
            io.cilium.k8s.policy.cluster: cluster-east
      toPorts:
        - ports:
            - port: '5432'
              protocol: TCP
    - fromEndpoints:
        - matchLabels:
            app: backend
            io.cilium.k8s.policy.cluster: cluster-west
      toPorts:
        - ports:
            - port: '5432'
              protocol: TCP

Calico Federation

Calico에서는 Federation을 통해 원격 클러스터의 서비스에 대한 네트워크 정책을 구성할 수 있다.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: federated-service-access
spec:
  selector: app == 'frontend'
  types:
    - Egress
  egress:
    - action: Allow
      destination:
        selector: app == 'api-gateway'
        namespaceSelector: global()
      protocol: TCP

마치며

Kubernetes 네트워크 정책은 클러스터 보안의 핵심 구성 요소이다. 기본 NetworkPolicy만으로도 L3/L4 수준의 격리를 구현할 수 있지만, 실제 프로덕션 환경에서는 Cilium이나 Calico 같은 CNI 플러그인의 고급 기능이 필수적이다.

Cilium은 eBPF 기반의 고성능 데이터 플레인과 L7 정책, DNS 기반 이그레스 제어, Hubble 관측성을 강점으로 한다. Calico는 BGP 라우팅 통합, GlobalNetworkPolicy, 정책 티어 시스템으로 엔터프라이즈 환경에 적합하다.

어떤 솔루션을 선택하든, Default Deny 전략을 기반으로 한 제로 트러스트 접근 방식을 채택하고, 정책 적용 전 반드시 감사 모드와 스테이징 환경에서 충분한 검증을 수행해야 한다. 네트워크 정책은 한 번 적용하고 끝나는 것이 아니라, 서비스가 진화함에 따라 지속적으로 관리하고 갱신해야 하는 살아있는 보안 요소라는 점을 잊지 말아야 한다.

참고자료

The Complete Guide to Kubernetes Network Policy: Zero Trust Network Security with Cilium and Calico

Kubernetes Network Policy with Cilium and Calico

Introduction

In a Kubernetes cluster, Pods can freely communicate with all other Pods by default. While this is convenient during early development, it becomes a serious security threat in production environments. If a single Pod is compromised, lateral movement to every service in the cluster becomes possible. In fact, 67% of Kubernetes security incidents investigated in the 2024 CNCF Security Audit were caused by insufficient internal network isolation.

Zero Trust Network architecture addresses this problem by trusting no traffic within the network and allowing only explicitly permitted communication. The core tool for implementing this in Kubernetes is the NetworkPolicy.

However, the standard Kubernetes NetworkPolicy only supports L3/L4 (IP, port) level controls and does not provide advanced features like DNS-based policies or HTTP path-based filtering. To overcome these limitations, CNI plugins such as Cilium and Calico emerged. Cilium uses eBPF to provide precise policies up to L7, while Calico implements enterprise-grade network security through BGP routing and GlobalNetworkPolicy.

This article covers everything from Kubernetes NetworkPolicy fundamentals through advanced Cilium and Calico policy implementation, comparative analysis, monitoring and troubleshooting, real-world failure cases and recovery procedures, and a production deployment checklist.

Kubernetes NetworkPolicy Architecture

NetworkPolicy Basic Structure

Kubernetes NetworkPolicy is a namespace-scoped resource that controls network traffic at the Pod level. The CNI plugin enforces the actual policy; on CNIs that do not support NetworkPolicy (e.g., Flannel), creating the resource has no effect.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-server-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              environment: production
          podSelector:
            matchLabels:
              role: frontend
        - ipBlock:
            cidr: 10.0.0.0/8
            except:
              - 10.0.1.0/24
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53

This policy works as follows:

  1. Target Pod Selection: Applied to Pods with the app: api-server label via podSelector
  2. Ingress Rules: Only allows access to TCP port 8080 from frontend Pods in the production namespace and the 10.0.0.0/8 CIDR range (excluding 10.0.1.0/24)
  3. Egress Rules: Only allows TCP 5432 access to database Pods and DNS (UDP 53) traffic

Default Deny Strategy

The foundation of Zero Trust is blocking all traffic first, then explicitly allowing only what is needed. A Default Deny policy blocks all ingress and egress for every Pod in a namespace.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

An empty podSelector selects all Pods in the namespace. Both Ingress and Egress are specified in policyTypes, but since there are no allow rules, all traffic is blocked. DNS must be separately allowed for service discovery to work.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Namespace Isolation Patterns

In large clusters, inter-namespace isolation is essential. The following pattern allows only intra-namespace communication.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: namespace-isolation
  namespace: team-alpha
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector: {}

When podSelector: {} is used without a namespaceSelector, only Pods in the current namespace are matched. This provides a concise implementation of inter-namespace isolation.

Cilium CiliumNetworkPolicy Deep Dive

Cilium Architecture and eBPF

Cilium uses the Linux kernel's eBPF (extended Berkeley Packet Filter) technology to enforce network policies at the kernel level. Unlike the traditional iptables-based approach, eBPF provides a programmable data plane, so there is virtually no performance degradation even as the number of policies increases.

The core components of Cilium are:

  • Cilium Agent: Runs as a DaemonSet on each node, compiling and loading eBPF programs into the kernel
  • Cilium Operator: Manages cluster-wide resources
  • Hubble: A monitoring tool that observes network flows in real time
  • Envoy Proxy: Acts as a transparent proxy when enforcing L7 policies

L3-L7 Filtering Implementation

Cilium's CiliumNetworkPolicy includes all the features of the standard NetworkPolicy while adding fine-grained L7 (HTTP, gRPC, Kafka) level controls.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: l7-api-policy
  namespace: production
spec:
  endpointSelector:
    matchLabels:
      app: api-server
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: frontend
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
          rules:
            http:
              - method: GET
                path: '/api/v1/products'
              - method: POST
                path: '/api/v1/orders'
                headers:
                  - 'Content-Type: application/json'
              - method: GET
                path: '/healthz'

This policy allows only GET /api/v1/products, POST /api/v1/orders (with required JSON Content-Type header), and GET /healthz requests from frontend Pods to the api-server. Other HTTP methods like PUT and DELETE are blocked.

DNS-Based Policies

Cilium can control egress traffic based on FQDN (Fully Qualified Domain Name). This is useful for restricting external API calls to specific domains.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: dns-based-egress
  namespace: production
spec:
  endpointSelector:
    matchLabels:
      app: payment-service
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: '53'
              protocol: ANY
          rules:
            dns:
              - matchPattern: '*.stripe.com'
              - matchPattern: '*.amazonaws.com'
    - toFQDNs:
        - matchPattern: '*.stripe.com'
      toPorts:
        - ports:
            - port: '443'
              protocol: TCP
    - toFQDNs:
        - matchPattern: '*.amazonaws.com'
      toPorts:
        - ports:
            - port: '443'
              protocol: TCP

This policy restricts the payment-service to HTTPS communication only with stripe.com and amazonaws.com domains. DNS queries themselves are also only allowed for those domains.

CiliumClusterwideNetworkPolicy

Policies that apply across the entire cluster use CiliumClusterwideNetworkPolicy.

apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: block-metadata-access
spec:
  endpointSelector: {}
  egressDeny:
    - toCIDR:
        - 169.254.169.254/32
      toPorts:
        - ports:
            - port: '80'
              protocol: TCP

This policy blocks all Pods in the cluster from accessing the cloud metadata service (169.254.169.254). This is a critical security measure to prevent credential theft attacks via IMDS.

Calico GlobalNetworkPolicy Deep Dive

Calico Architecture

Calico is a networking solution developed by Tigera that provides L3 routing and a policy engine based on BGP (Border Gateway Protocol). Its major components are:

  • Felix: An agent running on each node that manages routing tables and iptables/eBPF rules
  • BIRD: A BGP client that exchanges routing information between nodes
  • Typha: A proxy between Felix and the Kubernetes API Server that reduces API Server load
  • calicoctl: A CLI tool for managing Calico resources

GlobalNetworkPolicy Implementation

Calico's GlobalNetworkPolicy is a cluster-wide policy that is not namespace-scoped. It takes precedence over standard Kubernetes NetworkPolicies.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: deny-egress-to-internet
spec:
  selector: environment == 'production'
  types:
    - Egress
  egress:
    - action: Allow
      destination:
        nets:
          - 10.0.0.0/8
          - 172.16.0.0/12
          - 192.168.0.0/16
    - action: Allow
      protocol: UDP
      destination:
        ports:
          - 53
    - action: Deny
      destination:
        notNets:
          - 10.0.0.0/8
          - 172.16.0.0/12
          - 192.168.0.0/16

This policy allows all production Pods to communicate only with RFC 1918 private IP ranges and DNS traffic, blocking direct communication to the internet.

Calico Tier System

In Calico Enterprise, policy tiers can be used to clearly manage the order of policy evaluation.

apiVersion: projectcalico.org/v3
kind: Tier
metadata:
  name: security
spec:
  order: 100

---
apiVersion: projectcalico.org/v3
kind: Tier
metadata:
  name: platform
spec:
  order: 200

---
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: security.block-known-threats
spec:
  tier: security
  order: 10
  selector: all()
  types:
    - Ingress
    - Egress
  ingress:
    - action: Deny
      source:
        nets:
          - 198.51.100.0/24
    - action: Pass
  egress:
    - action: Deny
      destination:
        nets:
          - 198.51.100.0/24
    - action: Pass

Since the security team's policies are evaluated before the platform team's policies, known threat IPs can be blocked regardless of platform policies.

Cilium vs Calico vs Standard NetworkPolicy Comparison

A comparison of the key features and characteristics of each solution.

FeatureKubernetes NetworkPolicyCiliumCalico
Policy ScopeNamespaceNamespace + ClusterNamespace + Cluster
L3/L4 SupportYesYesYes
L7 SupportNoYes (HTTP, gRPC, Kafka, DNS)Partial (Enterprise only)
DNS-based PolicyNoYes (FQDN matching)Yes (Calico Enterprise)
Policy EngineCNI-dependenteBPF nativeiptables / eBPF selectable
Performance (1000+ policies)iptables-based degradationConsistent with eBPFGood in eBPF mode
MonitoringRequires separate toolsHubble built-incalicoctl + Prometheus
FQDN EgressNoYesYes (Enterprise)
Policy TiersNoNoYes (Enterprise)
Host FirewallNoYesYes
EncryptionNoWireGuard/IPsecWireGuard
Multi-clusterNoCluster MeshCalico Federation
CNCF StatusStandardGraduated- (Tigera commercial)
GUI ManagementNoHubble UICalico Enterprise UI

Implementation Guide

Step-by-Step Default Deny Rollout

Applying Default Deny all at once in a production environment can cause large-scale outages. Here is a safe step-by-step rollout procedure.

Step 1: Start with Audit Mode (Cilium)

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: audit-default-deny
  namespace: staging
  annotations:
    policy.cilium.io/audit-mode: 'true'
spec:
  endpointSelector: {}
  ingress:
    - fromEndpoints:
        - matchLabels:
            reserved:host: ''
  egress:
    - toEndpoints:
        - matchLabels:
            reserved:host: ''

In audit mode, the policy does not actually block traffic but logs traffic that would have been blocked via Hubble.

Step 2: Analyze Traffic with Hubble

# Check traffic audited by the policy using Hubble CLI
hubble observe --namespace staging --verdict AUDIT --output json | \
  jq '.flow | {src: .source.labels, dst: .destination.labels, port: .l4.TCP.destination_port}'

# Understand communication patterns within the namespace
hubble observe --namespace staging --type trace:to-endpoint \
  --output compact --last 1000

Step 3: Create Allow Policies then Activate Default Deny

After writing all necessary allow policies based on audit log analysis, remove the audit mode annotation to activate the policy.

Microservice Policy Pattern

Common policy patterns used in real microservice environments.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: microservice-pattern
  namespace: ecommerce
spec:
  endpointSelector:
    matchLabels:
      app: order-service
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: api-gateway
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
          rules:
            http:
              - method: POST
                path: '/orders'
              - method: GET
                path: '/orders/[0-9]+'
  egress:
    - toEndpoints:
        - matchLabels:
            app: inventory-service
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            app: payment-service
      toPorts:
        - ports:
            - port: '8080'
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: '53'
              protocol: ANY

This policy restricts the order-service to only receive order-related HTTP requests from the api-gateway, and to communicate only with the inventory-service and payment-service.

Monitoring and Troubleshooting

Cilium Monitoring with Hubble

Hubble is a network observability tool built into Cilium that collects all network flows in real time using eBPF.

# Enable Hubble (during Cilium Helm installation)
helm upgrade cilium cilium/cilium \
  --namespace kube-system \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true

# Check dropped traffic in a specific namespace
hubble observe --namespace production --verdict DROPPED \
  --output json --last 100

# Trace communication between specific Pods
hubble observe --from-pod production/api-server-7d9f8b6c5d-x2k4m \
  --to-pod production/database-5f7b9c8d6e-m3n7p --output compact

# Check policy application status
cilium policy get --endpoint 12345

# Check Cilium endpoint status
cilium endpoint list -o json | \
  jq '.[] | select(.status.policy.realized.denied > 0) | {id: .id, labels: .status.labels}'

Calico Troubleshooting with calicoctl

# Check Calico node status
calicoctl node status

# List applied policies
calicoctl get networkpolicy --all-namespaces -o wide
calicoctl get globalnetworkpolicy -o wide

# Simulate policy evaluation for a specific workload
calicoctl get workloadendpoint --all-namespaces -o yaml | \
  grep -A 5 "api-server"

# Check policy application in Felix logs
kubectl logs -n calico-system -l k8s-app=calico-node -c calico-node \
  --tail=100 | grep -i "policy"

# Check BGP peer status
calicoctl node status | grep -A 10 "BGP"

Prometheus Metrics Collection

Both Cilium and Calico expose Prometheus metrics.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: cilium-metrics
  namespace: monitoring
spec:
  selector:
    matchLabels:
      k8s-app: cilium
  namespaceSelector:
    matchNames:
      - kube-system
  endpoints:
    - port: metrics
      interval: 15s
      path: /metrics

Key monitoring metrics include:

  • cilium_policy_verdict_total: Policy verdict result counters (FORWARDED, DROPPED, AUDIT)
  • cilium_drop_count_total: Number of packets dropped by policies
  • cilium_policy_import_errors_total: Number of policy parsing errors
  • calico_felix_active_local_policies: Number of policies actively managed by Felix
  • calico_felix_iptables_save_errors: Number of iptables rule save errors

Failure Cases and Recovery Procedures

Case 1: Complete Service Outage from Bulk Default Deny Application

Situation: The operations team applied a Default Deny policy to the production namespace without testing. They forgot the DNS allow policy, causing all service discovery to fail and all microservices to lose inter-service communication.

Symptoms:

  • All Pod readiness probes failing
  • HTTP request timeouts between services
  • No DNS query responses

Recovery Procedure:

# 1. Emergency action: Remove the problematic Default Deny policy
kubectl delete networkpolicy default-deny-all -n production

# 2. Verify status
kubectl get pods -n production -o wide
kubectl get endpoints -n production

# 3. Verify DNS communication
kubectl exec -n production deploy/api-server -- nslookup kubernetes.default

# 4. Reapply correct policy set after root cause analysis
# - Apply DNS allow policy first
kubectl apply -f allow-dns-egress.yaml
# - Apply inter-service communication policies
kubectl apply -f service-communication-policies/
# - Apply Default Deny last
kubectl apply -f default-deny-all.yaml

Lesson Learned: Default Deny must always be applied last, after all allow policies are in place. Pre-validation in a staging environment is mandatory.

Case 2: Cilium DNS Policy and CoreDNS Cache Mismatch

Situation: After applying Cilium FQDN-based egress policies, CoreDNS cached DNS responses bypassed Cilium's DNS proxy, causing the policy to not be enforced.

Symptoms:

  • Certain FQDN policies intermittently not working
  • DNS queries for the FQDN not observed in Hubble
  • Policy works normally after CoreDNS cache TTL expires

Recovery Procedure:

# 1. Check CoreDNS cache
kubectl exec -n kube-system deploy/coredns -- \
  curl -s http://localhost:9153/metrics | grep 'coredns_cache'

# 2. Check Cilium DNS proxy logs
cilium monitor --type drop --related-to fqdn

# 3. Reapply DNS policy and restart Cilium Agent
kubectl rollout restart daemonset/cilium -n kube-system

# 4. Verify CoreDNS configuration for forwarding through Cilium DNS proxy
kubectl get configmap coredns -n kube-system -o yaml

Case 3: Calico Policy Order Conflict

Situation: Two teams independently created GlobalNetworkPolicies with the same order value, and an unintended Allow rule was evaluated before a Deny rule, effectively bypassing the security policy.

Symptoms:

  • Traffic that should be blocked is being allowed
  • Policy order conflict discovered during calicoctl inspection

Recovery Procedure:

# 1. Check order of all GlobalNetworkPolicies
calicoctl get globalnetworkpolicy -o yaml | grep -E "name:|order:"

# 2. Fix the order of conflicting policies
calicoctl apply -f - <<EOF
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: security-block-external
spec:
  order: 50
  selector: all()
  types:
    - Egress
  egress:
    - action: Deny
      destination:
        notNets:
          - 10.0.0.0/8
EOF

# 3. Verify policy application order
calicoctl get globalnetworkpolicy -o wide | sort -k3 -n

Production Deployment Checklist

Pre-Deployment

  • Verify CNI plugin (Cilium/Calico) is installed and functioning correctly
  • Confirm all namespaces have appropriate labels
  • Confirm all workloads have labels (app, role, etc.) for policy selection
  • Prepare allow policies for critical kube-system services (CoreDNS, metrics-server)

Policy Application Order

  • Step 1: Apply DNS allow policies to all namespaces
  • Step 2: Allow essential communication with kube-system namespace
  • Step 3: Apply inter-service communication allow policies
  • Step 4: Apply external communication allow policies
  • Step 5: Test Default Deny policy in staging first
  • Step 6: Apply Default Deny to production (with concurrent traffic monitoring)

Monitoring Essentials

  • Configure real-time dropped traffic monitoring via Hubble or calicoctl
  • Add policy-related metrics to Prometheus + Grafana dashboards
  • Set up PagerDuty/Slack alerts for policy violations
  • Automate periodic policy audits

Operational Notes

  • Always use --dry-run=client for pre-validation when changing policies
  • Prefer label-based policies over CIDR-based policies (CIDR is vulnerable to IP changes)
  • Consider separate policies for Headless Services (Pod IPs used directly)
  • Verify ingress traffic paths for NodePort and LoadBalancer services
  • Be cautious about blocking inter-node communication (kubelet, etcd) when using Cilium Host Policies
  • Manage policy backups in Git repositories (GitOps integration recommended)

Rollback Plan

  • Prepare scripts to immediately remove Default Deny policies in emergencies
  • Establish Hubble/calicoctl snapshot comparison processes before and after policy changes
  • Set up pipelines to immediately restore previous policy versions from Git

Advanced Patterns: Multi-Cluster Network Policies

Cilium Cluster Mesh

With Cilium Cluster Mesh, network policies can be applied across multiple clusters.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: cross-cluster-policy
  namespace: shared-services
spec:
  endpointSelector:
    matchLabels:
      app: shared-database
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: backend
            io.cilium.k8s.policy.cluster: cluster-east
      toPorts:
        - ports:
            - port: '5432'
              protocol: TCP
    - fromEndpoints:
        - matchLabels:
            app: backend
            io.cilium.k8s.policy.cluster: cluster-west
      toPorts:
        - ports:
            - port: '5432'
              protocol: TCP

Calico Federation

With Calico, Federation enables configuring network policies for services in remote clusters.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: federated-service-access
spec:
  selector: app == 'frontend'
  types:
    - Egress
  egress:
    - action: Allow
      destination:
        selector: app == 'api-gateway'
        namespaceSelector: global()
      protocol: TCP

Conclusion

Kubernetes network policies are a core component of cluster security. While the standard NetworkPolicy provides L3/L4 level isolation, advanced features from CNI plugins like Cilium or Calico are essential in production environments.

Cilium excels with its eBPF-based high-performance data plane, L7 policies, DNS-based egress control, and Hubble observability. Calico is well-suited for enterprise environments with its BGP routing integration, GlobalNetworkPolicy, and policy tier system.

Regardless of which solution you choose, adopt a Zero Trust approach based on a Default Deny strategy, and always perform thorough validation in audit mode and staging environments before applying policies. Network policies are not a one-time deployment but a living security element that must be continuously managed and updated as services evolve.

References