이전 스터디에서는 Kubernetes에서 제공하는 서비스에 대해 간략하게 확인해보았는데,
여기에 이어서 LoadBalancer에 대해서도 실습을 진행해보았다.
LoadBalancer 종류
External LoadBalancer는 종류로만 크게 분류한다면 3가지로 분류할 수 있을 것 같다.
- 클라우드 환경에서 제공하는 LoadBalancer 리소스 (AWS의 ELB 등)
- On-Premise환경에서 사용할 수 있는 LoadBalancer 네트워크 장치 (citrix, F5 등의 네트워크 장치)
- 프로그램으로 구현되어 설치해 사용하는 LoadBalancer 소프트웨어 (MetalLB, LoxiLB 등)
이 중 클라우드 환경과 네트워크 장치의 경우 크게 2가지 방식을 지원한다.
NodePort를 거쳐 전달하는 방식
기존에 통신 과정을 확인한 구조에 External LoadBalancer만 추가된 방식으로 사용자 트래픽은 User Traffic -> External LoadBalancer -> Service(NodePort) -> Pod 순서로 전달된다.
이 과정에서 접속할 Node로의 분산과 Pod로의 분산, 총 2번의 부하분산을 거쳐서 서비스 Pod에 도달하게 된다.
직접 Pod로 전달하는 방식
이 경우 사용자 트래픽은 User Traffic -> External LoadBalancer -> Pod 순서로 전달된다.
이전에 비해 불필요한 iptables 정책이나 NAT가 동작하지 않기 때문에 더 간결하고 빠르게 동작한다.
다만 External LoadBalancer에서 Pod에 대한 정보를 알 수 있는 내부 동작의 지원(AWS같은 경우 VPC CNI를 사용하는 등) 이 필요하다.
MetalLB
BareMetalLoadBalancer의 약자로 On-Premise 환경에서 LoadBalancer를 소프트웨어로 구현한 오픈소스 프로젝트이다.
이름에서 알 수 있듯이 기본적으로 BareMetal 환경을 가정하고 구현되었고, 클라우드 플랫폼과 호환되지 않는다.
In general, MetalLB is not compatible with cloud providers.
- 클라우드 플랫폼과의 호환성에 제약이 존재 : https://metallb.universe.tf/installation/clouds/
- CNI 호환성에 제약이 존재(일부 CNI에 연동 이슈) : https://metallb.universe.tf/installation/network-addons/
BGP support is uncommon, and is usually designed to support inbound route advertisement from off-cloud locations (e.g. Google Cloud Router). MetalLB’s BGP mode therefore either doesn’t work at all, or doesn’t do what most people want - and definitely does it worse than the cloud platform’s own load-balancer products.
ARP is emulated by the virtual network layer, meaning that only IPs assigned to VMs by the cloud platform can be resolved. This breaks MetalLB’s L2 mode, which relies on ARP’s behavior on normal Ethernet networks.
Even in cases where ARP works normally, the IP allocation process for public IPs (often called “floating IPs”) doesn’t integrate with the virtual network in a standard way, so MetalLB can’t “grab” the IP with ARP and route it to the correct place. Instead, you have to talk to the cloud’s proprietary API to reroute the floating IP, which MetalLB can’t do.
https://metallb.universe.tf/installation/clouds/#why-doesnt-metallb-work-on-most-cloud-platforms
MetalLB는 ARP를 사용하는 Layer2 모드와 BGP를 사용하는 BGP 모드 중 하나로 동작하는데,
BGP모드로 테스트하기엔 다소 복잡하고 네트워크 지식도 요구되어 Layer2 모드로 테스트하였다.
Layer2 모드는 아래와 같은 구성으로 cluster 안에 별도의 MetalLB Pod(speaker)가 생성되어 로드밸런싱을 수행한다.
MetalLB 환경 구성
기본 k8s와 환경 설정
기존과 동일하게 아래와 같은 환경에서 MetalLB를 설치할 예정이다.
yaml 파일 출처 : https://gasidaseo.notion.site/K8S-Service-2-MetalLB-e49962be02724fe6945e57ffe500e406
k8s cluster 및 node 생성
cat <<EOT> kind-svc-2w.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
"InPlacePodVerticalScaling": true #실행 중인 파드의 리소스 요청 및 제한을 변경할 수 있게 한다.
"MultiCIDRServiceAllocator": true #서비스에 대해 여러 CIDR 블록을 사용할 수 있게 설정한다.
nodes:
- role: control-plane
labels:
mynode: control-plane
topology.kubernetes.io/zone: ap-northeast-2a
extraPortMappings: #컨테이너 포트를 호스트 포트에 매핑하여 클러스터 외부에서 서비스에 접근할 수 있도록 한다.
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- containerPort: 30004
hostPort: 30004
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs: #API 서버에 추가 인수를 제공
runtime-config: api/all=true #모든 API 버전을 활성화
controllerManager:
extraArgs:
bind-address: 0.0.0.0
etcd:
local:
extraArgs:
listen-metrics-urls: http://0.0.0.0:2381
scheduler:
extraArgs:
bind-address: 0.0.0.0
- |
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0
- role: worker
labels:
mynode: worker1
topology.kubernetes.io/zone: ap-northeast-2a
- role: worker
labels:
mynode: worker2
topology.kubernetes.io/zone: ap-northeast-2b
- role: worker
labels:
mynode: worker3
topology.kubernetes.io/zone: ap-northeast-2c
networking:
podSubnet: 10.10.0.0/16 #파드 IP를 위한 CIDR 범위를 정의한다. 파드는 이 범위에서 IP를 할당받는다.
serviceSubnet: 10.200.1.0/24 #서비스 IP를 위한 CIDR 범위를 정의한다. 서비스는 이 범위에서 IP를 할당받는다.
EOT
# k8s 클러스터 설치
kind create cluster --config kind-svc-2w.yaml --name myk8s --image kindest/node:v1.31.0
# 컨트롤 플레인과 워커 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils 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 dnsutils ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping -y'; echo; done
# 설치 후 확인
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get nodes -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
myk8s-control-plane Ready control-plane 5m1s v1.31.0 172.18.0.2 <none> Debian GNU/Linux 12 (bookworm) 5.15.153.1-microsoft-standard-WSL2 containerd://1.7.18
myk8s-worker Ready <none> 4m45s v1.31.0 172.18.0.4 <none> Debian GNU/Linux 12 (bookworm) 5.15.153.1-microsoft-standard-WSL2 containerd://1.7.18
myk8s-worker2 Ready <none> 4m51s v1.31.0 172.18.0.5 <none> Debian GNU/Linux 12 (bookworm) 5.15.153.1-microsoft-standard-WSL2 containerd://1.7.18
myk8s-worker3 Ready <none> 4m51s v1.31.0 172.18.0.3 <none> Debian GNU/Linux 12 (bookworm) 5.15.153.1-microsoft-standard-WSL2 containerd://1.7.18
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl describe cm -n kube-system kube-proxy | grep iptables -A 5
iptables:
localhostNodePorts: null
masqueradeAll: false
masqueradeBit: null
minSyncPeriod: 1s
syncPeriod: 0s
--
mode: iptables
nftables:
masqueradeAll: false
masqueradeBit: null
minSyncPeriod: 0s
syncPeriod: 0s
MetalLB 모니터링을 위한 그라파나 대시보드 설치
cat <<EOT > monitor-values.yaml
prometheus:
service:
type: NodePort
nodePort: 30001
prometheusSpec:
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
nodeSelector:
mynode: control-plane
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Equal"
effect: "NoSchedule"
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: test1234
service:
type: NodePort
nodePort: 30002
nodeSelector:
mynode: control-plane
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Equal"
effect: "NoSchedule"
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
- name: 'default' # 프로바이더 이름
orgId: 1 # 조직 ID
folder: 'default' # 대시보드가 저장될 폴더
type: file # 프로바이더 타입 (파일 기반)
disableDeletion: true # 대시보드 삭제 비활성화
editable: true # 대시보드 편집 가능 여부
options:
path: /var/lib/grafana/dashboards/standard # 대시보드 파일 경로
dashboards:
default:
custom-dashboard:
gnetId: 20162 # import할 Grafana.com 대시보드 ID
revision: 5 # 대시보드 버전
datasource:
- name: DS_PROMETHEUS # 데이터 소스 이름
value: Prometheus # 실제 사용할 데이터 소스
defaultRules:
create: false
alertmanager:
enabled: false
EOT
# helm chart 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# ns 생성 및 배포
kubectl create ns monitoring
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 62.3.0 -f monitor-values.yaml --namespace monitoring
# 삭제 시
helm uninstall kube-prometheus-stack -n monitoring
생성 후 웹브라우저 http://localhost:30001는 프로메테우스, http://localhost:30002는 그라파나로 접속할 수 있다.
그라파나의 기본 계정은 admin에 yaml에 설정한 adminPassword로 설정된다.
기본 webpod 2개 생성
로드밸런싱을 확인하기 위해 node를 지정해서 배포한다.
cat <<EOF | kubectl apply -f -
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
EOF
# 배포 확인
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webpod1 1/1 Running 0 43s 10.10.4.12 myk8s-worker <none> <none>
webpod2 1/1 Running 0 43s 10.10.1.11 myk8s-worker2 <none> <none>
# 통신 확인 (각 Pod IP를 추출해 변수로 저장하고 curl로 pod의 web페이지에 접근)
WPOD1=$(kubectl get pod webpod1 -o jsonpath="{.status.podIP}")
WPOD2=$(kubectl get pod webpod2 -o jsonpath="{.status.podIP}")
echo $WPOD1 $WPOD2
docker exec -it myk8s-control-plane curl -s --connect-timeout 1 $WPOD1 | grep Hostname
docker exec -it myk8s-control-plane curl -s --connect-timeout 1 $WPOD2 | grep Hostname
MetalLB 설치
MetalLB 기본 리소스 설치
여러가지 설치방법이 있으나, 스터디에선 manifests 로 설치한다.
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml
해당 파일을 받아서 열어보면 namespace 등을 모두 설정해둔 것을 볼 수 있다.
# 2개씩 Pod가 배포된 이유는 프로메테우스의 exporter가 같이 배포되었기 때문이다.
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get all -n metallb-system
NAME READY STATUS RESTARTS AGE
pod/controller-679855f7d7-7qwxf 2/2 Running 0 3m2s
pod/speaker-42tgr 2/2 Running 0 3m2s
pod/speaker-b86qw 2/2 Running 0 3m2s
pod/speaker-dpdq5 2/2 Running 0 3m2s
pod/speaker-fwcqw 2/2 Running 0 3m2s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/controller-monitor-service ClusterIP None <none> 9120/TCP 3m2s
service/metallb-webhook-service ClusterIP 10.200.1.222 <none> 443/TCP 3m2s
service/speaker-monitor-service ClusterIP None <none> 9120/TCP 3m2s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/speaker 4 4 4 4 4 kubernetes.io/os=linux 3m2s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/controller 1/1 1 1 3m2s
NAME DESIRED CURRENT READY AGE
replicaset.apps/controller-679855f7d7 1 1 1 3m2s
(참고) 그 외 다른 설치 방법 : https://metallb.universe.tf/installation/
MetalLB의 서비스 대역(external IP) 및 모드를 설정
k8s 환경 구성을 kind로 수행하였으므로 kind가 사용하는 네트워크 대역에서 사용하지 않는 범위를 확인하고 지정할 IP 대역을 설정한다. (MetalLB의 서비스 대역은 node와 같은 IP 대역을 꼭 사용하진 않아도 되지만, 이 경우 GW 역할을 수행하는 라우터 등에서 각 node들로 라우팅을 설정해줘야 한다.)
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker network ls | grep kind
82f7f6bc330d kind bridge local
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~#
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker inspect kind | grep Subnet -B 3 -A 3
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
},
{
"Subnet": "fc00:f853:ccd:e793::/64",
"Gateway": "fc00:f853:ccd:e793::1"
}
]
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/myk8s-worker2 172.18.0.5
/myk8s-control-plane 172.18.0.2
/myk8s-worker3 172.18.0.3
/myk8s-worker 172.18.0.4
kind의 subnet은 172.18.0.0/16이지만 node가 사용하는 대역도 존재하므로 피해서 설정
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl explain ipaddresspools.metallb.io
GROUP: metallb.io
KIND: IPAddressPool
VERSION: v1beta1
...
# IPAddressPool을 생성한다.(LoadBalancer External IP로 사용할 IP 대역)
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: my-ippool
namespace: metallb-system
spec:
addresses:
- 172.18.255.200-172.18.255.250
EOF
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get ipaddresspools -n metallb-system
NAME AUTO ASSIGN AVOID BUGGY IPS ADDRESSES
my-ippool true false ["172.18.255.200-172.18.255.250"]
Layer2 모드로 사용해야 하므로 L2Advertisement를 생성한다.
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl explain l2advertisements.metallb.io
GROUP: metallb.io
KIND: L2Advertisement
VERSION: v1beta1
...
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: my-l2-advertise
namespace: metallb-system
spec:
ipAddressPools:
- my-ippool
EOF
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get l2advertisements -n metallb-system
NAME IPADDRESSPOOLS IPADDRESSPOOL SELECTORS INTERFACES
my-l2-advertise ["my-ippool"]
LoadBalancer 서비스 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: svc1
spec:
ports:
- name: svc1-webport
port: 80
targetPort: 80
selector:
app: webpod
type: LoadBalancer # 서비스 타입이 LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
name: svc2
spec:
ports:
- name: svc2-webport
port: 80
targetPort: 80
selector:
app: webpod
type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
name: svc3
spec:
ports:
- name: svc3-webport
port: 80
targetPort: 80
selector:
app: webpod
type: LoadBalancer
EOF
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.200.1.1 <none> 443/TCP 96m
svc1 LoadBalancer 10.200.1.230 172.18.255.200 80:30381/TCP 21s
svc2 LoadBalancer 10.200.1.141 172.18.255.201 80:31502/TCP 21s
svc3 LoadBalancer 10.200.1.116 172.18.255.202 80:32739/TCP 21s
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 172.18.0.2:6443 96m
svc1 10.10.1.11:80,10.10.4.12:80 42s
svc2 10.10.1.11:80,10.10.4.12:80 42s
svc3 10.10.1.11:80,10.10.4.12:80 42s
MetalLB 로드밸런싱 접속 테스트
접속 테스트를 위해 kind와 같은 네트워크 대역에 존재하지만 cluster에는 속하지 않은 임의의 container를 하나 실행하자.
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5ae304d2c65f nicolaka/netshoot "sleep infinity" 5 seconds ago Up 5 seconds mypc
443d02b86545 kindest/node:v1.31.0 "/usr/local/bin/entr…" 2 hours ago Up 2 hours myk8s-worker2
7ad2a3842129 kindest/node:v1.31.0 "/usr/local/bin/entr…" 2 hours ago Up 2 hours 0.0.0.0:30000-30004->30000-30004/tcp, 127.0.0.1:34477->6443/tcp myk8s-control-plane
65aa1df19724 kindest/node:v1.31.0 "/usr/local/bin/entr…" 2 hours ago Up 2 hours myk8s-worker3
2bee747dddd3 kindest/node:v1.31.0 "/usr/local/bin/entr…" 2 hours ago Up 2 hours myk8s-worker
해당 mypc 컨테이너(외부 클라이언트라고 가정)에서 LoadBalancer의 IP들로 접속을 테스트해본다.
# 현재 SVC EXTERNAL-IP를 변수에 지정
SVC1EXIP=$(kubectl get svc svc1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
SVC2EXIP=$(kubectl get svc svc2 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
SVC3EXIP=$(kubectl get svc svc3 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC1EXIP $SVC2EXIP $SVC3EXIP
# 각 서비스 IP들로 접속을 수행해본다.
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC1EXIP | grep Hostname; done | sort | uniq -c | sort -nr"
55 Hostname: webpod1
45 Hostname: webpod2
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC2EXIP | grep Hostname; done | sort | uniq -c | sort -nr"
53 Hostname: webpod1
47 Hostname: webpod2
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC3EXIP | grep Hostname; done | sort | uniq -c | sort -nr"
58 Hostname: webpod1
42 Hostname: webpod2
MetalLB 통신 과정 확인
아래 구성을 보면서 하나씩 확인해보자.
기존의 NodePort는 Node의 IP(이더넷 장치)를 통해서 특정 Port로의 접속을 허용하고 iptables를 통해 Pod로 연결해줬다.
하지만 MetalLB의 IP는 실제 Node가 가진 IP가 아닌데 어떻게 동작하는걸까? 아래의 과정을 보자.
1. 맨처음에 클라이언트1(예: 172.18.0.100)에서 MetalLB의 LoadBalancer 서비스 IP(예: 172.18.255.200)으로 접근할 때 네트워크에서는 해당 IP에 해당하는 MAC 주소가 필요하다.
하지만 LoadBalancer 서비스 IP(예: 172.18.255.200)에 해당하는 MAC 주소는 현재 가지고 있지 않은 상태다.
(⎈|kind-myk8s:N/A) root@DESKTOP-O4EPQ9T:~# docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet
Interface: eth0, type: EN10MB, MAC: 02:42:ac:12:00:02, IPv4: 172.18.0.2
Starting arp-scan 1.10.0 with 65536 hosts (https://github.com/royhills/arp-scan)
172.18.0.1 02:42:47:1e:d7:6f (Unknown: locally administered)
172.18.0.3 02:42:ac:12:00:03 (Unknown: locally administered)
172.18.0.4 02:42:ac:12:00:04 (Unknown: locally administered)
172.18.0.5 02:42:ac:12:00:05 (Unknown: locally administered)
172.18.0.100 02:42:ac:12:00:64 (Unknown: locally administered)
2. 이 때 해당 MAC주소를 알기 위해서 LoadBalancer 서비스 IP(예: 172.18.255.200)를 사용하는 장치가 있으면 응답해달라는 ARP(Address Resolution Protocol) 요청을 브로드캐스트한다.
3. MetalLB는 Layer2 모드에서 클러스터 내의 speaker Pod가 특정 Node의 MAC 주소(여기서는 Service로 등록된 Node)를 ARP 응답으로 반환한다.
GARP(Gratuitous Address Resolution Protocol : 자신의 IP와 MAC주소를 알리는 목적)를 사용하는데, 해당 GARP를 응답하는 녀석이 각 MetalLB Service의 리더 스피커 역할을 한다.
4. 이제 클라이언트1(예 : 172.18.0.100)에서는 해당 LoadBalancer 서비스 IP(예: 172.18.255.200)에 대한 MAC주소를 myk8s-worker Node(172.18.0.4)와 같은 MAC주소로 인식하고 해당 Node로 전달할 수 있게 된다.
5. 이후는 이전에 알아본 ClusterIP 와 유사하게 동작한다.
...
Chain KUBE-SERVICES
0 0 KUBE-SVC-GZ25SP4UFGF7SAVL 6 -- * * 0.0.0.0/0 10.200.1.222 /* metallb-system/metallb-webhook-service cluster IP */ tcp dpt:443
...
Chain KUBE-SVC-GZ25SP4UFGF7SAVL
2 120 KUBE-MARK-MASQ 6 -- * * !10.10.0.0/16 10.200.1.222 /* metallb-system/metallb-webhook-service cluster IP */ tcp dpt:443
2 120 KUBE-SEP-G3DUKK6SGDF6OOCS 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* metallb-system/metallb-webhook-service -> 10.10.1.12:9443 */
...
Chain KUBE-SEP-G3DUKK6SGDF6OOCS
0 0 KUBE-MARK-MASQ 0 -- * * 10.10.1.12 0.0.0.0/0 /* metallb-system/metallb-webhook-service */
2 120 DNAT 6 -- * * 0.0.0.0/0 0.0.0.0/0 /* metallb-system/metallb-webhook-service */ tcp to:10.10.1.12:9443
...
Chain KUBE-SERVICES
0 0 KUBE-EXT-DLGPAL4ZCYSJ7UPR 6 -- * * 0.0.0.0/0 172.18.255.200 /* default/svc1:svc1-webport loadbalancer IP */ tcp dpt:80
...
Chain KUBE-EXT-DLGPAL4ZCYSJ7UPR
0 0 KUBE-MARK-MASQ 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* masquerade traffic for default/svc1:svc1-webport external destinations */
0 0 KUBE-SVC-DLGPAL4ZCYSJ7UPR 0 -- * * 0.0.0.0/0 0.0.0.0/0
...
Chain KUBE-SVC-DLGPAL4ZCYSJ7UPR
0 0 KUBE-MARK-MASQ 6 -- * * !10.10.0.0/16 10.200.1.230 /* default/svc1:svc1-webport cluster IP */ tcp dpt:80
0 0 KUBE-SEP-ZNBEHD3XV5ZTBRAT 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc1:svc1-webport -> 10.10.1.11:80 */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-W6APR3NU75YJK7VG 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc1:svc1-webport -> 10.10.4.12:80 */
...
Chain KUBE-SEP-ZNBEHD3XV5ZTBRAT
0 0 KUBE-MARK-MASQ 0 -- * * 10.10.1.11 0.0.0.0/0 /* default/svc1:svc1-webport */
0 0 DNAT 6 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc1:svc1-webport */ tcp to:10.10.1.11:80
...
Chain KUBE-SEP-W6APR3NU75YJK7VG
0 0 KUBE-MARK-MASQ 0 -- * * 10.10.4.12 0.0.0.0/0 /* default/svc1:svc1-webport */
0 0 DNAT 6 -- * * 0.0.0.0/0 0.0.0.0/0 /* default/svc1:svc1-webport */ tcp to:10.10.4.12:80
...
-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
Node의 iptables 정책을 타고 아래와 같은 과정을 거친다.
- 클라이언트(172.18.0.100)가 172.18.255.200:80으로 패킷을 전송
- 패킷이 KUBE-SERVICES 체인에 도달하면, 목적지 IP와 포트가 일치하는 규칙(KUBE-EXT-DLGPAL4ZCYSJ7UPR)으로 이동
- KUBE-EXT-DLGPAL4ZCYSJ7UPR 체인에서 KUBE-MARK-MASQ 규칙을 통해 패킷에 마스커레이딩 마크를 추가하고, KUBE-SVC-DLGPAL4ZCYSJ7UPR 체인으로 이동
- KUBE-SVC-DLGPAL4ZCYSJ7UPR 체인에서는 두 개의 백엔드 Pod중 하나를 무작위(50% 확률)로 선택
- 50% 확률로 KUBE-SEP-ZNBEHD3XV5ZTBRAT (10.10.1.11:80)
- 50% 확률로 KUBE-SEP-W6APR3NU75YJK7VG (10.10.4.12:80)
- 선택된 KUBE-SEP 체인(예: KUBE-SEP-ZNBEHD3XV5ZTBRAT)에서 DNAT 규칙을 통해 패킷의 목적지 IP와 포트를 선택된 Pod의 IP와 포트(예: 10.10.1.11:80)로 변경
- 변경된 목적지로 패킷이 전달되어 해당 Pod에 도달
- Pod에서 처리된 응답은 역경로를 따라 클라이언트로 전송되고 이때 KUBE-MARK-MASQ에 의해 추가된 마크를 기반으로 소스 IP가 노드의 IP로 변경(SNAT)되어 클라이언트에게 전달된다.
참고자료