쿠버네티스에서 동작하는 애플리케이션을 내/외부에서 유연하게 접속하기 위한 오브젝트
iptables, ipvs, nftables proxy mode 모두 Netfilter framework 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity

ClusterIP

NodePort

LoadBalancer

서비스 통신 동작에 대한 설정 관리
데몬셋으로 배포되어, 모든 쿠버네티스 노드에 파드가 생성됨
- IPTABLES
리눅스의 호스트 방화벽 / NAT 역할
IPTABLES에 정책 설정 시, 커널 영역에 내장된 Netfilter 를 통해 통제 수행

IPVS
Netfilter에서 동작하는 L4 Load Balancer

nftables
nftables API → netfilter subsystem
eBPF + XDP
기존 netfilter/iptables 기반 통신

eBPF + XDP 네트워킹 모듈

다음과 같은 환경 구성

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

⇒ ClusterIP의 9000번 포트로 넘어오면 어떤 룰에 통해서 jump 처리!
⇒ 목적지 IP와 포트를 iptables Rule에 의해서 처리
클라이언트(TestPod)가 'CLUSTER-IP' 접속 시 해당 노드의 iptables 룰(랜덤 분산)에 의해서 DNAT 처리가 되어 목적지(backend) 파드와 통신!
참고) tcp listen, routing 정보는 없음

kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000

Port 9000 / TargetPort 80

마치 L4 Service처럼 작동하며, NAT는 위와 같이 동작한다.
docker exec -it myk8s-worker bash
**tcpdump** -i eth0 tcp port 80 -nnq
tcpdump -i eth0 tcp port 9000 -nnq
eth0 >> tcp 9000 트래픽은 존재하지 않는다.
⇒ TestPod가 있는 Node에서 iptables에 의해 이미 NAT 처리가 완료 됐기 때문.

app=webpod label을 가지고 있는 파드가 대상, 랜덤 부하분산 선택 NAT 수행시 연결 정보를 기록하여 Return 트래픽을 확인 후 TestPod로 전달
PREROUTING
모든 트래픽이 매칭, KUBE-SERVICES로 전달 Jump
KUBE-SERVICES
ClusterIP(Port)에 매칭되면 KUBE-SVC-YYY로 전달
KUBE-SVC-YYY
랜덤으로 KUBE-SEP-ZZZ로 부하분산 처리
KUBE-SEP-ZZZ
Target IP / Port로 DNAT 처리
결론 : 내부에서 클러스터 IP로 접속 시, PREROUTE(nat) 에서 DNAT(3개 파드) 되고, POSTROUTE(nat) 에서 SNAT 되지 않고 나간다!
docker exec -it myk8s-control-plane bash
----------------------------------------
# 정책 확인 : 아래 정책 내용은 핵심적인 룰(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
...
클라이언트의 요청을 매번 동일한 목적지 파드로 전달되기 위한 설정
0d10-4ff4-aa55-8afb44fce245/b8a8001a-b1c7-4407-b3c8-003d7f55f363/image.png)
kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinity":"**ClientIP**"}}'
# kubectl get svc svc-clusterip -o yaml
...
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
...
접근시 Client IP 세션 정보 기록하여 timeoutSeconds 안에 똑같은 클라이언트 호출시 동일 파드로 전달
외부에서 클러스터의 NodePort로 접근 가능, 이후에는 Cluster IP 통신과 동일


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
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
kubectl apply -f echo-deploy.yaml,svc-nodeport.yaml

⇒ ClusterIP와 다르게 32624 외부 포트가 열린 것을 확인

⇒ 현재 k8s 버전에서는 포트 Listen 되지 않고, iptables rules 처리
docker exec -it mypc curl -s $CNODE:$NPORT | jq

headers.host 의 경우 SNAT 처리 된 모습
KUBE-EXT-#(MARK) 의 경우 외부 → Node1 → Node2가 되는 경우에서 Node1으로 SNAT을 해야하기 때문에 적용 ⇒ return을 위함
1.PREROUTING

2.KUBE-SERVICE

3.KUBE-NODEPORT

4.KUBE-EXT-XXX

⇒ 마킹 및 점프!
5. KUBE-SVC-XXX

⇒ 3개의 파드로 전달
6. KUBE-SEP-XXX

⇒ DNAT 처리
7. POSTROUTING

마킹돼 있어, 출발지 IP를 접속한 노드의 IP로 SNAT 처리, 최초 출발지 Port는 랜덤 Port
⇒ 다른 노드의 Pod로 넘어가기 전에 SNAT 처리 해주는 것
외부 클라이언트 IP를 수집
externalTrafficPolicy: Local : NodePort 로 접속 시 해당 노드에 배치된 파드로만 접속됨, 이때 SNAT 되지 않아서 외부 클라이언트 IP가 보존됨!
externalTrafficPolicy: local 사용 시 파드가 없는 노드 IP로 NodePort 접속 시 실패 ⇒ LoadBalancer 서비스에서 헬스체크(Probe) 로 대응 가능!