ポリシーに「拒否」はない
한국어 원문으로 표시합니다.
한 줄 요약
NetworkPolicy 에는 거부라는 개념이 없습니다. 파드에 정책이 하나도 없으면 전부 허용이고, 하나라도 붙는 순간 그 방향이 허용 목록으로 뒤집힙니다.
왜 이 전환을 알아야 하나
쿠버네티스의 기본값은 전부 허용입니다. 어느 네임스페이스의 어느 파드든 다른 모든 파드에 닿습니다. 침입자가 웹 파드 하나를 잡으면 그 자리에서 데이터베이스가 보입니다.
기본값이 뒤집히는 지점
NetworkPolicy 의 동작 방식이 직관과 다릅니다.
파드에 정책이 하나도 붙지 않으면 전부 허용이고, 하나라도 붙는 순간 그 파드는 그 방향에 대해 허용 목록 방식으로 바뀝니다.
그래서 정책에는 '거부' 라는 개념이 아예 없습니다. default-deny 라는 이름의 정책도 사실은 아무것도 허용하지 않는 정책일 뿐입니다.
spec:
podSelector: {} # 이 네임스페이스의 모든 파드
policyTypes: [Ingress] # ingress 만 다룬다
# ingress: 항목이 없다 → 허용하는 것이 하나도 없다
이 전환을 모르면 "정책 하나 추가했을 뿐인데 관계없는 것이 끊겼다" 가 됩니다.
방향을 헷갈리지 않는 법
정책은 언제나 받는 쪽에 붙습니다.
| 필드 | 고르는 것 |
|---|---|
podSelector |
보호받는 파드 (db) |
ingress.from |
허용할 출처 (api) |
보내는 쪽에 붙이면 문법은 통과하고 오브젝트도 만들어지는데 아무 일도 일어나지 않습니다. 오류가 없어서 더 헷갈립니다.
여러 정책은 더해진다
같은 파드에 붙은 정책이 여럿이면 합집합입니다. 뒤에 붙인 정책이 앞의 것을 덮어쓰지 않습니다. 그래서 "좁히려고 정책을 추가했는데 더 넓어지는" 일이 생깁니다. 좁히려면 기존 정책을 고쳐야 합니다.
가장 자주 나는 사고
egress 를 기본 차단하는 순간 DNS 가 함께 끊깁니다. CoreDNS 로 나가는 53번 포트도 egress 이기 때문입니다.
증상은 "이름을 못 찾습니다" 이고, CoreDNS 는 멀쩡하며, 다른 네임스페이스는 정상입니다. 그래서 아무도 방금 붙인 네트워크 정책을 의심하지 않습니다.
처음 정책을 넣는 순서
기본 차단부터 켜면 그 순간 전부 끊깁니다. 순서가 있습니다.
1. 무엇이 통신하는지 먼저 본다. 정책을 쓰기 전에 실제 흐름을 봅니다. Cilium 이면 Hubble, 그 밖에는 conntrack 이나 애플리케이션 로그로 그립니다.
hubble observe --namespace labhub-prod --last 500 -o json | jq -r '"\(.source.namespace)/\(.source.pod_name) → \(.destination.namespace)/\(.destination.pod_name):\(.l4.TCP.destination_port)"' | sort | uniq -c | sort -rn
2. 허용 정책을 먼저 넣는다. 아직 기본 차단이 없으므로 아무것도 막히지 않습니다.
3. 그 다음 기본 차단을 켠다. 이때 빠뜨린 것이 드러납니다.
4. DNS 를 꼭 확인한다. egress 를 쓰는 순간 DNS(53/UDP)도 막힙니다. 이름 조회가 안 되면 모든 것이 타임아웃으로 보여 원인을 찾기 어렵습니다.
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}
정책이 못 막는 것
네트워크 정책은 만능이 아닙니다. 못 막는 것을 알아야 다른 층으로 보냅니다.
| 못 막는 것 | 대신 |
|---|---|
| 같은 파드 안 컨테이너끼리 | 파드를 나눈다 |
| hostNetwork 파드 | 파드 시큐리티로 hostNetwork 를 금지 |
| 노드 자체에서 나가는 트래픽 | 노드 방화벽·보안그룹 |
| L7(경로·메서드) | Cilium L7 정책, 서비스 메시 |
| 이미 맺힌 연결 | 정책은 새 연결에만 적용된다 |
마지막 줄이 함정입니다. 정책을 넣어도 진행 중이던 연결은 계속 살아 있습니다. 그래서 "정책을 넣었는데 아직 통한다" 가 나옵니다. 확인하려면 새 연결을 맺어 봐야 합니다.
디버깅 순서
# 1. 이 파드를 고르는 정책이 있나 (없으면 기본 허용)
kubectl get netpol -n <ns> -o json | jq -r '
.items[] | select(.spec.podSelector.matchLabels // {} | to_entries | length > 0) |
"\(.metadata.name): \(.spec.podSelector.matchLabels) \(.spec.policyTypes)"'
# 2. 양쪽 다 열려 있나 — 출발지 egress, 도착지 ingress
# 3. 실제로 막혔는지 확인 (Connection refused 는 정책이 아니다)
kubectl exec -n <ns> <pod> -- nc -zv -w3 <대상> <포트>
3번의 구분이 중요합니다. 정책에 막히면 timeout 이고, 아무도 안 듣고 있으면 refused 입니다. refused 가 나오면 정책 문제가 아닙니다.
실무에서 진짜 중요한 것
egress 를 막을 때는 DNS 를 같은 커밋에서 함께 엽니다. CoreDNS 로 나가는 53번도 egress 라 함께 끊기는데, 증상은 "이름을 못 찾습니다" 이고 CoreDNS 는 멀쩡하며 다른 네임스페이스는 정상입니다. 그래서 아무도 방금 붙인 정책을 의심하지 않습니다.
정책은 언제나 받는 쪽에 붙입니다. 보내는 쪽에 붙이면 문법은 통과하고 오브젝트도 만들어지는데 아무 일도 일어나지 않습니다. 오류가 없어서 더 오래 헤맵니다.
좁히려면 정책을 추가하지 말고 기존 정책을 고칩니다. 같은 파드에 붙은 정책은 합집합이라, 뒤에 붙인 것이 앞의 것을 덮어쓰지 않습니다. 좁히려고 추가했는데 더 넓어지는 일이 여기서 나옵니다.
다음 실습에서 이 사고를 직접 일으켰다가 고칩니다. 그리고 고칠 때 UDP 53 만 열면 왜 더 나쁜 상태가 되는지도 함께 봅니다.