K8s NetworkPolicy 심화 가이드

K8s NetworkPolicy란?

Kubernetes NetworkPolicy는 Pod 간 네트워크 트래픽을 L3/L4 수준에서 제어하는 리소스입니다. 기본적으로 K8s 클러스터 내 모든 Pod는 서로 통신할 수 있지만, NetworkPolicy를 적용하면 화이트리스트 기반으로 허용된 트래픽만 통과시킬 수 있습니다. Zero Trust 네트워크 모델의 핵심 구현체로, 프로덕션 환경에서는 반드시 적용해야 할 보안 레이어입니다.

NetworkPolicy가 동작하려면 CNI 플러그인이 이를 지원해야 합니다. Calico, Cilium, Weave Net 등이 지원하며, Flannel은 기본적으로 지원하지 않습니다.

기본 구조와 셀렉터 메커니즘

NetworkPolicy의 핵심은 podSelector로 대상 Pod를 선택하고, ingress/egress 규칙으로 허용할 트래픽을 정의하는 것입니다.

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:
        - podSelector:
            matchLabels:
              app: frontend
        - namespaceSelector:
            matchLabels:
              env: production
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

위 정책은 app: api-server 라벨이 붙은 Pod에 대해: Ingress는 app: frontend Pod 또는 env: production 네임스페이스에서 오는 8080 포트 트래픽만 허용하고, Egress는 app: postgres Pod의 5432 포트로만 나갈 수 있도록 제한합니다.

Default Deny 패턴: 보안의 시작점

가장 중요한 패턴은 Default Deny입니다. 네임스페이스 전체에 기본 차단을 걸고, 필요한 통신만 하나씩 열어주는 방식입니다.

# 네임스페이스 전체 Ingress 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}    # 빈 셀렉터 = 모든 Pod
  policyTypes:
    - Ingress
---
# 네임스페이스 전체 Egress 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress

podSelector: {}는 네임스페이스 내 모든 Pod를 선택합니다. 이 상태에서 명시적으로 허용하는 정책을 추가해야 트래픽이 흐릅니다. NetworkPolicy는 합집합(OR)으로 동작하므로, 여러 정책이 있으면 하나라도 허용하면 통과됩니다.

AND vs OR: from 배열의 함정

NetworkPolicy에서 가장 흔한 실수는 from 배열의 동작 방식을 혼동하는 것입니다.

# 패턴 1: OR 조건 (두 개의 from 항목)
ingress:
  - from:
      - podSelector:
          matchLabels:
            app: frontend
      - namespaceSelector:
          matchLabels:
            env: staging
    ports:
      - port: 8080

# 패턴 2: AND 조건 (하나의 from 항목에 두 셀렉터)
ingress:
  - from:
      - podSelector:
          matchLabels:
            app: frontend
        namespaceSelector:
          matchLabels:
            env: staging
    ports:
      - port: 8080

패턴 1은 “frontend Pod 또는 staging 네임스페이스의 모든 Pod”가 접근 가능합니다. 패턴 2는 “staging 네임스페이스 이면서 frontend 라벨을 가진 Pod”만 접근 가능합니다. YAML 들여쓰기 하나 차이로 보안 정책이 완전히 달라집니다.

마이크로서비스 3-Tier 정책 설계

실제 프로덕션에서 자주 사용하는 3-Tier 아키텍처(Frontend → API → DB) 네트워크 정책을 설계해 봅니다.

# 1. Frontend: Ingress Controller에서만 수신
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
      ports:
        - port: 3000
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: api
      ports:
        - port: 8080
    - to:    # DNS 허용 (필수!)
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP
---
# 2. API: Frontend에서만 수신, DB로만 송신
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: api
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: database
      ports:
        - port: 5432
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - port: 53
          protocol: UDP
---
# 3. DB: API에서만 수신, 외부 송신 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: api
      ports:
        - port: 5432
  egress: []    # 외부 통신 완전 차단

핵심 포인트: DNS(kube-dns) Egress를 반드시 허용해야 합니다. Egress를 차단하면 서비스 디스커버리가 동작하지 않아 Pod가 다른 서비스를 찾지 못합니다.

ipBlock: 외부 CIDR 기반 제어

클러스터 외부 IP 대역을 기반으로 트래픽을 제어할 때 ipBlock을 사용합니다.

ingress:
  - from:
      - ipBlock:
          cidr: 10.0.0.0/8
          except:
            - 10.0.1.0/24    # 특정 서브넷 제외
    ports:
      - port: 443

ipBlock클러스터 외부 트래픽에 주로 사용됩니다. Pod 간 통신은 podSelector가 더 적합합니다. except 필드로 특정 대역을 제외할 수 있어, VPN 대역만 허용하거나 특정 오피스 IP만 열어주는 패턴에 유용합니다.

디버깅과 검증 전략

NetworkPolicy 적용 후 의도대로 동작하는지 검증하는 것이 중요합니다.

# 1. 임시 Pod으로 연결 테스트
kubectl run test-curl --rm -it --image=curlimages/curl 
  --labels="app=frontend" -- 
  curl -v --connect-timeout 3 http://api-server:8080/health

# 2. 적용된 정책 확인
kubectl get networkpolicy -n production -o wide

# 3. Pod에 적용된 정책 상세 확인
kubectl describe networkpolicy api-server-policy -n production

# 4. Calico 사용 시 calicoctl로 상세 디버깅
calicoctl get networkPolicy -n production -o yaml

# 5. Cilium 사용 시 정책 상태 확인
cilium policy get -n production
kubectl exec -n kube-system cilium-xxxxx -- cilium monitor --type policy-verdict

특히 Cilium의 policy-verdict 모니터링은 실시간으로 어떤 패킷이 허용/차단되는지 보여줘서 디버깅에 매우 효과적입니다. 프로덕션에 적용하기 전에 반드시 스테이징 환경에서 테스트하세요.

Cilium CiliumNetworkPolicy: L7 레벨 제어

기본 NetworkPolicy는 L3/L4까지만 제어합니다. HTTP 경로, 메서드 수준의 제어가 필요하면 Cilium의 확장 리소스를 사용합니다.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: api-l7-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/users.*"
              - method: "POST"
                path: "/api/v1/orders"

이렇게 하면 frontend Pod는 GET /api/v1/users*POST /api/v1/orders만 호출할 수 있습니다. DELETE나 다른 경로는 차단됩니다. K8s Custom Metrics HPA와 결합하면 트래픽 패턴에 따른 동적 스케일링과 보안을 동시에 구현할 수 있습니다.

운영 베스트 프랙티스

항목 권장 사항
기본 정책 Default Deny를 먼저 적용, 필요한 것만 허용
DNS kube-dns Egress는 항상 허용
라벨 전략 tier, app, version 등 일관된 라벨링
네임스페이스 격리 환경별 네임스페이스 + 크로스NS 정책
모니터링 Cilium Hubble 또는 Calico 로그 활성화
GitOps 정책을 코드로 관리, PR 리뷰 필수
테스트 CI에서 정책 검증 (kube-score, conftest)

NetworkPolicy는 K8s Taint·Toleration과 함께 사용하면 노드 수준 격리 + 네트워크 수준 격리를 동시에 달성할 수 있습니다. 보안은 레이어를 쌓을수록 강해집니다.

정리

K8s NetworkPolicy는 클러스터 내 Zero Trust 네트워크를 구현하는 핵심 도구입니다. Default Deny로 시작해서 필요한 통신만 명시적으로 허용하고, DNS Egress를 잊지 말아야 합니다. AND/OR 조건의 YAML 문법 차이를 정확히 이해하고, L7 제어가 필요하면 Cilium을 도입하세요. 정책은 반드시 GitOps로 관리하고, CI 파이프라인에서 자동 검증하는 것이 프로덕션 운영의 핵심입니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux