Theme:

Kubernetes 클러스터에서 기본적으로 모든 Pod이 서로 통신할 수 있다면, 보안상 어떤 문제가 생길 수 있을까요?

기본 상태의 Kubernetes 클러스터에서는 어떤 Pod이든 다른 모든 Pod에 접근할 수 있습니다. 프론트엔드 Pod이 데이터베이스 Pod에 직접 접근하거나, 한 네임스페이스의 Pod이 다른 네임스페이스의 내부 서비스에 접근하는 것이 가능합니다. Network Policy는 이런 무분별한 통신을 레이블 기반으로 제어하는 방화벽 역할을 합니다.

Network Policy 기본 구조

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: production
spec:
  podSelector:              # 이 정책이 적용되는 Pod
    matchLabels:
      role: backend
  policyTypes:
    - Ingress               # 들어오는 트래픽 제어
    - Egress                # 나가는 트래픽 제어
  ingress:                  # 허용할 인바운드 규칙
    - from:
        - podSelector:
            matchLabels:
              role: frontend
      ports:
        - protocol: TCP
          port: 8080
  egress:                   # 허용할 아웃바운드 규칙
    - to:
        - podSelector:
            matchLabels:
              role: database
      ports:
        - protocol: TCP
          port: 5432

이 정책은 다음과 같이 동작합니다.

  • 대상: role: backend 레이블을 가진 Pod
  • Ingress: role: frontend Pod에서 오는 8080 포트 트래픽만 허용
  • Egress: role: database Pod의 5432 포트로 나가는 트래픽만 허용
  • 그 외 모든 트래픽은 차단됩니다

Default Deny — 제로 트러스트의 시작

보안의 기본 원칙은 "기본 차단, 필요한 것만 허용"입니다.

모든 인바운드 차단

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}          # 네임스페이스의 모든 Pod
  policyTypes:
    - Ingress              # Ingress 규칙이 비어있으므로 모두 차단

모든 아웃바운드 차단

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress               # Egress 규칙이 비어있으므로 모두 차단

모든 방향 차단

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

default-deny를 먼저 적용하고, 필요한 통신만 별도 정책으로 허용하는 것이 권장되는 방식입니다.

셀렉터 조합 규칙

Network Policy의 from/to에서 셀렉터를 조합하는 방식이 중요합니다. 이 부분을 헷갈리기 쉬우니 주의해서 봐야 합니다.

AND 조건 (같은 항목 내)

YAML
ingress:
  - from:
      - namespaceSelector:      # 이 네임스페이스에서
          matchLabels:
            env: production
        podSelector:             # 이 레이블의 Pod만
          matchLabels:
            role: frontend

production 네임스페이스의 role: frontend Pod에서 오는 트래픽만 허용합니다.

OR 조건 (별도 항목)

YAML
ingress:
  - from:
      - namespaceSelector:      # 이 네임스페이스의 모든 Pod
          matchLabels:
            env: production
      - podSelector:            # 또는 같은 네임스페이스의 이 Pod
          matchLabels:
            role: frontend

배열의 별도 항목이므로 OR 조건입니다. production 네임스페이스의 모든 Pod 또는 같은 네임스페이스의 role: frontend Pod이 허용됩니다.

이 차이는 YAML 들여쓰기 하나로 결정되므로 주의가 필요합니다.

실무 시나리오별 정책 예제

웹 → API → DB 3티어 구조

YAML
# DB: API 서버에서만 접근 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: api
      ports:
        - protocol: TCP
          port: 5432
---
# API: 웹에서만 접근 허용, DB로만 나갈 수 있음
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
spec:
  podSelector:
    matchLabels:
      tier: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: web
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: database
      ports:
        - protocol: TCP
          port: 5432
    # DNS 허용 (필수!)
    - to: []
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

DNS 허용은 필수

Egress를 제한할 때 DNS(UDP/TCP 53) 트래픽을 허용하지 않으면 서비스 이름 해석이 불가능합니다. 거의 항상 DNS 허용 규칙을 추가해야 합니다.

특정 네임스페이스 간 통신 허용

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-monitoring
  namespace: production
spec:
  podSelector: {}           # 모든 Pod
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              purpose: monitoring   # monitoring 네임스페이스에서 허용

네임스페이스에 레이블이 있어야 합니다.

SHELL
kubectl label namespace monitoring purpose=monitoring

IP 블록 기반 규칙

YAML
ingress:
  - from:
      - ipBlock:
          cidr: 10.0.0.0/8
          except:
            - 10.0.1.0/24   # 이 대역은 제외

외부 시스템(사내 네트워크 등)에서의 접근을 제어할 때 사용합니다.

Network Policy 동작 확인

SHELL
# 정책 목록 확인
kubectl get networkpolicies -n production

# 정책 상세 확인
kubectl describe networkpolicy backend-policy -n production

# 연결 테스트 (허용된 경우)
kubectl exec frontend-pod -- curl -s --max-time 3 http://backend-service:8080
# 200 OK

# 연결 테스트 (차단된 경우)
kubectl exec unauthorized-pod -- curl -s --max-time 3 http://backend-service:8080
# (타임아웃)

CNI 플러그인별 Network Policy 지원

CNI기본 Network Policy확장 기능
Calico지원GlobalNetworkPolicy, HostEndpoint
Cilium지원CiliumNetworkPolicy (L7 지원)
Flannel미지원Canal(Flannel+Calico)로 해결
Weave Net지원기본 기능만

Cilium의 CiliumNetworkPolicy는 HTTP 메서드나 경로 수준의 L7 정책을 지원합니다.

YAML
# Cilium L7 정책 예제
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: l7-policy
spec:
  endpointSelector:
    matchLabels:
      app: api
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: frontend
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:
              - method: GET
                path: "/api/.*"

실무에서 주의할 점

  • Network Policy는 네임스페이스 범위입니다: 다른 네임스페이스에는 영향을 주지 않습니다
  • 정책은 합집합(OR)으로 동작합니다: 여러 정책이 같은 Pod에 적용되면 허용 규칙이 합쳐집니다
  • Egress 제한 시 DNS를 빠뜨리지 마세요: 가장 흔한 실수입니다
  • CNI 플러그인을 먼저 확인하세요: Network Policy를 지원하지 않는 CNI에서는 리소스만 생성되고 실제 효과가 없습니다

정리

Network Policy는 Kubernetes에서 Pod 간 트래픽을 레이블 기반으로 제어하는 방화벽입니다. "default-deny 후 필요한 것만 허용" 전략을 기본으로, AND/OR 조건의 셀렉터 조합과 DNS 허용을 주의하면 됩니다. CNI 플러그인이 Network Policy를 지원하는지 반드시 확인하세요.

댓글 로딩 중...