CloudNetaStudy - Kubernets Networtk 3기 실습 스터디 게시글입니다.
[ 서비스 타입 ]
- Cluster Type : 서비스(Cluster Type) 연결 : K8S 클러스터 내부에서만 접속
- 동일한 애플리케이션의 다수의 파드의 접속을 용이하게 하기 위한 서비스에 접속
- 고정 접속(호출) 방법을 제공 : 흔히 말하는 ‘고정 VirtualIP’ 와 ‘Domain주소’ 생성
- NodePort Type : 서비스(NodePort Type) 연결 : 외부 클라이언트가 서비스를 통해서 클러스터 내부의 파드로 접속
- 서비스(NodePort Type)의 일부 단점을 보완한 서비스(LoadBalancer Type) 도 있다!
출처 : https://kim-dragon.tistory.com/52
- 서비스(NodePort Type)의 일부 단점을 보완한 서비스(LoadBalancer Type) 도 있다!
- LoadBalancer Type : Nodeport 타입의 확장판이라고 할 수 있으며 서비스를 외부에 노출 할 수 있다.
- NodePort타입 앞단에 Loadbalancer가 붙어서 살아있는 노드를 체크하여 트래픽을 전달 할 수 있는 장점이 있다.
- 클라우드 공급업체(AWS, GCP 등)에서 지원하는 기능
더보기
- 약자 소개
- S.IP : Source IP , 출발지(소스) IP
- D.IP : Destination IP, 도착치(목적지) IP
- S.Port : Source Port , 출발지(소스) 포트
- D.Port : Destination Port , 도착지(목적지) 포트
- NAT : Network Address Translation , 네트워크 주소 변환
- SNAT : Source IP 를 NAT 처리, 일반적으로 출발지 IP를 변환
- DNAT : Destination IP 를 NAT 처리, 일반적으로 목적지 IP와 목적지 포트를 변환
ClusterIP
[ ClusterIP 통신 흐름 ]
요약 : 클라이언트(TestPod)가 'CLUSTER-IP' 접속 시 해당 노드의 iptables 룰(랜덤 분산)에 의해서 DNAT 처리가 되어 목적지(backend) 파드와 통신
- 클러스터 내부에서만 'CLUSTER-IP' 로 접근 가능 ⇒ 서비스에 DNS(도메인) 접속도 가능!
- 서비스(ClusterIP 타입) 생성하게 되면, apiserver → (kubelet) → kube-proxy → iptables 에 rule(룰)이 생성됨.
- 모든 노드(마스터 포함)에 iptables rule 이 설정되므로, 파드에서 접속 시 해당 노드에 존재하는 iptables rule 에 의해서 분산 접속이 됨.
[ 실습 구성 ]
- 목적지(backend) 파드(pod) 생성 : 3pod.yaml
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
- 클라이언트(TestPod) 생성 : netpod.yaml
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
- 서비스(ClusterIP) 생성
svc-clusterip.yaml ← spec.ports.port 와 spec.ports.targetPort 가 어떤 의미인지 꼭 이해하자!cat <<EOT> svc-clusterip.yaml apiVersion: v1 kind: Service metadata: name: svc-clusterip spec: ports: - name: svc-webport port: 9000 # 서비스 IP 에 접속 시 사용하는 포트 port 를 의미 targetPort: 80 # 타킷 targetPort 는 서비스를 통해서 목적지 파드로 접속 시 해당 파드로 접속하는 포트를 의미 selector: app: webpod # 셀렉터 아래 app:webpod 레이블이 설정되어 있는 파드들은 해당 서비스에 연동됨 type: ClusterIP # 서비스 타입 EOT
- 생성 및 확인
# 모니터링 watch -d 'kubectl get pod -owide ;echo; kubectl get svc,ep svc-clusterip' # 생성 kubectl apply -f 3pod.yaml,netpod.yaml,svc-clusterip.yaml # 파드와 서비스 사용 네트워크 대역 정보 확인 kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range" # 확인 kubectl get pod -owide kubectl get svc svc-clusterip # spec.ports.port 와 spec.ports.targetPort 가 어떤 의미인지 꼭 이해하자! kubectl describe svc svc-clusterip # 서비스 생성 시 엔드포인트를 자동으로 생성, 물론 수동으로 설정 생성도 가능 kubectl get endpoints svc-clusterip kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip
[ 서비스(ClusterIP) 접속 확인 ]
- 클라이언트(TestPod) Shell 에서 접속 테스트 & 서비스(ClusterIP) 부하분산 접속 확인
클라이언트 파드 (10.10.0.5) → service (10.200.1.139) → web pod 1 (10.10.1.2)
→ web pod 2 (10.10.2.3)
→ web pod 3 (10.10.3.2)# webpod 파드의 IP 를 출력 kubectl get pod -l app=webpod -o jsonpath="{.items[*].status.podIP}" # webpod 파드의 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 # net-pod 파드에서 webpod 파드의 IP로 직접 curl 로 반복 접속 for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod; done for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod | grep Hostname; done for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod | grep Host; done for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod | egrep 'Host|RemoteAddr'; done # 서비스 IP 변수 지정 : svc-clusterip 의 ClusterIP주소 SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP}) echo $SVC1 # 위 서비스 생성 시 kube-proxy 에 의해서 iptables 규칙이 모든 노드에 추가됨 docker exec -it myk8s-control-plane iptables -t nat -S | grep $SVC1 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 -A KUBE-SERVICES -d 10.200.1.52/32 -p tcp -m comment --comment "default/svc-clusterip:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-SVC-KBDEBIL6IU6WL7RF ## (참고) ss 툴로 tcp listen 정보에는 없음 , 별도 /32 host 라우팅 추가 없음 -> 즉, iptables rule 에 의해서 처리됨을 확인 docker exec -it myk8s-control-plane ss -tnlp docker exec -it myk8s-control-plane ip -c route # TCP 80,9000 포트별 접속 확인 : 출력 정보 의미 확인 kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1 kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000 kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000 | grep Hostname kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000 | grep Hostname # 서비스(ClusterIP) 부하분산 접속 확인 ## for 문을 이용하여 SVC1 IP 로 100번 접속을 시도 후 출력되는 내용 중 반복되는 내용의 갯수 출력 ## 반복해서 실행을 해보면, SVC1 IP로 curl 접속 시 3개의 파드로 대략 33% 정도로 부하분산 접속됨을 확인 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" kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr" 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" 혹은 kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s $SVC1:9000 | grep Hostname; sleep 1; done" kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s $SVC1:9000 | grep Hostname; sleep 0.1; done" kubectl exec -it net-pod -- zsh -c "for i in {1..10000}; do curl -s $SVC1:9000 | grep Hostname; sleep 0.01; done" # conntrack 확인 docker exec -it myk8s-control-plane bash ---------------------------------------- conntrack -h conntrack -E conntrack -C conntrack -S conntrack -L --src 10.10.0.6 # net-pod IP conntrack -L --dst $SVC1 # service ClusterIP exit ---------------------------------------- # (참고) Link layer 에서 동작하는 ebtables ebtables -L
- 각 워커노드에서 패킷 덤프 확인
# 방안1 : 1대 혹은 3대 bash 진입 후 tcpdump 해둘 것 docker exec -it myk8s-worker bash docker exec -it myk8s-worker2 bash docker exec -it myk8s-worker3 bash ---------------------------------- # nic 정보 확인 ip -c link ip -c route ip -c addr # tcpdump/ngrep : eth0 >> tcp 9000 포트 트래픽은 왜 없을까? iptables rule 동작 그림을 한번 더 확인하고 이해해보자 ## ngrep 네트워크 패킷 분석기 활용해보기 : 특정 url 호출에 대해서만 필터 등 깔끔하게 볼 수 있음 - 링크 tcpdump -i eth0 tcp port 80 -nnq tcpdump -i eth0 tcp port 80 -w /root/svc1-1.pcap tcpdump -i eth0 tcp port 9000 -nnq ngrep -tW byline -d eth0 '' 'tcp port 80' # tcpdump/ngrep : vethX VETH1=<각자 자신의 veth 이름> tcpdump -i $VETH1 tcp port 80 -nn tcpdump -i $VETH1 tcp port 80 -w /root/svc1-2.pcap tcpdump -i $VETH1 tcp port 9000 -nn ngrep -tW byline -d $VETH1 '' 'tcp port 80' exit ---------------------------------- # 방안2 : 노드(?) 컨테이너 bash 직접 접속하지 않고 호스트에서 tcpdump 하기 docker exec -it myk8s-worker tcpdump -i eth0 tcp port 80 -nnq VETH1=<각자 자신의 veth 이름> # docker exec -it myk8s-worker ip -c route docker exec -it myk8s-worker tcpdump -i $VETH1 tcp port 80 -nnq # 호스트PC에 pcap 파일 복사 >> wireshark 에서 분석 docker cp myk8s-worker:/root/svc1-1.pcap . docker cp myk8s-worker:/root/svc1-2.pcap .
- 클라이언트(TestPod) → 서비스(ClusterIP) 접속 시 : 3개의 목적지(backend) 파드로 랜덤 부하 분산 접속됨
[ IPTABLES 정책 확인 ]
- iptables 정책 적용 순서 : PREROUTING → KUBE-SERVICES → KUBE-SVC-### → KUBE-SEP-#<파드1> , KUBE-SEP-#<파드2> , KUBE-SEP-#<파드3>
- 결론 : 내부에서 클러스터 IP로 접속 시, PREROUTE(nat) 에서 DNAT(3개 파드) 되고, POSTROUTE(nat) 에서 SNAT 되지 않고 나간다!
# 컨트롤플레인에서 확인 : 너무 복잡해서 리턴 트래픽에 대해서는 상세히 분석 정리하지 않습니다. docker exec -it myk8s-control-plane bash ---------------------------------------- # iptables 확인 iptables -t filter -S iptables -t nat -S iptables -t nat -S | wc -l iptables -t mangle -S # iptables 상세 확인 - 매칭 패킷 카운트, 인터페이스 정보 등 포함 iptables -nvL -t filter iptables -nvL -t nat iptables -nvL -t mangle # rule 갯수 확인 iptables -nvL -t filter | wc -l iptables -nvL -t nat | wc -l # 규칙 패킷 바이트 카운트 초기화 iptables -t filter --zero; iptables -t nat --zero; iptables -t mangle --zero # 정책 확인 : 아래 정책 내용은 핵심적인 룰(rule)만 표시했습니다! iptables -t nat -nvL iptables -v --numeric --table nat --list PREROUTING | column -t Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 778 46758 KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */ iptables -v --numeric --table nat --list KUBE-SERVICES | column # 바로 아래 룰(rule)에 의해서 서비스(ClusterIP)를 인지하고 처리를 합니다 Chain KUBE-SERVICES (2 references) pkts bytes target prot opt in out source destination 92 5520 KUBE-SVC-KBDEBIL6IU6WL7RF tcp -- * * 0.0.0.0/0 10.105.114.73 /* default/svc-clusterip:svc-webport cluster IP */ tcp dpt:9000 iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF | column watch -d 'iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF' SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP}) kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s $SVC1:9000 | grep Hostname; sleep 1; done" # SVC-### 에서 랜덤 확률(대략 33%)로 SEP(Service EndPoint)인 각각 파드 IP로 DNAT 됩니다! ## 첫번째 룰에 일치 확률은 33% 이고, 매칭되지 않을 경우 아래 2개 남을때는 룰 일치 확률은 50%가 됩니다. 이것도 매칭되지 않으면 마지막 룰로 100% 일치됩니다 Chain KUBE-SVC-KBDEBIL6IU6WL7RF (1 references) pkts bytes target prot opt in out source destination 38 2280 KUBE-SEP-6TM74ZFOWZXXYQW6 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ statistic mode random probability 0.33333333349 29 1740 KUBE-SEP-354QUAZJTL5AR6RR all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ statistic mode random probability 0.50000000000 25 1500 KUBE-SEP-PY4VJNJPBUZ3ATEL all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ iptables -v --numeric --table nat --list KUBE-SEP-<각자 값 입력> Chain KUBE-SEP-6TM74ZFOWZXXYQW6 (1 references) pkts bytes target prot opt in out source destination 38 2280 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ tcp to:172.16.158.3:80 iptables -v --numeric --table nat --list KUBE-SEP-354QUAZJTL5AR6RR | column -t Chain KUBE-SEP-6TM74ZFOWZXXYQW6 (1 references) pkts bytes target prot opt in out source destination 29 1500 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ tcp to:172.16.184.3:80 iptables -v --numeric --table nat --list KUBE-SEP-PY4VJNJPBUZ3ATEL | column -t Chain KUBE-SEP-6TM74ZFOWZXXYQW6 (1 references) pkts bytes target prot opt in out source destination 25 1740 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ tcp to:172.16.34.3:80 iptables -t nat --zero iptables -v --numeric --table nat --list POSTROUTING | column; echo ; iptables -v --numeric --table nat --list KUBE-POSTROUTING | column watch -d 'iptables -v --numeric --table nat --list POSTROUTING; echo ; iptables -v --numeric --table nat --list KUBE-POSTROUTING' # POSTROUTE(nat) : 0x4000(2진수로 0100 0000 0000 0000, 10진수 16384) 마킹 되어 있지 않으니 RETURN 되고 그냥 빠져나가서 SNAT 되지 않는다! Chain KUBE-POSTROUTING (1 references) pkts bytes target prot opt in out source destination 572 35232 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 mark match ! 0x4000/0x4000 0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK xor 0x4000 0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ random-fully iptables -t nat -S | grep KUBE-POSTROUTING -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN -A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0 -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully ... exit ---------------------------------------- # 위 서비스 생성 시 kube-proxy 에 의해서 iptables 규칙이 모든 노드에 추가됨을 한번 더 확이 docker exec -it myk8s-control-plane iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF ... for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF; echo; done ...
[ 파드 1개 장애 발생 시 동작 확인 ]
- 동작 확인을 위한 모니터링
# 터미널1 >> ENDPOINTS 변화를 잘 확인해보자! watch -d 'kubectl get pod -owide;echo; kubectl get svc,ep svc-clusterip;echo; kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip' # 터미널2 SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP}) 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" 혹은 kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
- 파드 1개 삭제 후 확인
# (방안1) 파드3번 삭제 >> 서비스의 엔드포인트가 어떻게 변경되는지 확인 하자!, 지속적인 curl 접속 결과 확인!, for 문 실행 시 결과 확인!, 절체 시간(순단) 확인! kubectl delete pod webpod3 # (방안1) 결과 확인 후 다시 파드 3번 생성 >> 서비스 디스커버리! kubectl apply -f 3pod.yaml --------------------------------- # (방안2) 파드3번에 레이블 삭제 kubectl get pod --show-labels ## 레이블(라벨)의 키값 바로 뒤에 하이픈(-) 입력 시 해당 레이블 삭제됨! >> 레이블과 셀렉터는 쿠버네티스 환경에서 매우 많이 활용된다! kubectl label pod webpod3 app- kubectl get pod --show-labels # (방안2) 결과 확인 후 파드3번에 다시 레이블 생성 kubectl label pod webpod3 app=webpod
쿠버네티스의 Endpoint Controller 는 지속적으로 엔드포인트를 Watch 하고 List 에 추가, 삭제를 합니다!
- k8s 클러스터 외부(mypc)에서는 서비스(ClusterIP)로 접속이 불가능
docker ps docker exec -it mypc ping -c 1 172.18.0.2 docker exec -it mypc curl <SVC1_IP>:9000 docker exec -it mypc curl <Pod IP>:80
[ sessionAffinity: ClientIP ]
- sessionAffinity: ClientIP : 클라이언트가 접속한 목적지(파드)에 고정적인 접속을 지원 - k8s_Docs
- 설정 및 파드 접속 확인
- 클라이언트(TestPod) → 서비스(ClusterIP) 접속 시 : 1개의 목적지(backend) 파드로 고정 접속됨
- iptables 정책 적용 확인 : 기존 룰에 고정 연결 관련 추가됨
- 다음 실습을 위해 오브젝트 삭제 kubectl delete svc,pods --all
[ 서비스(ClusterIP) 부족한 점 ]
- 클러스터 외부에서는 서비스(ClusterIP)로 접속이 불가능 ⇒ NodePort 타입으로 외부에서 접속 가능!
- IPtables 는 파드에 대한 헬스체크 기능이 없어서 문제 있는 파드에 연결 가능 ⇒ 서비스 사용, 파드에 Readiness Probe 설정으로 파드 문제 시 서비스의 엔드포인트에서 제거되게 하자! ← 이 정도면 충분한가? 혹시 부족한 점이 없을까?
- 서비스에 연동된 파드 갯수 퍼센트(%)로 랜덤 분산 방식, 세션어피니티 이외에 다른 분산 방식 불가능 ⇒ IPVS 경우 다양한 분산 방식(알고리즘) 가능
- 목적지 파드 다수가 있는 환경에서, 출발지 파드와 목적지 파드가 동일한 노드에 배치되어 있어도, 랜덤 분산으로 다른 노드에 목적지 파드로 연결 가능
'Kubernetes' 카테고리의 다른 글
[ Kans 3 Study - 5w ] 1. LoadBalancer (0) | 2024.10.05 |
---|---|
[ Kans 3 Study - 4w ] 2. Service - NodePort (1) | 2024.09.29 |
[ Kans 3 Study - 4w ] 실습 환경 구성 (0) | 2024.09.22 |
[ Kans 3 Study - 3w ] 4. Calico 네트워크 모드 (1) | 2024.09.22 |
[ Kans 3 Study - 3w ] 3. Calico 기본 통신 이해 (1) | 2024.09.21 |