새소식

Kubernetes

[Study 5주차] 쿠버네티스 Service - LoadBalancer (MetalLB)

  • -

CoudNet@ 팀의 가시다님께서 리딩하시는 KANS Study (Kubernetes Advanced Networking Study) 5주차 스터디 내용 정리

 

 

이번 글에는 Service 타입 중 LoadBalancer 에 대해서 알아보겠습니다.
그 중, 온프레미스 환경에서 테스트할 수 있는 MetalLB 에 대해서 더 자세히 공부해보겠습니다.

 

 

1. LoadBalancer

 

Service 를 외부의 LoadBalancer 를 사용하여 노출시켜주는 역할을 합니다.

 

AWS, Azure 등의 퍼블릭 클라우드 환경에서 동작하는 지,

온프레미스 환경에서 동작하는 지에 따라 LoadBalancer 서비스의 동작이 차이가 있습니다.

 

 

또한, 클라우드 환경에서의 LoadBalancer 서비스도 2가지의 동작 방식이 존재 합니다.

 

 

1. NodePort 접근 방식

외부 로드밸런서가 NodePort 로 오픈되어 있는 서비스에 먼저 접근을 합니다.
해당 서비스는 한 번 더 목적지 파드로 부하를 분산 시킵니다.


외부 로드밸런서에서 1번, 서비스에서 1번, 총 2번의 부하분산 과정이 수행되며

그만큼 성능이 저하되며 네트워크 비용이 발생할 수 있습니다.

 

2. Pod Direct 접근 방식

외부 로드밸런서가 목적지 파드의 IP 로 직접 부하를 분산시킵니다.
LoadBalancer Controller 파드가 목적지 파드의 IP를 외부 로드밸런서에게 전달해줍니다.
NodePort 접근 방식과는 다르게, 1번의 부하분산 과정이 수행되어 그만큼 효율적으로 동작합니다.

 

 

2. MetalLB

MetalLB 는 BareMetalLoadBalancer 의 약자로,
온프레미스 환경에서 LoadBalancer 서비스를 구현해주는 프로그램입니다.

 

MetalLB 공식 문서

 

https://metallb.universe.tf/

 

MetalLB 동작원리

 

쿠버네티스의 데몬셋으로 스피커 파드를 생성하여, External IP 를 전파합니다.
이를 통해 외부에서 쿠버네티스의 서비스로 접근이 가능합니다.

 

MetalLB 의 스피커 파드가 다른 노드에게 External IP 를 전파하기 위해서 ARP 또는 BGP 를 사용합니다.
이 때 ARP 를 사용하는 것을 Layer2 모드, BGP 를 사용하는 것을 BGP 모드라고 지칭합니다.



2.1. Layer2 모드 (ARP)

 

출처: kans 스터디

 

Layer2 모드 동작방식

 

여러개의 스피커 파드 중, 1개의 리더 파드가 선출되고 해당 리더 파드가 있는 노드로만 트래픽이 인입되며

해당 노드에서 iptables 을 통해 부하 분산되어 목적지 파드로 접속됩니다.
스피커 파드는 호스트 네트워크를 사용합니다.

 

리더 파드 혹은 리더 노드에 장애 발생 시 나머지 스피커 파드 중 1개가 리더로 선출됩니다.

 

Layer2 모드 제한사항

 

모든 서비스 접근 트래픽은 리더 파드가 존재하는 노드로만 인입되기 때문에, 부하가 집중되어 병목 현상이 발생할 수 있습니다.
리더 장애 시, 다른 파드가 리더로 선출되고 해당 내용이 전파되기까지 10~20초 정도 소요되어 그 사이에는 장애가 발생합니다.

 

그렇기 때문에 Layer2 모드는 실제 환경에서는 사용이 권장되지 않습니다.



2.2. BGP 모드

 

출처: kans 스터디

 

BGP 모드 동작방식

 

스피커 파드가 BGP 를 통해 External IP 를 전파하고, 외부의 라우터를 통해 ECMP 라우팅으로 부하를 분산합니다.
Layer2 모드는 리더 파드가 존재하는 노드로 트래픽이 집중되지만,

BGP 모드는 외부의 라우터가 해당 트래픽을 분산하기 때문에 훨씬 효율적입니다.

 

 

3. MetalLB Layer2 모드 실습

 

MetalLB 는 클라우드 환경에서 배포할 수 없기 때문에, kind 를 통해 실습환경을 구성했습니다.



3.1. 실습 환경 구성 - kind 클러스터

 

kind 클러스터 생성

 

# Kind 클러스터 YAML
cat <<EOT> kind-svc-2w.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  "InPlacePodVerticalScaling": true
  "MultiCIDRServiceAllocator": true
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:
        runtime-config: api/all=true
    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
  serviceSubnet: 10.200.1.0/24
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

# 테스트 컨테이너 배포
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시

 

실습 파드 생성

 

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

 

MetalLB 설치

 

MetalLB 는 간단하게 manifest 로 설치를 진행하였습니다.

 

# MetalLB 설치
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml

# metallb crd 확인
kubectl get crd | grep metallb

# 생성된 리소스 확인
kubectl get all,configmap,secret,ep -n metallb-system

 



3.2. 컨피그맵 생성

 

IP Pool 선언

 

가장 우선적으로 해줘야할 것은 MetalLB 가 사용할 External IP 의 IP Pool 을 선언하는 것입니다.
'ipaddresspools' 컨피그맵을 통해 IP Pool 을 선언할 수 있습니다.

 

# IP Pool 생성 (172.18.255.200 부터 172.18.255.250 까지 약 50개)
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

 

IP Advertisement 선언

 

IP Pool 을 선언 한 후, Layer2 모드에서 해당 IP Pool 사용을 허용하는 것이 필요합니다.

 

cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: my-l2-advertise
  namespace: metallb-system
spec:
  ipAddressPools:
  - my-ippool
EOF



3.3. 서비스 생성

 

서비스 타입이 로드밸런서인 서비스를 생성합니다.

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

 

 

NodePort 제거

 

로드밸런서 타입은 기본적으로 노드포트를 포함합니다.
보안 상 사용하지 않는 포트는 닫는 것이 좋기 때문에, 노드포트를 사용하지 않기 위해서 allocate 설정을 해줄 수 있습니다.

kubectl get svc svc1 -o json | jq

 

 

리더 스피커 파드 확인

 

배포된 MetalLB 의 스피커 파드 중, 각각 어떤 파드가 리더 파드로 선출했는 지 확인해보겠습니다.
Layer2 모드는 ARP 를 사용하기 때문에, ARP scan 과 arping 기능을 사용하여 확인해보겠습니다.

 

# Control Plane 의 ARP Scan 활성화
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet

# ARP Ping 테스트
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}')
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc arping -I eth0 -f -c 1 $i; done

 

 

위 사진과 같이 구성될 경우,
svc1 과 svc2 로 들어오는 트래픽은 무조건 Node1 로 인입되며,
svc3 으로 들어오는 트래픽은 무조건 Node3 으로 인입됩니다.

 

External IP 접근

 

External IP 를 통해서 서비스에 접근해보면, 접근이 잘되는 것을 알 수 있습니다.

 

for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do echo ">> Access Service External-IP : $i <<" ;docker exec -it mypc curl -s $i | egrep 'Hostname|RemoteAddr|Host:' ; echo ; done

 



3.4. Failover 테스트

 

리더파드가 장애가 나는 상황을 테스트해보겠습니다.

 

테스트 방식

  1. 서비스에 지속적으로 접근
  2. 리더 스피커파드가 존재하는 노드 중지
  3. 서비스 접근 모니터링

 

# 서비스에 지속적으로 접근
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"

# 리더 노드 중지
docker stop myk8s-worker --signal 15

 

 

리더 파드가 다시 선출되기 전까지 불안정한 접속이 지속됩니다.

 

 


 

이번 주차에는 MetalLB 를 활용하여 로드밸런서 서비스 타입을 공부했습니다.
MetalLB 는 듣기만 했었지 실제로 사용해볼 기회는 없었는데,

이번 스터디를 통해서 직접 구현해보고 동작 원리 또한 이해할 수 있었습니다.

Contents

포스팅 주소를 복사했습니다