개요
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
결론적으론 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.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
복사