KANS 3기 4주차 - Service : ClusterIP, NodePort

Oasis·2024년 9월 23일

KANS

목록 보기
4/9

가시다님의 KANS [3기] 스터디 내용을 정리한 포스트 입니다.

실습 환경 구성

  • mac 환경에서 kind 설치, Controll노드 1대, worker 노드 3대로 구성함
  • 실습 환경은 K8S v1.31.0 , CNI(Kindnet, Direct Routing mode) , IPTABLES proxy mode
  • 노드(실제로는 컨테이너) 네트워크 대역 : 172.17.0.0/16
  • 파드 사용 네트워크 대역 : 10.10.0.0/16 ⇒ 각각 10.10.1.0/24, 10.10.2.0/24, 10.10.3.0/24, 10.10.4.0/24
  • 서비스 사용 네트워크 대역 : 10.200.1.0/24

#
cat <<EOT> kind-svc-1w.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  "InPlacePodVerticalScaling": true
nodes:
- role: control-plane
  labels:
    mynode: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
- role: worker
  labels:
    mynode: worker1
- role: worker
  labels:
    mynode: worker2
- role: worker
  labels:
    mynode: worker3
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOT

# k8s 클러스터 설치
kind create cluster --config kind-svc-1w.yaml --name myk8s --image kindest/node:v1.31.0
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping git vim arp-scan -y'
for i in worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping -y'; echo; done


# k8s v1.31.0 버전 확인
kubectl get node

# 노드 labels 확인
kubectl get nodes -o jsonpath="{.items[*].metadata.labels}" | grep mynode
kubectl get nodes -o jsonpath="{.items[*].metadata.labels}" | jq | grep mynode

# kind network 중 컨테이너(노드) IP(대역) 확인 : 172.18.0.2~ 부터 할당되며, control-plane 이 꼭 172.18.0.2가 안될 수 도 있음
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/myk8s-control-plane 172.17.0.4
/myk8s-worker 172.17.0.3
/myk8s-worker2 172.17.0.5
/myk8s-worker3 172.17.0.2

# 파드CIDR 과 Service 대역 확인 : CNI는 kindnet 사용
kubectl get cm -n kube-system kubeadm-config -oyaml | grep -i subnet
      podSubnet: 10.10.0.0/16
      serviceSubnet: 10.200.1.0/24
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"

# feature-gates 확인 : https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/
kubectl describe pod -n kube-system | grep feature-gates
      --feature-gates=InPlacePodVerticalScaling=true

# 노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath="{.items[*].spec.podCIDR}"
10.10.0.0/24 10.10.4.0/24 10.10.3.0/24 10.10.1.0/24

# kube-proxy configmap 확인
kubectl describe cm -n kube-system kube-proxy
...
mode: iptables
iptables:
  localhostNodePorts: null
  masqueradeAll: false
  masqueradeBit: null
  minSyncPeriod: 1s
  syncPeriod: 0s
...


# 노드 별 네트워트 정보 확인 : CNI는 kindnet 사용
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ls /opt/cni/bin/; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i cat /etc/cni/net.d/10-kindnet.conflist; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c route; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c addr; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c -4 addr show dev eth0; echo; done

# iptables 정보 확인
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-control-plane  iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker  iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker2 iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker3 iptables -t $i -S ; echo; done

# 각 노드 bash 접속
docker exec -it myk8s-control-plane bash
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
docker exec -it myk8s-worker3 bash
----------------------------------------

exit
----------------------------------------

#
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
docker ps
docker exec -it mypc ping -c 1 172.18.0.1
for i in {1..5} ; do docker exec -it mypc ping -c 1 172.18.0.$i; done
docker exec -it mypc zsh
-------------
ifconfig
ping -c 1 172.18.0.2
exit
-------------

# kube-ops-view 설치
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30000 --set env.TZ="Asia/Seoul" --namespace kube-system

# myk8s-control-plane 배치
kubectl -n kube-system edit deploy kube-ops-view
---
spec:
  ...
  template:
    ...
    spec:
      nodeSelector:
        mynode: control-plane
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        operator: "Equal"
        effect: "NoSchedule"
---

# 설치 확인
kubectl -n kube-system get pod -o wide -l app.kubernetes.io/instance=kube-ops-view

1. Service

Kubernetes의 Service 오브젝트는 Kubernetes 클러스터 내에서 Pod들 간의 통신을 안정적이고 동적으로 연결해주기 위해 도입되었습니다. Pod는 일시적인 존재로, 스케일링이나 장애 복구 시 IP 주소가 계속 바뀔 수 있기 때문에, 다른 Pod나 클라이언트가 특정 Pod에 안정적으로 접근하기 어려운 문제점이 발생합니다. 이러한 문제를 해결하기 위해 Service는 고정된 IP 주소를 제공하고, 여러 Pod를 대상으로 부하 분산을 적용하여 Pod의 동적 변화를 관리하는 역할을 합니다.

1.1 Service 오브젝트의 탄생 배경

•	Pod의 휘발성 문제: Pod는 언제든지 스케일링이나 장애 복구에 의해 삭제되고 재생성될 수 있으며, 이 과정에서 Pod의 IP 주소가 바뀝니다. 따라서 특정 Pod의 IP 주소를 직접 참조하는 것은 바람직하지 않으며, 안정적인 연결을 보장하기 어렵습니다.
•	Pod 간 통신의 복잡성: 여러 Pod가 하나의 애플리케이션을 구성할 때, Pod들 간의 통신을 용이하게 관리하고 부하 분산하는 것이 필요합니다. Pod의 IP가 계속 바뀌는 상황에서 이를 수동으로 관리하는 것은 비효율적입니다.
•	부하 분산 필요성: 애플리케이션이 여러 인스턴스(Pod)로 구성된 경우, 각 요청을 적절히 분산하여 처리해야 하는데, Service는 이러한 부하 분산(Load Balancing) 역할도 담당합니다.

따라서 Service는 Pod의 IP 주소 변경 문제를 해결하고, 여러 Pod에 대한 안정적이고 일관된 접근 방식을 제공하기 위해 탄생했습니다. 이를 통해 클러스터 내에서 네트워크 통신을 간소화하고, 부하 분산, 서비스 디스커버리 등의 기능을 지원합니다.

1.2 Service 타입

주요 Service 타입으로는 ClusterIP, NodePort, LoadBalancer, ExternalName이 있으며, 각각 클러스터 내부와 외부 간의 통신 요구사항에 맞게 사용됩니다.

1.2.1 ClusterIP

•	기본적으로 설정되는 서비스 타입입니다. 클러스터 내부에서만 접근 가능한 가상 IP 주소를 제공합니다.
•	용도: 클러스터 내에서만 Pod 간에 통신이 필요할 때 사용합니다.
•	특징:
•	외부에서는 접근 불가능 (클러스터 내부에서만 접근 가능).
•	클러스터 내부에서 고정된 IP를 통해 Pod들에 대한 부하 분산을 지원.
•	사용 예: 백엔드 서비스 간의 통신, 예를 들어, 클러스터 내에서 데이터베이스에 접근할 때

1.2.2 NodePort

•	각 노드의 특정 포트를 통해 외부에서 클러스터 내부로 접근할 수 있도록 하는 서비스 타입입니다. 클러스터 외부에서 노드의 IP와 지정된 포트로 접근할 수 있습니다.
•	용도: 클러스터 외부에서 직접 노드의 IP와 포트를 통해 애플리케이션에 접근해야 할 때 사용합니다.
•	특징:
•	노드의 포트를 고정으로 할당하여 외부 트래픽을 받을 수 있습니다.
•	NodePort는 30000~32767 범위 내의 포트를 할당받습니다.
•	사용 예: 개발 및 테스트 환경에서 간단하게 외부에서 접근할 수 있도록 설정할 때

1.2.3 LoadBalancer

•	클라우드 환경에서 사용할 수 있는 서비스 타입으로, 외부 로드 밸런서를 자동으로 프로비저닝하여 클러스터 외부에서 접근할 수 있도록 합니다. 주로 AWS, GCP, Azure와 같은 클라우드 플랫폼에서 지원됩니다.
•	용도: 외부에서 트래픽을 받을 때, 클라우드 제공자의 로드 밸런서를 통해 관리하고 싶을 때 사용합니다.
•	특징:
•	클라우드 환경에서 자동으로 외부 로드 밸런서가 생성됩니다.
•	노드 포트와 클러스터 IP 서비스가 함께 구성되어 동작합니다.
•	부하 분산과 외부 트래픽 관리를 쉽게 할 수 있습니다.
•	사용 예: 프로덕션 환경에서 외부 트래픽을 처리하는 웹 애플리케이션

2. ClusterIP

2.1 통신 흐름 개요

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

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


2.2 ClusterIP 통신 흐름 보기

2.2.1 실습 구성

  1. 목적지(backend) 파드(Pod) 생성
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

2.클라이언트(TestPod) 생성

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
  1. 서비스(ClusterIP) 생성
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

4.생성 및 확인

# 모니터링
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

# kubectl describe svc svc-clusterip
Name:                     svc-clusterip
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=webpod
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.200.1.97
IPs:                      10.200.1.97
Port:                     svc-webport  9000/TCP
TargetPort:               80/TCP
Endpoints:                10.10.3.3:80,10.10.2.2:80,10.10.1.3:80
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>

# 서비스 생성 시 엔드포인트를 자동으로 생성, 물론 수동으로 설정 생성도 가능
kubectl get endpoints svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip

2.2.2 서비스(ClusterIP) 접속 확인

  • 클라이언트(net-pod) Shell 에서 접속 테스트 & 서비스(ClusterIP) 부하분산 접속 확인
# service IP 변수 저장
$ SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP})

# 클라이언트에서 service IP로 100회 접속 확인
$ 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"
     34 Hostname: webpod2
     33 Hostname: webpod3
     33 Hostname: webpod1

endpoint 3대로 균등하게 부하분산되어 접속되는 것을 볼 수 있다.

  • 각 워커노드에서 패킷 덤프 확인

    각 워커노드의 eth0 과 vethx에 tcpdump를 걸어두고 클러아이언트(net-pod)에서 서비스로 ping 테스트를 수행합니다. 이후 덤프 분석을 통해서 DNAT 내역도 확인해 보겠습니다.
# 1대 혹은 3대 bash 진입 후 tcpdump 해둘 것
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
docker exec -it myk8s-worker3 bash

##네트워크 패킷 분석
tcpdump -i eth0 tcp port 80 -nnq
tcpdump -i eth0 tcp port 9000 -nnq
tcpdump -i $VETH1 tcp port 80 -nnq

## 클러아이언트(net-pod)에서 서비스로 ping 테스트(10회)
kubectl exec -it net-pod -- zsh -c "for i in {1..10};   do curl -s $SVC1:9000 | grep Hostname; sleep 1; done"

[결과1] 각 워커노드의 eth0/80port에 tcpdump 모니터링

  • "10.10.0.5.33690 > 10.10.3.3.80", "10.10.3.3.80 > 10.10.0.5.33690" 패킷 확인됨

[결과2] 각 워커노드의 eth0/9000port에 tcpdump 모니터링

  • 패킷이 확인이 안됨. 이유는 control plane의 iptables에서 target이 "service IP:9000"에서 "endpoint IP:80"으로 변경되었기 때문입니다.

[결과3] 각 워커노드의 vethx/80port에 tcpdump 모니터링

  • "10.10.0.5.33690 > 10.10.3.3.80", "10.10.3.3.80 > 10.10.0.5.33690" 패킷 확인됨
  • 컨트롤 노드에서 패킷 덤프 확인

    출발지 net-pod에서 serviceIP:Port로 요청되는것을 확인하기 위해서 컨트롤노드의 vethx에 tcpdump를 걸어 목적지가 "10.200.1.97:9000"으로 보이는지 패킷을 확인해보겠습니다.
# 컨트롤 노드에 접속
$docker exec -it myk8s-control-plane bash

$ip -c route
default via 172.17.0.1 dev eth0
...
10.10.0.5 dev vethae32e82d scope host
...

# 컨트롤 노드의 "net-pod"와 연결된 vethx
$VETH1=vethae32e82d ; echo $VETH1
vethae32e82d

# ngrep을 통한 패킷 확인
$ngrep -tW byline -d $VETH1 '' 'tcp port 9000'
...
########
T 2024/09/24 07:14:53.981852 10.10.0.5:56716 -> 10.200.1.97:9000 [AP] #144
GET / HTTP/1.1.
Host: 10.200.1.97:9000.
User-Agent: curl/8.7.1.
Accept: */*.
.

##
T 2024/09/24 07:14:53.983120 10.200.1.97:9000 -> 10.10.0.5:56716 [AP] #146
HTTP/1.1 200 OK.
Date: Tue, 24 Sep 2024 07:14:53 GMT.
Content-Length: 191.
Content-Type: text/plain; charset=utf-8.
.
Hostname: webpod1
IP: 127.0.0.1
IP: ::1
IP: 10.10.3.3
IP: fe80::cc8b:56ff:fe5e:12c9
RemoteAddr: 10.10.0.5:56716
GET / HTTP/1.1.
Host: 10.200.1.97:9000.
User-Agent: curl/8.7.1.
Accept: */*.
...

[결과4] 컨트롤 노드의 vethx/9000port에 tcpdump 모니터링

목적지가 "10.200.1.97:9000"(ServiceIP:Port)로 지정된 것을 확인 할 수 있습니다.
"T 2024/09/24 07:14:53.981852 10.10.0.5:56716 -> 10.200.1.97:9000 [AP] #144"

2.3 IPTABLES 정책 확인

ClusterIP 서비스 생성 시 적용되는 주요 IPTABLES NAT 테이블의 규칙과 ClusterIP를 통한 통신 흐름을 알아보겠습니다.

  • testpod 에서 ClusterIP 주소인 10.200.1.97:9000(TCP) 로 접속을 시도
  • 출발지 IP는 testpod 의 IP인 10.10.0.3 이고, 출발지 Port 는 랜덤포트가 할당됨
  • 마스터 노드에 iptables 의 NAT 테이블에 규칙과 매칭되어 목적지 IP 와 목적지 Port가 변환됨
  • 목적지 IP는 app=webpod 레이블을 가지고 있는 파드 3개 가 대상이며, 랜덤 부하분산 선택됨
  • NAT 가 수행 시 NAT 연결 정보를 기록하고 webpod 를 통해 되돌아온 트래픽을 확인 후 testpod 로 전달함
  • IPTABLES 규칙
  • Iptables 체인 chain 은 규칙의 연결 그룹입니다. 규칙에 매칭이 되면 해당 패킷을 어떻게 처리(ange) 할 지 결정함
  • Iptables 정책 적용 순서 : PREROUTING → KUBE-SERVICES → KUBE-SVC-### → KUBE-SEP-#<파드1> , KUBE-SEP-#<파드2> , KUBE-SEP-#<파드3>

  • IPTABLES 상세 확인
# 컨트롤플레인에 접속하여 확인
docker exec -it myk8s-control-plane bash

#PREROUTING 확인
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list PREROUTING
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-SERVICES  0    --  *      *       0.0.0.0/0            0.0.0.0/0            /* 

#KUBE-SERVICES 확인
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SERVICES
Chain KUBE-SERVICES (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-SVC-KBDEBIL6IU6WL7RF  6    --  *      *       0.0.0.0/0            10.200.1.97          /* default/svc-clusterip:svc-webport cluster IP */ tcp dpt:9000

#KUBE-SVC-YYY 확인
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF
Chain KUBE-SVC-KBDEBIL6IU6WL7RF (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  6    --  *      *      !10.10.0.0/16         10.200.1.97          /* default/svc-clusterip:svc-webport cluster IP */ tcp dpt:9000
    0     0 KUBE-SEP-TBW2IYJKUCAC7GB3  0    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport -> 10.10.1.2:80 */ statistic mode random probability 0.33333333349
    0     0 KUBE-SEP-DOIEFYKPESCDTYCH  0    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport -> 10.10.2.2:80 */ statistic mode random probability 0.50000000000
    0     0 KUBE-SEP-K7ALM6KJRBAYOHKX  0    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport -> 10.10.3.3:80 */

#KUBE-SEP's 확인
# SVC-### 에서 랜덤 확률(대략 33%)로 SEP(Service EndPoint)인 각각 파드 IP로 DNAT
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SEP-TBW2IYJKUCAC7GB3
Chain KUBE-SEP-TBW2IYJKUCAC7GB3 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.1.2            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
   36  2160 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.1.2:80

root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SEP-DOIEFYKPESCDTYCH
Chain KUBE-SEP-DOIEFYKPESCDTYCH (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.2.2            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
   41  2460 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.2.2:80

root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SEP-K7ALM6KJRBAYOHKX
Chain KUBE-SEP-K7ALM6KJRBAYOHKX (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.3.3            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
   23  1380 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.3.3:80

#KUBE-POSTROUTING 확인
#0x4000 마킹 되어 있지 않으니 RETURN 되고 그냥 빠져나가서 SNAT 되지 않음
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list POSTROUTING; echo ; iptables -v --numeric --table nat --list KUBE-POSTROUTING
Chain POSTROUTING (policy ACCEPT 27313 packets, 1639K bytes)
 pkts bytes target     prot opt in     out     source               destination
27313 1639K KUBE-POSTROUTING  0    --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */

Chain KUBE-POSTROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination
 2439  146K RETURN     0    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
    0     0 MARK       0    --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
    0     0 MASQUERADE  0    --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */ random-fully
    
#모든 규칙이 노든 노드에 추가되어 있다.
#local console에서 수행함
$ 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-SEP-TBW2IYJKUCAC7GB3 ; echo; done
>> node myk8s-control-plane <<
Chain KUBE-SEP-TBW2IYJKUCAC7GB3 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.1.2            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
   36  2160 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.1.2:80

>> node myk8s-worker <<
Chain KUBE-SEP-TBW2IYJKUCAC7GB3 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.1.2            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
    0     0 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.1.2:80

>> node myk8s-worker2 <<
Chain KUBE-SEP-TBW2IYJKUCAC7GB3 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.1.2            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
    0     0 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.1.2:80

>> node myk8s-worker3 <<
Chain KUBE-SEP-TBW2IYJKUCAC7GB3 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  0    --  *      *       10.10.1.2            0.0.0.0/0            /* default/svc-clusterip:svc-webport */
    0     0 DNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport */ tcp to:10.10.1.2:80

2.4 파드 장애시 동작 확인

Service Endpoints 3대중에 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"

정상적인 상태에서는 3대의 파드에 골고루 접속이 되는 것을 확인 할 수 있습니다.

  • 파드 1개 삭제 후 확인
#파드3번에 레이블 삭제
kubectl get pod --show-labels
NAME      READY   STATUS    RESTARTS        AGE    LABELS
net-pod   1/1     Running   3 (6h50m ago)   3d4h   <none>
webpod1   1/1     Running   3 (6h50m ago)   3d4h   app=webpod
webpod2   1/1     Running   3 (6h50m ago)   3d4h   app=webpod
webpod3   1/1     Running   2 (6h51m ago)   3d4h   app=webpod

## 레이블(라벨)의 키값 바로 뒤에 하이픈(-) 입력 시 해당 레이블 삭제됨
kubectl label pod webpod3 app-
kubectl get pod --show-labels

# (방안2) 결과 확인 후 파드3번에 다시 레이블 생성
kubectl label pod webpod3 app=webpod

파드3번에 레이블을 삭제한 후 접속 테스트를 수행한 결과 파드1~2번에만 접속이 된 것으로 확인됩니다. 서비스 오브젝트를 이용할때 부하분산 뿐만아니라 서비스의 가용성도 확보할 수 있다는 것으로 확인하였습니다.

2.5 세션어피니티

Kubernetes에서 sessionAffinity를 사용하는 이유는 클러스터 내에서 특정 클라이언트의 요청이 항상 같은 파드로 전달되도록 보장하기 위해서입니다. 주로 상태를 저장하는 애플리케이션이나 세션 데이터를 유지해야 하는 경우에 사용됩니다. sessionAffinity가 활성화되면, 클라이언트의 첫 번째 요청을 처리한 파드가 이후의 요청도 계속 처리하게 되어 세션 일관성을 유지할 수 있습니다.

sessionAffinity는 ClusterIP 서비스에서 설정되며, sessionAffinity를 clientIP 또는 None 값을 설정할 수 있습니다. clientIP로 설정하면 클라이언트의 IP 주소를 기준으로 세션을 유지합니다.

  • 설정 및 파드 접속 확인
# 기본 정보 확인
kubectl get svc svc-clusterip -o yaml | grep sessionAffinity

# 반복 접속
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"

# sessionAffinity: ClientIP 설정 변경
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 -

#
kubectl get svc svc-clusterip -o yaml
...
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
...

# 클라이언트(TestPod) Shell 실행
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"

"sessionAffinity: ClientIP" 설정 후 서비스 접속 요청 시 "webpod2"으로만 접속이 됩니다.

  • Iptables 정책 적용 확인
    모든 노드에 고정 연결 관련 룰이 추가됨(기본 10800초=3시간 유지)
#local console에서 수행
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i iptables -t nat -S | grep recent; echo; done

  • 최대 세션 고정 시간 설정 변경
#설정 변경 - 30초로
kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinityConfig":{"clientIP":{"timeoutSeconds":30}}}}'

#설정 확인
kubectl get svc svc-clusterip -o yaml | grep sessionAffinity -A3

# iptables 확인
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i iptables -t nat -S | grep recent; echo; done

3. NodePort

NodePort는 Kubernetes에서 클러스터 외부에서 서비스를 접근할 수 있게 해주는 Service 타입 중 하나입니다. 기본적으로 Kubernetes 클러스터 내부의 Pod는 외부에서 직접 접근할 수 없기 때문에, 이를 해결하기 위해 여러 가지 방식의 서비스 타입을 제공합니다. 그 중 하나가 NodePort입니다.

NodePort를 사용하는 이유:

•	클러스터 외부에서의 접근이 필요한 경우.
•	간단한 외부 트래픽 처리가 필요할 때. (로드 밸런서 없이 간단히 노드의 포트를 열어 접근)
•	테스트 환경에서 간단하게 설정하고 서비스를 외부에 노출할 때 유용합니다.

하지만 프로덕션 환경에서는 보통 NodePort 대신 LoadBalancer 타입을 사용하는 것이 일반적입니다. NodePort는 클러스터 외부에서 직접 노드에 접근할 수 있게 하기 때문에, 보안과 확장성 측면에서 다소 제약이 있을 수 있습니다.

3.1 통신 흐름

외부 클라이언트가 '노드IP:NodePort' 접속 시 해당 노드의 iptables 룰에 의해서 SNAT/DNAT 되어 목적지 파드와 통신 후 리턴 트래픽은 최초 인입 노드를 경유해서 외부로 되돌아갑니다.

  • 외부에서 클러스터의 '서비스(NodePort)' 로 접근 가능 → 이후에는 Cluster IP 통신과 동일함
  • 모드 노드(마스터 포함)에 iptables rule 이 설정되므로, 모든 노드에 NodePort 로 접속 시 iptables rule 에 의해서 분산 접속이 됨
  • Node 의 모든 Loca IP(Local host Interface IP : loopback 포함) 사용 가능 & Local IP를 지정 가능
  • 쿠버네티스 NodePort 할당 범위 기본 (30000-32767) & 변경하기


3.2 실습 환경 구성

  • 목적지(backend) 디플로이먼트(Pod) 파일 생성 : echo-deploy.yaml
cat <<EOT> echo-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-echo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: kans-websrv
        image: mendhak/http-https-echo
        ports:
        - containerPort: 8080
EOT
  • 서비스(NodePort) 파일 생성 : svc-nodeport.yaml
cat <<EOT> svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc-nodeport
spec:
  ports:
    - name: svc-webport
      port: 9000
      targetPort: 8080
  selector:
    app: deploy-websrv
  type: NodePort
EOT
  • 생성 및 확인
# 생성
kubectl apply -f echo-deploy.yaml,svc-nodeport.yaml

# 모니터링(터미널2에서)
watch -d 'kubectl get pod -owide;echo; kubectl get svc,ep svc-nodeport'

# 배포 확인
kubectl get deploy,pod -o wide
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS    IMAGES                    SELECTOR
deployment.apps/deploy-echo   3/3     3            3           5m15s   kans-websrv   mendhak/http-https-echo   app=deploy-websrv

NAME                               READY   STATUS    RESTARTS   AGE     IP          NODE            NOMINATED NODE   READINESS GATES
pod/deploy-echo-5c689d5454-qffkc   1/1     Running   0          5m15s   10.10.3.4   myk8s-worker    <none>           <none>
pod/deploy-echo-5c689d5454-qrxzw   1/1     Running   0          5m15s   10.10.1.3   myk8s-worker3   <none>           <none>
pod/deploy-echo-5c689d5454-zkzjs   1/1     Running   0          5m15s   10.10.2.3   myk8s-worker2   <none>           <none

# 아래 30158은 서비스(NodePort) 정보
kubectl get svc svc-nodeport
NAME           TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
svc-nodeport   NodePort   10.200.1.171   <none>        9000:32186/TCP   5m35s

kubectl get endpoints svc-nodeport
NAME           ENDPOINTS                                              AGE
svc-nodeport   10.10.1.3:8080,10.10.2.3:8080,10.10.3.4:8080   5m47s

#
kubectl describe svc svc-nodeport
Name:                     svc-nodeport
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=deploy-websrv
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.200.1.171
IPs:                      10.200.1.171
Port:                     svc-webport  9000/TCP   #서비스 포트
TargetPort:               8080/TCP  #endpoint pod의 포트
NodePort:                 svc-webport  32186/TCP   #각 노드의 포트
Endpoints:                10.10.1.3:8080,10.10.3.4:8080,10.10.2.3:8080
Session Affinity:         None
External Traffic Policy:  Cluster
Internal Traffic Policy:  Cluster
Events:                   <none>

3.3 서비스 접속 확인

외부 클라이언트(mypc 컨테이너)에서 접속 테스트 & 서비스(NodePort) 부하분산 접속 확인해본다.

(1) client 가상머신에서 마스터노드에 NodePort로 접속을 시도합니다. 이때 출발지 IP는 client 가상머신의 IP인 192.168.10.200 이고, 출발지 Port 는 랜덤포트가 할당됩니다. 목적지 IP는 마스터노드의 IP인 192.168.10.10 이고, 목적 지 Port 는 NodePort 서비스 생성 시 할당된 30286(포트 번호는 실습 환경에 따라 다름)입니다.

(2) 마스터 노드에 iptables 의 NAT 테이블에 규칙과 매칭되어 목적지 IP와 목적지 Port는 변환(Destination NAT)이 됩니다. 목적지 IP는 app=deploy-websrv 레이블 을 가지고 있는 파드 3개가 대상이 되며, 랜덤 부하분산 선택이 됩니다.
현재 실습 환경에서는 마스터 노드에는 목적지 파드가 배포되어 있지 않습니다. 즉, 마스터노드는 파드가 배포된 워커노드에 전달을 하게 됩니다. 이때 출발지 IP는 마스터 노드의 IP인192.168.10.10 로 변환(Source NAT)됩니다.
부하분산시 워커노드1에 배포된 파드로 선택 된 경우, 172.16.158.1:8080 (TargetPort) 로 요청을 전달합니다. 목적지 파드에서 확인되는 출발지 IP는 client 가상머신의 IP가 아니라, 마스터노드의 IP 입니다.

# NodePort 확인
kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}'
32186

# NodePort 를 변수에 지정
NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
echo $NPORT

# 현재 k8s 버전에서는 포트 Listen 되지 않고, iptables rules 처리됨
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ss -tlnp; echo; done
#32186 port로 Listen되는 것은 없음

# 파드 로그 실시간 확인 (웹 파드에 접속자의 IP가 출력)
kubectl logs -l app=deploy-websrv -f

# 외부 클라이언트(mypc 컨테이너)에서 접속 시도를 해보자

# 노드의 IP와 NodePort를 변수에 지정
CNODE=172.17.0.5
NODE1=172.17.0.2
NODE2=172.17.0.3
NODE3=172.17.0.4

# 서비스(NodePort) 부하분산 접속 확인
docker exec -it mypc curl -s $CNODE:$NPORT | jq # headers.host 주소는  그런거죠?
for i in $CNODE $NODE1 $NODE2 $NODE3 ; do echo ">> node $i <<"; docker exec -it mypc curl -s $i:$NPORT; echo; done

# 컨트롤플레인 노드에는 목적지 파드가 없는데도, 접속을 받아준다! 이유는?
# Iptables의 NAT 규칙에 의해서 해당 endpoint로 목적지가 변환되기 때문이다.
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $CNODE:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
 100   "hostname": "172.17.0.5",
  38     "hostname": "deploy-echo-5c689d5454-zkzjs"
  35     "hostname": "deploy-echo-5c689d5454-qffkc"
  27     "hostname": "deploy-echo-5c689d5454-qrxzw"
  
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE1:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
 100   "hostname": "172.17.0.2",
  37     "hostname": "deploy-echo-5c689d5454-qrxzw"
  35     "hostname": "deploy-echo-5c689d5454-zkzjs"
  28     "hostname": "deploy-echo-5c689d5454-qffkc"
  
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE2:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
 100   "hostname": "172.17.0.3",
  34     "hostname": "deploy-echo-5c689d5454-zkzjs"
  33     "hostname": "deploy-echo-5c689d5454-qrxzw"
  33     "hostname": "deploy-echo-5c689d5454-qffkc"
  
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE3:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
 100   "hostname": "172.17.0.4",
  35     "hostname": "deploy-echo-5c689d5454-qffkc"
  33     "hostname": "deploy-echo-5c689d5454-qrxzw"
  32     "hostname": "deploy-echo-5c689d5454-zkzjs"
  
# 아래 반복 접속 실행 해두자
docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $CNODE:$NPORT | grep hostname; date '+%Y-%m-%d %H:%M:%S' ; echo ;  sleep 1; done"

# NodePort 서비스는 ClusterIP  포함
# CLUSTER-IP:PORT  접속 가능! <- 컨트롤노드에서 아래 실행 해보자
kubectl get svc svc-nodeport
NAME           TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
svc-nodeport   NodePort   10.111.1.238   <none>         9000:30158/TCP   3m3s

CIP=$(kubectl get service svc-nodeport -o jsonpath="{.spec.clusterIP}")
CIPPORT=$(kubectl get service svc-nodeport -o jsonpath="{.spec.ports[0].port}")
echo $CIP $CIPPORT
docker exec -it myk8s-control-plane curl -s $CIP:$CIPPORT | jq

{
  "path": "/",
  "headers": {
    "host": "10.200.1.171:9000",
    "user-agent": "curl/7.88.1",
    "accept": "*/*"
  },
  "method": "GET",
  "body": "",
  "fresh": false,
  "hostname": "10.200.1.171",
  "ip": "::ffff:172.17.0.5",
  "ips": [],
  "protocol": "http",
  "query": {},
  "subdomains": [],
  "xhr": false,
  "os": {
    "hostname": "deploy-echo-5c689d5454-qrxzw"
  },
  "connection": {}

# mypc에서 CLUSTER-IP:PORT 로 접속 가능할까?  => 접속이 안된다!
docker exec -it mypc curl -s $CIP:$CIPPORT

3.4 Iptables 정책 확인

iptables 정책 적용 순서는 REROUTING → KUBE-SERVICES → KUBE-NODEPORTS → KUBE-EXT-#(MARK) → KUBE-SVC-# → KUBE-SEP-# ⇒ KUBE-POSTROUTING (MASQUERADE) 이다.
기본 규칙은 ClusterIP 서비스 동작 처리를 위한 규칙과 동일합니다. 차이점은 KUBE-NODEPORTS, KUBE-MARK-MASQ, KUBE-POSTROUTING 체인입니다.
핵심 내용은 NodePort에 매칭 시 마킹 Mark 후 출발지 IP를 해당 노드에 있는 네트워크 인터페이스의 IP로 변환(SNAT) 하여 목적지 파드로 전달합니다.

외부에서 쿠버네티스 NodePort 서비스를 통해서 접속 시에는 출발지 IP가 접속한 노드의 IP 로 변환 SNAT 되어서 목적지 파드로 접속하게 됩니다.
즉, 클라이언트 IP 가 보존되지 않습니다. 법적인 규제나 혹은 다른 이유로 인해 클 라이언트 IP 수집이 필요한 경우가 있습니다.

컨트롤플레인 노드 - iptables 분석

docker exec -it myk8s-control-plane bash
----------------------------------------

# 패킷 카운트 초기화
iptables -t nat --zero


PREROUTING 정보 확인
iptables -t nat -S | grep PREROUTING
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES


# 외부 클라이언트가 노드IP:NodePort 로 접속하기 때문에 --dst-type LOCAL 에 매칭되어서 -j KUBE-NODEPORTS 로 점프!
iptables -t nat -S | grep KUBE-SERVICES
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS


# KUBE-NODEPORTS 에서 KUBE-EXT-# 로 점프!
## -m nfacct --nfacct-name localhost_nps_accepted_pkts 추가됨 : 패킷 flow 카운팅 - 카운트 이름 지정 
NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
echo $NPORT
32186

iptables -t nat -S | grep KUBE-NODEPORTS | grep $NPORT
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -j KUBE-EXT-VTR7MTHHNMFZ3OFS

# (참고) nfacct 확인
nfacct list
{ pkts = 00000000000000000000, bytes = 00000000000000000000 } = ct_state_invalid_dropped_pkts;
{ pkts = 00000000000000000000, bytes = 00000000000000000000 } = localhost_nps_accepted_pkts;

## nfacct flush # 초기화


## KUBE-EXT-# 에서 'KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000' 마킹 및 KUBE-SVC-# 로 점프!
# docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $CNODE:$NPORT | grep hostname; date '+%Y-%m-%d %H:%M:%S' ; echo ;  sleep 1; done" 반복 접속 후 아래 확인
watch -d 'iptables -v --numeric --table nat --list KUBE-EXT-VTR7MTHHNMFZ3OFS'
iptables -t nat -S | grep "A KUBE-EXT-VTR7MTHHNMFZ3OFS"
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -m comment --comment "masquerade traffic for default/svc-nodeport:svc-webport external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -j KUBE-SVC-VTR7MTHHNMFZ3OFS


# KUBE-SVC-# 이후 과정은 Cluster-IP 와 동일! : 3개의 파드로 DNAT 되어서 전달
iptables -t nat -S | grep "A KUBE-SVC-VTR7MTHHNMFZ3OFS -"
-A KUBE-SVC-VTR7MTHHNMFZ3OFS -m comment --comment "default/svc-nodeport:svc-webport -> 10.10.1.3:8080" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-XEXGJWEWSC2GPNPZ
-A KUBE-SVC-VTR7MTHHNMFZ3OFS -m comment --comment "default/svc-nodeport:svc-webport -> 10.10.2.3:8080" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-2AEFSWYPQGZTCWEI
-A KUBE-SVC-VTR7MTHHNMFZ3OFS -m comment --comment "default/svc-nodeport:svc-webport -> 10.10.3.4:8080" -j KUBE-SEP-C5MCUQGLTHD455UI


POSTROUTING 정보 확인
# 마킹되어 있어서 출발지IP를 접속한 노드의 IP 로 SNAT(MASQUERADE) 처리함! , 최초 출발지Port는 랜덤Port 로 변경
iptables -t nat -S | grep "A KUBE-POSTROUTING"
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN  # 0x4000/0x4000 되어 있으니 여기에 매칭되지 않고 아래 Rule로 내려감
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully


# docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $CNODE:$NPORT | grep hostname; date '+%Y-%m-%d %H:%M:%S' ; echo ;  sleep 1; done" 반복 접속 후 아래 확인
watch -d 'iptables -v --numeric --table nat --list KUBE-POSTROUTING;echo;iptables -v --numeric --table nat --list POSTROUTING'

exit
----------------------------------------
  • 서비스(NodePort) 생성 시 kube-proxy 에 의해서 iptables 규칙이 모든 노드에 추가되는지 확인

    실습결과 iptables 규칙이 모든 노드에 추가되는 것을 확인 할 수 있습니다.

NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
docker exec -it myk8s-control-plane iptables -t nat -S | grep KUBE-NODEPORTS | grep $NPORT
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -j KUBE-EXT-VTR7MTHHNMFZ3OFS


for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i iptables -t nat -S | grep KUBE-NODEPORTS | grep $NPORT; echo; done
>> node myk8s-control-plane <<
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -j KUBE-EXT-VTR7MTHHNMFZ3OFS

>> node myk8s-worker <<
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -j KUBE-EXT-VTR7MTHHNMFZ3OFS

>> node myk8s-worker2 <<
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -j KUBE-EXT-VTR7MTHHNMFZ3OFS

>> node myk8s-worker3 <<
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 32186 -j KUBE-EXT-VTR7MTHHNMFZ3OFS

3.5 ExternalTrafficPolicy 설정

NodePort 서비스는 클라이언트 IP 가 보존되지 않습니다. 여러 이유로 클라이언트 IP 수집이 필요한 경우가 있습니다. 이럴때 externalTrafficPolicy 설정으로 외부 클라이언트의 IP가 보존 될 수 있도록 할 수 있습니다.

  • 설정 및 접속 확인
# 기본 정보 확인
kubectl get svc svc-nodeport -o json | grep 'TrafficPolicy"'
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster

# 기존 통신 연결 정보(conntrack) 제거 후 아래 실습 진행하자! : (모든 노드에서) conntrack -F
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i conntrack -F; echo; done
kubectl delete -f svc-nodeport.yaml
kubectl apply -f svc-nodeport.yaml

# externalTrafficPolicy: local 설정 변경
kubectl patch svc svc-nodeport -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl get svc svc-nodeport -o json | grep 'TrafficPolicy"'
	"externalTrafficPolicy": "Local",
  "internalTrafficPolicy": "Cluster",

# 파드 3개를 2개로 줄임
kubectl scale deployment deploy-echo --replicas=2

# 파드 존재하는 노드 정보 확인
kubectl get pod -owide
NAME                           READY   STATUS    RESTARTS   AGE   IP          NODE            NOMINATED NODE   READINESS GATES
deploy-echo-5c689d5454-qffkc   1/1     Running   0          20h   10.10.3.4   myk8s-worker    <none>           <none>
deploy-echo-5c689d5454-zkzjs   1/1     Running   0          20h   10.10.2.3   myk8s-worker2   <none>           <none>


# 파드 로그 실시간 확인 (웹 파드에 접속자의 IP가 출력)
kubectl logs -l app=deploy-websrv -f
    "protocol": "http",
    "query": {},
    "subdomains": [],
    "xhr": false,
    "os": {
        "hostname": "deploy-echo-5c689d5454-zkzjs"
    },
    "connection": {}
}
::ffff:172.17.0.5 - - [27/Sep/2024:09:03:24 +0000] "GET / HTTP/1.1" 200 397 "-" "curl/8.7.1"
    "protocol": "http",
    "query": {},
    "subdomains": [],
    "xhr": false,
    "os": {
        "hostname": "deploy-echo-5c689d5454-qffkc"
    },
    "connection": {}
}
::ffff:172.17.0.5 - - [27/Sep/2024:09:03:22 +0000] "GET / HTTP/1.1" 200 397 "-" "curl/8.7.1"

# 외부 클라이언트(mypc)에서 접속 시도

# 노드의 IP와 NodePort를 변수에 지정
CNODE=172.17.0.5
NODE1=172.17.0.2
NODE2=172.17.0.3
NODE3=172.17.0.4

## NodePort 를 변수에 지정
NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
echo $NPORT
31153

# 서비스(NodePort) 부하분산 접속 확인 : 파드가 존재하지 않는 노드로는 접속 실패!, 파드가 존재하는 노드(myk8s-worker, myk8s-worker2)는 접속 성공 및 클라이언트 IP 확인!
docker exec -it mypc curl -s --connect-timeout 1 $CNODE:$NPORT | jq
for i in $CNODE $NODE1 $NODE2 $NODE3 ; do echo ">> node $i <<"; docker exec -it mypc curl -s --connect-timeout 1 $i:$NPORT; echo; done

>> node 172.17.0.5 <<

>> node 172.17.0.2 <<
{
  "path": "/",
  "headers": {
    "host": "172.17.0.2:31153",
    "user-agent": "curl/8.7.1",
    "accept": "*/*"
  },
  "method": "GET",
  "body": "",
  "fresh": false,
  "hostname": "172.17.0.2",
  "ip": "::ffff:172.17.0.0",
  "ips": [],
  "protocol": "http",
  "query": {},
  "subdomains": [],
  "xhr": false,
  "os": {
    "hostname": "deploy-echo-5c689d5454-qffkc"
  },
  "connection": {}
}
>> node 172.17.0.3 <<
{
  "path": "/",
  "headers": {
    "host": "172.17.0.3:31153",
    "user-agent": "curl/8.7.1",
    "accept": "*/*"
  },
  "method": "GET",
  "body": "",
  "fresh": false,
  "hostname": "172.17.0.3",
  "ip": "::ffff:172.17.0.0",
  "ips": [],
  "protocol": "http",
  "query": {},
  "subdomains": [],
  "xhr": false,
  "os": {
    "hostname": "deploy-echo-5c689d5454-zkzjs"
  },
  "connection": {}
}
>> node 172.17.0.4 <<

# 목적지 파드가 배치되지 않은 노드는 접속이 안된다.
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $CNODE:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
...none...

docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE1:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
 100   "hostname": "172.17.0.2",
 100     "hostname": "deploy-echo-5c689d5454-qffkc"
 
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE2:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
 100   "hostname": "172.17.0.3",
 100     "hostname": "deploy-echo-5c689d5454-zkzjs"
 
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE3:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
...none...

# 아래 반복 접속 실행 해두자
docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $NODE2:$NPORT | grep hostname; date '+%Y-%m-%d %H:%M:%S' ; echo ;  sleep 1; done"

# (옵션) 노드에서 Network Connection
conntrack -E
conntrack -L --any-nat
  • 외부 클라이언트 -> 각각 접속 시: 각각 노드에 생성된 파드로만 접속됩니다.
for i in {1..100}; do curl -s $NODE1:$NPORT | grep hostname; done | sort | uniq -c | sort -n
 100     "hostname": "deploy-echo-5c689d5454-qffkc"
 100   "hostname": "172.17.0.2",

for i in {1..100}; do curl -s $NODE2:$NPORT | grep hostname; done | sort | uniq -c | sort -n
 100     "hostname": "deploy-echo-5c689d5454-zkzjs"
 100   "hostname": "172.17.0.3",
  • Iptables 정책 확인

    정책을 확인해 보면 각각 노드에 생성된 파드만 DNAT 되는것을 볼 수 있습니다.

docker exec -it myk8s-worker bash

$iptables -t nat -S | grep 31153
-A KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 31153 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-VTR7MTHHNMFZ3OFS
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 31153 -j KUBE-EXT-VTR7MTHHNMFZ3OFS

iptables -t nat -S | grep 'A KUBE-EXT-VTR7MTHHNMFZ3OFS'
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -s 10.10.0.0/16 -m comment --comment "pod traffic for default/svc-nodeport:svc-webport external destinations" -j KUBE-SVC-VTR7MTHHNMFZ3OFS
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -m comment --comment "masquerade LOCAL traffic for default/svc-nodeport:svc-webport external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -m comment --comment "route LOCAL traffic for default/svc-nodeport:svc-webport external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-VTR7MTHHNMFZ3OFS
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -j KUBE-SVL-VTR7MTHHNMFZ3OFS

iptables -t nat -S | grep 'A KUBE-SVL-VTR7MTHHNMFZ3OFS'
-A KUBE-SVL-VTR7MTHHNMFZ3OFS -m comment --comment "default/svc-nodeport:svc-webport -> 10.10.3.4:8080" -j KUBE-SEP-C5MCUQGLTHD455UI

iptables -t nat -S | grep 'A KUBE-SEP-C5MCUQGLTHD455UI'
-A KUBE-SEP-C5MCUQGLTHD455UI -s 10.10.3.4/32 -m comment --comment "default/svc-nodeport:svc-webport" -j KUBE-MARK-MASQ
-A KUBE-SEP-C5MCUQGLTHD455UI -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp -j DNAT --to-destination 10.10.3.4:8080

0개의 댓글