결제 API는 주문 서비스에서만 접근해야 하는데 같은 Namespace의 임시 파드에서도 접속이 됩니다. 클러스터 내부 네트워크를 모두 신뢰하면 침해 사고가 한 서비스에서 다른 서비스로 쉽게 번집니다. NetworkPolicy는 Pod 간 통신을 필요한 경로로만 제한하는 방화벽 역할을 합니다.
NetworkPolicy — 파드 간 통신 화이트리스트
보안 감사 결과가 나왔습니다. "클러스터 내 모든 파드가 서로 통신 가능한 상태입니다. 결제 서비스가 사용자 서비스 DB에 직접 접근할 수 있고, 개발 네임스페이스에서 프로덕션 DB로 쿼리를 날릴 수 있습니다." 이건 단순한 설정 실수가 아니라 아키텍처 문제입니다. 기본적으로 K8s 클러스터 안의 파드들은 네임스페이스나 서비스 경계와 관계없이 모두 서로 통신할 수 있습니다. 이 열린 상태에서 한 서비스가 침해되면 공격자는 내부 네트워크에서 횡이동(lateral movement)하며 모든 서비스에 접근할 수 있습니다. NetworkPolicy는 이 문제를 해결합니다. 어떤 파드가 어떤 파드와 통신할 수 있는지 명시적으로 선언하는 화이트리스트 방식으로, 허용되지 않은 모든 트래픽은 자동으로 차단됩니다.
- 1NetworkPolicy 없는 클러스터의 기본 통신 구조
- 2Ingress/Egress 규칙과 podSelector, namespaceSelector
- 3화이트리스트 기반 정책 설계 원칙
- 4네임스페이스 격리와 서비스 간 통신 허용 패턴
- 5기본 거부(Deny All) 정책 구성
- 6NetworkPolicy 디버깅 도구와 트러블슈팅
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave'kubectl create namespace netpol-demokubectl label namespace netpol-demo env=demokubectl get networkpolicy -ANetworkPolicy 규칙 구조 이해
NetworkPolicy는 podSelector로 대상 파드를 고르고, ingress(들어오는 트래픽)와 egress(나가는 트래픽) 규칙을 정의합니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: 규칙-이름
namespace: 적용-네임스페이스
spec:
podSelector: # 이 정책이 적용될 파드
matchLabels:
app: 대상파드
policyTypes: # Ingress, Egress 또는 둘 다
- Ingress
- Egress
ingress: # 허용할 인바운드 트래픽
- from:
- podSelector: # 이 파드에서 오는 트래픽
matchLabels:
app: 허용파드
ports:
- protocol: TCP
port: 8080
egress: # 허용할 아웃바운드 트래픽
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: 허용네임스페이스
ports:
- protocol: TCP
port: 5432
기본 거부 정책 (Deny All)
NetworkPolicy를 개별 서비스에 하나씩 추가하다 보면 설정이 없는 서비스가 모든 파드에서 접근 가능한 상태로 방치됩니다. 새 서비스가 배포될 때마다 보안팀이 일일이 허용 목록을 관리해야 해서 누락이 생깁니다. 화이트리스트 방식으로 전환하려면 먼저 네임스페이스 전체 트래픽을 기본 차단해야 합니다. "기본 거부 후 필요한 것만 허용"이 실무에서 NetworkPolicy를 적용하는 표준 순서입니다. 이 CB에서는 네임스페이스 전체에 Deny All 정책을 적용하는 YAML 패턴과 적용 후 확인 방법을 다룹니다.

# deny-all-ingress.yaml — 모든 인바운드 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {} # 네임스페이스의 모든 파드에 적용
policyTypes:
- Ingress # from을 지정하지 않으면 모두 차단
---
# deny-all-egress.yaml — 모든 아웃바운드 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress # to를 지정하지 않으면 모두 차단
kubectl apply -f deny-all-ingress.yaml -f deny-all-egress.yaml
# 통신 차단 확인
kubectl run test-pod --image=curlimages/curl -n production -- sleep 3600
kubectl exec -it test-pod -n production -- curl http://payment-service:8080 --max-time 5
# curl: (28) Connection timed out after 5001 milliseconds
서비스 간 통신 허용 패턴
Deny All 정책을 적용하고 나면 기존에 잘 동작하던 서비스들이 타임아웃이 납니다. 결제 서비스가 주문 서비스의 호출을 받지 못하고, 모니터링 에이전트가 메트릭을 수집하지 못합니다. 어떤 서비스가 어떤 서비스와 통신해야 하는지 명확하게 정의하지 않으면 복구 과정에서 너무 넓은 허용 규칙을 열어버리는 실수가 생깁니다. deny-all을 설정한 뒤 필요한 통신만 명시적으로 허용하는 것이 올바른 접근입니다. 이 CB에서는 결제 서비스 예시로 인바운드와 아웃바운드 허용 규칙을 조합하는 NetworkPolicy 패턴을 다룹니다. 아래는 결제 서비스가 주문 서비스의 요청만 받고, PostgreSQL에만 쿼리할 수 있는 예시입니다.
# payment-netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payment-service-policy
namespace: production
spec:
podSelector:
matchLabels:
app: payment # payment 파드에 적용
policyTypes:
- Ingress
- Egress
ingress:
# 주문 서비스에서 오는 트래픽만 허용
- from:
- podSelector:
matchLabels:
app: order-service
ports:
- protocol: TCP
port: 8080
# 모니터링 에이전트도 허용 (메트릭 수집)
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090
egress:
# PostgreSQL DB에만 쿼리 허용
- to:
- podSelector:
matchLabels:
app: payment-db
ports:
- protocol: TCP
port: 5432
# DNS 조회는 항상 허용 (없으면 서비스 이름 조회 불가)
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
kubectl apply -f payment-netpol.yaml
# 허용된 통신 확인 (order-service 파드에서)
kubectl exec -it order-pod -n production -- curl http://payment-service:8080/health
# {"status": "ok"}
# 차단된 통신 확인 (catalog-service 파드에서 — 정책에 없음)
kubectl exec -it catalog-pod -n production -- curl http://payment-service:8080 --max-time 5
# curl: (28) Connection timed out after 5001 milliseconds
네임스페이스 격리 — dev와 prod 분리
개발자가 개발 환경에서 DB 연결 테스트를 하다가 운영 DB 주소를 잘못 입력했고, 운영 Namespace의 PostgreSQL에 쿼리가 직접 날아갔습니다. Namespace를 나눠도 기본적으로 파드 간 네트워크 통신은 열려 있어 실수가 대형 사고로 이어질 수 있습니다. 네임스페이스 단위 격리 정책을 적용하면 개발 Namespace에서 운영 Namespace로의 트래픽을 차단하고, 외부 진입은 Ingress Controller 경로만 허용할 수 있습니다. 이 CB에서는 production Namespace를 내부 파드와 Ingress Controller에서만 접근 가능하도록 격리하는 NetworkPolicy 설정을 다룹니다.
# prod-namespace-isolation.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: prod-namespace-isolation
namespace: production
spec:
podSelector: {} # production 전체 파드에 적용
policyTypes:
- Ingress
ingress:
# production 네임스페이스 내부 파드 간 통신만 허용
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: production
# 인그레스 컨트롤러에서 오는 외부 트래픽 허용
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
# 네임스페이스 레이블 확인
kubectl get namespace --show-labels
# 개발 파드에서 프로덕션 서비스 접근 시도 (차단됨)
kubectl exec -it debug-pod -n development -- \
curl http://payment-service.production.svc.cluster.local:8080 --max-time 5
# curl: (28) Connection timed out
실습: 3계층 아키텍처 트래픽 제어
프론트엔드 → API → DB 구조에서 각 계층 간 통신만 허용하는 정책을 만듭니다.
# 실습 환경 구성
kubectl create namespace three-tier
# 계층별 파드 배포
kubectl run frontend --image=nginx --labels="tier=frontend,app=web" -n three-tier
kubectl run api --image=nginx --labels="tier=api,app=backend" -n three-tier
kubectl run db --image=postgres --labels="tier=db,app=database" \
--env="POSTGRES_PASSWORD=test" -n three-tier
kubectl expose pod api --port=8080 --target-port=80 -n three-tier
kubectl expose pod db --port=5432 --target-port=5432 -n three-tier
# three-tier-netpol.yaml
# 1. 기본 거부
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: three-tier
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
# 2. API가 프론트엔드의 요청만 수신
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-frontend
namespace: three-tier
spec:
podSelector:
matchLabels:
tier: api
policyTypes: [Ingress, Egress]
ingress:
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
tier: db
ports:
- port: 5432
- to: [{}]
ports:
- port: 53
protocol: UDP
---
# 3. DB가 API의 연결만 수신
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-api-only
namespace: three-tier
spec:
podSelector:
matchLabels:
tier: db
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
tier: api
ports:
- port: 5432
kubectl apply -f three-tier-netpol.yaml
# 허용: 프론트엔드 → API
kubectl exec -it frontend -n three-tier -- curl http://api:8080 --max-time 5
# 성공
# 차단: 프론트엔드 → DB (직접 접근 불가)
kubectl exec -it frontend -n three-tier -- \
nc -zv db 5432 --wait 5 2>&1
# nc: connect to db port 5432 (tcp) timed out: Operation in progress
NetworkPolicy를 적용한 직후부터 파드가 서비스 이름으로 다른 서비스에 연결하지 못합니다. IP로는 접근이 되지만 서비스 이름(hostname)으로는 타임아웃이 발생합니다.
# 증상
kubectl logs payment-pod -n production
# Error: dial tcp: lookup order-service on 10.96.0.10:53: i/o timeout
# ← DNS 조회 실패! IP가 아닌 호스트명 조회 실패
# 진단: DNS 서버로의 Egress 규칙 확인
kubectl get networkpolicy payment-service-policy -n production -o yaml | grep -A 20 egress
# Egress에 DNS 허용 규칙이 있는지 확인
# (없으면 kube-dns로의 UDP 53 포트가 차단됨)
# 원인: Egress NetworkPolicy를 설정했지만 DNS 트래픽(UDP 53)을 허용하지 않음
# kube-dns는 kube-system 네임스페이스의 10.96.0.10에서 동작
# 해결: DNS 허용 규칙 추가
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {} # 모든 파드에 적용
policyTypes:
- Egress
egress:
# kube-dns 허용 (CoreDNS가 있는 kube-system 네임스페이스)
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
EOF
# 적용 후 DNS 조회 확인
kubectl exec -it payment-pod -n production -- \
nslookup order-service.production.svc.cluster.local
# Server: 10.96.0.10
# Name: order-service.production.svc.cluster.local
# Address: 10.100.42.15
# 추가 디버깅 도구
# Cilium 클러스터에서 정책 시각화
kubectl exec -it -n kube-system cilium-xxx -- \
cilium policy get
# NetworkPolicy 적용 현황 확인 (Calico)
kubectl get networkpolicies -A
# 특정 파드의 실효 정책 확인
kubectl describe pod payment-pod -n production | grep -A 5 "Labels"
# 레이블을 기반으로 어떤 NetworkPolicy가 적용되는지 수동으로 매칭
핵심 교훈: Egress NetworkPolicy를 적용할 때는 DNS(UDP/TCP 53)를 반드시 허용해야 합니다. kube-dns가 차단되면 서비스 이름 조회가 실패하여 마치 서비스가 죽은 것처럼 보입니다. 또한 CNI가 NetworkPolicy를 지원하지 않는 경우 정책이 생성되어도 아무 효과가 없으므로, Calico/Cilium/Weave 중 하나를 사용 중인지 먼저 확인하세요.
시나리오: PCI DSS 컴플라이언스를 위한 결제 서비스 네트워크 격리
보안팀으로부터 "결제 서비스는 PCI DSS 규정상 다른 서비스와 네트워크 격리가 필요합니다"라는 요건을 받았습니다. 결제 서비스가 받는 요청은 API 게이트웨이에서만 와야 하고, 결제 서비스가 연결하는 것은 자체 DB와 외부 PG사 API뿐이어야 합니다.
# payment 네임스페이스 생성 및 레이블 부여
kubectl create namespace payment-zone
kubectl label namespace payment-zone compliance=pci-dss
# 결제 서비스 격리 정책 배포
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payment-strict-isolation
namespace: payment-zone
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes: [Ingress, Egress]
ingress:
# API 게이트웨이에서만 수신
- from:
- namespaceSelector:
matchLabels:
app: api-gateway
podSelector:
matchLabels:
app: api-gateway
ports:
- port: 8080
egress:
# 자체 DB
- to:
- podSelector:
matchLabels:
app: payment-db
ports:
- port: 5432
# 외부 PG사 API (IP 기반 egress)
- to:
- ipBlock:
cidr: 203.0.113.0/24 # PG사 IP 대역
ports:
- port: 443
# DNS
- to: [{}]
ports:
- port: 53
protocol: UDP
EOF
# 정책 적용 검증
kubectl describe networkpolicy payment-strict-isolation -n payment-zone
실무 포인트: NetworkPolicy는 컴플라이언스 감사에서 "네트워크 세그멘테이션 증적"으로 활용됩니다. 정책 YAML 파일을 Git에 관리하고, CI/CD에서 변경 시 보안팀 승인을 요구하는 프로세스를 만들면 "언제 누가 결제 서비스 통신 규칙을 바꿨는지" 추적할 수 있습니다.
핵심 요약
| 개념 | 명령/설정 | 실무 사용 빈도 |
|---|---|---|
| NetworkPolicy 조회 | kubectl get networkpolicy -n <ns> | 디버깅 시 |
| Deny All 적용 | podSelector: {} + policyTypes: [Ingress] (from 없음) | 네임스페이스 격리 시 |
| 파드 간 허용 | ingress.from.podSelector | 서비스 연결 허용 |
| 네임스페이스 간 허용 | ingress.from.namespaceSelector | 크로스 네임스페이스 통신 |
| 외부 IP 허용 | egress.to.ipBlock.cidr | 외부 API 연결 |
| DNS 허용 (필수!) | egress: port 53 UDP/TCP | Egress 정책 시 항상 |
| CNI 확인 | kubectl get pods -n kube-system | 정책 미작동 시 |