Kubernetes/Network Study

kube-proxy IPVS 모드

백곰곰 2024. 10. 18. 23:06
728x90
반응형

가시다님의 Kubernetes Advanced Networking Study에 참여하게 되어, 스터디 때 다룬 주제를 정리하려고 합니다.
5주차는 LoadBalancer(MetalLB), IPVS + LoxiLB를 주제로 진행되었습니다.

이번 글에서는 kube-proxy IPVS 모드에 대해 다루고 있습니다.

아래 테스트는 kubeadm를 사용한 EC2 기반 클러스터에서 진행됩니다.

IPVS 세부 설정

클러스터 생성 후 kube-proxy configmap을 보면, 아래와 같은 옵션을 확인할 수 있습니다.

...
ipvs:
  excludeCIDRs: null
  minSyncPeriod: 0s
  scheduler: ""
  strictARP: false
  syncPeriod: 0s
  tcpFinTimeout: 0s
  tcpTimeout: 0s
  udpTimeout: 0s
...
    mode: "ipvs"

위 옵션 중 몇가지를 살펴보겠습니다.

  • excludeCIDRs : kube-proxy에서 특정 CIDR을 예외로 처리
  • minSyncPeriod : iptables 규칙을 동기화하는 최소 기간이며, Service나 EndpointSlice 변경이 있을 때 변경 사항을 반영하는 주기
  • scheduler : 트래픽 분산에 사용할 알고리즘 지정
  • strictARP : kube-ipvs0 인터페이스가 불필요하게 ARP 쿼리에 응답하지 않도록 설정 가능. kube-ipvs0 인터페이스는 클러스터 내에서 VIP를 처리하며, IPVS 모드 사용 시 클러스터 내 트래픽 라우팅이 해당 인터페이스를 통해 이루어짐. strictARP를 true로 설정 시, 각 노드의 인터페이스는 자신에게 할당된 IP에만 ARP 응답을 보내어 ARP 패킷이 잘못된 인터페이스로 전달되는 문제를 방지함

IPVS 트래픽 분산 옵션

IPVS 사용 시 더욱 다양한 트래픽 분산 옵션을 사용할 수 있습니다. 기본은 Round Robin 입니다.

- rr (Round Robin)
- wrr (Weighted Round Robin)
- lc (Least Connections)
- wlc (Weighted Least Connections)
- lblc (Locality Based Least Connections)
- lblcr (Locality Based Least Connections with Replication)
- sh (Source Hashing)
- dh (Destination Hashing)
- sed (Shortest Expected Delay)
- nq (Never Queue)
  • rr : 서비스에 연결되는 요청을 순차적으로 분배하여 모든 백엔드 서버에 동일한 수의 요청이 분배되도록 순환
  • wrr : 트래픽이 서버의 가중치에 따라 백엔드 서버로 라우팅, 가중치가 높은 서버가 더 많은 요청을 처리하고, 가중치가 낮은 서버보다 더 많은 연결을 받음
  • lc : 더 적은 활성 연결을 가진 서버에 더 많은 트래픽이 할당
  • wlc : 각 서버의 활성 연결 수와 가중치를 고려하여 트래픽 분배, '서버의 연결 수 / 가중치' 값이 적은 서버에 더 많은 트래픽이 살당
  • lblc : 동일한 IP 주소로부터의 트래픽은 해당 서버가 과부하가 아니고 사용 가능하다면 항상 동일한 서버로 전송
  • lblcr : 동일한 IP 주소로부터의 트래픽은 연결 수가 가장 적은 서버로 전송. 모든 백엔드 서버가 과부하 상태일 경우, 연결 수가 적은 서버를 선택하여 대상 서버 집합에 추가. 대상 서버 집합이 지정된 시간 동안 변경되지 않으면, 가장 부하가 많은 서버가 집합에서 제거되어 높은 복제를 피하도록 함
  • sh : 소스 IP 주소를 기반으로 미리 할당된 해시 테이블을 참조하여 백엔드 서버로 전송
  • dh : 목적지 IP 주소를 기반으로 미리 할당된 해시 테이블을 참조하여 백엔드 서버로 전송
  • sed : 트래픽을 예상 지연 시간이 가장 짧은 서버로 전송
  • nq : 유휴 상태인 서버로 전송하며, 모든 서버가 바쁘다면 sed 알고리즘으로 처리

참고) https://kubernetes.io/docs/reference/networking/virtual-ips/#proxy-mode-ipvs

 

위 옵션 중 몇가지 알고리즘을 테스트해보겠습니다.

 

[테스트에 사용할 파드, service]

파드에 노드 이름을 지정했기 때문에 사용 시 주의가 필요합니다.

cat <<EOT> 3pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webpod1
  labels:
    app: webpod
spec:
  nodeName: k8s-w0
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: webpod2
  labels:
    app: webpod
spec:
  nodeName: k8s-w1
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: webpod3
  labels:
    app: webpod
spec:
  nodeName: k8s-w2
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
EOT
cat <<EOT> netpod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: net-pod
spec:
  nodeName: k8s-m
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOT
cat <<EOT> svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc-clusterip
spec:
  ports:
    - name: svc-webport
      port: 9000
      targetPort: 80
  selector:
    app: webpod
  type: ClusterIP
EOT
kubectl apply -f 3pod.yaml,netpod.yaml,svc-clusterip.yaml

 

Round Robin

[트래픽 분산 테스트]

SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP})

kubectl exec -it net-pod -- zsh -c "for i in {1..10};   do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
      4 Hostname: webpod1
      3 Hostname: webpod3
      3 Hostname: webpod2
kubectl exec -it net-pod -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
    334 Hostname: webpod2
    333 Hostname: webpod3
    333 Hostname: webpod1

트래픽이 균등하게 분산되는 것을 확인할 수 있습니다.

iptables 모드의 경우 랜덤 부하 분산이어서 요청 수에 차이가 발생하는데, IPVS 모드의 rr 옵션을 사용하는 경우 비교적 정확하게 트래픽을 균등 분산할 수 있습니다.

Least Connection

kube-proxy 설정을 변경해보겠습니다.

k edit cm kube-proxy -n kube-system
apiVersion: v1
data:
  config.conf: |-
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    bindAddress: 0.0.0.0
    bindAddressHardFail: false
...
    ipvs:
      excludeCIDRs: null
      minSyncPeriod: 0s
      scheduler: "lc" ## scheduler 변경
...

## kube-proxy 파드 재시작
k rollout restart ds kube-proxy -n kube-system

임의로 POD1, POD2에 1000개의 요청을 보낸 상태에서, 새로운 요청 1000개를 보내보겠습니다.

POD1=$(k get po webpod1 -o jsonpath={.status.podIP})
POD2=$(k get po webpod2 -o jsonpath={.status.podIP})
POD3=$(k get po webpod3 -o jsonpath={.status.podIP})
echo $POD1 $POD2 $POD3
172.16.34.1 172.16.158.1 172.16.184.4

파드 3개에 대해 요청 수가 조금 다른 것을 확인할 수 있습니다.

webpod1, webpod2 에 대한 요청이 많은 것을 봤을 때, curl의 연결 시간이 짧다보니 연결 수에 큰 영향을 미치지는 않은 것으로 보입니다.

(⎈|HomeLab:N/A) root@k8s-m:~# kubectl exec -it net-pod2 -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
    338 Hostname: webpod2
    337 Hostname: webpod3
    325 Hostname: webpod1
(⎈|HomeLab:N/A) root@k8s-m:~# kubectl exec -it net-pod2 -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
    347 Hostname: webpod1
    333 Hostname: webpod3
    320 Hostname: webpod2
(⎈|HomeLab:N/A) root@k8s-m:~# kubectl exec -it net-pod2 -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
    335 Hostname: webpod1
    334 Hostname: webpod2
    331 Hostname: webpod3

Locality Based Least Connections

이번에는 lblc 옵션을 사용해보겠습니다.

동일하게 kube-proxy 설정 변경을 진행합니다.

k edit cm kube-proxy -n kube-system
apiVersion: v1
data:
  config.conf: |-
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    bindAddress: 0.0.0.0
    bindAddressHardFail: false
...
    ipvs:
      excludeCIDRs: null
      minSyncPeriod: 0s
      scheduler: "lblc" ## scheduler 변경
...

## kube-proxy 파드 재시작
k rollout restart ds kube-proxy -n kube-system

 

curl을 실행해보면 모든 요청이 하나의 파드로 전달되는 것을 볼 수 있습니다.

(⎈|HomeLab:N/A) root@k8s-m:~# kubectl exec -it net-pod -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
   1000 Hostname: webpod2

 


kube-proxy ipvs 모드 사용 시 iptables 규칙의 수가 간소화되고, 다양한 분산 알고리즘을 적용할 수 있다는 장점이 있습니다.

클러스터 내 서비스가 많은 경우에 iptables 규칙의 수가 늘어나서 트래픽 분산 시 부하가 생길 수 있기 때문에, 규모가 큰 클러스터의 경우 ipvs 모드를 사용하는 것을 고려할 필요가 있습니다.

(아래 글에 의하면 서비스가 약 10,000개 정도일 때 iptables 모드와 ipvs 모드 간 성능이 2배정도 차이나는 것을 볼 수 있습니다.)

참고) https://www.tigera.io/blog/comparing-kube-proxy-modes-iptables-or-ipvs/

728x90