Search

Service: ClusterIP

Tags
k8s
network
service
clusterip
Created
2024/09/28 21:23
Created time
2024/09/28 12:22
category
kubernetes

개요

ClusterIP 타입으로 생성한 Service의 접속을 시도하면서, iptables 규칙과 부하 분산 동작을 확인
sessionAffinity: ClientIP로 설정하고, 고정 접속 가능 여부를 직접 확인

실습 준비

실습은 이전 글에서 준비한 클러스터를 이용

목적지 Pod 3개 생성

# Pod 3개짜리 manifest 생성 cat <<EOT > 3pod.yaml apiVersion: v1 kind: Pod metadata: name: webpod1 labels: app: webpod spec: nodeName: myk8s-worker containers: - name: container image: traefik/whoami terminationGracePeriodSeconds: 0 --- apiVersion: v1 kind: Pod metadata: name: webpod2 labels: app: webpod spec: nodeName: myk8s-worker2 containers: - name: container image: traefik/whoami terminationGracePeriodSeconds: 0 --- apiVersion: v1 kind: Pod metadata: name: webpod3 labels: app: webpod spec: nodeName: myk8s-worker3 containers: - name: container image: traefik/whoami terminationGracePeriodSeconds: 0 EOT # manifest 적용 kubectl apply -f 3pod.yaml
Shell
복사

클라이언트 Pod 생성

# 클라이언트 Pod로 사용할 manifest 생성 cat <<EOT > netpod.yaml apiVersion: v1 kind: Pod metadata: name: net-pod spec: nodeName: myk8s-control-plane containers: - name: netshoot-pod image: nicolaka/netshoot command: ["tail"] args: ["-f", "/dev/null"] terminationGracePeriodSeconds: 0 EOT # manifest 적용 kubectl apply -f netpod.yaml
Shell
복사

ClusterIP 타입 Service 생성

# ClusterIP 타입 manifest 생성 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 # manifest 적용 kubectl apply -f svc-clusterip.yaml
Shell
복사

리소스 확인

# 생성 확인 watch -d 'kubectl get po -o wide ; echo ; kubectl get svc,ep svc-clusterip' # podCIDR과 Service 대역 확인 kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"
Shell
복사

부하 분산

클라이언트 Pod를 이용하여 Service 접속 시 부하 분산되어 접속됨을 확인
# 목적지 Pod IP들을 확인 kubectl get pod -l app=webpod -o jsonpath="{.items[*].status.podIP}" # 목적지 Pod IP들을 변수에 할당 WEBPOD1=$(kubectl get pod webpod1 -o jsonpath={.status.podIP}) WEBPOD2=$(kubectl get pod webpod2 -o jsonpath={.status.podIP}) WEBPOD3=$(kubectl get pod webpod3 -o jsonpath={.status.podIP}) echo $WEBPOD1 $WEBPOD2 $WEBPOD3 # Service IP를 변수에 할당 SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP}) echo $SVC1
Shell
복사
# Service 생성 시 kube-proxy에 의해서 iptables 규칙이 각 노드에 추가 됨을 확인 가능 for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i iptables -t nat -S | grep $SVC1; echo ; done
Shell
복사
# 클라이언트 Pod를 이용하여 Service IP로 1만회 요청 시 33% 정도로 부하 분산 됨을 확인 # 대략 10 ~ 15초 정도 소요 kubectl exec -it net-pod -- zsh -c "for i in {1..10000} ; do curl -s $SVC1:9000 | grep Hostname ; done | sort | uniq -c | sort -nr"
Shell
복사
# Control Plane에서 conntrack으로 커넥션 상태 확인 conntrack -L --src <net-pod-ip> | head -n 10 conntrack -L --dst <service-ip> | head -n 10
Shell
복사

iptables

위와 같이 ClusterIP가 부하 분산이 가능한 이유는 iptables를 이용하기 때문이라고 했었는데, ClusterIP 생성 시 적용되는 NAT 테이블의 규칙을 확인
ClusterIP는 dNAT으로 처리되어 통신한다고 했으므로 dNAT에 해당되는 PREROUTING부터 확인 (iptables의 Compatibility를 참고)
결론적으론 PREROUTING → KUBE-SERVICES → KUBE-SVC-XXX → KUBE-SEP-(#POD1|#POD2|#POD3) 으로 정책이 적용되면서 부하가 3개의 Pod로 분산
** PREROUTING 외의 나머지 체인들은 사용자 정의 체인으로 JUMP를 통해 이동
# Control Plane 접속 docker exec -it myk8s-control-plane bash
Shell
복사
# PREROUTING 체인의 규칙을 확인 # KUBE-SERVICE 체인으로 점프하는 규칙이 존재 iptables -t nat -nvL PREROUTING | column -t
Shell
복사
# KUBE-SERVICES 체인의 규칙을 확인 # KUBE-SVC-XXX 체인으로 점프하는 규칙들 중 svc-clusterip로 점프하는 규칙이 존재 iptables -t nat -nvL KUBE-SERVICES | column -t
Shell
복사
# KUBE-SVC-XXX 체인의 규칙을 확인 # KUBE-SEP라는 엔드포인트 관련 체인이 Pod 수만큼 존재 iptables -t nat -nvL KUBE-SVC-KBDEBIL6IU6WL7RF | column -t
Shell
복사
watch 명령어로 위 KUBE-SVC-XXX를 확인하는 중에 다른 터미널에서 클라이언트 Pod로 요청을 날리면 부하 분산 비율을 확인 가능
** 모든 체인의 바이트 및 카운터 값 초기화는 --zero 옵션을 사용
# KUBE-SEP-(#POD1|#POD2|#POD3) 체인의 규칙을 확인 # 각 엔드포인트들은 dNAT 체인으로 이어지는 것을 볼 수 있음 iptables -t nat -nvL KUBE-SEP-TBW2IYJKUCAC7GB3 | column -t iptables -t nat -nvL KUBE-SEP-SZHENXPAXVOCHRDA | column -t iptables -t nat -nvL KUBE-SEP-X47GKN7LA32LZ4H7 | column -t
Shell
복사
# dNAT만을 이용하는 것을 확인 # POSTROUTING 체인을 확인하여 KUBE-POSTROUTING의 체인이 존재함을 확인 # KUBE-POSTROUTING에는 mark match ! 0x4000/0x4000 # 마킹되어 있지 않은 상태임에 따라 RETURN 되어 sNAT은 수행되지 않음 iptables -t nat -nvL POSTROUTING | column -t iptables -t nat -nvL KUBE-POSTROUTING | column -t
Shell
복사

장애 발생 시

좌측 터미널에서 Pod, Service, 엔드포인트 상태를 확인
우측에서는 클라이언트 Pod로 트래픽 요청
# 터미널 1 watch -d 'kubectl get po -o wide ; echo ; kubectl get svc,ep svc-clusterip ; echo ; kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip' # 터미널 2 kubectl exec -it net-pod -- zsh -c "while true; do curl -s --connect-timeout 1 $SVC1:9000 | egrep 'Hostname|IP: 10'; date '+%Y-%m-%d %H:%M:%S' ; echo ; sleep 1 ; done"
Shell
복사
Pod 삭제는 Pod를 직접 지우는 방법도 있고, Service가 Pod를 연동한 방식이 레이블을 셀렉터에 등록한 것이므로 레이블을 삭제하는 방법도 존재
** 파일 의존성 없게 간단히 레이블을 삭제하는 방식을 이용 예정
# 정상) Pod를 레이블과 함께 조회 kubectl get po --show-labels # 장애) 3번 Pod의 레이블을 삭제 kubectl label po webpod3 app- # 복구) 3번 Pod에 다시 레이블을 추가하여 Service Discovery kubectl label po webpod3 app=webpod
Shell
복사

정상 상태

장애 상태

복구 상태

sessionAffinity: ClientIP

Service를 소개하는 글에서 sessionAffinity에 대해 언급했었는데, 클라이언트가 접속했던 Pod에 최대한 고정하여 접속할 수 있도록 돕는 설정
service.spec.sessionAffinityConfig.clientIP.timeoutSeconds를 이용하여 최대 세션 고정 시간을 설정 가능 (기본 값은 10800초)

설정 확인

# sessionAffinity 설정 확인 kubectl get svc svc-clusterip -o yaml | grep sessionAffinity
Shell
복사

접속 확인

# 클라이언트 Pod를 이용하여 접속 시도 시 3개의 Pod로 부하 분산 kubectl exec -it net-pod -- zsh -c "while true; do curl -s --connect-timeout 1 $SVC1:9000 | egrep 'Hostname|IP: 10|Remote'; date '+%Y-%m-%d %H:%M:%S' ; echo ; sleep 1 ; done"
Shell
복사

설정 변경

# 아래 두 방식 중 하나를 이용하여 sessionAffinity의 값을 변경 kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinity":"ClientIP"}}' kubectl get svc svc-clusterip -o yaml | sed -e "s/sessionAffinity: None/sessionAffinity: ClientIP/" | kubectl apply -f -
Shell
복사

접속 확인

# 고정된 Pod로만 접속 되는 것을 볼 수 있음 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"
Shell
복사

iptables 확인

# Control Plane 접속 docker exec -it myk8s-control-plane bash
Shell
복사
# 목적지 Pod로 향하는 KUBE-SVC-XXX를 조회 # dNAT의 연결 유지 관련 --second 설정이 추가된 상태 # recent 모듈 - 동적으로 IP 주소 목록을 생성 및 확인 # rcheck 모듈 - 현재 IP 주소 목록에 존재하는지 확인 # reap 모듈 - 오래된 엔트리를 삭제 iptables -t nat -nvL KUBE-SVC-KBDEBIL6IU6WL7RF | column -t
Shell
복사

리소스 삭제

kubectl delete svc,po --all
Shell
복사