다양한 네트워크 기능들
September 2024 (1427 Words, 8 Minutes)
Endpoints와 Endpointslice
그리고 Readiness Probe
Readiness Probe 를 사용하여 헬스체크 실패 시 ‘Endpoints, EndpointSlice 어떤게 다른지’ 알아보자
Endpoint Slices - k8s_Docs
- kube-proxy 가 매번 모든 endpoint 를 watch 해야 한다
apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
name: demo-slice-1
labels:
kubernetes.io/service-name: demo
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.0.0.1"
conditions:
ready: true
kubectl get endpointslice
NAME ADDRESSTYPE PORTS ENDPOINTS
clusterip-service-l2n9q IPv4 8080 10.244.2.7,10.244.2.8,10.244.1.5
+ 1 more...
수퍼마리오로 Readiness Probe 테스트
설정 및 Service(Nodeport) 설정 배포
# 모니터링
watch kubectl get pod,svc,ep,endpointslice -owide
# 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: mario
labels:
app: mario
spec:
replicas: 1
selector:
matchLabels:
app: mario
template:
metadata:
labels:
app: mario
spec:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
effect: "NoSchedule"
nodeSelector:
node-role.kubernetes.io/control-plane: ""
containers:
- name: mario
image: pengbai/docker-supermario
readinessProbe:
exec:
command:
- cat
- healthcheck
---
apiVersion: v1
kind: Service
metadata:
name: mario
spec:
ports:
- name: mario-webport
port: 80
targetPort: 8080
nodePort: 30001
selector:
app: mario
type: NodePort
externalTrafficPolicy: Local
EOF
# 모니터링
watch kubectl get pod,svc,ep,endpointslice -owide
# 상태 확인 : ‘Endpoints, EndpointSlice 출력 정보 차이’를 알아보자
kubectl describe svc
kubectl describe ep
kubectl describe endpointslice
# 파드에 파일 생성
kubectl exec -it deployments/mario -- touch healthcheck
kubectl exec -it deployments/mario -- ls -l
# 상태 확인
kubectl describe svc
kubectl describe ep
kubectl describe endpointslice
# 게임 접속
echo -e "Mario Game URL = http://localhost:30001" # macOS
echo -e "Mario Game URL = http://Y.Y.Y.Y:30001" # Windows
# 파드에 파일 삭제
kubectl exec -it deployments/mario -- rm healthcheck
kubectl exec -it deployments/mario -- ls
# 상태 확인
kubectl describe svc
kubectl describe ep
kubectl describe endpointslice
# 삭제
kubectl delete deploy,svc mario
서비스 EndpointSlice 등장 배경
scaling-kubernetes-networking-with-endpointslices
EndpointSlices는 Kubernetes 네트워크 확장성 문제를 해결하기 위해 등장했습니다.
기존 Endpoints API는 서비스에 연결된 모든 Pod의 IP와 포트를 하나의 리소스로 관리했는데, 이 방식은 수천 개의 Pod을 가진 대규모 클러스터에서 성능과 확장성의 한계를 드러냈습니다. 특히, 리소스 크기 제한으로 인해 kube-proxy가 전체 클러스터에 변경 사항을 전파하는 과정에서 네트워크 부하가 크게 증가합니다.
EndpointSlices는 이러한 문제를 해결하기 위해 서비스의 네트워크 엔드포인트 정보를 여러 작은 조각으로 나누어 관리합니다. 기본적으로 각 EndpointSlice는 최대 100개의 엔드포인트를 저장하며, Pod이 추가되거나 삭제될 때 전체 리소스 대신 해당 조각만 업데이트하면 됩니다. 이 방식은 네트워크 부하를 대폭 줄이고 성능을 개선하며, 서비스가 수십만 개의 엔드포인트로 확장될 수 있도록 지원합니다.
또한, EndpointSlices는 이중 스택 서비스(Dual-Stack Services)나 지리적 토폴로지 기반 라우팅(Topology Aware Routing) 같은 새로운 기능을 가능하게 하여, 서비스가 동일한 지역 내에서 효율적으로 라우팅될 수 있도록 도와줍니다.
EndpointSlice 등장 이후
정말 좋아하는 게시글인 커피고래님의 블로그, 그리고 OpenAI 조직의 쿠버네티스 7500대 운영기의 게시글입니다.
운영 서버의 수가 증가하면서, API 서버에 큰 부담을 주는 요인 중 하나는 Endpoint
리소스를 Watch
하는 것이었습니다. 클러스터에 있는 모든 노드에서 동작하는 서비스들, 예를 들어 kubelet
이나 node-exporter
같은 서비스들은, 노드에 변경사항이 발생할 때마다 클러스터 관점에서 이를 Watch
하게 됩니다.
특히 각 노드가 kube-proxy
를 통해 kubelet
서비스를 감시(watching)하고 있었기 때문에, 노드가 추가되거나 삭제될 때마다 이러한 Watch
이벤트가 발생했습니다.
결과적으로, 응답에 필요한 네트워크 대역폭과 요청 수는 노드 수의 제곱(N^2)만큼 증가하게 되었고, 가끔은 1GB/s 이상의 대역폭이 요구되기도 했습니다.
Kubernetes 1.17 버전에서 도입된 EndpointSlices 덕분에 이 부하는 1000배 가까이 감소했습니다.
문서를 자세히 읽다보니 사용했던 exporter중에서 iptables라는 exporter가 보이네요. 조직에서 관리하는 iptable 메트릭이 필요하다면 검토해볼만한 메트릭으로 보입니다.
https://github.com/madron/iptables-exporter
Topology Aware Hint
Kubernetes 클러스터 내에서 네트워크 트래픽이 발생한 영역(zone) 내에서 처리되도록 라우팅을 조정하는 기능입니다. 이를 통해 네트워크 성능(지연 시간, 처리량)을 개선하거나 비용을 절감할 수 있습니다
< 1.27 에서는 Topology Aware Routing이라는 이름을 사용했습니다.
동작 방식
EndpointSlice 컨트롤러는 각 영역에 할당할 엔드포인트의 비율을 계산해 힌트를 추가합니다. 이 비율은 해당 영역에서 사용할 수 있는 CPU 코어 수를 기준으로 합니다. kube-proxy는 이 힌트를 기반으로 트래픽을 라우팅하며, 필요할 때 다른 영역으로 트래픽을 분산시켜 부하를 조정할 수 있습니다.
Safeguards(보호장치): 이 기능은 엔드포인트의 수가 충분하지 않거나, 특정 조건을 충족하지 못하면 기본 클러스터 전체 라우팅으로 되돌아갑니다. 이를 통해 잘못된 힌트로 인한 과부하나 성능 저하를 방지합니다.
Constraints(제약사항):
Topology Aware Routing은 트래픽이 특정 영역에 집중되거나 노드의 리소스가 균등하지 않으면 제대로 작동하지 않을 수 있습니다. 또한, 자동 확장(autoscaling)과 함께 사용할 때 예상치 못한 문제들이 발생할 수 있습니다.
클러스터가 여러 영역(멀티존)으로 분할된 경우에 유용합니다. 서비스에 연결된 Pod들의 위치 정보를 기반으로, 트래픽이 가능한 한 같은 영역 내의 엔드포인트로 라우팅되도록 도와줍니다. 이를 위해 EndpointSlice 컨트롤러는 각 엔드포인트의 토폴로지 정보를 참고하여 힌트 필드를 채워 각 엔드포인트를 특정 영역에 할당합니다. kube-proxy는 이러한 힌트를 읽어 트래픽을 가까운 엔드포인트로 라우팅합니다.
Topology Aware Routing은 EndpointSlice API를 기반으로 동작합니다. EndpointSlice는 서비스에 연결된 Pod의 엔드포인트 정보를 여러 조각으로 나눠 관리하는 API로, 각 조각에는 Pod의 IP, 포트, 토폴로지 정보 등이 포함됩니다. Topology Aware Routing은 이 EndpointSlice에 포함된 “힌트” 정보를 활용해, 트래픽을 발생한 영역 내에서 처리하도록 라우팅 방향을 조정합니다.
EndpointSlices는 각 Pod의 위치 정보를 제공하고, Topology Aware Routing은 이 정보를 기반으로 같은 영역 내에서 트래픽이 처리되도록 돕는 역할 해주기때문에 멀티존 환경에서 네트워크 성능을 최적화합니다.
테스트를 위한 디플로이먼트와 서비스 배포
# 현재 노드 AZ 배포 확인
kubectl get node --label-columns=topology.kubernetes.io/zone
NAME STATUS ROLES AGE VERSION ZONE
ip-192-168-1-225.ap-northeast-2.compute.internal Ready <none> 70m v1.24.11-eks-a59e1f0 ap-northeast-2a
ip-192-168-2-248.ap-northeast-2.compute.internal Ready <none> 70m v1.24.11-eks-a59e1f0 ap-northeast-2b
ip-192-168-3-228.ap-northeast-2.compute.internal Ready <none> 70m v1.24.11-eks-a59e1f0 ap-northeast-2c
# 테스트를 위한 디플로이먼트와 서비스 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 3
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: websrv
image: registry.k8s.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-clusterip
spec:
ports:
- name: svc-webport
port: 8080
targetPort: 80
selector:
app: deploy-websrv
type: ClusterIP
EOF
# 확인
kubectl get svc,ep svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml | yh
# 접속 테스트를 수행할 클라이언트 파드 배포
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: netshoot-pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 확인
kubectl get pod -owide
테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인 : AZ(zone) 상관없이 랜덤 확률 부하분산 동작
# 디플로이먼트 파드가 배포된 AZ(zone) 확인
kubectl get pod -l app=deploy-websrv -owide
# 테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인
kubectl exec -it netshoot-pod -- curl svc-clusterip | grep Hostname
Hostname: deploy-echo-7f67d598dc-h9vst
kubectl exec -it netshoot-pod -- curl svc-clusterip | grep Hostname
Hostname: deploy-echo-7f67d598dc-45trg
# 100번 반복 접속 : 3개의 파드로 AZ(zone) 상관없이 랜덤 확률 부하분산 동작
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
35 Hostname: deploy-echo-7f67d598dc-45trg
33 Hostname: deploy-echo-7f67d598dc-hg995
32 Hostname: deploy-echo-7f67d598dc-h9vst
Topology Aware Hint 설정 후 ClusterIP 접속 시 부하분산 확인
- 테스트 파드(netshoot-pod)에서 같은 AZ(zone)의 목적지 파드로만 접속
# Topology Aware Hint 설정 : 서비스에 annotate에 아래처럼 추가
kubectl annotate service svc-clusterip "service.kubernetes.io/topology-aware-hints=auto"
# 100번 반복 접속 : 테스트 파드(netshoot-pod)와 같은 AZ(zone)의 목적지 파드로만 접속
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
100 Hostname: deploy-echo-7f67d598dc-45trg
# endpointslices 확인 시, 기존에 없던 hints 가 추가되어 있음 >> 참고로 describe로는 hints 정보가 출력되지 않음
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml | yh
...
(참고) Topology Aware Hint 설정 제거
kubectl annotate service svc-clusterip "service.kubernetes.io/topology-aware-hints -"
실습 리소스 삭제:
kubectl delete deploy deploy-echo; kubectl delete svc svc-clusterip
파드 토폴로지 분배 topologySpreadConstraints
# 디플로이먼트 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 6
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: websrv
image: registry.k8s.io/echoserver:1.5
ports:
- containerPort: 8080
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: deploy-websrv
EOF
# 파드 토폴로지 분배 확인 : AZ별 2개씩 파드 배포 확인
kubectl get pod -owide