쿠버네티스 운영 실무 · NetworkPolicy 적용 · 이론
NetworkPolicy — 라벨로 그리는 방화벽
한 줄 요약
쿠버네티스의 기본값은 "모든 파드가 모든 파드와 통신 가능"이고, NetworkPolicy 는 그 기본값을 파드 단위로 뒤집는 유일한 표준 수단이다.
왜 이게 필요했나
파드 하나가 뚫렸다고 하자. 기본 설정의 클러스터에서 그 파드는 데이터베이스에도, 결제 서비스에도, 내부 관리 API 에도 그냥 접속할 수 있다. 방화벽이 클러스터 바깥 경계에만 있고 안쪽은 평평한 벌판이기 때문이다. 침해가 한 파드에서 끝나지 않고 옆으로 번지는 이 현상을 측면 이동이라고 부른다.
NetworkPolicy 는 이 벌판에 담을 세운다. 다만 담을 세우는 방식이 전통적인 방화벽과 다르다. IP 가 아니라 라벨로 대상을 고른다. 파드는 죽고 다시 뜨면 IP 가 바뀌지만 라벨은 그대로이기 때문이다. 정책이 IP 대신 "app=api 인 파드"라고 말하면, 오토스케일로 파드가 열 개가 되든 노드를 옮기든 정책은 그대로 유효하다.
어떻게 동작하나
세 가지 규칙만 이해하면 대부분이 설명된다.
첫째, 정책은 받는 쪽에 붙는다. podSelector 는 이 정책이 적용될 파드를 고르고, ingress.from 은 그 파드에게 들어오는 것을 허용할 상대를 고른다. web 에서 api 로 가는 트래픽을 허용하려면 정책의 podSelector 는 api 여야 한다. 이 방향을 거꾸로 잡는 실수가 압도적으로 흔하다.
둘째, 정책은 화이트리스트다. 어떤 파드를 고르는 정책이 하나라도 생기는 순간, 그 파드의 해당 방향 트래픽은 "정책에 적힌 것만 허용"으로 바뀐다. 그래서 기본 거부는 규칙이 하나도 없는 정책으로 표현된다.
spec: podSelector: {} # 네임스페이스의 모든 파드 policyTypes: [Ingress] # 인그레스에 대해 # ingress 규칙 없음 → 전부 거부셋째, from 배열의 항목끼리는 OR, 한 항목 안의 셀렉터끼리는 AND 다. 이것이 가장 미묘하다.
from: - namespaceSelector: {matchLabels: {purpose: monitoring}} podSelector: {matchLabels: {app: prom}} # ← 같은 항목: AND는 "monitoring 네임스페이스의 prom 파드"를 뜻하지만, 하이픈을 하나 더 넣어 항목을 둘로 나누면 "monitoring 네임스페이스의 모든 파드 또는 아무 네임스페이스의 prom 파드"가 된다. 들여쓰기 한 칸이 의미를 통째로 바꾼다.
이그레스에는 함정이 하나 더 있다. 이그레스를 기본 거부로 막으면 DNS 부터 죽는다. 파드는 서비스 이름을 IP 로 바꾸기 위해 kube-system 의 CoreDNS 에 물어봐야 하는데 그 경로가 막히기 때문이다. 그래서 이그레스 기본 거부에는 언제나 DNS 예외가 짝으로 따라온다. 그리고 DNS 는 UDP 53 만 쓰지 않는다. 응답이 크면 TCP 53 으로 넘어가므로 두 프로토콜을 모두 열어야 한다.
IP 대역으로 여는 ipBlock 도 있다. cidr 로 넓게 열고 except 로 좁게 빼는 방식이다. 다만 클러스터 안쪽 통신에 IP 를 쓰는 것은 라벨 기반 정책의 장점을 버리는 일이라, 사무실 대역이나 외부 게이트웨이처럼 클러스터 밖 주소에만 쓰는 것이 좋다.
현장에서 만나는 모습
첫째, CNI 가 지원하지 않으면 정책은 장식이다. NetworkPolicy 오브젝트는 만들어져도 실제 차단은 CNI 플러그인이 한다. Flannel 처럼 지원하지 않는 CNI 에서는 정책을 아무리 만들어도 아무 일도 일어나지 않는다. 저자의 홈랩은 Cilium 을 eBPF 모드로 쓰고 있어 L7 규칙과 Hubble 관측까지 가능하다. 이 랩 환경은 파드 간 실제 트래픽이 흐르지 않는 시뮬레이션 클러스터이므로, 채점은 패킷이 떨어지는지가 아니라 정책을 정확히 작성했는지와 셀렉터 의미를 제대로 이해했는지로 이루어진다. 문법과 의미를 여기서 익히고, 실제 차단 검증은 CNI 가 있는 환경에서 Hubble 이나 calicoctl 로 하면 된다.
둘째, 기본 거부를 먼저 넣는 사고. 실제로 있었던 장애다. 운영팀이 프로덕션에 기본 거부를 테스트 없이 적용했는데 DNS 허용을 빠뜨렸다. 모든 서비스 디스커버리가 실패하고 readiness probe 가 연쇄로 죽었다. 올바른 순서는 허용 정책을 먼저 다 깔고 기본 거부를 마지막에 넣는 것이다.
셋째, 라벨 위생. 정책은 라벨로만 대상을 고르므로, 라벨이 없는 파드나 네임스페이스는 정책의 사각지대가 된다. 네임스페이스를 고를 때 흔히 쓰는 kubernetes.io/metadata.name 은 쿠버네티스가 모든 네임스페이스에 자동으로 붙여 주는 라벨이라 편리하다.
다음 실습에서 할 것
네임스페이스 하나에 web·api·db 세 파드를 놓고, 인그레스 기본 거부부터 시작해 web 에서 api 로 가는 8080 만 여는 정책, 이그레스 기본 거부와 DNS 예외, 다른 네임스페이스에서 오는 모니터링 트래픽 허용, IP 대역 허용과 예외, 그리고 양방향을 한 정책에 담는 종합 문제까지 만든다. 마지막에는 어떤 파드에 어떤 정책이 적용되는지 지도를 JSON 으로 그린다.