1 · 걸리는 순간 기본 거부
정책이 하나라도 붙으면 그 Pod은 기본 거부가 된다. 명시적으로 허용한 것 외에는 전부 막힌다.
Pod 사이의 통신을 막는 법
Kubernetes 네트워크 모델의 전제 —
flowchart LR
subgraph NS1["네임스페이스 prod"]
A["Pod A"]
B["Pod B"]
end
subgraph NS2["네임스페이스 dev"]
C["Pod C"]
end
A <-->|"NAT 없이 직접 ✅"| B
A <-->|"네임스페이스가 달라도 ✅"| C
B <-->|"노드가 달라도 ✅"| C
classDef pod fill:#dcfce7,stroke:#16a34a,color:#14532d
class A,B,C pod
즉 아무것도 안 하면 클러스터 안은 완전히 평평하다.
NetworkPolicy는 이 기본값을 선택적으로 좁히는 도구다.
sequenceDiagram
participant KL as kubelet
participant CNI as CNI 플러그인
participant POD as Pod 네트워크 네임스페이스
KL->>CNI: Pod 생성 — ADD 호출
Note over CNI: 설정 /etc/cni/net.d/<br/>바이너리 /opt/cni/bin/
CNI->>POD: IP 할당 · veth 인터페이스 연결 · 라우팅
CNI-->>KL: 결과 반환
Note over KL: 실패하면 Pod 은 ContainerCreating 에서 멈춘다
/etc/cni/net.d/ 에 있고, 바이너리는 /opt/cni/bin/ 에 있다ContainerCreating 에서 멈춘다| CNI | 특징 | NetworkPolicy |
|---|---|---|
| Calico | 널리 쓰인다. BGP 또는 오버레이 | 지원 (확장 정책도) |
| Cilium | eBPF 기반. L7 정책까지 | 지원 (강력) |
| Flannel | 가장 단순한 오버레이 | 미지원 |
| Weave | 오버레이 | 지원 |
kubectl get pods -n kube-system | grep -Ei 'calico|cilium|flannel|weave'ls /etc/cni/net.d/apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: api-policy namespace: prod # ← 정책은 네임스페이스 안에서만 적용된다spec: podSelector: # 누구에게 적용할 것인가 matchLabels: { app: api } policyTypes: # 어느 방향을 통제할 것인가 - Ingress - Egress ingress: - from: - podSelector: matchLabels: { app: web } ports: - protocol: TCP port: 8080 egress: - to: - podSelector: matchLabels: { app: db } ports: - protocol: TCP port: 5432네 부분이 각각 다른 질문에 답한다.
flowchart LR
P["NetworkPolicy"]
P --> Q1["podSelector<br/>누구에게 적용하나"]
P --> Q2["policyTypes<br/>어느 방향을 통제하나"]
P --> Q3["ingress.from<br/>들어오는 것 중 무엇을 허용하나"]
P --> Q4["egress.to<br/>나가는 것 중 무엇을 허용하나"]
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class Q1,Q2 key
class Q3,Q4 mute
1 · 걸리는 순간 기본 거부
정책이 하나라도 붙으면 그 Pod은 기본 거부가 된다. 명시적으로 허용한 것 외에는 전부 막힌다.
2 · 정책은 더해진다
여러 정책이 같은 Pod에 걸리면 허용의 합집합이다.
순서도, 우선순위도, deny 규칙도 없다.
3 · 없는 방향은 통제되지 않는다
policyTypes: [Ingress]만 쓰면
egress는 전혀 제한되지 않는다.
첫 번째 규칙이 가장 중요하다 — 정책 하나가 스위치를 뒤집는다.
flowchart LR
S1["정책이 하나도 없다"] --> A1["전부 허용 ✅"]
S2["정책이 하나라도 걸렸다"] --> A2["기본 거부로 전환"]
A2 --> A3["명시적으로 허용한 것만 통과"]
A2 --> A4["나머지는 전부 차단 ❌"]
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
class A1,A3 ok
class A4 bad
class A2 warn
이 셋을 알면 대부분의 혼란이 정리된다. 특히 “막는 정책”을 쓰려 하지 말 것 — 막는 방법은 허용하지 않는 것뿐이다.
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default-deny-all namespace: prodspec: podSelector: {} # 빈 셀렉터 = 이 네임스페이스의 모든 Pod policyTypes: - Ingress - Egress# ingress / egress 규칙이 없다 = 아무것도 허용하지 않는다podSelector |
뜻 |
|---|---|
{} (빈 값) |
네임스페이스의 모든 Pod |
matchLabels: {app: api} |
라벨이 맞는 Pod만 |
표준 패턴 — 화이트리스트
default-deny-all을 먼저 깐다
네임스페이스 전체가 기본 거부로 바뀐다.
필요한 통신만 정책을 추가해 뚫는다
정책은 더해지므로, 허용 규칙을 하나씩 얹으면 된다.
egress를 켰다면 DNS를 잊지 않는다
UDP/TCP 53을 안 열면 이름 해석부터 죽는다. 아래 절에서 다룬다.
ingress: - from: - podSelector: # ① 같은 네임스페이스의 Pod matchLabels: app: web
- namespaceSelector: # ② 다른 네임스페이스의 모든 Pod matchLabels: kubernetes.io/metadata.name: frontend
- ipBlock: # ③ IP 대역 (클러스터 밖 포함) cidr: 10.0.0.0/16 except: - 10.0.5.0/24flowchart LR
T["대상 Pod<br/>podSelector 로 지정"]
S1["① podSelector<br/>같은 네임스페이스 안에서만 찾는다"] --> T
S2["② namespaceSelector<br/>다른 네임스페이스 전체"] --> T
S3["③ ipBlock<br/>패킷의 소스 IP · 클러스터 밖 포함"] --> T
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class T key
class S1,S2,S3 mute
podSelector 만 쓰면 정책이 있는 네임스페이스 안에서만 찾는다kubernetes.io/metadata.name 라벨이 자동으로 붙어 있다 — 이걸로 지정하면 편하다ipBlock은 Pod IP가 아니라 패킷의 소스 IP 기준이다from: - namespaceSelector: matchLabels: team: frontend - podSelector: matchLabels: app: web“frontend 네임스페이스의 모든 Pod”
또는
“같은 네임스페이스의 app=web Pod”
flowchart LR
A["frontend 네임스페이스의<br/>모든 Pod"] --> T["허용"]
B["같은 네임스페이스의<br/>app=web Pod"] --> T
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
class A,B,T okfrom: - namespaceSelector: matchLabels: team: frontend podSelector: matchLabels: app: web“frontend 네임스페이스이면서 app=web인 Pod”
flowchart LR
A["frontend 네임스페이스"] --> X{"둘 다 만족"}
B["app=web 라벨"] --> X
X --> T["허용"]
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
class T ok
class X keyspec: podSelector: matchLabels: { app: api } policyTypes: [Egress] egress: - to: - podSelector: matchLabels: { app: db } ports: - protocol: TCP port: 5432
- to: # ★ DNS를 반드시 열어야 한다 - namespaceSelector: # ← 이 둘은 같은 항목 = AND matchLabels: { kubernetes.io/metadata.name: kube-system } podSelector: matchLabels: { k8s-app: kube-dns } ports: - { protocol: UDP, port: 53 } - { protocol: TCP, port: 53 }DNS를 안 열면 증상이 엉뚱한 곳에서 나타난다.
flowchart LR
P["egress 정책을 켰다"] --> D["CoreDNS 로 가는<br/>UDP 53 도 막힌다"]
D --> N["이름 해석 실패"]
N --> S["증상: 연결 거부가 아니라<br/>Name or service not known"]
S --> W["엉뚱하게 Service · 앱을 의심하게 된다 ❌"]
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class W bad
class D,N,S warn
class P mute
ports: - protocol: TCP port: 8080 - protocol: TCP port: http # Pod의 이름 있는 포트도 가능 - protocol: TCP port: 8000 endPort: 9000 # 범위ports를 생략하면 모든 포트가 허용된다port는 Pod의 포트(targetPort) 다. Service의 port가 아니다NetworkPolicy는 Service를 모른다.
flowchart LR
C["클라이언트"] --> SVC["Service<br/>port 80 → targetPort 8080"]
SVC --> POD["Pod<br/>8080 에서 수신"]
NP["NetworkPolicy"] -.->|"여기를 본다<br/>→ 8080 을 써야 한다"| POD
NP -.->|"이건 안 본다 ❌"| SVC
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class POD key
class SVC bad
class C,NP mute
같은 이유로 NetworkPolicy로 Service를 막을 수는 없다. 막히는 것은 최종적으로 도달하는 Pod이다.
# db는 api에서 오는 5432만 받는다apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: db-allow-api namespace: prodspec: podSelector: matchLabels: tier: db policyTypes: [Ingress] ingress: - from: - podSelector: matchLabels: tier: api ports: - protocol: TCP port: 5432정책 하나가 어디까지 바꾸는지 보면 감이 온다.
flowchart LR
WEB["web<br/>정책 없음"] -->|"아무 데나 갈 수 있다 ✅"| API["api<br/>정책 없음"]
API -->|"5432 허용 ✅"| DB["db<br/>tier=db · 정책 걸림"]
WEB -.->|"차단 ❌"| DB
EXT["다른 네임스페이스"] -.->|"차단 ❌"| DB
API -->|"egress 는 여전히 자유 ⚠️"| OUT["외부 · 아무 데나"]
classDef locked fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef free fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
class DB locked
class WEB,API free
class OUT warn
kubectl get netpol -Akubectl describe netpol db-allow-api -n prod # 해석된 규칙을 보여준다
# 실제로 되는지 확인 — 이게 가장 확실하다kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -n prod -- sh wget -qO- --timeout=3 http://api-svc:8080 nc -zv 10.244.2.7 5432
# 라벨이 정말 맞는지kubectl get pods -n prod --show-labelskubectl get ns --show-labelsflowchart TD
S{"증상"}
S -->|"정책을 만들었는데 안 막힌다"| A["CNI 가 NetworkPolicy 를 지원하지 않는다<br/>Flannel 기본 설정을 의심"]
S -->|"의도보다 많이 막힌다"| B{"무엇을 확인하나"}
B --> B1["AND/OR 하이픈 위치"]
B --> B2["egress 라면 DNS 허용 여부"]
B --> B3["포트를 Service 포트로 쓰지 않았나"]
S -->|"특정 네임스페이스만 안 된다"| C["namespaceSelector 라벨 불일치<br/>kubectl get ns --show-labels"]
S -->|"이름 해석부터 실패"| D["egress 에서 UDP 53 미허용"]
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class A,D bad
class B1,B2,B3,C warn
class S,B mute
| 증상 | 원인 후보 |
|---|---|
| 정책을 만들었는데 안 막힌다 | CNI가 NetworkPolicy를 지원하지 않는다 |
| 의도보다 많이 막힌다 | AND/OR 실수, DNS 미허용, 포트를 Service 포트로 씀 |
| 특정 네임스페이스만 안 된다 | namespaceSelector 라벨 불일치 |
| 이름 해석부터 실패 | egress에서 UDP 53 미허용 |
deny 규칙도, 우선순위도 없다policyTypes에 없는 방향은 통제되지 않는다podSelector: {} = 네임스페이스 전체 → default-deny-all 패턴