CloudNetaStudy - Kubernets Networtk 3기 실습 스터디 게시글입니다.
MetalLB(BareMetalLoadBalancer)
- 온프레미스 환경에서 표준 프로토콜을 사용해 Loadbalancer 서비스를 구현
- 쿠버네티스의 데몬셋으로 스피커 파드를 생성해 External IP를 전파
- External IP는 노드의 IP 대신 외부에서 접속 가능 → 노드 IP가 노출되지 않아 보안성 향상
- 스피커 파드는 External IP 전파를 위해 ARP 혹은 BGP를 사용
- MetalLB는 ARP를 사용하는 Layer2 모드나 BGP를 사용하는 BGP 모드 중 하나를 선택해 동작
- 퍼블릭 클라우드 환경에서는 동작하지 않는다. - 링크
- 일부 CNI와 연동불가 - 링크
- 예시) Calico IPIP(BGP)와 MetalLB BGP 모드를 상단 라우터와 동시 사용 시 문제 발생 - 링크
- 일반적인 BGP 데몬과 다르게 BGP 업데이트(외부에서 광고하는 네트워크 대역)을 받지 않는다.
[ Layer2 모드 ]
- 권장 사용 환경 : 테스트 및 소규모의 환경(동일 네트워크 1개 사용)에서 클라이언트 IP 보존이 필요 없을 때
LoadBalancer 생성
↓
MetalLB 리더 스피커 파드 선출
↓
리더 스피커 파드는 해당 로드밸런서의 External IP를 전파
(ARP 메세지로 동일 네트워크 대역 내에 브로드캐스트 주소로 전파)
- 데몬셋으로 배포된 speaker 파드는 호스트 네트워크를 사용합니다 ⇒ "NetworkMode": "host"
- 리더 스피커파드 장애 시 나머지 스피커파드 중 리더 재선출
- 멤버 리스터 및 장애 발견은 hashicorp 의 memberlist 를 사용 - Gossip based membership and failure detection
- 제한 사항 : single-node bottlenecking, potentially slow failover
- single-node bottlenecking : 서비스 1개 생성 사용 시, 모든 서비스 접근 트래픽은 리더 파드가 존재하는 노드로만 인입되어서 부하가 집중
- ⇒ 공식 가이드는 아니지만, 외부 라우터에서 접근 시 ECMP 로 부하 분산이 가능 - 아래 2.6 참고
- potentially slow failover : 리더(노드)가 장애 시 나머지 노드 리더가 선출되고, ARP 전파 및 갱신완료 전까지는 장애가 발생됨 (대략 10초~20초 정도)
- ⇒ 공식 가이드는 아니지만, 외부 라우터에서 노드(파드) 장애 헬스 체크로 빠르게 절체 가능 - 아래 2.6 참고
[ BGP 모드 ]
- 권장 사용 환경 : 규모가 있고, 클라이언트 IP보존과 장애 시 빠른 절체가 필요하며, 네트워크 팀 협조가 가능할때
- speaker 파드가 BGP로서비스 정보(EXTERNAL-IP)를 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속
- 기본은 IP주소(32bit)를 전파하며, 설정으로 축약된 네트워크 정보를 전파할 수 있다 → bgp-advertisements 에 aggregation-length 설정
- BGP 커뮤니티, localpref 등 BGP 관련 설정을 할 수 있다
- IP 주소 마지막이 0 과 255 를 처리를 못하는 라우터 장비가 있을 경우 avoid-buggy-ips: true 옵션으로 할당되지 않게 할 수 있다
- 외부 클라이언트에서 SVC(서비스, EXTERNAL-IP)로 접속이 가능하며, 라우터에서 ECMP 라우팅을 통해 부하 분산 접속 할 수 있다
- 일반적으로 ECMP 는 5-tuple(프로토콜, 출발지IP, 목적지IP, 출발지Port, 목적지Port) 기준으로 동작합니다.
- 물론 라우터 장비에 따라 다양한 라우팅(분산) 처리가 가능합니다
- 라우터에서 서비스로 인입이 되기 때문에, 라우터의 관련 설정이 중요한 만큼 네트워크팀과 협업을 적극 권장
- 노드(speaker) 파드 장애 시 BGP Timer 설정 등 구성하고 있는 네트워크 환경에 맞게 최적화 작업이 필요
- ECMP 부하 분산 접속 시 특정 파드에 몰리거나 혹은 세션 고정, flapping 등 다양한 환경에 대응이 필요
- BGP 라우팅 설정 및 라우팅 전파 관련 최적화 설정이 필요
실습 환경 소개
[ 기본 정보 확인 ]
# 파드와 서비스 사용 가능 네트워크 대역
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"
"--service-cluster-ip-range=10.200.1.0/24",
"--cluster-cidr=10.10.0.0/16",
# kube-proxy 모드 확인 : iptables proxy 모드
kubectl describe configmap -n kube-system kube-proxy | grep mode
mode: "iptables"
# 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
[ 파드 생성 ]
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
[ 파드 접속 확인 ]
# 파드 정보 확인
kubectl get pod -owide
# 파드 IP주소를 변수에 지정
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 ping -i 1 -W 1 -c 1 $WPOD1
docker exec -it myk8s-control-plane ping -i 1 -W 1 -c 1 $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
docker exec -it myk8s-control-plane curl -s --connect-timeout 1 $WPOD1 | egrep 'Hostname|RemoteAddr|Host:'
docker exec -it myk8s-control-plane curl -s --connect-timeout 1 $WPOD2 | egrep 'Hostname|RemoteAddr|Host:'
MetalLB 설치
[ Layer2 모드 ]
- 설치 방법 지원 : Kubernetes manifests, using Kustomize, or using Helm
- 참고 : kube-proxy 의 ipvs 모드 사용 시 'strictARP: true' 설정 필요
참고 : https://kschoi728.tistory.com/264
- 참고 : kube-proxy 의 ipvs 모드 사용 시 'strictARP: true' 설정 필요
- manifests 로 설치 진행! https://github.com/metallb/metallb/tree/main/config/manifests
metallb/config/manifests at main · metallb/metallb
A network load-balancer implementation for Kubernetes using standard routing protocols - metallb/metallb
github.com
# Kubernetes manifests 로 설치
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml
## 혹은 프로메테우스 미설치시 아래 manifests 로 설치
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native.yaml
# metallb crd 확인
kubectl get crd | grep metallb
bfdprofiles.metallb.io 2024-09-28T15:24:06Z
bgpadvertisements.metallb.io 2024-09-28T15:24:06Z
bgppeers.metallb.io 2024-09-28T15:24:06Z
communities.metallb.io 2024-09-28T15:24:06Z
ipaddresspools.metallb.io 2024-09-28T15:24:06Z
l2advertisements.metallb.io 2024-09-28T15:24:06Z
servicel2statuses.metallb.io 2024-09-28T15:24:06Z
# 생성된 리소스 확인 : metallb-system 네임스페이스 생성, 파드(컨트롤러, 스피커) 생성, RBAC(서비스/파드/컨피그맵 조회 등등 권한들), SA 등
kubectl get-all -n metallb-system # kubectl krew 플러그인 get-all 설치 후 사용 가능
kubectl get all,configmap,secret,ep -n metallb-system
# 파드 내에 kube-rbac-proxy 컨테이너는 프로메테우스 익스포터 역할 제공
kubectl get pods -n metallb-system -l app=metallb -o jsonpath="{range .items[*]}{.metadata.name}{':\n'}{range .spec.containers[*]}{' '}{.name}{' -> '}{.image}{'\n'}{end}{end}"
## metallb 컨트롤러는 디플로이먼트로 배포됨
kubectl get ds,deploy -n metallb-system
## 데몬셋으로 배포되는 metallb 스피커 파드의 IP는 네트워크가 host 모드이므로 노드의 IP를 그대로 사용
kubectl get pod -n metallb-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
controller-679855f7d7-2dvbm 2/2 Running 0 9m17s 10.10.2.6 myk8s-worker3 <none> <none>
speaker-jfvh9 2/2 Running 0 9m17s 172.18.0.2 myk8s-worker <none> <none>
speaker-l2tdn 2/2 Running 0 9m17s 172.18.0.5 myk8s-worker3 <none> <none>
speaker-pzs8z 2/2 Running 0 9m17s 172.18.0.3 myk8s-worker2 <none> <none>
speaker-vfsdj 2/2 Running 0 9m17s 172.18.0.4 myk8s-control-plane <none> <none>
## metallb 데몬셋으로 배포되는 스피커 파드는 hostNetwork 를 사용함
kubectl get ds -n metallb-system -o yaml
...
ports:
- containerPort: 7472
name: monitoring
protocol: TCP
- containerPort: 7946
name: memberlist-tcp
protocol: TCP
- containerPort: 7946
name: memberlist-udp
protocol: UDP
...
hostNetwork: true
...
## 스피커 파드의 Ports 정보 확인 : Host Ports 같이 확인
kubectl describe pod -n metallb-system -l component=speaker| grep Ports:
Ports: 7472/TCP, 7946/TCP, 7946/UDP
Host Ports: 7472/TCP, 7946/TCP, 7946/UDP
# (참고) 상세 정보 확인
kubectl get sa,cm,secret -n metallb-system
kubectl describe role -n metallb-system
kubectl describe deploy controller -n metallb-system
kubectl describe ds speaker -n metallb-system
- 컨피그맵 생성 : 모드 및 서비스 대역 지정
- 서비스(External-IP) 대역을 노드가 속한 eth0의 대역이 아니여도 상관없다! → 다만 이 경우 GW 역할의 라우터에서 노드들로 라우팅 경로 지정 필요
# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역 docker network ls docker inspect kind # 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}}' # IPAddressPool 생성 : LoadBalancer External IP로 사용할 IP 대역 ## MetalLB는 서비스를 위한 외부 IP 주소를 관리하고, 서비스가 생성될 때 해당 IP 주소를 동적으로 할당할 수 있습니다. kubectl explain ipaddresspools.metallb.io 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 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"] # L2Advertisement 생성 : 설정한 IPpool을 기반으로 Layer2 모드로 LoadBalancer IP 사용 허용 ## Kubernetes 클러스터 내의 서비스가 외부 네트워크에 IP 주소를 광고하는 방식을 정의 kubectl explain l2advertisements.metallb.io cat <<EOF | kubectl apply -f - apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: my-l2-advertise namespace: metallb-system spec: ipAddressPools: - my-ippool EOF kubectl get l2advertisements -n metallb-system NAME IPADDRESSPOOLS IPADDRESSPOOL SELECTORS INTERFACES my-l2-advertise ["my-ippool"]
- 서비스(External-IP) 대역을 노드가 속한 eth0의 대역이 아니여도 상관없다! → 다만 이 경우 GW 역할의 라우터에서 노드들로 라우팅 경로 지정 필요
서비스 생성 및 확인
[ 서비스(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
[ 서비스 확인 및 리더 Speaker 파드 확인 ]
# arp scan 해두기
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet
# LoadBalancer 타입의 서비스 생성 확인 : EXTERNAL-IP가 서비스 마다 할당되며, 실습 환경에 따라 다를 수 있음
## LoadBalancer 타입의 서비스는 NodePort 와 ClusterIP 를 포함함 - 'allocateLoadBalancerNodePorts : true' 기본값
## ExternalIP 로 접속 시 사용하는 포트는 PORT(S) 의 앞에 있는 값을 사용 (아래의 경우는 TCP 80 임)
## 만약 노드의 IP에 NodePort 로 접속 시 사용하는 포트는 PORT(S) 의 뒤에 있는 값을 사용 (아래는 30485 임)
kubectl get service,ep
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.200.1.1 <none> 443/TCP 121m
service/svc1 LoadBalancer 10.200.1.69 172.18.255.200 80:30485/TCP 3m37s
service/svc2 LoadBalancer 10.200.1.218 172.18.255.201 80:31046/TCP 3m37s
service/svc3 LoadBalancer 10.200.1.81 172.18.255.202 80:30459/TCP 3m37s
NAME ENDPOINTS AGE
endpoints/kubernetes 172.18.0.5:6443 31m
endpoints/svc1 10.10.1.6:80,10.10.3.6:80 8m4s
endpoints/svc2 10.10.1.6:80,10.10.3.6:80 8m4s
endpoints/svc3 10.10.1.6:80,10.10.3.6:80 8m4s
# LoadBalancer 타입은 기본적으로 NodePort를 포함 사용. NodePort는 ClusterIP를 포함 사용.
## 클라우드사업자 LB Type이나 온프레미스환경 HW LB Type 경우 LB 사용 시 NodePort 미사용 설정 가능
kubectl describe svc svc1
## 아래 처럼 LB VIP 별로 이던 speaker 배포된 노드가 리더 역할을 하는지 확인 가능
kubectl describe svc | grep Events: -A5
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal IPAllocated 40m metallb-controller Assigned IP ["172.18.255.201"]
Normal nodeAssigned 40m metallb-speaker announcing from node "myk8s-worker" with protocol "layer2"
...
kubectl get svc svc1 -o json | jq
...
"spec": {
"allocateLoadBalancerNodePorts": true,
...
"status": {
"loadBalancer": {
"ingress": [
{
"ip": "172.18.255.202",
"ipMode": "VIP" # https://kubernetes.io/blog/2023/12/18/kubernetes-1-29-feature-loadbalancer-ip-mode-alpha/
} # https://kubernetes.io/docs/concepts/services-networking/service/#load-balancer-ip-mode
# metallb CRD인 servicel2status 로 상태 정보 확인
kubectl explain servicel2status
kubectl get servicel2status -n metallb-system
kubectl describe servicel2status -n metallb-system
kubectl get servicel2status -n metallb-system -o json --watch # watch 모드
# 현재 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
# mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾는법 : arping 툴 사용
## Unicast reply from 172.18.255.200: 해당 IP 주소에서 응답을 받았음을 의미합니다.
## Sent 1 probes (1 broadcast(s)): 하나의 ARP 요청을 보냈고, 브로드캐스트 방식으로 요청을 전송했음을 나타냅니다.
## Received 1 response(s): 하나의 응답을 수신했음을 나타냅니다.
docker exec -it mypc arping -I eth0 -f -c 1 $SVC1EXIP
docker exec -it mypc arping -I eth0 -f -c 1 $SVC2EXIP
docker exec -it mypc arping -I eth0 -f -c 1 $SVC3EXIP
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc arping -I eth0 -f -c 1 $i; done
docker exec -it mypc ip -c neigh
docker exec -it mypc ping -c 1 -w 1 -W 1 $SVC1EXIP
docker exec -it mypc ping -c 1 -w 1 -W 1 $SVC2EXIP
docker exec -it mypc ping -c 1 -w 1 -W 1 $SVC3EXIP
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc ping -c 1 -w 1 -W 1 $i; done
for i in 172.18.0.2 172.18.0.3 172.18.0.4 172.18.0.5; do docker exec -it mypc ping -c 1 -w 1 -W 1 $i; done
# mypc/mypc2 에서 arp 테이블 정보 확인 >> SVC IP별로 리더 파드(스피커) 역할의 노드를 확인!
docker exec -it mypc ip -c neigh | sort
172.18.0.2 dev eth0 lladdr 02:42:ac:12:00:02 REACHABLE
172.18.0.3 dev eth0 lladdr 02:42:ac:12:00:03 REACHABLE
172.18.0.4 dev eth0 lladdr 02:42:ac:12:00:04 REACHABLE
172.18.0.5 dev eth0 lladdr 02:42:ac:12:00:05 DELAY
172.18.255.200 dev eth0 lladdr 02:42:ac:12:00:04 STALE
172.18.255.201 dev eth0 lladdr 02:42:ac:12:00:04 STALE
172.18.255.202 dev eth0 lladdr 02:42:ac:12:00:02 STALE
kubectl get node -owide # mac 주소에 매칭되는 IP(노드) 찾기
# (옵션) 노드에서 ARP 패킷 캡쳐 확인
docker exec -it myk8s-control-plane tcpdump -i eth0 -nn arp
docker exec -it myk8s-worker tcpdump -i eth0 -nn arp
docker exec -it myk8s-worker2 tcpdump -i eth0 -nn arp
docker exec -it myk8s-worker3 tcpdump -i eth0 -nn arp
# (옵션) metallb-speaker 파드 로그 확인
kubectl logs -n metallb-system -l app=metallb -f
kubectl logs -n metallb-system -l component=speaker --since 1h
kubectl logs -n metallb-system -l component=speaker -f
# (옵션) kubectl krew 플러그인 stern 설치 후 아래 명령 사용 가능
kubectl stern -n metallb-system -l app=metallb
kubectl stern -n metallb-system -l component=speaker --since 1h
kubectl stern -n metallb-system -l component=speaker # 기본 설정이 follow
kubectl stern -n metallb-system speaker # 매칭 사용 가능
Failover 테스트
- 리더 Speaker 파드가 존재하는 노드를 재부팅! → curl 접속 테스트 시 10~20초 정도의 장애 시간이 발생하였다 ⇒ 이후 자동 원복 되며, 원복 시 5초 정도 장애 시간 발생!
# 사전 준비 ## 지속적으로 반복 접속 SVC1EXIP=$(kubectl get svc svc1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $SVC1EXIP | egrep 'Hostname|RemoteAddr'; date '+%Y-%m-%d %H:%M:%S' ; echo ; sleep 1; done" ## 상태 모니터링 watch -d kubectl get pod,svc,ep ## 실시간 로그 확인 kubectl logs -n metallb-system -l app=metallb -f 혹은 kubectl stern -n metallb-system -l app=metallb # 장애 재연 ## 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)를 중지 docker stop <svc1 번 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)> --signal 9 docker stop myk8s-worker --signal 9 혹은 docker stop <svc1 번 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)> --signal 15 docker stop myk8s-worker --signal 15 docker ps -a docker ps -a | grep worker$ ## 지속적으로 반복 접속 상태 모니터링 ### curl 연속 접속 시도 >> 대략 10초 이내에 정상 접근 되었지만, 20초까지는 불안정하게 접속이 되었다 ### 실제로는 다른 노드의 speaker 파드가 리더가 되고, 이후 다시 노드(컨테이너)가 정상화되면, 다시 리더 speaker 가 됨 docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $SVC1EXIP | egrep 'Hostname|RemoteAddr'; date '+%Y-%m-%d %H:%M:%S' ; echo ; sleep 1; done" Hostname: webpod1 RemoteAddr: 172.18.0.2:25432 2024-09-29 06:31:07 2024-09-29 06:31:09 Hostname: webpod2 RemoteAddr: 172.18.0.2:26011 2024-09-29 06:31:10 2024-09-29 06:31:12 2024-09-29 06:31:14 ... # 변경된 리더 Speaker 파드 확인 # mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기 for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc ping -c 1 -w 1 -W 1 $i; done # mypc/mypc2 에서 arp 테이블 정보 확인 >> SVC IP별로 리더 파드(스피커) 역할의 노드를 확인! docker exec -it mypc ip -c neigh | sort kubectl get node -owide # mac 주소에 매칭되는 IP(노드) 찾기 # 장애 원복(노드 정상화) ## 노드(실제 컨테이너) 정상화 docker start <svc1 번 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)> docker start myk8s-worker # 변경된 리더 Speaker 파드 확인 # mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기 for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc ping -c 1 -w 1 -W 1 $i; done # mypc/mypc2 에서 arp 테이블 정보 확인 >> SVC IP별로 리더 파드(스피커) 역할의 노드를 확인! docker exec -it mypc ip -c neigh | sort kubectl get node -owide # mac 주소에 매칭되는 IP(노드) 찾기
- 다음 실습을 위해 오브젝트 삭제 kubectl delete pod,svc --all
(옵션) externalTrafficPolicy: Local 테스트
[ 서비스에 externalTrafficPolicy: Local 설정 ]
kubectl patch svc svc1 -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl patch svc svc2 -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl patch svc svc3 -p '{"spec":{"externalTrafficPolicy": "Local"}}'
- 클라이언트 → 서비스(External-IP) 접속 테스트 : 리더 speaker 파드가 존재하는 노드에 위치한 애플리케이션 파드로만 접속되며, 클라이언트 IP 보존!, 다만 부하분산이 되지 않아 비효율적이며, 만약 리더 노드에 애플리케이션 파드가 없을 시는 접속 불가(타임 아웃)로 문제가 발생한다 ⇒ 즉, 해당 조합의 설정은 하지 말것을 권장한다
# 100번 접속 시도 시 for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC2EXIP | grep Hostname; done | sort | uniq -c | sort -nr # 애플리케이션 파드 접속 시 클라이언트 IP가 보존 curl -s --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr|Host:'
[ BGP모드 ]
- MetalLB ConfigMap 생성
cat <<EOF | kubectl replace --force -f - apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | peers: - peer-address: 192.168.10.254 peer-asn: 64513 my-asn: 64512 address-pools: - name: default protocol: bgp avoid-buggy-ips: true addresses: - 172.20.1.0/24 EOF
- avoid-buggy-ips: true ⇒ Handling buggy networks - 링크
Some old consumer network equipment mistakenly blocks IP addresses ending in .0 and .255, because of misguided smurf protection. If you encounter this issue with your users or networks, you can set avoid-buggy-ips: true on an address pool to mark .0 and .255 addresses as unusable.
- avoid-buggy-ips: true ⇒ Handling buggy networks - 링크
- 리눅스 라우터에 BGP 설정 (예시) ← 미리 되어 있음
router bgp 64513 bgp router-id 192.168.10.254 maximum-paths 4 network 10.1.1.0/24 neighbor 192.168.10.10 remote-as 64512 neighbor 192.168.10.101 remote-as 64512 neighbor 192.168.10.102 remote-as 64512 ...
서비스 생성 및 확인
[ 파드 생성 ]
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: webpod1
labels:
app: webpod
spec:
#nodeName: k8s-w1
containers:
- name: container
image: traefik/whoami
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: webpod2
labels:
app: webpod
spec:
#nodeName: k8s-w2
containers:
- name: container
image: traefik/whoami
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: webpod3
labels:
app: webpod
spec:
#nodeName: k8s-w3
containers:
- name: container
image: traefik/whoami
terminationGracePeriodSeconds: 0
EOF
[ 서비스(LoadBalancer 타입) 생성 ]
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Service
metadata:
name: svc1
spec:
ports:
- name: svc1-webport
port: 80
targetPort: 80
selector:
app: webpod
type: LoadBalancer
#externalTrafficPolicy: Local
---
apiVersion: v1
kind: Service
metadata:
name: svc2
spec:
ports:
- name: svc2-webport
port: 80
targetPort: 80
selector:
app: webpod
type: LoadBalancer
#externalTrafficPolicy: Local
---
apiVersion: v1
kind: Service
metadata:
name: svc3
spec:
ports:
- name: svc3-webport
port: 80
targetPort: 80
selector:
app: webpod
type: LoadBalancer
#externalTrafficPolicy: Local
EOF
[ 서비스 확인 (노드) ]
# 서비스 확인
kubectl get service
# 현재 SVC EXTERNAL-IP를 변수에 지정
SVC1EXIP=$(kubectl get svc svc1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC1EXIP
SVC2EXIP=$(kubectl get svc svc2 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC2EXIP
SVC3EXIP=$(kubectl get svc svc3 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC3EXIP
# TCP 179 포트로 BGP 정보 전파(speaker)
ss -tunp | egrep 'Netid|speaker'
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp ESTAB 0 0 192.168.10.10:39171 192.168.10.254:179 users:(("speaker",pid=3057,fd=27))
...
# (옵션) 노드에서 BGP 패킷 캡쳐 확인
tcpdump -i <노드NIC> -nnq tcp port 179
tcpdump -i enp0s8 -nnq tcp port 179
ngrep -tW byline -d enp0s8 '' 'tcp port 179'
- BGP 정보 확인 (k8s-rtr) : 서비스(EX-IP)들을 32bit IP를 전달 받음 → 축약 대역 전파 설정 가능 ⇒ 다만, 노드들은 상대측 BGP 네트워크 정보를 받지 않는다!
# 서비스(EX-IP) 3개의 각각 IP에 대해서 K8S 노드로 ECMP 라우팅 정보가 업데이트되었다 ip -c route | grep 172.20. -A3 172.20.1.1 proto zebra metric 20 nexthop via 192.168.10.10 dev enp0s8 weight 1 nexthop via 192.168.10.101 dev enp0s8 weight 1 nexthop via 192.168.10.102 dev enp0s8 weight 1 172.20.1.2 proto zebra metric 20 nexthop via 192.168.10.10 dev enp0s8 weight 1 nexthop via 192.168.10.101 dev enp0s8 weight 1 nexthop via 192.168.10.102 dev enp0s8 weight 1 172.20.1.3 proto zebra metric 20 nexthop via 192.168.10.10 dev enp0s8 weight 1 nexthop via 192.168.10.101 dev enp0s8 weight 1 nexthop via 192.168.10.102 dev enp0s8 weight 1 # vtysh 를 이용한 Quagga 정보 확인 vtysh -c 'show ip route bgp' vtysh -c 'show bgp ipv4 unicast summary' vtysh -c 'show ip bgp' vtysh -c 'show running-config' ---------------------- # Quagga 모드 진입 vtysh # 라우팅 정보 확인 show ip route bgp B>* 172.20.1.1/32 [20/0] via 192.168.10.10, enp0s8, 00:30:34 * via 192.168.10.101, enp0s8, 00:30:34 * via 192.168.10.102, enp0s8, 00:30:34 B>* 172.20.1.2/32 [20/0] via 192.168.10.10, enp0s8, 00:30:34 * via 192.168.10.101, enp0s8, 00:30:34 * via 192.168.10.102, enp0s8, 00:30:34 B>* 172.20.1.3/32 [20/0] via 192.168.10.10, enp0s8, 00:30:34 * via 192.168.10.101, enp0s8, 00:30:34 * via 192.168.10.102, enp0s8, 00:30:34 ... # BGP 상세 정보 확인 show bgp ipv4 unicast summary show ip bgp show running-config ... exit
서비스 접속 테스트
[ 클라이언트(k8s-pc) → 서비스(External-IP) 접속 테스트 ]
# 라우팅 테이블 정보 확인
ip -c route | grep zebra
10.1.1.0/24 via 192.168.20.254 dev enp0s8 proto zebra metric 20
172.20.1.1 via 192.168.20.254 dev enp0s8 proto zebra metric 20
172.20.1.2 via 192.168.20.254 dev enp0s8 proto zebra metric 20
172.20.1.3 via 192.168.20.254 dev enp0s8 proto zebra metric 20
# vtysh 를 이용한 Quagga 정보 확인
vtysh -c 'show ip route bgp'
vtysh -c 'show bgp ipv4 unicast summary'
vtysh -c 'show ip bgp'
vtysh -c 'show running-config'
# 현재 SVC EXTERNAL-IP를 변수에 지정
SVC1EXIP=$(kubectl get svc svc1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC1EXIP
SVC2EXIP=$(kubectl get svc svc2 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC2EXIP
SVC3EXIP=$(kubectl get svc svc3 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SVC3EXIP
# 접속 테스트
curl -s --interface 192.168.20.100 $SVC1EXIP | grep Hostname
curl -s --interface 192.168.20.100 $SVC2EXIP | grep Hostname
curl -s --interface 192.168.20.100 $SVC3EXIP | grep Hostname
# RemoteAddr 주소 확인
curl -s --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr|Host:'
curl -s --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr|Host:'
curl -s --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr|Host:'
# 부하분산 접속됨
for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC1EXIP | grep Hostname; done | sort | uniq -c | sort -nr
for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC2EXIP | grep Hostname; done | sort | uniq -c | sort -nr
for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC3EXIP | grep Hostname; done | sort | uniq -c | sort -nr
# (옵션) 지속적으로 접속
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
- 클라이언트 → 서비스(External-IP) 접속 시 : 라우터 ECMP 분산 접속으로 노드 인입 후 Service 에 매칭된 iptables rules 에 따라 랜덤 부하 분산되어서(SNAT) 파드로 접속!
- (옵션) iptables 정책 적용 확인 : 2.3 의 iptables 정책과 동일!
# 정책 확인 : 아래 정책 내용은 핵심적인 룰(rule)만 표시했습니다! iptables -t nat -S # SVC(External-IP) 접속 시 iptables 에서 DNAT 되어 파드로 전달된다 (SNAT 도 동작) -A KUBE-SERVICES -d 172.20.1.1/32 -p tcp -m comment --comment "default/svc1:svc1-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-DLGPAL4ZCYSJ7UPR -A KUBE-SERVICES -d 172.20.1.2/32 -p tcp -m comment --comment "default/svc2:svc2-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-52NWEQM6QLDFKRCQ -A KUBE-SERVICES -d 172.20.1.3/32 -p tcp -m comment --comment "default/svc3:svc3-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-CY3XQR2NWKKCV4WA -A KUBE-FW-CY3XQR2NWKKCV4WA -m comment --comment "default/svc3:svc3-webport loadbalancer IP" -j KUBE-SVC-CY3XQR2NWKKCV4WA -A KUBE-SVC-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-RR4AIWWPC73NKIRU -A KUBE-SVC-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport" -j KUBE-SEP-M5K3PIW2STI5BGSH -A KUBE-SEP-RR4AIWWPC73NKIRU -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 172.16.46.15:80 -A KUBE-SEP-M5K3PIW2STI5BGSH -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 172.16.46.16:80
externalTrafficPolicy: Local 테스트
[ 서비스에 externalTrafficPolicy: Local 설정 ]
[k8s-pc] 지속적으로 접속 시도 : 아래 중 하나 실행
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
[k8s-rtr] 모니터링 >> 설정 후 최적인 정보 확인
watch -d ip route
# 설정
kubectl patch svc svc1 -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl patch svc svc2 -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl patch svc svc3 -p '{"spec":{"externalTrafficPolicy": "Local"}}'
[ 리눅스 라우터에서 BGP 정보 확인 ]
[k8s-rtr] 파드가 있는 노드만 nexthop 올라옴 (현재 마스터 노드에 파드가 없는 상태)
ip -c route
172.20.1.1 proto zebra metric 20
nexthop via 192.168.100.102 dev enp0s8 weight 1
nexthop via 192.168.100.101 dev enp0s8 weight 1
172.20.1.2 proto zebra metric 20
nexthop via 192.168.100.101 dev enp0s8 weight 1
nexthop via 192.168.100.102 dev enp0s8 weight 1
172.20.1.3 proto zebra metric 20
nexthop via 192.168.100.102 dev enp0s8 weight 1
nexthop via 192.168.100.101 dev enp0s8 weight 1
...
- 클라이언트 → 서비스(External-IP) 접속 : 라우터 ECMP 분산 접속, 클라이언트의 IP가 보존!
- (옵션) iptables 정책 적용 확인 : 일반적인 NodePort 에 externalTrafficPolicy: Local 설정과 동일!
# iptables nat 중 172.20.1.1 예시 : 해당 노드에 파드가 존재 시 해당 파드로만 전달 -A KUBE-SERVICES -d 172.20.1.1/32 -p tcp -m comment --comment "default/svc1:svc1-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-DLGPAL4ZCYSJ7UPR -A KUBE-FW-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport loadbalancer IP" -j KUBE-XLB-DLGPAL4ZCYSJ7UPR -A KUBE-XLB-DLGPAL4ZCYSJ7UPR -m comment --comment "Balancing rule 0 for default/svc1:svc1-webport" -j KUBE-SEP-RJWQ4OMGKELL22XG -A KUBE-SEP-RJWQ4OMGKELL22XG -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 172.16.228.78:80 # 만약 해당 노드에 파드가 없을 시 아래 처럼 DROP 된다 -A KUBE-XLB-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport has no local endpoints" -j KUBE-MARK-DROP
- MetalLB 사용 시 가장 권장하는 방법은 BGP 모드에 externalTrafficPolicy: Local 설정을 하는 것이다.
- 네트워크팀과 협조하여 최적의 라우팅과 장애 시 빠른 절체, 그리고 클라이언트 IP보존 등의 기능 제공이 가능하다
- 추가 고려사항은 한 노드에 목적지 파드가 다수일 경우 부하분산의 불균형이 발생할 수 있으니, pod anti-affinity 로 최대한 노드별로 목적지 파드를 균등하게 배치하자!
'Kubernetes' 카테고리의 다른 글
[ Kans 3 Study - 6w ] 1.실습환경 구성 (6) | 2024.10.12 |
---|---|
[ Kans 3 Study - 5w ] 3. External IP / IPVS Proxy 모드 (2) | 2024.10.05 |
[ Kans 3 Study - 5w ] 1. LoadBalancer (0) | 2024.10.05 |
[ Kans 3 Study - 4w ] 2. Service - NodePort (1) | 2024.09.29 |
[ Kans 3 Study - 4w ] 1. Service - ClusterIP (1) | 2024.09.22 |