5주차 - LoadBalancer(MetalLB)

0. 목적성

  • 온프레미스에서 사용할 수 있는 LoadBalancer인 MetalLB 의 네트워크 흐름을 이해합니다.

0.1 LoadBalancer

  • 온프레미스에서는 로드밸런서 파드를 생성해서 직접 부하분산을 진행합니다.

  • aws 나 클라우드 환경에서는 managed service인 로드밸런서를 사용해서 부하분산을 하게 됩니다.

NodePort 방식
Pod 방식
  • NodePort 방식

    • 외부 로드 밸런서에서 1번 부하분산되고 그 이후 추가로 서비스에서 부하분산 과정이 수행되어서 성능이 저하됩니다.

  • Pod 방식

    • 외부 로드밸런서가 목적지 pod로 직접 부하분산을 하게되어 1번의 부하분산과정으로 수행됩니다.

1. MetalLB

1.1 MetalLB 소개

  • 온프레미스 환경에서 LoadBalancer 서비스를 구현하게 됩니다.

1.1.1 동작원리

  • 쿠버네티스 데몬셋으로 스피커 파드 생성으로 ExternalIP를 전파합니다.

  • 이 덕분에, 외부에서 쿠버네티스 서비스로 접근이 가능해집니다.

  • ARP 테이블 모드와 BGP 모드가 있습니다.

1.2 MetalLB - 네트워크 흐름 layer 2 모드

  • SVC1 IP 로 접속하게 되면, Node에 배포된 speaker 파드를 통해 iptables로 각 파드로 DNAT이 됩니다.

  • 리더 파드(호스트네트워크사용)를 선출해서 리더 파드가 있는 노드로만 트래픽 인입이 됩니다.

  • 하지만, 다른 파드가 리더로 선출되는데 10초가 소요됩니다 하지만, 그 사이에 장애가 발생할 수 있습니다.

1.2.1 [실습] 환경구성

// 1. KIND를위한EC2 생성
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-5w.yaml
aws cloudformation deploy --template-file kans-5w.yaml --stack-name mylab --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

// 2. K8S 클러스터 생성 및 기본 툴들 설치
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

## mypc 컨테이너를 기동해 둡니다. (src ip 가 될 컨테이너입니다.)
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
  • 각 노드의 IP 정보를 확인할 수 있습니다.

// 1. pod를 생성합니다.
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

1.2.2 [실습] 서비스 접속 테스트


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

// 2. metallb crd 확인
bfdprofiles.metallb.io                      2024-10-05T12:02:54Z
// metallb 네트워크 장애 custom Resource DEFINITION
bgpadvertisements.metallb.io                2024-10-05T12:02:54Z
// bgp 정의 설정 (metallb가 클러스터의 ip를 bgp를 통해 광고하는데 필요한 정보입니다.)
bgppeers.metallb.io                         2024-10-05T12:02:54Z
// bgp 피어 설정 (bgp를 사용해서 다른 라우터와 연결하기 위해 설정하는 피어 정보를 포함)
communities.metallb.io                      2024-10-05T12:02:54Z
ipaddresspools.metallb.io                   2024-10-05T12:02:54Z
// MetalLB 가 할당할 수 있는 IP 주소 풀을 설정 합니다. (LB에서 설정할 수 있는 IP 주소 범위 설정)
l2advertisements.metallb.io                 2024-10-05T12:02:54Z
servicel2statuses.metallb.io                2024-10-05T12:02:54Z

## metallb 데몬셋으로 배포되는 스피커 파드는 hostNetwork 를 사용함
kubectl get ds -n metallb-system -o yaml
          
          name: speaker
          ports:
          - containerPort: 7472
            name: monitoring
            protocol: TCP
          - containerPort: 7946
            name: memberlist-tcp
            protocol: TCP
          - containerPort: 7946
            name: memberlist-udp
            protocol: UDP
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /metrics
              port: monitoring
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          resources: {}
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              add:
              - NET_RAW
              drop:
              - ALL
            readOnlyRootFilesystem: true
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
          - mountPath: /etc/ml_secret_key
            name: memberlist
            readOnly: true
          - mountPath: /etc/metallb
            name: metallb-excludel2
            readOnly: true
        dnsPolicy: ClusterFirst
        hostNetwork: true
// 서비스(External-IP) 대역을 노드가 속한 eth0의 대역이 아니여도 상관없다!
// GW 역할의 라우터에서 노드들로 라우팅 경로 지정합니다

# IPAddressPool 생성 : LoadBalancer External IP로 사용할 IP 대역
## MetalLB는 서비스를 위한 외부 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
# L2Advertisement 생성 : 설정한 IPpool을 기반으로 Layer2 모드로 LoadBalancer IP 사용 허용
## Kubernetes 클러스터 내의 서비스가 외부 네트워크에 IP 주소를 광고하는 방식을 정의
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: my-l2-advertise
  namespace: metallb-system
spec:
  ipAddressPools:
  - my-ippool
EOF

# LoadBalancer 타입의 서비스 생성 확인 : EXTERNAL-IP가 서비스 마다 할당되며, 실습 환경에 따라 다를 수 있음
## LoadBalancer 타입의 서비스는 NodePort 와 ClusterIP 를 포함함 - 'allocateLoadBalancerNodePorts : true' 기본값
## ExternalIP 로 접속 시 사용하는 포트는 PORT(S) 의 앞에 있는 값을 사용 (아래의 경우는 TCP 80 임)
## 만약 노드의 IP에 NodePort 로 접속 시 사용하는 포트는 PORT(S) 의 뒤에 있는 값을 사용 (아래는 30485 임)
kubectl get service,ep

# LoadBalancer 타입은 기본적으로 NodePort를 포함 사용. NodePort는 ClusterIP를 포함 사용.
## 클라우드사업자 LB Type이나 온프레미스환경 HW LB Type 경우 LB 사용 시 NodePort 미사용 설정 가능
kubectl describe svc svc1

## 아래 처럼 LB VIP 별로 이던 speaker 배포된 노드가 리더 역할을 하는지 확인 가능
kubectl describe svc | grep Events: -A5
  • external ip 할당 확인할 수 있습니다.

  • arp table에도 externalIP 할당되어있는것을 확인할 수 있습니다.

// EXTERNAL IP 를 통해 ETAL LB 스피커가 잘 동작하는지 확인해봅시다.

// 1. PING을 던져봅시다.
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc ping -c 1 -w 1 -W 1 $i; done
# arp scan 해두기
// 2. docker control plane 의 arp scan을 해보면, lb에서 mac address로 매칭되는것을 볼 수 있습니다.
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet

// 부하분산 접속 확인
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC1EXIP | grep Hostname; done | sort | uniq -c | sort -nr"
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC2EXIP | grep Hostname; done | sort | uniq -c | sort -nr"
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC3EXIP | grep Hostname; done | sort | uniq -c | sort -nr"

1.2.3 [실습] MetalLB Failover

## 지속적으로 반복 접속 
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"

# 장애 재연
## 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)를 중지
docker stop <svc1 번 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)> --signal 9
docker stop myk8s-worker --signal 9

## 지속적으로 반복 접속 상태 모니터링
### 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"

# mypc/mypc2 에서 arp 테이블 정보 확인 >> SVC IP별로 리더 파드(스피커) 역할의 노드를 확인!
docker exec -it mypc ip -c neigh | sort
kubectl get node -owide # mac 주소에 매칭되는 IP(노드) 찾기

  • 잠시동안, 통신이 안되는것을 확인할 수 있습니다.

  • 다시 리더가 존재하는 노드를 생성했을때, 원복된것을 확인할 수 있었다.

1.3 MetalLB - BGP 모드

  • Speaker 파드가 BGP 로 서비스 정보를 전파합니다. 외부에서 ECMP 라우팅으로 부하분산 접속합니다.

  • 클라이언트 IP 보존이 가능합니다.

  • BGP 모드는 외부의 라우터가 트래픽을 분산해서 효율적입니다.

Last updated