외부 사용자
-> Gateway 또는 Ingress
-> Service
-> EndpointSlice
-> Pod
NetworkPolicy
-> Pod 기준으로 ingress / egress 허용 범위를 제한
Service, Ingress, Gateway는 트래픽을 어디로 보낼지 정하는 쪽에 가깝다. NetworkPolicy는 어떤 트래픽을 허용할지 정한다. 이 차이를 잡고 들어가면 troubleshooting할 때도 어느 리소스를 봐야 하는지 빨리 좁힐 수 있다.
참고 문서:
Pod는 언제든지 새로 만들어질 수 있고 IP도 바뀐다. 그래서 client가 Pod IP를 직접 바라보게 만들면 운영이 불안정해진다. Service는 이 문제를 해결한다. Service는 안정적인 IP와 DNS 이름을 제공하고, selector와 일치하는 Pod들을 backend로 묶는다.
예를 들어 app=web label을 가진 Pod들을 web Service 뒤에 두려면 다음처럼 쓴다.
apiVersion: v1
kind: Service
metadata:
name: web
namespace: demo
spec:
type: ClusterIP
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080
여기서 port는 client가 Service로 접근할 때 쓰는 포트다. targetPort는 Service가 backend Pod로 보낼 때 쓰는 포트다. Pod의 container가 8080에서 listen한다면 targetPort: 8080이 맞다.
Service는 Deployment를 직접 바라보지 않는다. Service selector가 Pod label을 찾고, Kubernetes는 그 결과를 EndpointSlice로 관리한다. 그래서 Service가 있는데 연결이 안 될 때는 Service 존재 여부보다 selector, Pod label, EndpointSlice를 먼저 봐야 한다.
kubectl get svc -n demo
kubectl get pod -n demo --show-labels
kubectl get endpointslice -n demo -l kubernetes.io/service-name=web
Service는 cluster 내부 접근 지점을 만든다. 외부 HTTP/HTTPS 요청을 여러 Service로 나눠 보내려면 Ingress를 쓸 수 있다.
Ingress는 host, path 같은 HTTP 규칙을 보고 요청을 backend Service로 라우팅한다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Ingress 리소스만 만든다고 외부 트래픽이 자동으로 들어오는 것은 아니다. Ingress를 실제 load balancer나 reverse proxy 설정으로 반영하는 Ingress controller가 필요하다. nginx ingress controller, cloud provider의 controller 같은 구현체가 이 역할을 한다.
Kubernetes 공식 문서는 현재 Gateway API 사용을 권장한다. Ingress API는 stable이지만 새 기능 개발은 멈춘 상태다. 그렇다고 Ingress가 바로 사라지는 것은 아니다. 이미 많은 cluster에서 쓰이고 있고, 단순 HTTP routing에는 여전히 자주 등장한다.
Ingress troubleshooting에서는 이 순서가 좋다.
kubectl get ingress -n demo
kubectl describe ingress web -n demo
kubectl get svc -n demo
kubectl get endpointslice -n demo -l kubernetes.io/service-name=web
Ingress rule이 맞아도 backend Service에 endpoint가 없으면 결국 Pod까지 트래픽이 가지 않는다.
Gateway API는 Ingress의 다음 세대에 가까운 Kubernetes networking API다. Ingress보다 더 표현력이 넓고, 역할을 나눠서 설계됐다. 공식 Gateway API 문서는 Gateway API를 L4/L7 routing을 위한 Kubernetes project로 설명한다.
핵심 리소스는 보통 세 층으로 본다.
GatewayClass
-> 어떤 Gateway controller가 처리할지 정의
Gateway
-> listener, port, hostname 같은 진입 지점 정의
HTTPRoute
-> host/path/header 조건과 backend Service 연결
간단한 HTTPRoute는 다음처럼 Service로 트래픽을 보낸다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
namespace: demo
spec:
parentRefs:
- name: public-gateway
hostnames:
- example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web
port: 80
Gateway API가 Ingress보다 좋은 점은 복잡한 routing을 API 차원에서 더 자연스럽게 표현한다는 데 있다. 예를 들어 header 기반 routing, traffic splitting, 여러 namespace를 넘나드는 routing 같은 기능을 annotation에 덜 의존하고 표현할 수 있다.
다만 Gateway API도 controller가 필요하다. Gateway, HTTPRoute 같은 리소스는 의도한 상태를 선언하는 API이고, 실제 dataplane을 구성하는 것은 Gateway controller다.
Gateway API를 볼 때는 다음 순서로 확인한다.
kubectl get gatewayclass
kubectl get gateway -n demo
kubectl get httproute -n demoㅌ
kubectl describe httproute web -n demo
kubectl get svc,endpointslice -n demo
Service, Ingress, Gateway가 트래픽을 어디로 보낼지 다룬다면 NetworkPolicy는 어떤 트래픽을 허용할지 다룬다. NetworkPolicy는 Pod를 기준으로 ingress와 egress 규칙을 정의한다.
중요한 전제가 있다. NetworkPolicy는 이를 지원하는 network plugin이 있어야 실제로 적용된다. API object는 만들 수 있어도 CNI가 enforcement를 지원하지 않으면 기대한 차단이 일어나지 않을 수 있다.
기본적으로 NetworkPolicy가 어떤 Pod도 선택하지 않으면 Pod는 격리되지 않는다. 특정 Pod를 선택하는 NetworkPolicy가 생기면, 그 Pod에는 policy가 허용한 트래픽만 들어오거나 나갈 수 있다. 여러 NetworkPolicy가 같은 Pod를 선택하면 규칙은 합쳐지는 방식으로 동작한다.
예를 들어 app=web Pod가 app=client Pod에서 오는 80번 포트 트래픽만 받게 하려면 다음처럼 쓴다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-client-to-web
namespace: demo
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: client
ports:
- protocol: TCP
port: 80
전체 차단을 먼저 만들고 필요한 트래픽만 여는 방식도 자주 쓴다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: demo
spec:
podSelector: {}
policyTypes:
- Ingress
podSelector: {}는 namespace 안의 모든 Pod를 선택한다. ingress 규칙이 없으므로 ingress traffic을 허용하지 않는 policy가 된다.
NetworkPolicy troubleshooting에서는 “policy가 존재하는가”보다 “어떤 Pod를 선택하는가”가 중요하다.
kubectl get networkpolicy -n demo
kubectl describe networkpolicy allow-client-to-web -n demo
kubectl get pod -n demo --show-labels
그리고 반드시 실제 통신으로 확인해야 한다.
kubectl exec -n demo client -- curl -sS http://web
kubectl exec -n demo other -- curl -sS --max-time 3 http://web
client에서는 성공하고 other에서는 실패해야 한다면, 성공 evidence와 실패 evidence를 둘 다 남겨야 한다.
외부에서 들어온 HTTP 요청을 기준으로 보면 흐름은 이렇다.
1. 사용자가 example.com으로 요청한다.
2. Gateway 또는 Ingress가 host/path 규칙을 보고 backend Service를 고른다.
3. Service가 EndpointSlice에 있는 Pod IP와 port로 트래픽을 보낸다.
4. NetworkPolicy가 해당 Pod로 들어오는 traffic을 허용하는지 판단한다.
5. 허용되면 컨테이너가 요청을 받는다.
troubleshooting도 같은 방향으로 볼 수 있다.
외부 접근 실패
-> Gateway / Ingress rule 확인
-> backend Service 확인
-> EndpointSlice 확인
-> Pod readiness와 label 확인
-> NetworkPolicy 차단 여부 확인
반대로 cluster 내부에서 Service DNS는 되는데 연결이 안 되면 Ingress나 Gateway보다 Service, EndpointSlice, NetworkPolicy를 먼저 봐야 한다.
Service와 Ingress의 경계는 내부 안정 주소와 외부 HTTP routing이다. Service는 Pod 앞의 안정적인 내부 endpoint를 만든다. Ingress는 외부 HTTP/HTTPS 요청을 Service로 보낸다.
Ingress와 Gateway의 경계는 세대와 표현력에 가깝다. Ingress는 오래된 stable API이고 여전히 많이 쓰인다. Gateway API는 더 역할 지향적이고 복잡한 routing을 더 명확하게 표현하려는 API다.
Service와 NetworkPolicy의 경계는 routing과 filtering이다. Service는 backend를 찾아 트래픽을 보낸다. NetworkPolicy는 그 트래픽이 Pod에 들어오거나 Pod 밖으로 나갈 수 있는지 제한한다.
Gateway와 NetworkPolicy도 역할이 다르다. Gateway는 진입점과 routing을 표현한다. NetworkPolicy는 Pod network 수준에서 허용 범위를 정의한다.
Service, Ingress, Gateway, NetworkPolicy는 모두 Kubernetes networking에 속하지만 보는 층이 다르다.