왜 NetworkPolicy가 필요한가
Kubernetes 클러스터에서 기본적으로 모든 Pod는 다른 모든 Pod와 자유롭게 통신할 수 있습니다. 공식 문서에 따르면, “네임스페이스에 정책이 없으면 해당 네임스페이스의 Pod에 대한 모든 인그레스/이그레스 트래픽이 허용됩니다.” 이는 개발 편의성은 높지만, 보안 관점에서는 하나의 Pod가 침해되면 클러스터 전체로 수평 이동(lateral movement)이 가능하다는 뜻입니다.
NetworkPolicy는 OSI 3~4계층(IP/포트)에서 Pod 간 트래픽을 제어하는 Kubernetes 네이티브 리소스입니다. 이 글에서는 공식 문서를 기준으로 NetworkPolicy의 동작 원리, 셀렉터 조합의 미묘한 차이, 그리고 실무 시나리오별 정책 설계를 다룹니다.
1. 핵심 개념: 격리(Isolation)의 두 방향
공식 문서는 Pod 격리를 두 가지 독립적인 방향으로 정의합니다:
| 방향 | 기본 상태 | 격리 조건 | 격리 후 동작 |
|---|---|---|---|
| Ingress (수신) | 비격리 — 모든 인바운드 허용 | 해당 Pod를 선택하는 NetworkPolicy에 Ingress가 policyTypes에 포함 |
해당 정책의 ingress 규칙에 명시된 연결만 허용 |
| Egress (송신) | 비격리 — 모든 아웃바운드 허용 | 해당 Pod를 선택하는 NetworkPolicy에 Egress가 policyTypes에 포함 |
해당 정책의 egress 규칙에 명시된 연결만 허용 |
핵심 원칙 (공식 문서):
- NetworkPolicy는 충돌하지 않습니다 — 여러 정책이 같은 Pod에 적용되면, 허용 규칙의 합집합(union)이 최종 결과입니다.
- 연결이 성립하려면 출발지의 egress 정책과 목적지의 ingress 정책 모두가 해당 연결을 허용해야 합니다.
- Pod와 자신이 실행되는 노드 간 트래픽은 항상 허용됩니다 (IP에 관계없이).
- 허용된 연결의 응답 트래픽(reply traffic)은 암시적으로 허용됩니다.
2. 셀렉터 조합의 함정: AND vs OR
공식 문서가 특별히 강조하는 부분이 from/to 배열 내 셀렉터 조합입니다. YAML의 하이픈 하나 차이로 의미가 완전히 바뀝니다.
패턴 A: 하나의 from 요소에 두 셀렉터 (AND)
ingress:
- from:
- namespaceSelector:
matchLabels:
project: myproject
podSelector: # namespaceSelector와 같은 레벨 → AND
matchLabels:
role: frontend
의미: project=myproject 네임스페이스 안에서 role=frontend 레이블을 가진 Pod만 허용.
패턴 B: 두 개의 from 요소 (OR)
ingress:
- from:
- namespaceSelector:
matchLabels:
project: myproject
- podSelector: # 별도 배열 요소 → OR
matchLabels:
role: frontend
의미: project=myproject 네임스페이스의 모든 Pod 또는 같은 네임스페이스에서 role=frontend Pod. 전자가 훨씬 넓은 범위를 허용합니다.
공식 문서 권고: “확실하지 않으면
kubectl describe로 Kubernetes가 정책을 어떻게 해석했는지 확인하세요.”
| 패턴 | YAML 구조 | 의미 | 허용 범위 |
|---|---|---|---|
| AND | 같은 - 아래에 두 셀렉터 |
두 조건을 동시에 만족 | 좁음 (특정 NS의 특정 Pod) |
| OR | 각각 별도 - |
둘 중 하나만 만족해도 허용 | 넓음 (NS 전체 + 로컬 Pod) |
3. Default 정책: Zero-Trust 네트워크의 시작점
공식 문서는 네임스페이스별 기본 정책 4가지 패턴을 제시합니다. 실무에서 가장 많이 사용하는 조합을 시나리오별로 정리합니다.
패턴 1: 모든 트래픽 차단 (Default Deny All)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # 네임스페이스의 모든 Pod 선택
policyTypes:
- Ingress
- Egress
이 정책을 적용하면 해당 네임스페이스의 모든 Pod가 즉시 격리됩니다. 이후 필요한 통신만 개별 NetworkPolicy로 허용하는 것이 Zero-Trust 접근입니다.
주의: Default Deny Egress를 적용하면 DNS 조회도 차단됩니다. CoreDNS에 대한 egress를 반드시 허용해야 합니다.
패턴 2: DNS 허용 정책 (Default Deny와 항상 함께)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
4. 실전 시나리오: 3-Tier 애플리케이션 보안
Frontend → Backend → Database 구조를 NetworkPolicy로 보호하는 전체 예시입니다.
네임스페이스/레이블 구조
# 네임스페이스: production
# Pod 레이블:
# tier: frontend (Nginx/React)
# tier: backend (Spring Boot/NestJS)
# tier: database (MySQL)
정책 1: Database — Backend에서만 3306 접근 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-backend-only
namespace: production
spec:
podSelector:
matchLabels:
tier: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 3306
정책 2: Backend — Frontend에서 8080, Database로 3306 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: production
spec:
podSelector:
matchLabels:
tier: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
tier: database
ports:
- protocol: TCP
port: 3306
- to: # DNS 허용
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
정책 3: Frontend — 외부 Ingress 허용, Backend로만 Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: production
spec:
podSelector:
matchLabels:
tier: frontend
policyTypes:
- Ingress
- Egress
ingress:
- ports: # 모든 소스에서 80/443 허용 (Ingress Controller)
- protocol: TCP
port: 80
- protocol: TCP
port: 443
egress:
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 8080
- to: # DNS 허용
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
5. 전제 조건과 제약사항
공식 문서가 명시한 중요한 제약사항들:
| 항목 | 내용 |
|---|---|
| CNI 플러그인 필수 | NetworkPolicy는 네트워크 플러그인이 구현합니다. 지원하지 않는 CNI(예: Flannel 기본 모드)에서는 리소스를 생성해도 아무 효과가 없습니다. |
| 지원 CNI 예시 | Calico, Cilium, Weave Net, Antrea 등 |
| L4만 제어 | TCP, UDP, SCTP만 보장됩니다. ICMP 등 다른 프로토콜은 CNI에 따라 동작이 다릅니다. |
| 포트 범위 | endPort 필드로 범위 지정 가능 (v1.25 stable). endPort는 port 이상이어야 합니다. |
| ipBlock과 SNAT | 클러스터 외부 IP 기반 필터링은 LoadBalancer/NodePort의 SNAT에 영향받습니다. 실제 소스 IP가 보존되는지 확인 필요합니다. |
| 노드 트래픽 | Pod가 실행되는 노드와의 트래픽은 NetworkPolicy와 무관하게 항상 허용됩니다. |
6. 운영 체크리스트: NetworkPolicy 도입 절차
| # | 단계 | 설명 |
|---|---|---|
| 1 | CNI 확인 | kubectl get pods -n kube-system에서 Calico/Cilium 등 확인. Flannel만 있으면 NetworkPolicy 미지원. |
| 2 | Staging 먼저 적용 | 운영 환경에 바로 적용하지 마세요. Default Deny가 DNS를 차단하면 모든 서비스가 동시에 실패합니다. |
| 3 | Default Deny + DNS 허용을 세트로 | 반드시 DNS 허용 정책을 먼저 적용한 후 Default Deny를 적용합니다. |
| 4 | 필요한 통신만 개별 허용 | 각 tier/서비스별로 최소 권한 원칙에 따라 ingress/egress를 허용합니다. |
| 5 | kubectl describe netpol로 검증 |
YAML AND/OR 실수를 즉시 탐지할 수 있습니다. |
| 6 | 통신 테스트 | kubectl exec로 Pod 간 curl/nc 테스트. 차단된 연결은 timeout으로 나타납니다. |
| 7 | 모니터링 연동 | Cilium Hubble이나 Calico의 Flow Logs로 차단된 트래픽을 가시화합니다. |
7. 자주 발생하는 실수와 디버깅
실수 1: Default Deny 후 DNS 허용을 빠뜨림
증상: Pod 내부에서 서비스 이름 해석 실패, curl: Could not resolve host
원인: Egress Default Deny가 CoreDNS(UDP 53)도 차단
해결: kube-system 네임스페이스의 kube-dns Pod에 대한 egress를 명시적으로 허용
실수 2: 셀렉터 AND/OR 혼동
증상: 특정 네임스페이스의 특정 Pod만 허용하려 했는데, 같은 네임스페이스의 모든 Pod가 접근 가능
원인: YAML에서 namespaceSelector와 podSelector를 별도 배열 요소로 작성 (OR)
해결: 같은 - 아래에 두 셀렉터를 배치 (AND)
실수 3: CNI가 NetworkPolicy를 지원하지 않음
증상: NetworkPolicy를 적용했는데 트래픽이 전혀 차단되지 않음
원인: Flannel 기본 모드 등 NetworkPolicy 미지원 CNI
해결: Calico 또는 Cilium으로 교체. k3s의 경우 --flannel-backend=none으로 Flannel을 비활성화하고 Calico를 별도 설치
# 디버깅: Pod 간 연결 테스트
kubectl exec -it frontend-pod -- curl -m 5 http://backend-svc:8080/health
# 차단된 경우 → timeout (5초 후 실패)
# 정책 해석 확인
kubectl describe networkpolicy backend-policy -n production
마무리
NetworkPolicy는 Kubernetes 클러스터의 보안 경계를 정의하는 핵심 리소스입니다. 기본 상태인 “모든 트래픽 허용”에서 벗어나 Default Deny로 시작하고, DNS 허용을 반드시 포함하며, 셀렉터 조합의 AND/OR 차이를 정확히 이해하면 실무에서 Zero-Trust 네트워크를 구현할 수 있습니다. 단, CNI 플러그인이 NetworkPolicy를 지원하는지 반드시 먼저 확인하세요.