⭐ 가시다(gasida) 님이 진행하는 KANS(**K**ubernetes **A**dvanced **N**etworking **S**tudy)3기 실습 게시글입니다.
게시글 상 소스코드, 사진에서 ****굵게**** '''코드쉘''' 에 대한 부분이 들어가있을수도 있습니다.

⭐코드에서 실습 시 사용하는 변수가 들어갈 수 있습니다. 아래의 정보를 사용하고있습니다. 
#route53 Domain: jjongguet.com
#AWS Secret Key: jjongkey
#Aws .pem Key: jjong-key.pem

Kubernetes 의 Pod 를외부로 노출하기 위한 방법

  1. 파드 생성 : K8S 클러스터 내부에서만 접속

2. 서비스(Cluster Type) 연결 : K8S 클러스터 내부에서만 접속

  • 동일한 애플리케이션의 다수의 파드의 접속을 용이하게 하기 위한 서비스에 접속
  • 고정 접속(호출) 방법을 제공 : 흔히 말하는 ‘고정 VirtualIP’ 와 ‘Domain주소’ 생성

 

 

3. 서비스(NodePort Type) 연결 : 외부 클라이언트가 서비스를 통해서 클러스터 내부의 파드로 접속

  • 서비스(NodePort Type)의 일부 단점을 보완한 서비스(LoadBalancer Type) 도 있습니다!

 

 

 

서비스 종류

 

ClusterIP 타입

 

이번스터디의 핵심: iptables 에 의해 트래픽이 변경된다. 라는 게 핵심

 

 

 

NodePort 타입

 

 

 

 

LoadBalancer 타입

 

 

 

Service(ClusterIP) 설명

요약 : 클라이언트(TestPod)가 'CLUSTER-IP' 접속 시 해당 노드의 iptables 룰(랜덤 분산)에 의해서 DNAT 처리가 되어 목적지(backend) 파드와 통신

 

 

iptables 분산룰(정책)은 모든 노드에 자동으로 설정됨

 

 

10.96.0.1 은 CLUSTER-IP 주소

  • 클러스터 내부에서만 'CLUSTER-IP' 로 접근 가능 ⇒ 서비스에 DNS(도메인) 접속도 가능!
  • 서비스(ClusterIP 타입) 생성하게 되면, apiserver → (kubelet) → kube-proxy → iptables 에 rule(룰)이 생성됨
  • 모든 노드(마스터 포함)에 iptables rule 이 설정되므로, 파드에서 접속 시 해당 노드에 존재하는 iptables rule 에 의해서 분산 접속이 됨

 

 

 

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**

 

 

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**

 

 

 

svc-clusterip.yaml

**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

 

요약

⇒ spec.ports.port 는 service 가 받는 port번호

⇒ spec.ports.targetPort 는 service 에 연결된 pod 가 받는 port 번호

 

 

 

Service(ClsuterIP) 접속확인

# 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

⇒ Service(ClusterIP) 로 접근할때에는 iptablers 에 의해 영향을 받으며, 이때는 랜덤 부하분산 접속이 진행된다.

 

 

 

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
...**

 

 

Pod 1개 장애 발생 시 동작

기초상태

Pod 모니터링

# 터미널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"

Pod삭제 후 확인

# (방안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

클러스터 외부에서 서비스로 접근가능한지 확인

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

 

 

'외부활동' 카테고리의 다른 글

[OSSCA] LoxiLB 1주차: yaml, swagger, cmd, api  (0) 2024.09.28
[KANS3] Pod & Pause container  (1) 2024.09.07
[KANS3] Kubernetes, kind  (0) 2024.09.07
[KANS3] 도커 없이 컨테이너 만들기  (5) 2024.09.01
[KANS3] Docker  (2) 2024.09.01
jjongguet