스터디

[KANS 3기] 5주차 MetalLB

엔지니어-여리 2024. 10. 5. 16:49
반응형

LoadBalancer 타입 Service

서비스는 분산된 애플리케이션의 단일 외부 엔드포인트를 노출할 수 있도록 해줍니다. 서비스 유형은 Cluster IP, NodePort, LoadBalancer, ExternalName 등이 있습니다.  이중 ClusterIP는 클러스터 내부에서만 서비스에 접근 가능하고 NodePort는 각 노드와 포트 ip를 통해 외부에서 접근 가능합니다. 클러스터 외부에 노출시키기에 우아한 방법은 아닙니다. LoadBalancer 유형의 서비스는 외부의 로드밸런서를 이용하여 클러스터의 서비스를 외부에 노출시키는 유형입니다. 이번 시간에는 로드밸런서 타입의 서비스에 대해서 살펴보도록 하겠습니다.

 

 

실습환경 구성하기

실습환경 구성에서 이전 포스팅과 동일한 부분은 생략하고 차이가 있는 부분만 다루겠습니다.

인스턴스 구성

# in AWS 

# ec2 서비스에서 key-pair 를 생성 후 다운로드 받습니다.
# cloudformation 서비스에서 template를 아래 url로 설정 후 stack을 생성합니다.
 https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-5w.yaml # 실습 cloudformation 파일을 다운로드 받습니다.



# in cli

STACK_NAME=mylab
KEY_PAIR=yeoli
PROFILE=lab

curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-5w.yaml # 실습 cloudformation 파일을 다운로드 받습니다.
 aws cloudformation deploy \
  --template-file kans-5w.yaml \
  --stack-name $STACK_NAME \
  --parameter-overrides \
    KeyName=$KEY_PAIR \
    SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 \
  --region ap-northeast-2 \
  --profile $PROFILE
  
  
# ec2 인스턴스의 userdata에는 다음 항목이 포함되어 있습니다.

# hostname 변경
# ufw 방화벽 해제
# kind 설치
# 도커 엔진 설치
# alias 설정
# kubectl, helm, kubectx, kubens, kubeps
# kubectl 자동완성
# 리소스 제한 상승

 

클러스터 구성

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 ps



# 노드에 기본 툴 설치
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

 

 

노드별 네트워크 정보 확인

for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i cat /etc/cni/net.d/10-kindnet.conflist; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c route; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c addr; echo; done

1. 클러스터에 포함된 노드들의 CNI 정보를 조회합니다. kindnet을 CNI로 사용합니다.

2. 각 노드의 routing table을 조회합니다.

3. 각노드의 ip 주소 정보를 조회합니다.

 

 

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

선택된 노드에 대해서 iptables 프로그램으로 각 테이블(filter, nat, mangle, raw)에 대해 조회해서 확인합니다.

 

docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'

 명령어를 살펴보면 현재 실행중인 파드의 네트워크 정보를 확인하는 명령어 입니다.

그리고, docker network inspect kind 명령어로 kind 네트워크 인터페이스의 정보를 확인해보면 다음과 같습니다.

 

결국, 4개의 도커 컨테이너 (kind node)는 kind라는 도커 네트워크를 공유하여 네트워크 내에서 ip를 할당받았다는 것을 알 수 있습니다. 이는 각 노드 사이에는 통신이 가능하다는 점을 시사합니다.

 

로드밸런서 타입

로드밸런서 타입의 서비스는 앞서 설명한 것과 같이 외부의 로드밸런서를 사용하여 서비스를 외부로 노출시킵니다.

쿠버네티스에서는 로드밸런스 타입 서비스를 직접 제공하지는 않습니다. AWS와 같은 CSP에서 제공하는 로드 밸런서 혹은 LoxiLB, MetalLB와 같은 오픈소스를 사용하여 로드밸런서 타입 서비스를 사용할 수 있습니다.

김경보님의 블로그에는 온프레미스 혹은 클라우드 환경에서의 로드밸런서 혹은 클라우드 내에서 로드밸런서 타입의 서비스의 작동 방식에 대해 잘 기술되어 있습니다.

https://kimalarm.tistory.com/102

 

 

CSP의 로드밸런서는 2가지 방식으로 동작합니다.

1.NodePort 접근 방식은 로드밸런서로 들어온 요청을 노드포트를 거친 후 목적지 application pod로 전달하는 방식입니다.

2. pod direct 접근 방식은 로드밸런서로 들어온 요청을 목적지 application pod로 직접 전달하는 방식입니다. 

 

MetalLB 에 대하여

MetalLB는 BareMetal LoadBalancer의 약자입니다. 이름에서 보시다시피 MetalLB는 베어메탈 쿠버네티스 클러스터를 위한 로드밸런서입니다. MetalLB는 2가지 모드를 제공합니다. Layer2 모드와 BGP 모드를 제공합니다. 베어메탈 환경이 목적이기 때문에 대부분의 클라우드 환경에서는 호환되지 않을 수 있습니다. 또한, 일부 CNI와 연동에 문제가 있을 수 있습니다.

https://metallb.universe.tf/configuration/calico/

 

Issues with Calico :: MetalLB, bare metal load-balancer for Kubernetes

The easy way As of Calico 3.18 (from early 2021), Calico now supports limited integration with MetalLB. Calico can be configured to announce the LoadBalancer IPs via BGP. Simply run MetalLB, apply an IPAddressPool without any BGPAdvertisement CR. When usin

metallb.universe.tf

 

 

 

MetalLB 구성시 동작 순서

1. 로드밸런서 서비스 리소스를 생성하면 스피커 파드를 데몬셋 형태로 생성합니다.

2. 스피커 파드중 리더가 선출됩니다.

3. 리더 파드는 External IP를 전파합니다.

4. External IP 전파를 위해 ARP 혹은 BGP 프로토콜을 사용합니다. 여기서 MetalLB의 2가지 모드가 구분됩니다.

 

Layer2 모드

ARP 프로토콜을 사용하는 모드를 Layer2 모드라고 합니다.  Layer2 모드에서는 External IP를 전파할 때 ARP 프로토콜을 통해서 전파합니다. 동일한 내부 네트워크에서 통신을 위해서는 목적지의 MAC 주소를 알아야합니다. ARP 프로토콜을 통하면 목적지 IP에 해당하는 MAC 주소를 요청하여 알수 있습니다.

 

가시다님 제공

 

위 그림에서는 노드1의 스피커파드가 리더 파드입니다. SVC1 의 External IP로 접속하게 되면 노드1의 iptables 규칙을 통해 목적지 파드를 찾아 요청이 적절히 전달될 수 있습니다. 이때 1번 노드의 스피커 파드에 장애가 발생하면 나머지 스피커 파드 중에서 다시 리더를 선출합니다.

 

BGP 모드

BGP 모드로 MetalLB를 구성하게되면, 스피커 파드는 BGP로 External IP를 전파하고, 외부 라우터를 통해 ECMP 라우팅으로 요청을 분산합니다. 앞서 설명한 Layer2 모드에 비해 더 규모가 있고 복잡하고 네트워크 팀 협조가 가능할 때 유용한 모드입니다.

 

MetalLB 구성하기

https://metallb.universe.tf/installation/ 문서를 참고하였습니다.

 

Installation :: MetalLB, bare metal load-balancer for Kubernetes

Before starting with installation, make sure you meet all the requirements. In particular, you should pay attention to network addon compatibility. If you’re trying to run MetalLB on a cloud platform, you should also look at the cloud compatibility page

metallb.universe.tf

공식문서에 MetalLB를 구성하는 아주 간단한 방법이 나와있어 인용하였습니다.

# MetalLB native 구성
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml
 
 
# 리소스 확인
watch -d n 1 'kubectl get all,configmap,secret,ep -n metallb-system  -o wide'

 

 

ConfigMap 구성

 

다음으로 할 작업은 MetalLB가 External IP로 사용할 영역을 지정하고 설정해주는 일입니다.

 

IPAddress Pool 생성

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

 

L2advertisements 생성

L2 모드로 LoadBalancer 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

 

아래와 같이 구성할 수 있습니다.

 

 

 

서비스 생성

3개의 서비스를 생성해보면 다음과 같이 kubectl get svc 명령어로 확인해볼 수 있습니다. 각 노드의 external IP가 노드의 스피커파드 IP로 구성되어 있습니다. 

 

 

또한 로드밸런서 서비스 타입은 노드포트 타입을 포함하기 때문에 아래와 같이 allocateLoadBalancerNodePorts 값이 true로 설정되어 있습니다. 이를 false로 변경해 노드포트 32258 (svc1 기준) 를 사용하지 않고 80포트를 통해 요청할 수 있습니다.

 

서비스를 활용하여 외부에서 접근

각 서비스의 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}')


이후, 테스트 컨테이너에서 각 서비스의 externalIP에 대해  각각 curl 요청을 보내어 확인합니다.

for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc curl -s $i | grep Hostname ; done

 

 

 

정보를 더욱 상세하게 표기하도록 코드를 수정하여 테스트 합니다.

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

 

 

External IP로 요청하는 걸 확인하였습니다.

 

 

 

후기

스터디에서 다루는 내용들을 따라가기 위해서 별도로 네트워크 공부를 하지만 부족함이 많은 것 같습니다. 지금 다 이해 못한다면 두번이고 세번이고 보면서 이해해야겠습니다. 항상 알찬 내용을 준비해주시는 분들과 어렵고 복잡한 과제들을 해네는 동료분들이 늘 저를 이끄는 동기부여가 됩니다.

 

읽어주셔서 감사합니다.

반응형