Network Policy란? — Pod 간 트래픽의 방화벽
Kubernetes 클러스터에서는 기본적으로 모든 Pod이 모든 Pod과 통신할 수 있다. 프론트엔드 Pod이 DB Pod에 직접 접근하고, 결제 서비스가 로그 수집기와 통신할 수 있다. 이것은 보안적으로 위험하다. 하나의 Pod이 침해되면 클러스터 전체가 노출된다.
Network Policy는 Pod 레벨의 방화벽 규칙이다. 어떤 Pod이 어떤 Pod과 통신할 수 있는지를 레이블 셀렉터와 네임스페이스 기반으로 선언한다. RBAC이 API 서버 접근을 제어한다면, Network Policy는 네트워크 레벨 접근을 제어한다.
1. 기본 개념 — Ingress와 Egress
| 방향 | 의미 | 예시 |
|---|---|---|
| Ingress | 해당 Pod으로 들어오는 트래픽 | DB Pod에 접근 가능한 서비스 제한 |
| Egress | 해당 Pod에서 나가는 트래픽 | 프론트엔드가 외부 API만 호출 가능 |
핵심 원칙: Network Policy가 하나도 없으면 모든 트래픽이 허용된다. 하나라도 적용되면 명시적으로 허용된 트래픽만 통과한다(화이트리스트 방식).
2. Default Deny — 제로 트러스트의 시작
보안의 첫 단계는 모든 트래픽을 차단하고, 필요한 것만 열어주는 것이다.
# 네임스페이스의 모든 Ingress 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # 빈 셀렉터 = 네임스페이스의 모든 Pod
policyTypes:
- Ingress # Ingress 규칙이 비어있음 = 모든 인바운드 차단
---
# 네임스페이스의 모든 Egress 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress # Egress 규칙이 비어있음 = 모든 아웃바운드 차단
---
# Ingress + Egress 모두 차단 (한 번에)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
주의: Egress를 전부 차단하면 DNS 조회도 차단된다. kube-dns/CoreDNS 접근은 반드시 허용해야 한다.
# DNS 허용 (Egress deny와 항상 함께)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {} # 모든 Pod
policyTypes:
- Egress
egress:
- to: [] # 모든 대상 (DNS 서버)
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
3. Pod 셀렉터 기반 — 서비스 간 통신 허용
# DB Pod: order-service와 payment-service만 접근 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-backends
namespace: production
spec:
podSelector:
matchLabels:
app: postgresql # 이 정책이 적용되는 Pod
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: order-service # order-service에서 오는 트래픽 허용
- podSelector:
matchLabels:
app: payment-service # payment-service에서 오는 트래픽 허용
ports:
- protocol: TCP
port: 5432 # PostgreSQL 포트만
중요 — from 배열의 OR vs AND:
# OR 조건 (배열의 각 항목) — 둘 중 하나만 만족하면 허용
ingress:
- from:
- podSelector: # 조건 A
matchLabels:
app: order-service
- namespaceSelector: # 조건 B
matchLabels:
env: staging
# A 또는 B를 만족하면 허용
# AND 조건 (같은 항목에 합침) — 둘 다 만족해야 허용
ingress:
- from:
- podSelector: # 조건 A AND 조건 B
matchLabels:
app: order-service
namespaceSelector:
matchLabels:
env: staging
# staging 네임스페이스의 order-service만 허용
4. 네임스페이스 셀렉터 — 크로스 네임스페이스 제어
# 먼저 네임스페이스에 레이블 부여
kubectl label namespace monitoring purpose=monitoring
kubectl label namespace production env=production
kubectl label namespace staging env=staging
# production의 모든 Pod: monitoring 네임스페이스에서만 메트릭 수집 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-monitoring
namespace: production
spec:
podSelector: {} # production의 모든 Pod
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: monitoring # monitoring 네임스페이스에서만
podSelector:
matchLabels:
app: prometheus # AND prometheus Pod만
ports:
- protocol: TCP
port: 8080 # 메트릭 포트만
# staging에서 production DB 접근 차단 (staging → production 격리)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-staging-to-prod-db
namespace: production
spec:
podSelector:
matchLabels:
app: postgresql
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
env: production # production 네임스페이스에서만 허용
ports:
- protocol: TCP
port: 5432
5. CIDR 기반 — 외부 IP 범위 제어
# 특정 외부 API만 호출 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payment-egress
namespace: production
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Egress
egress:
# DNS 허용
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# 내부 서비스 (클러스터 CIDR)
- to:
- podSelector:
matchLabels:
app: order-service
# 외부 결제 API (특정 IP 대역만)
- to:
- ipBlock:
cidr: 203.0.113.0/24 # 결제 게이트웨이 IP 대역
ports:
- protocol: TCP
port: 443
# 사내 네트워크
- to:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.100.0/24 # 민감한 서브넷 제외
6. 실전 아키텍처 — 3-Tier 네트워크 정책
프론트엔드 → 백엔드 → 데이터베이스의 전형적인 3계층 아키텍처에 Network Policy를 적용한다.
# 1. Default Deny
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
# 2. DNS 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- ports:
- {protocol: UDP, port: 53}
- {protocol: TCP, 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:
- {protocol: TCP, port: 80}
- {protocol: TCP, port: 443}
egress:
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- {protocol: TCP, port: 8080}
- ports:
- {protocol: UDP, port: 53}
---
# 4. Backend: Frontend에서만 Ingress, DB로만 Egress
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: 5432}
- to:
- podSelector:
matchLabels:
app: redis
ports:
- {protocol: TCP, port: 6379}
- ports:
- {protocol: UDP, port: 53}
---
# 5. Database: Backend에서만 Ingress, Egress 없음
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: backend
ports:
- {protocol: TCP, port: 5432}
egress:
- ports:
- {protocol: UDP, port: 53}
7. CNI 호환성 — 모든 클러스터에서 되는 것은 아니다
Network Policy는 CNI(Container Network Interface) 플러그인이 구현해야 동작한다. API에서 리소스는 생성되지만, 실제 트래픽 필터링은 CNI가 담당한다.
| CNI | Network Policy 지원 | 비고 |
|---|---|---|
| Calico | ✅ 완전 지원 | 가장 널리 사용, GlobalNetworkPolicy 확장 |
| Cilium | ✅ 완전 지원 | eBPF 기반, L7 정책까지 지원 |
| Weave Net | ✅ 지원 | 기본적인 Ingress/Egress |
| Flannel | ❌ 미지원 | Calico와 함께 사용 (Canal) |
| AWS VPC CNI | ⚠️ Calico 추가 필요 | EKS에서 Calico addon 설치 |
확인 방법:
# CNI 확인
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave|flannel'
# Network Policy 동작 테스트
kubectl run test-pod --image=busybox --rm -it -- wget -qO- --timeout=2 http://target-service
# 차단되면 timeout, 허용되면 응답 반환
8. 디버깅과 검증
# 적용된 Network Policy 목록
kubectl get networkpolicy -n production
# 특정 Policy 상세 확인
kubectl describe networkpolicy backend-policy -n production
# Pod에 적용된 정책 확인 (Calico)
calicoctl get workloadEndpoint -n production --output=yaml
# 연결 테스트 — 임시 Pod에서
kubectl run debug --image=nicolaka/netshoot --rm -it -n production -- bash
# 내부 연결 테스트
curl -v --connect-timeout 2 http://order-service:8080/health
# DNS 테스트
nslookup order-service.production.svc.cluster.local
# 포트 스캔
nc -zv postgresql 5432
9. Kustomize와 조합 — 환경별 Network Policy
# base/network-policies/kustomization.yaml
resources:
- default-deny.yaml
- allow-dns.yaml
- backend-policy.yaml
- database-policy.yaml
# overlays/dev/kustomization.yaml — 개발 환경에서는 완화
patches:
- target:
kind: NetworkPolicy
name: default-deny-all
patch: |
- op: remove
path: /spec/policyTypes/1 # Egress deny 제거 (개발 편의)
# overlays/prod/kustomization.yaml — 프로덕션은 모든 정책 적용
resources:
- ../../base/network-policies
10. 운영 체크리스트
| 항목 | 권장 사항 | 위반 시 증상 |
|---|---|---|
| CNI 확인 | Calico/Cilium 등 지원 CNI 사용 | Policy 생성되지만 실제 차단 안 됨 |
| Default Deny | 네임스페이스마다 적용 | 어떤 Pod이든 다른 Pod에 자유 접근 |
| DNS 허용 | Egress deny 시 UDP/TCP 53 반드시 허용 | 서비스 디스커버리 불가 |
| from 배열 | OR vs AND 구분 명확히 | 의도보다 넓거나 좁은 허용 범위 |
| NS 레이블 | 네임스페이스에 레이블 부여 | namespaceSelector 매칭 불가 |
| 연결 테스트 | netshoot Pod으로 적용 후 검증 | 배포 후 통신 장애 발생 |
마무리 — 네트워크 격리는 선택이 아닌 필수다
Kubernetes Network Policy는 클러스터 내부 트래픽을 Pod 레벨에서 제어하는 유일한 네이티브 메커니즘이다. Default Deny로 시작하여 필요한 통신만 화이트리스트로 열어주는 것이 제로 트러스트 보안의 기본이다.
핵심은 세 가지다. 첫째, Default Deny를 먼저 적용하고 필요한 트래픽만 허용하라. 둘째, Egress deny 시 DNS(포트 53)를 반드시 허용하라. 셋째, from 배열의 OR과 AND 조건을 정확히 구분하라 — YAML 들여쓰기 한 줄 차이로 보안 구멍이 생긴다.