KANS3) Service 1
이번 주차는 서비스가 어떻게 파드로 패킷을 전달하는지와 서비스 종류에 대해 알아보는 주차였다. 테스트 환경은 가시다님이 제공주신 vagrant file을 사용하여 가상머신 올려서 사용했다.
테스트 환경 설정
curl -O https://raw.githubusercontent.com/gasida/KANS/main/kind/Vagrantfile
curl -O https://raw.githubusercontent.com/gasida/KANS/main/kind/init_cfg.sh
❯ vagrant up
❯ vagrant status
Current machine states:
default running (virtualbox)
...
❯ vagrant ssh
# kind cni를 사용하는 cluster를 배포
root@kind:~# 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:
EOTerviceSubnet: 10.200.1.0/24
root@kind:~# kind create cluster --config kind-svc-1w.yaml --name myk8s --image kindest/node:v1.31.0
Creating cluster "myk8s" ...
...
# docker ps로 노드 확인
(⎈|kind-myk8s:N/A) root@kind:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec7adbe5350c kindest/node:v1.31.0 "/usr/local/bin/entr…" 31 minutes ago Up 31 minutes myk8s-worker
f15642db9183 kindest/node:v1.31.0 "/usr/local/bin/entr…" 31 minutes ago Up 31 minutes 0.0.0.0:30000-30002->30000-30002/tcp, 127.0.0.1:42371->6443/tcp myk8s-control-plane
9418511b3e6f kindest/node:v1.31.0 "/usr/local/bin/entr…" 31 minutes ago Up 31 minutes myk8s-worker2
5d730ae049de kindest/node:v1.31.0 "/usr/local/bin/entr…" 31 minutes ago Up 31 minutes
# 컨트롤플레인와 워커노드에 필요한 툴들 설치
(⎈|kind-myk8s:N/A) root@kind:~# 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'
(⎈|kind-myk8s:N/A) root@kind:~# 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
# 노드에 붙은 라벨 확인
(⎈|kind-myk8s:N/A) root@kind:~# kubectl get nodes -o jsonpath="{.items[*].metadata.labels}" | jq | grep mynode
"mynode": "control-plane",
"mynode": "worker1"
"mynode": "worker2"
"mynode": "worker3"
# 노드 컨테이너 대역대 확인
(⎈|kind-myk8s:N/A) root@kind:~# docker ps -q | xargs docker inspect --format ' '
/myk8s-worker 172.18.0.2
/myk8s-control-plane 172.18.0.5
/myk8s-worker2 172.18.0.4
/myk8s-worker3 172.18.0.3
# 파드와 Service 대역 확인
(⎈|kind-myk8s:N/A) root@kind:~# kubectl get cm -n kube-system kubeadm-config -oyaml | grep -i subnet
podSubnet: 10.10.0.0/16
serviceSubnet: 10.200.1.0/24
# 노드마다 할당된 파드 고정 대역대 확인
(⎈|kind-myk8s:N/A) root@kind:~# kubectl get nodes -o custom-columns='NODE_NAME:.metadata.name,POD_CIDR:.spec.podCIDR'
NODE_NAME POD_CIDR
myk8s-control-plane 10.10.0.0/24
myk8s-worker 10.10.1.0/24
myk8s-worker2 10.10.2.0/24
myk8s-worker3 10.10.3.0/24
kindnet 🔗
# kind network를 사용
(⎈|kind-myk8s:N/A) root@kind:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
a7cb493939e6 bridge bridge local
bd171c4c05b9 host host local
b2152e16fdcb kind bridge local
32658cbe2101 none null local
# 맨 마지막 이름(=kind network id)
(⎈|kind-myk8s:N/A) root@kind:~# ip -br -4 addr show
lo UNKNOWN 127.0.0.1/8
enp0s3 UP 10.0.2.15/24 metric 100
enp0s8 UP 192.168.50.10/24
docker0 DOWN 172.17.0.1/16
br-b2152e16fdcb UP 172.18.0.1/16
# controlplane에 들어가서 확인해보았다.
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-control-plane bash
# 모든 veth인터페이스들이 동일한 IP 주소를 갖는다.
# 각 파드들이 기본 gw역할을 해서 bridge를 통해 통신 하는 것이 아님
root@myk8s-control-plane:/# ip -br -4 addr show
lo UNKNOWN 127.0.0.1/8
vethe37588be@if2 UP 10.10.0.1/32
vethb19fa5cc@if2 UP 10.10.0.1/32
vethcbf739f8@if2 UP 10.10.0.1/32
veth5011a3b2@if2 UP 10.10.0.1/32
eth0@if13 UP 172.18.0.5/16
# type: ptp = point to point 로 두 엔트포인트간의 직접적인 연결을 의미한다.
# 네트워크 레이어에서 ip 주소를 그대로 사용 하는 것
root@myk8s-control-plane:/# cat /etc/cni/net.d/10-kindnet.conflist
{
"cniVersion": "0.3.1",
"name": "kindnet",
"plugins": [
{
"type": "ptp",
"ipMasq": false,
"ipam": {
"type": "host-local",
...
테스트 파드를 띄워서 확인해보았다.
(⎈|kind-myk8s:N/A) root@kind:~# cat test.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod1
spec:
containers:
- name: pod1
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
spec:
containers:
- name: pod2
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
(⎈|kind-myk8s:N/A) root@kind:~# k apply -f test.yaml
pod/pod1 created
pod/pod2 created
(⎈|kind-myk8s:N/A) root@kind:~# k get po -owide
pod2 1/1 Running 0 103s 10.10.1.2 myk8s-worker <none> <none>
pod1 1/1 Running 0 103s 10.10.2.3 myk8s-worker2 <none> <none>
# pod1에서 pod2로 가는 길을 확인해보면,
# pod1에서 Pod2로 가기 위해 eht0인터페이스를 통해 10.10.2.1에 패킷을 전송하게 된다.
(⎈|kind-myk8s:N/A) root@kind:~# kubectl exec -it pod1 -- ip route get 10.10.1.2
10.10.1.2 via 10.10.2.1 dev eth0 src 10.10.2.3 uid 0
# 10.10.2.1은 pod1이 있는 worker2 node에서의 가상인터페이스
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-worker2 ip -br -4 add show
lo UNKNOWN 127.0.0.1/8
veth9fddd097@if2 UP 10.10.2.1/32
eth0@if11 UP 172.18.0.4/16
# worker2 node에서 pod2 경로를 화인해보면,
# eth0터널을 통해 172.18.0.2로 전달된다.
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-worker2 ip route get 10.10.1.2
10.10.1.2 via 172.18.0.2 dev eth0 src 172.18.0.4 uid 0
# 172.18.0.2는 pod2가 위치한 myk8s-worker node
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-worker ip -br -4 add show
lo UNKNOWN 127.0.0.1/8
veth1af37920@if2 UP 10.10.1.1/32
eth0@if7 UP 172.18.0.2/16
# 해당 노드에서 pod2의 경로를 확인해보면, pod2에 연결된 가상인터페이스로 연결된다.
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-worker ip route get 10.10.1.2
10.10.1.2 dev veth1af37920 src 10.10.1.1 uid 0
위와 같이 보면 브릿지를 사용한 오버레이 네트워크처럼 보일 수 있지만, 실제 통신은 l3 라우팅을 통해 이뤄지기 때문에 pod ip가 변경되지 않고 전달된다.
# pod1 > pod2
(⎈|kind-myk8s:N/A) root@kind:~# kubectl exec -it pod1 -- /bin/sh
~ # ping -c 3 10.10.1.2
# pod2에서 tcpdump로 확인해보면, pod1의 client ip를 확인 할 수 있다.
(⎈|kind-myk8s:N/A) root@kind:~# kubectl exec -it pod2 -- /bin/sh
~ # tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:58:36.180460 ARP, Request who-has pod2 tell 10.10.1.1, length 28
18:58:36.180469 ARP, Reply pod2 is-at 36:56:b7:dc:58:9d (oui Unknown), length 28
18:58:36.180471 IP 10.10.2.3 > pod2: ICMP echo request, id 57, seq 1, length 64
18:58:36.181208 IP pod2 > 10.10.2.3: ICMP echo reply, id 57, seq 1, length 64
...
Service
pod는 생성되었다 죽었다 하다보니 고정하여 호출 할 수 있는 고정된 vip나 domain 주소가 필요하여 등장한 것이 Service다. 이 Service의 요청을 파드로 전달하는 과정을 kube-proxy에서 기능을 제공해준다. 쿠버네티스를 설치하면 kube-system 네임스페이스에 데몬셋으로 생성되는데, 해당 컴포넌트가 클러스터의 서비스와 파드간의 네트워크 통신을 관리하는 것이다.
kube-proxy
kube-proxy 동작하는데에 여러 mode가 있는데, 이전에 사용되었던 user space proxy mode와 현재 default로 사용되는 iptables proxy mode, 그리고 ipvs proxy mode 등이 있다.
user space proxy mode
처음에는 user space proxy mode로 동작했었다.
- 클라이언트가 svc ip로 요청을 보내면, 먼저 커널 스페이스에 도착한다.
- 커널에서는 netfilter라는 프레임워크를 사용하여 네트워크 패킷을 필터링하고 조작할 수 있는데, 이 때, 서비스 IP로 들어온 패킷이 netfilter에 의해 가로채어지고, 이를 처리하기 위해 user space의 kube-proxy 프로세스가 호출된다.
- 패킷이 커널에서 user space로 이동하는 과정에서 커널은 user space에서 실행 중인 kube-proxy 프로세스에 패킷을 전달한다.
- user space에서 실행되는 kube-proxy는 패킷을 수신하고 이에 맞는 pod를 선택하고 그 정보를 커널 스페이스로 전달한다.
- 이렇게 커널로 다시 전달된 패킷은 netfilter를 통해 다시 한번 처리되면서, 최종적으로 pod에 전달되는 방식이다.
클라이언트 요청이 kernel space에서 user space로 넘어가고 kernel space 돌아오는 과정이 발생하게 되는데, 이런 컨텍스트 스위칭이 빈번하게 일어나면서 오버헤드가 증가하게되어 성능이 좋지 않아 더이상 사용하지 않게 되었다.
iptables proxy mode
현재 기본으로 쓰이는 것은 Iptables proxy mode이다.
- 클라이언트가 svc ip로 요청을 보내면, 커널스페이스에 도착한다.
- 커널의 netfilter는 이미 user space에서 설정한 iptables 규칙에 따라 설정되어있어, user space까지 가지 않고 netfilter에서 규칙에 맞는 파드로 연결해준다.
iptables mode에서는 kube-proxy가 클러스터의 서비스와 Pod 간의 라우팅 정보를 관리하며, 이 정보를 바탕으로 iptables 규칙을 업데이트하는데, 이러한 규칙들은 NAT 및 FORWARD 체인에 추가되어 실제 패킷을 필터링하고 라우팅하는데에 사용된다. kube-proxy에서는 직접적인 관여를 하지 않지만, iptables 규칙의 수가 많아지면 iptables 알고리즘에 따라 순차적으로 규칙을 조회해야하기 때문에 성능에 영향을 끼칠 수 있다. (규칙 수가 많을 경우 패킷 처리 지연 발생 가능)
이런 단점이 있어 l4 lb인 ipvs를 사용한 ipvs proxy mode가 있다. ipvs 모드는 커널 스페이스에서 동작하고, 데이터 구조를 해시 테이블로 저장하는 등 빠르고 성능이 좋다. 다양한 로드밸런싱 알고리즘을 지원되는데, ipvs에 대해서는 다음 주차에서 확인 할 예정이다.
Service type
이렇게 kube-proxy로 동작하는 svc 종류에는 cluster ip와 node port가 있다.
- cluster ip: 클러스터 내부 통신
- node port: 클러스터 외부 통신
현재 테스트 클러스터는 default인 iptables proxy mode로 설정되어있다.
(⎈|kind-myk8s:N/A) root@kind:~# kubectl describe cm -n kube-system kube-proxy
...
iptables:
localhostNodePorts: null
masqueradeAll: false
masqueradeBit: null
minSyncPeriod: 1s
syncPeriod: 0s
...
mode: iptables
...
Custer IP
# 서비스 파드 배포
(⎈|kind-myk8s:N/A) root@kind:~# 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
(⎈|kind-myk8s:N/A) root@kind:~# k apply -f 3pod.yaml
pod/webpod1 created
pod/webpod2 created
pod/webpod3 created
# 테스트 파드 배포
(⎈|kind-myk8s:N/A) root@kind:~# 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
(⎈|kind-myk8s:N/A) root@kind:~# k apply -f netpod.yaml
pod/net-pod created
# 서비스 배포
(⎈|kind-myk8s:N/A) root@kind:~# cat <<EOT> svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-clusterip
spec:
ports:
- name: svc-webport
port: 9000 # svc의 port
targetPort: 80 # 연결 할 pod의 port
selector:
app: webpod # 연결 할 pod의 selector
type: ClusterIP # 서비스 타입
EOT
(⎈|kind-myk8s:N/A) root@kind:~# k apply -f svc-clusterip.yaml
service/svc-clusterip created
(⎈|kind-myk8s:N/A) root@kind:~# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
net-pod 1/1 Running 0 24m 10.10.0.6 myk8s-control-plane <none> <none>
webpod1 1/1 Running 0 26m 10.10.1.3 myk8s-worker <none> <none>
webpod2 1/1 Running 0 26m 10.10.2.4 myk8s-worker2 <none> <none>
webpod3 1/1 Running 0 26m 10.10.3.3 myk8s-worker3 <none> <none>
(⎈|kind-myk8s:N/A) root@kind:~# kubectl get svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.200.1.1 <none> 443/TCP 18h <none>
svc-clusterip ClusterIP 10.200.1.63 <none> 9000/TCP 23m app=webpod
(⎈|kind-myk8s:N/A) root@kind:~# kubectl get endpoints svc-clusterip
NAME ENDPOINTS AGE
svc-clusterip 10.10.1.3:80,10.10.2.4:80,10.10.3.3:80 22m
clustIP로 어떻게 접근하는지 확인
# 테스트 파드에서 svc와 pod ip로 접근 가능한 것을 확인
(⎈|kind-myk8s:N/A) root@kind:~# k exec -it net-pod -- /bin/sh
~ # curl -s 10.200.1.63:9000 | grep Hostname
Hostname: webpod3
~ # curl -s 10.10.1.3 | grep Hostname
Hostname: webpod1
# net-pod에서 svc clusterip:port로 호출하는 동안의 tcpdump를 확인해보면,
(⎈|kind-myk8s:N/A) root@kind:~# k exec -it net-pod -- curl -s 10.200.1.63:9000
Hostname: webpod1
# webpod1의 node에 접근하여 확인
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-worker bash
root@myk8s-worker:/# tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on vethe9251aa3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
22:08:23.864882 IP 10.10.0.6.38158 > 10.10.1.3.http: Flags [S], seq 2027460615, win 64240, options [mss 1460,sackOK,TS val 1215853224 ecr 0,nop,wscale 7], length 0
22:08:23.866038 IP 10.10.1.3.http > 10.10.0.6.38158: Flags [S.], seq 1612625038, ack 2027460616, win 65160, options [mss 1460,sackOK,TS val 2760627948 ecr 1215853224,nop,wscale 7], length 0
22:08:23.866107 IP 10.10.0.6.38158 > 10.10.1.3.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1215853226 ecr 2760627948], length 0
...
# 해당 워커 노드에서 들어오면 어떤 룰을 타고 가는지 iptables를 확인
# iptables로 들어올 때 가장 먼저 PREROUTING을 타서 여기서 먼저 확인
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it myk8s-control-plane bash
# packet 수 변한거를 >> 으로 하여 표시함
# 계속해서 packet 수 변한 것을 보고 쫓아간다
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list PREROUTING | column -t
Chain PREROUTING (policy ACCEPT 242 packets, 14662 bytes)
pkts bytes target prot opt in out source destination
271 16510 KUBE-SERVICES 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
>>
275 16750 KUBE-SERVICES 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
# KUBE-SERVICES를 확인해보면
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SERVICES | column
Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
42 2520 KUBE-SVC-KBDEBIL6IU6WL7RF 6 -- * * 0.0.0.0/0 10.200.1.63 /* default/svc-clusterip:svc-webport cluster IP */ tcp dpt:9000
>>
43 2580 KUBE-SVC-KBDEBIL6IU6WL7RF 6 -- * * 0.0.0.0/0 10.200.1.63 /* default/svc-clusterip:svc-webport cluster IP */ tcp dpt:9000
# KUBE-SVC-KBDEBIL6IU6WL7RF
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF | column
Chain KUBE-SVC-KBDEBIL6IU6WL7RF (1 references)
pkts bytes target prot opt in out source destination
16 960 KUBE-SEP-W5ROWLZT7Z7YVFGJ 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport -> 10.10.2.4:80 */ statistic mode random probability 0.50000000000
>>
17 1020 KUBE-SEP-W5ROWLZT7Z7YVFGJ 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport -> 10.10.2.4:80 */ statistic mode random probability 0.50000000000
# KUBE-SEP-K7ALM6KJRBAYOHKX
# 마지막으로 DNAT되는 것을 확인
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
17 1020 DNAT 6 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ tcp to:10.10.3.3:80
>>
18 1080 DNAT 6 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport */ tcp to:10.10.3.3:80
# 나갈 때는 어떻게 나가는지 확인하기 위해 POSTROUTING 을 모니터링으로 확인
# RETURN에 걸려서 룰처리 되는 것을 확인함
root@myk8s-control-plane:/# watch -d 'iptables -v --numeric --table nat --list POSTROUTING | column; echo ; iptables -v --numeric --table nat --list KUBE-POSTROUTING | column'
Every 2.0s: iptables -v --numeric --table nat --list POSTROUTING | column; echo ; iptables -v --numeric --table nat --list... myk8s-control-plane: Sat Sep 28 22:28:12 2024
...
Chain KUBE-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
853 51180 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
# conntrack 명령어로 연결 상태 확인
# 테스트 파드(10.10.0.6)을 소스로 잡고 보면, svc ip(10.200.1.63): dport(9000)으로 호출하고
# 원본 파드(10.10.1.3혹은10.10.2.4): 원래 포트(80)에서 테스트 파드(10.10.0.6)로 연결한 것을 확인 할 수 있다.
root@myk8s-control-plane:/# conntrack -L --src 10.10.0.6
tcp 6 42 TIME_WAIT src=10.10.0.6 dst=10.200.1.63 sport=55702 dport=9000 src=10.10.1.3 dst=10.10.0.6 sport=80 dport=55702 [ASSURED] mark=0 use=1
tcp 6 72 TIME_WAIT src=10.10.0.6 dst=10.200.1.63 sport=42890 dport=9000 src=10.10.2.4 dst=10.10.0.6 sport=80 dport=42890 [ASSURED] mark=0 use=1
conntrack v1.4.7 (conntrack-tools): 2 flow entries have been shown.
clientIP로 분산 처리가 되는데 분산율에 대하여..
# 위에서 확인 한 것처럼 보면 각 ep에 퍼센트가 써있다.
root@myk8s-control-plane:/# iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF | column
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.63 /* default/svc-clusterip:svc-webport cluster IP */ tcp dpt:9000
11 660 KUBE-SEP-2WDU4O4C4SDCYWRE 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport -> 10.10.1.3:80 */ statistic mode random probability 0.33333333349
17 1020 KUBE-SEP-W5ROWLZT7Z7YVFGJ 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport -> 10.10.2.4:80 */ statistic mode random probability 0.50000000000
17 1020 KUBE-SEP-K7ALM6KJRBAYOHKX 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc-clusterip:svc-webport -> 10.10.3.3:80 */
보기 편하게 끝에 컬럼만 떼오면…
- 1/3 만큼 이곳으로 > default/svc-clusterip:svc-webport -> 10.10.1.3:80 */ statistic mode random probability 0.33333333349
- 1/2 만큼 이곳으로.. > default/svc-clusterip:svc-webport -> 10.10.2.4:80 */ statistic mode random probability 0.50000000000
- 하나 남았으니, 이곳에 모두 보내게 된다. > default/svc-clusterip:svc-webport -> 10.10.3.3:80 */ iptables 규칙이 위에서부터 순차적으로 처리 하기 때문에 위와 같이 확률 기반으로 적용된다.
# 실제로 보내보면 비슷한 비율로 호출된다.
(⎈|kind-myk8s:N/A) root@kind:~# kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s 10.200.1.63:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
34 Hostname: webpod3
33 Hostname: webpod2
33 Hostname: webpod1
# Client IP로 세션을 설정하게 되면
(⎈|kind-myk8s:N/A) root@kind:~# kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinity":"ClientIP"}}'
service/svc-clusterip patched
# 최근에 연결한 pod로만 전송된다.
(⎈|kind-myk8s:N/A) root@kind:~# kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s 10.200.1.63:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
100 Hostname: webpod2
# iptables 규칙을 확인보면, 모듈에 recent가 추가되어 최근 패킷을 추적할 수 있게 되었다.
# --rsource: source IP에 해당 규칙을 적용
root@myk8s-control-plane:/# iptables -t nat -S | grep recent
-A KUBE-SEP-2WDU4O4C4SDCYWRE -p tcp -m comment --comment "default/svc-clusterip:svc-webport" -m recent --set --name KUBE-SEP-2WDU4O4C4SDCYWRE --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.10.1.3:80
-A KUBE-SEP-K7ALM6KJRBAYOHKX -p tcp -m comment --comment "default/svc-clusterip:svc-webport" -m recent --set --name KUBE-SEP-K7ALM6KJRBAYOHKX --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.10.3.3:80
-A KUBE-SEP-W5ROWLZT7Z7YVFGJ -p tcp -m comment --comment "default/svc-clusterip:svc-webport" -m recent --set --name KUBE-SEP-W5ROWLZT7Z7YVFGJ --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.10.2.4:80
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.1.3:80" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-2WDU4O4C4SDCYWRE --mask 255.255.255.255 --rsource -j KUBE-SEP-2WDU4O4C4SDCYWRE
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.2.4:80" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-W5ROWLZT7Z7YVFGJ --mask 255.255.255.255 --rsource -j KUBE-SEP-W5ROWLZT7Z7YVFGJ
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.3.3:80" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-K7ALM6KJRBAYOHKX --mask 255.255.255.255 --rsource -j KUBE-SEP-K7ALM6KJRBAYOHKX
NodePort
외부 통신시, node ip에 svc에서 제공하는 node port를 붙여서 접근한다. (node port 할당범위: 30000~32767) node port 접근 후에는 cluster ip통신과 동일하다.
# 기존 파드와 서비스는 삭제 후 진행하였다.
(⎈|kind-myk8s:N/A) root@kind:~# cat echo-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 2
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
(⎈|kind-myk8s:N/A) root@kind:~# cat svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-nodeport
spec:
ports:
- name: svc-webport
port: 9000 # 내부 접근시 사용 (Cluster IP용)
targetPort: 8080 # 연결 할 파드 port
selector:
app: deploy-websrv
type: NodePort
(⎈|kind-myk8s:N/A) root@kind:~# kubectl apply -f echo-deploy.yaml,svc-nodeport.yaml
deployment.apps/deploy-echo created
service/svc-nodeport created
# 배포된 파드와 서비스 확인
(⎈|kind-myk8s:N/A) root@kind:~# k get po,svc,ep -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/deploy-echo-5c689d5454-mgn4p 1/1 Running 0 84m 10.10.3.5 myk8s-worker3 <none> <none>
pod/deploy-echo-5c689d5454-w4nb6 1/1 Running 0 84m 10.10.2.8 myk8s-worker2 <none> <none>
pod/net-pod 1/1 Running 0 3h13m 10.10.0.6 myk8s-control-plane <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.200.1.1 <none> 443/TCP 21h <none>
service/svc-nodeport NodePort 10.200.1.217 <none> 9000:31662/TCP 84m app=deploy-websrv
NAME ENDPOINTS AGE
endpoints/kubernetes 172.18.0.5:6443 21h
endpoints/svc-nodeport 10.10.2.8:8080,10.10.3.5:8080 84m
# node ip 확인
(⎈|kind-myk8s:N/A) root@kind:~# k get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
myk8s-control-plane Ready control-plane 19h v1.31.0 172.18.0.5 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-119-generic containerd://1.7.18
myk8s-worker Ready <none> 19h v1.31.0 172.18.0.2 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-119-generic containerd://1.7.18
myk8s-worker2 Ready <none> 19h v1.31.0 172.18.0.4 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-119-generic containerd://1.7.18
myk8s-worker3 Ready <none> 19h v1.31.0 172.18.0.3 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-119-generic containerd://1.7.18
# 테스트 컨테이너 배포
(⎈|kind-myk8s:N/A) root@kind:~# docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity
0fa3ba5fe630519214de4ac59414d85ec7805dc5b23b56e27eae484c818d8ba4
(⎈|kind-myk8s:N/A) root@kind:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0fa3ba5fe630 nicolaka/netshoot "sleep infinity" 24 seconds ago Up 24 seconds mypc
...
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it 0f
# clusterIP로는 접근 불가
0fa3ba5fe630 ~ curl -s 10.200.1.217:9000
# nodeIP:nodePort로 호출
(⎈|kind-myk8s:N/A) root@kind:~# docker exec -it 0f zsh
0fa3ba5fe630 ~ curl -s 172.18.0.2:31662 | grep hostname
"hostname": "172.18.0.2",
"hostname": "deploy-echo-5c689d5454-w4nb6"
0fa3ba5fe630 ~ curl -s 172.18.0.3:31662 | grep hostname
"hostname": "172.18.0.3",
"hostname": "deploy-echo-5c689d5454-mgn4p"
0fa3ba5fe630 ~ curl -s 172.18.0.4:31662 | grep hostname
"hostname": "172.18.0.4",
"hostname": "deploy-echo-5c689d5454-mgn4p"
0fa3ba5fe630 ~ curl -s 172.18.0.5:31662 | grep hostname
"hostname": "172.18.0.5",
"hostname": "deploy-echo-5c689d5454-w4nb6"
myk8s-worker(172.17.0.2)나 myk8s-control-plane(172.17.0.5)에는 svc에 연결된 pod가 없는데에도 접근이 잘되고, hostname에도 노드 ip가 반환된다.
# worker 노드를 호출하고 worker 노드에서 tcpdump로 확인
0fa3ba5fe630 ~ curl -s 172.18.0.2:31662 | grep hostname
"hostname": "172.18.0.2",
"hostname": "deploy-echo-5c689d5454-w4nb6"
# myk8s-worker = w1
# myk8s-worker2 = w2
# 테스트 컨테이너(mypc)에서 들어온 패킷 확인
(w1) 00:57:28.215640 IP mypc.kind.40906 > myk8s-worker.31662: Flags [S], seq 2911406569, win 64240, options [mss 1460,sackOK,TS val 470432859 ecr 0,nop,wscale 7], length 0
# w1에서 10.10.2.8(svc와 연결된 pod ip)로 전달
(w2) 00:57:28.215722 IP myk8s-worker.kind.64570 > 10.10.2.8.http-alt: Flags [S], seq 2911406569, win 64240, options [mss 1460,sackOK,TS val 470432859 ecr 0,nop,wscale 7], length 0
(w2) 00:57:28.215736 IP 10.10.2.8.http-alt > myk8s-worker.kind.64570: Flags [S.], seq 2028762515, ack 2911406570, win 65160, options [mss 1460,sackOK,TS val 2654379599 ecr 470432859,nop,wscale 7], length 0
(w1) 00:57:28.215741 IP 10.10.2.8.http-alt > myk8s-worker.64570: Flags [S.], seq 2028762515, ack 2911406570, win 65160, options [mss 1460,sackOK,TS val 2654379599 ecr 470432859,nop,wscale 7], length 0
...
# 확인해보면, w1는 떠있는 파드가 없어서 파드가 있는 w2에 가서 응답을 가져온 것으로 보인다.
iptables를 따라가보자
root@myk8s-worker:/# iptables -t nat -S | grep PREROUTING
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
...
# node port를 타고 오기 때문에, 아래의 규칙을 따라간다.
root@myk8s-worker:/# 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
# node port인 31662를 따라..
root@myk8s-worker:/# iptables -t nat -S | grep KUBE-NODEPORTS
...
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-nodeport:svc-webport" -m tcp --dport 31662 -j KUBE-EXT-VTR7MTHHNMFZ3OFS
# source ip를 node ip로 변경한다
# (w2) 00:57:28.215722 에서, 이렇게 변환되어서 w2의 pod에게 요청이 간 것
root@myk8s-worker:/# iptables -t nat -S | grep KUBE-EXT-VTR7MTHHNMFZ3OFS
...
-A KUBE-EXT-VTR7MTHHNMFZ3OFS -m comment --comment "masquerade traffic for default/svc-nodeport:svc-webport external destinations" -j KUBE-MARK-MASQ
# 이렇게 변환이 된 후로는 node 에서 요청이 가는 것으로 보여, clusterIP로 통신 할 때와 동일하게 규칙을 타게 된다.