4주차 : Service : ClusterIP, NodePort
※ 본 게재 글은 gasida님의 KANS(Kubernetes Advanced Networking Study) 강의내용과 실습예제 및 kubernetes, Calico 공식 가이드 문서, "쿠버네티스입문" 서적 등을 참고하여 작성하였습니다.
1. Kubernete Service란?
- Kubenetes node에 생성되는 POD 들은 Private IP가 할당 되는데, 이는 POD의 lifecycle에 따라
생성,삭제를 반복하면서 변경된다. 따라서, node 외부에서 유동적인 POD에 고정적으로 접속하기 위해
Service 란 개념이 생겨났다.
☞ 한마디로 정의하면, POD 집합에서 실행중인 Application을 외부에서 접속할 수 있도록 네트웍을
외부로 노출시키는 추상화 기법이다.
1-1. 서비스의 종류
- Service 에는 3가지 대표 type이 존재한다.
1) Cluster IP
- 클러스터 안에 있는 다른 Pod들이 접근할 수 있도록 IP를 할당한다.
- 내부 IP만을 할당하기 때문에 클러스터 외부에서는 접근이 불가능하다.
2) Node Port
- 고정 포트로 Pod이 배포된 노드들의 IP에 서비스를 노출시킨다.
- NodePort 서비스는 ClusterIP 서비스를 자동으로 생성한다.
- NodeIP:NodePort 형태로 요청하여 클러스터 외부에서 NodePort 서비스에 접근할 수 있다.
3) Load Balancer
- 로드 밸런서는 서비스를 외부에 노출시키는 표준 방법이다.
- 구성에 물리 장비가 필요하다.
2. 서비스 동작상세
1) 로드밸런싱
♣ 외부 클라이언트에서 서비스를 통해 노드 내부의 POD
들에 접속이 가능하며, 이때 그림과 같이 내부 iptables 의
random 방식의 traffic 분산 connection이 맺어지게 된다.
2) 포트와 레이블, 셀렉터
♣ 서비스 생성 시, Port 와 Target Port, Label, Selector가
같이 생성되며, 이를 통해 외부에서 특정 application이
떠 있는 POD를 선택하여 접속할 수 있다.
3. Kube-Proxy 모드
▼ 그림으로 보는 kube-proxy 동작방식의 이해 - Ref. Link
Phase I :
API Server는 Endpoint 라는 추상화 계층을 생성하고, svc01 은 2개의 Endpoint 즉, PODs 와 연동된다.

Phase II :
이 모든 구성은 컨트롤 플레인의 일부분이며, 이것이 적용되었을 때 트래픽은 SVC01의 IP로 오는 트래픽을
EP01, EP02로 전달된다. 이 과정에서 각 노드에 있는 kubu-proxy가 연결 중계자 역할을 하게 된다.

Phase III :
이제 SVC01로 향하던 트래픽은 DNAT 규칙을 따라가게 되고 파드로 연결되며,
EP01, EP02는 기본적으로 POD의 IP임을 잊지 말라!!

♣ kube-proxy는 daemon-set 형태로 운영되며, 통신 동작의 설정을 관리합니다. 설치 후 API 서버와의 인증을 거쳐 새로운 서비스나
엔드포인트가 추가/삭제되면, API 서버는 이러한 변화를 kube-proxy 에게 전달합니다. 이를 전달 받은 kube-proxy는 NAT 규칙에
반영하고, 서비스가 해당 룰에 따라 내부 POD로 traffic을 전달하게 됩니다.
♣ User-Space 모드 ( 1주차 docker - 3.2.4. user space 상세참조) 에서 3가지 (Proxy, IPtables Proxy, IPVS Proxy) 방식으로 동작함
◈ Tip & Tips : kube-proxy에 Round-Robin DNS를 사용하지 않는 이유?!
- 역사적 관행 고려 (검증된 방식 선호)
: 전통적으로 레코드 TTL을 고려하지 않고, 만료된 이름 검색 결과를 캐싱하는 DNS 구현을 해 왔다. - 일부 어플리케이션 동작 특성고려
: 일부 앱은 DNS 검색을 한 번만 수행하고 결과를 무기한으로 캐시하는 경우가 있다. - 관리 측면의 효율성 고려
: 앱과 라이브러리가 적절히 재확인을 했다고 하더라도, DNS 레코드의 TTL이 낮거나 0이면
DNS에 부하가 높아 지면 관리하기가 어려워 질 수 있다.
1) User-Space Proxy 모드
♣ 외부 클라이언트로부터 layer-2 계층의 NIC를 거쳐
커널영역 넷필터와 사용자 영역의 kube-proxy까지
패킷이 전달되며, kube-proxy의 NAT 처리에 의해
연동된 라우팅 규칙을 적용하여 목적지 파드까지 이동함
2) IPtables Proxy 모드
♣ 커널 영역은 운영체제 실행을 위한 메모리 공간이며,
사용자 영역은 어플리케이션이 동작하는 메모리 공간이다.
♣ IPtables는 호스트의 방화벽/NAT 역활을 하며,
사용자 영역에서 지정하는 IPtables의 규칙을 커널영역의
NetFilter를 통해서 Traffic 통제를 하게 된다.
※ IPtables로 인해 관리가 어려움
3) IPVS Proxy 모드
♣ IPVS는NetFilter에서 동작하는 Layer4 로드벨런서
이며, IPtables보다 이해가 쉽고, 규칙을 줄일 수 있다.
♣ 다양한 분산 알고리즘을 제공하여, 3가지 모드 중
가장 실무에 적합한 모드이다.
4) nftables 프록시 모드
- (nftables API → netfilter subsystem) - https://netfilter.org/projects/nftables/
- 해당 모드는 리눅스 모드와 kernel ver.5.1.3 이상에서만 사용가능한 제약사항이 있다.
♣ Traffic 처리 시, kube-proxy는 the kernel netfilter subsystem 의 nftables API를 통해 forwarding 한다.
♣ 기본적인 부하분산은 random logic을 사용한다.
♣ Kubernetes 1.31 버전 이상에서 지원하며, 아직까지 사용 reference가 많지 않아 Beta version 정도로 이해하고
좀 더 많은 적용 사례를 확보 후 실무에 적용할 것을 권장한다.
5) eBPF 모드 + XDP
- 기존의 네트웍 모드에서 한 단계 진보한 형태로, 통신 시 오버헤드가 큰 kernel Structure 할당을 우회하여
패킷 traffic 처리가 빨라 차세대 기술에서 활용범위가 넓다.
♣ 외부에서 Packet 이 NIC를 통해 수신되면,
L2-layer 에서 L4-layer를 거쳐 Socket layer
까지 갔다가, 역으로 L3-layer를 거쳐 L2-layer(NIC)
에 도달하는 경로를 통해 traffic이 처리된다.
( Packet Traffic 처리를 위한 effort-loss 가 많은 구조 )
♣ 기존 netfilter/iptables 통신과 달리 L2-layer 단에
구성된 eBPF/XDP Filter를 통해 Traffic 이 처리되므로,
상당히 빠른 통신 구현이 가능하다!!
4. 실습하기 (따라하며 배우기)
☆ 이제 실습을 통해 정리된 내용들에 대해 이해를 하도록 하자!!
[ 실습환경 구성 ]
- vagrant + kind 환경 구성 ( gasida 님 2주차 - 실습환경 구성 참조 )
- vagrant 기동 실패 시, 2주차 Trouble-shooting 부분 참조
▶ 실습환경 개요 및 상세코드
K8S v1.31.0 , CNI(Kindnet, Direct Routing mode) , IPTABLES proxy mode - 노드(실제로는 컨테이너) 네트워크 대역 : 172.18.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
"MultiCIDRServiceAllocator": true
nodes:
- role: control-plane
labels:
mynode: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
runtime-config: api/all=true
- 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 bsdmainutils 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 bsdmainutils 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.18.0.4
/myk8s-worker 172.18.0.3
/myk8s-worker2 172.18.0.5
/myk8s-worker3 172.18.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
kubectl describe pod -n kube-system | grep runtime-config
--runtime-config=api/all=true
# MultiCIDRServiceAllocator : https://kubernetes.io/docs/tasks/network/extend-service-ip-ranges/
kubectl get servicecidr
NAME CIDRS AGE
kubernetes 10.200.1.0/24 2m13s
# 노드마다 할당된 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
----------------------------------------
# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind
# arp scan 해두기
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet
# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 직접 지정
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
docker ps
## 만약 kind 네트워크 대역이 다를 경우 위 IP 지정이 실패할 수 있으니, 그냥 IP 지정 없이 mypc 컨테이너 기동 할 것
## docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity
# 통신 확인
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
# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : macOS 사용자
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=2"
# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : Windows 사용자
echo -e "KUBE-OPS-VIEW URL = http://192.168.50.10:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://192.168.50.10:30000/#scale=2"
▶ 실행결과 한 눈에 보기
4-1. Cluster IP 실습
요약 : 클라이언트(TestPod)가 'CLUSTER-IP' 접속 시 해당 노드의 iptables 룰(랜덤 분산)에 의해서 DNAT 처리가 되어 목적지(backend) 파드와 통신이 가능하게 된다.
♣ 클러스터 내부에서만 'CLUSTER-IP' 로 접근 가능 ⇒ 서비스에 DNS(도메인) 접속도 가능
♣ 서비스(ClusterIP 타입) 생성하게 되면, apiserver → (kubelet) → kube-proxy → iptables 에 rule(룰)이 생성됨
♣ 모드 노드(마스터 포함)에 iptables rule 이 설정되므로, 파드에서 접속 시 해당 노드에 존재하는 iptables rule 에 의해서 분산 접속이 됨
▶ 실습환경 개요 및 상세코드
Step1. 목적지(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
Step2. 클라이언트(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
Step3. 서비스(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
Step4. 생성 및 확인
# 모니터링
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) 부하분산 접속 확인
# 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
[ 실행 결과 - 한 눈에 보기 ]



4-3. 세션 Affinity 실습
개요 : 세션 Affinity 는 PODs 에 구성 된 application 접속 시, 최초 접속한 경로와 대상을 기록해 두었다가
동일 Client가 접속 시 기존 접속한 Pod의 health 상태를 체크하여 정상일 경우, 해당 Pod로 세션
연결해 준다. ( Cluster Service IP의 random 부하분산 방식을 효율적으로 처리하기 위한 로직 )
▼ 설정 및 파드 접속 확인
# 기본 정보 확인
kubectl get svc svc-clusterip -o yaml
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"
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 delete svc, pods --all
4-3. Node Port 실습
요약 : 외부 클라이언트가 '노드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) & 변경하기 - 링크
▶ 실습환경 개요 및 상세코드
Step1. 목적지(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
Step2. 서비스(NodePort) 파일 생성 : svc-nodeport.yaml
cat <<EOT> svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-nodeport
spec:
ports:
- name: svc-webport
port: 9000 # 서비스 ClusterIP 에 접속 시 사용하는 포트 port 를 의미
targetPort: 8080 # 타킷 targetPort 는 서비스를 통해서 목적지 파드로 접속 시 해당 파드로 접속하는 포트를 의미
selector:
app: deploy-websrv
type: NodePort
EOT
Step3. 생성 및 확인
# 생성
kubectl apply -f echo-deploy.yaml,svc-nodeport.yaml
# 모니터링
watch -d 'kubectl get pod -owide;echo; kubectl get svc,ep svc-nodeport'
# 확인
kubectl get deploy,pod -o wide
# 아래 31493은 서비스(NodePort) 정보!
kubectl get svc svc-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc-nodeport NodePort 10.200.1.76 <none> 9000:31493/TCP 19s
kubectl get endpoints svc-nodeport
NAME ENDPOINTS AGE
svc-nodeport 10.10.1.4:8080,10.10.2.3:8080,10.10.3.3:8080 48s
# Port , TargetPort , NodePort 각각의 차이점의 의미를 알자!
kubectl describe svc svc-nodeport
[ 결과 확인 - 한 눈에 보기 ]

▶ 설정 및 파드 접속 확인
Step1. 외부 클라이언트(mypc 컨테이너)에서 접속 테스트 & 서비스(NodePort) 부하분산 접속 확인

[ 테스트 시나리오 ]
1. 출발지로 가상머신 (192.168.10.200) 을 생성하면 random Port 를 할당받는다.
목적지인 마스터 노드는 192.168.10.10 이고, Nodeport 는 30286을 사용한다.
2. 마스터 노드의 iptables 에 등록된 NAT 룰에 따라 목적지의 대상 IP/Port가 변환된다.
부하 방식은 random load-balancing 이다. (목적지 IP는 'app=deploy-websrv' 레이블)
3. 부하 분산 시, workernode #1 에 할당된 Pod(172.16.158.1:8080) 로 선택이 된다.
이 때, 최종 목적지 Pod에서 관찰되는 Client IP와 Port는 중간 마스터노드의 IP와
Port가 된다.
[ 실습 환경 구성 - 개요도 ]

▼ 세부 실습 Source 코드
- mypc 생성 추가
# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind
# arp Scan 걸어둔다.
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet
# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 직접 지정
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
docker ps
## 만약 kind 네트워크 대역이 다를 경우 위 IP 지정이 실패할 수 있으니, 그냥 IP 지정 없이 mypc 컨테이너 기동 할 것
## docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity
# NodePort 확인 : 아래 NodePort 는 범위내 랜덤 할당으로 실습 환경마다 다릅니다
kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}'
30353
# 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
## (참고) 아래처럼 예전 k8s 환경에서 Service(NodePort) 생성 시, TCP Port Listen 되었었음
root@k8s-m:~# ss -4tlnp | egrep "(Process|$NPORT)"
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:30466 0.0.0.0:* users:(("kube-proxy",pid=8661,fd=10))
# 파드 로그 실시간 확인 (웹 파드에 접속자의 IP가 출력)
kubectl logs -l app=deploy-websrv -f
# 외부 클라이언트(mypc 컨테이너)에서 접속 시도를 해보자
# 노드의 IP와 NodePort를 변수에 지정
## CNODE=<컨트롤플레인노드의 IP주소>
## NODE1=<노드1의 IP주소>
## NODE2=<노드2의 IP주소>
## NODE3=<노드3의 IP주소>
CNODE=172.18.0.A
NODE1=172.18.0.B
NODE2=172.18.0.C
NODE3=172.18.0.D
CNODE=172.18.0.2
NODE1=172.18.0.4
NODE2=172.18.0.5
NODE3=172.18.0.3
NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
echo $NPORT
# 서비스(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
# 컨트롤플레인 노드에는 목적지 파드가 없는데도, 접속을 받아준다! 이유는?
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $CNODE:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE1:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE2:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE3:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
# 아래 반복 접속 실행 해두자
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
# mypc에서 CLUSTER-IP:PORT 로 접속 가능할까?
docker exec -it mypc curl -s $CIP:$CIPPORT
# (옵션) 노드에서 Network Connection
conntrack -E
conntrack -L --any-nat
# (옵션) 패킷 캡쳐 확인
tcpdump..
[ 실행 결과 - 한 눈에 보기 ]



[ 마무리 ]
이번 과정을 통해 Cluster에서 사용하는 "Service"에 대한 개념과 이의 중심에 있는 kube-proxy, 대표적인 서비스 제공 entity 별 특성에
대해 이해 할 수 있었습니다.
네트웍 iptables 연동 내용을 따라가는 것은 아직까지 좀 힘든 부분이 있네요 ~ ^^;;
이번 돌아오는 수업과정에서 새로운 도전을 또 한번 기대 해 봅니다~
[ 도움이 되는 링크모음 ]
[ K8S Docs ]
https://kubernetes.io/docs/concepts/services-networking/
https://kubernetes.io/docs/reference/networking/virtual-ips/
https://kubernetes.io/docs/concepts/cluster-administration/proxies/
https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/
https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/
https://kubernetes.io/docs/tasks/network/extend-service-ip-ranges/
https://kubernetes.io/docs/tutorials/stateless-application/expose-external-ip-address/
https://kubernetes.io/docs/tutorials/services/connect-applications-service/
https://kubernetes.io/docs/tutorials/services/source-ip/
https://kubernetes.io/docs/reference/kubernetes-api/service-resources/
https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/
[Sam.0] Netfilter and iptables - Link , A Deep Dive into Iptables and Netfilter Architecture - Link
[커피고래] 서비스 네트워크 2 - 링크
[dramasamy] Life of Packet - 링크 , Istio - 링크