home..

LB는 누가만들어주는데

mont-kim Docker Container Pod Container Kubernetes Service

LB는 누가만들어주는데?

항상 입버릇처럼 하는 말입니다.

쿠버네티스는 Service로 내부 통신용 ClusterIP,

모든 노드 통신을 열어주는 NodePort

그리고 Type LoadBalancer가 존재합니다.

하지만 LoadBalancer 자체를 쿠버네티스가 만들어주진 않기에 물리적인 구현, 혹은 추상화를 해줄 무언가가 필요합니다.

AWS라면 해줄수 있을지도 모르지만

사실 기존 쿠버네티스 설정에서 LB Controller 설치가 되어있지 않을경우, 생성되는것은 CLB 뿐입니다.

별도로 AWS LoadBalancer Controller를 설치할경우

L4 로드밸런서인 NLB와 L7 로드밸런서인 ALB를 모두 생성할 수 있기도한데

ALB는 쿠버네티스 클러스터 관점에서 L4를 관리하는 Service에서 직접 정의하는 유형의 리소스가 아니기도 합니다.

그래서 로드밸런서가 뭔데…?

설명을 시작해보겠습니다.

LoadBalancer 소개

k8s Docs : LoadBalancer Type - Link

  • Exposes the Service externally using an external load balancer.
  • Kubernetes does not directly offer a load balancing component
  • you must provide one, or you can integrate your Kubernetes cluster with a cloud provider.
  • To implement a Service of type: LoadBalancer, Kubernetes typically starts off by making the changes that are equivalent to you requesting a Service of type: NodePort. The cloud-controller-manager component then configures the external load balancer to forward traffic to that assigned node port.
  • You can configure a load balanced Service to omit assigning a node port, provided that the cloud provider implementation supports this.
    • Disabling load balancer NodePort allocation : k8s v1.24[stable] - Link

image.png

이런 기능들을 해주는 역할입니다.

image.png

VPC CNI를 이용하여 직관적인 네트워크 홉 구성이 가능하기도하지만, 온프레미스에서는 조금 더 고민하거나 관리해야할것들이 추가됩니다.

네트워크 장비가 똑똑하다면 직접적인 쿠버네티스 외부 → 내부의 인입점을 만들어주겠지만 실은 그런 장비가 많이 부족한 상태이기도하고

실제로 대고객 클라우드 서비스같은게 아닌 내부 자원들을 활용한 쿠버네티스 클러스터 자원을 활용한다면, 이러한 똑똑한 네트워크 장비가 없는게 더 일반적이기도 하죠

실제로 고객사의 인프라도 작지않게 운영중이지만, 보안상의 이유나 어른들의 사정으로인해 클러스터 자원의 외부노출은 NodePort로 운영중입니다.

Servcie : LoadBalancer의 한계점

  • 서비스(LoadBalancer) 생성 시 마다 LB(예 AWS NLB)가 생성되어 자원 활용이 비효율적임 ⇒ HTTP 경우 인그레스(Ingress) 를 통해 자원 활용 효율화 가능!
  • 서비스(LoadBalancer)는 HTTP/HTTPS 처리에 일부 부족함(TLS 종료, 도메인 기반 라우팅 등) ⇒ 인그레스(Ingress) 를 통해 기능 동작 가능!
  • (참고) 온프레미스 환경에서 제공 불가능??? ⇒ MetalLB 혹은 OpenELB(구 PorterLB) 를 통해서 온프레미스 환경에서 서비스(LoadBalancer) 기능 동작 가능!

그럼 물리적인 로드밸런서 구성이 불가능하다는 전제하에

SW상으로 구현되는 LB에 대해 알아보겠습니다.

MetalLB

MetalLB - 공홈 , Github

MetalLB Docs : MetalLB is a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols - Link

MetalLB - Layer2 모드

Untitled.png

  • 리더 파드가 선출되고 해당 리더 파드가 생성된 노드로만 트래픽이 인입되어 해당 노드에서 iptables 분산되어 파드로 접속
  • 권장 사용 환경 : 테스트 및 소규모의 환경(동일 네트워크 1개 사용)에서 클라이언트 IP 보존이 필요 없을 때

[https://metallb.universe.tf/configuration/_advanced_l2_configuration/](https://metallb.universe.tf/configuration/_advanced_l2_configuration/)

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

MetalLB - BGP 모드

Untitled1.png

  • speaker 파드가 BGP로 서비스 정보(EXTERNAL-IP)를 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속
  • 권장 사용 환경 : 규모가 있고, 클라이언트 IP보존과 장애 시 빠른 절체가 필요하며, 네트워크 팀 협조가 가능할때

MetalLB는 두 종류의 모드를 지원합니다

L2 모드와 L3 모드가 존재하지만 그 특성이 매우 달라 용법에 차이가 존재합니다.

그림 소개

Untitled

  • L2 mode (ARP)

    Untitled

  • BGP mode

    Untitled

MetalLB온프레미스 환경(IDC)에서 사용할 수 있는 서비스(로드밸런서 타입)입니다 ← 클라우드 환경서비스(로드밸런서 타입)와는 동작이 조금 다릅니다!

  • MetalLB는 BareMetalLoadBalancer 약자!

서비스(로드 밸런서)의 ‘External IP’ 전파를 위해서 표준 프로토콜인 ARP(IPv4)/NDP(IPv6), BGP 를 사용합니다

  • 데몬셋으로 speaker 파드를 생성하여 ‘External IP’ 전파합니다

일반적인 BGP 데몬과 다르게 BGP 업데이트(외부에서 광고하는 네트워크 대역)을 받지 않습니다.

  • Unlike standard BGP daemons, MetalLB BGP speakers do not accept any incoming updates,

클라우드 플랫폼 호환 : 대부분의 클라우드 플랫폼(예 AWS, Azure, GCP 등)과 호환되지 않습니다 - 링크

CNI(네트워크 플러그인) 호환 : 일부 CNI와 연동에 이슈가 있습니다 - 링크

  • 예시) Calico IPIP(BGP)와 MetalLB BGP 모드를 상단 라우터와 동시 사용 시 문제 발생 - 링크

L2 모드 (ARP/NDP)

링크

Layer2 모드에서 서비스 접속

MetalLB 컨트롤러 파드는 그림에서 생략하였습니다

MetalLB 컨트롤러 파드는 그림에서 생략하였습니다

모드 소개 : 리더 파드가 선출되고 해당 리더 파드가 생성된 노드로만 트래픽이 인입되어 해당 노드에서 iptables 분산되어 파드로 접속

  • 서비스(로드밸런서) ‘External IP’ 생성 시 speaker 파드 중 1개리더가 되고, 리더 speaker 파드가 존재하는 노드로 서비스 접속 트래픽이 인입되게 됩니다
    • 데몬셋으로 배포된 speaker 파드는 호스트 네트워크를 사용합니다 ⇒ “NetworkMode”: “host”
  • 리더는 ARP(GARP, Gratuitous APR)로 해당 ‘External IP’ 에 대해서 자신이 소유(?)라며 동일 네트워크에 전파를 합니다
  • 만약 리더(노드)가 장애 발생 시 자동으로 나머지 speaker 파드 중 1개가 리더가 됩니다.
    • 멤버 리스터 및 장애 발견은 hashicorp 의 memberlist 를 사용 - Gossip based membership and failure detection
  • Layer 2에서 멤버 발견 및 자동 절체에 Keepalived(VRRP)도 있지만 사용하지 않은 이유 - 링크

제한 사항 : single-node bottlenecking, potentially slow failover

  • single-node bottlenecking : 서비스 1개 생성 사용 시, 모든 서비스 접근 트래픽은 리더 파드가 존재하는 노드로만 인입되어서 부하가 집중

    ⇒ 공식 가이드는 아니지만, 외부 라우터에서 접근 시 ECMP 로 부하 분산이 가능 - 아래 2.6 참고

  • potentially slow failover : 리더(노드)가 장애 시 나머지 노드 리더가 선출되고, ARP 전파 및 갱신완료 전까지는 장애가 발생됨 (대략 10초~20초 정도)

    ⇒ 공식 가이드는 아니지만, 외부 라우터에서 노드(파드) 장애 헬스 체크로 빠르게 절체 가능 - 아래 2.6 참고

(참고) Virtualbox 환경 실습 : 노드(VM)네트워크 NIC 2 번무작위 모드모두 허용 설정 필요

image.png

BGP 모드

링크

BGP 모드에서 서비스 접속

Untitled6.png

모드 소개 : speaker 파드가 BGP로 서비스 정보(EXTERNAL-IP)를 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속

  • speaker 파드에 BGP 가 동작하여 서비스 정보(EXTERNAL-IP)를 전파한다
    • 기본은 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 <<"; iptables -t $i -S ; echo; done

파드 생성

CleanShot 2024-09-29 at 14.06.48.png

cat <<EOF | kubectl apply -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
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

# 접속 확인
ping -i 1 -W 1 -c 1 $WPOD1
ping -i 1 -W 1 -c 1 $WPOD2
curl -s --connect-timeout 1 $WPOD1 | grep Hostname
curl -s --connect-timeout 1 $WPOD2 | grep Hostname
curl -s --connect-timeout 1 $WPOD1 | egrep 'Hostname|RemoteAddr|Host:'
curl -s --connect-timeout 1 $WPOD2 | egrep 'Hostname|RemoteAddr|Host:'

image.png

MetalLB - L2 mode

설치 방법 지원 : Kubernetes manifests, using Kustomize, or using Helm

  • 참고 : kube-proxy 의 ipvs 모드 사용 시 ‘strictARP: true’ 설정 필요

간단하게 manifests 로 설치 진행!

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

# 생성된 리소스 확인 : metallb-system 네임스페이스 생성, 파드(컨트롤러, 스피커) 생성, RBAC(서비스/파드/컨피그맵 조회 등등 권한들), SA 등
kubectl get-all -n metallb-system # kubectl krew 플러그인 get-all 설치 후 사용 가능
kubectl get all -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

# (참고) 상세 정보 확인
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

image.png

  • 컨피그맵 생성 : 모드 및 서비스 대역 지정
    • 서비스(External-IP) 대역을 노드가 속한 eth0의 대역이 아니여도 상관없다! → 다만 이 경우GW 역할의 라우터에서 노드들로 라우팅 경로 지정 필요

# IPAddressPool 생성 : LoadBalancer External 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 사용 허용
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"]
  • (참고) 로그 확인 : 아래 로그 -f 모니터링 해두기
# (옵션) 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  # 매칭 사용 가능

생성 확인

서비스(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
  #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

서비스 확인 및 리더 Speaker 파드 확인

# arp scan 해두기
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

image.png

# arp scan 해두기
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

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

## 아래 처럼 LB VIP 별로 이던 speaker 배포된 노드가 리더 역할을 하는지 확인 가능
kubectl describe svc | grep Events: -A5

image.png


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

# 현재 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 툴 사용
arping -I eth0 -f -c 1 $SVC1EXIP
arping -I eth0 -f -c 1 $SVC2EXIP
arping -I eth0 -f -c 1 $SVC3EXIP

ping -c 1 -w 1 -W 1 $SVC1EXIP
ping -c 1 -w 1 -W 1 $SVC2EXIP
ping -c 1 -w 1 -W 1 $SVC3EXIP

# mypc/mypc2 에서 arp 테이블 정보 확인 >> SVC IP별로 리더 파드(스피커) 역할의 노드를 확인!
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(노드) 찾기

image.png

(참고) 책 원고 설명

CleanShot 2024-09-29 at 14.11.35.png

CleanShot 2024-09-29 at 14.11.44.png

ARP 를 통한 서비스 IP의 리더(파드/노드) 확인 및 MAC 주소 충돌방지 및 갱신

ARP 를 통한 서비스 IP의 리더(파드/노드) 확인 및 MAC 주소 충돌방지 및 갱신

접속 확인

클라이언트(mypc, mypc2) → 서비스(External-IP) 접속 테스트

image.png

# 현재 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 에서 접속 테스트
curl -s $SVC1EXIP
curl -s $SVC1EXIP | grep Hostname
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do curl -s $i | grep Hostname ; done
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do echo ">> Access Service External-IP : $i <<" ; curl -s $i | grep Hostname ; echo ; done

## RemoteAddr 주소는 어떻게 나오나요? 왜 그럴까요?
##  NodePort 기본 동작과 동일하게 인입한 노드의 인터페이스로 SNAT 되어서 최종 파드로 전달됨
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do echo ">> Access Service External-IP : $i <<" ; curl -s $i | egrep 'Hostname|RemoteAddr|Host:' ; echo ; done

image.png


# LoadBalancer Type은 기본값으로 NodePort 포함. NodePort 서비스는 ClusterIP 를 포함
# NodePort:PORT 및 CLUSTER-IP:PORT 로 접속 가능!
kubectl get svc svc1
NAME   TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)        AGE
svc1   LoadBalancer   10.200.1.82   172.18.255.202   80:30246/TCP   49m

# 컨트롤노드에서 각각 접속 확인 실행 해보자
curl -s 127.0.0.0:30246 # NodePor Type
curl -s 10.200.1.82     # ClusterIP Tpye

image.png

FailOver Test

장애 발생 시 동작

CleanShot 2024-09-29 at 15.47.20.png

CleanShot 2024-09-29 at 15.47.58.png

CleanShot 2024-09-29 at 15.48.28.png

CleanShot 2024-09-29 at 15.48.53.png

리더 Speaker 파드가 존재하는 노드를 재부팅! → curl 접속 테스트 시 10~20초 정도의 장애 시간이 발생하였다 ⇒ 이후 자동 원복 되며, 원복 시 5초 정도 장애 시간 발생!

# metallb CRD인 servicel2status 로 상태 정보 확인
kubectl get servicel2status -n metallb-system -o json --watch # watch 모드

# mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do 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 ping -c 1 -w 1 -W 1 $i; done

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

# (옵션) 노드에서 ARP 패킷 캡쳐 확인
tcpdump -i eth0 -nn arp
# 사전 준비
## 지속적으로 반복 접속
SVC1EXIP=$(kubectl get svc svc1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
sh -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

# 장애 재연
## 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)를 중지
vagrant reload k8s-w2

## 지속적으로 반복 접속 상태 모니터링
### curl 연속 접속 시도 >> 대략 10초 이내에 정상 접근 되었지만, 20초까지는 불안정하게 접속이 되었다
### 실제로는 다른 노드의 speaker 파드가 리더가 되고, 이후 다시 노드(컨테이너)가 정상화되면, 다시 리더 speaker 가 됨
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 파드 확인
# mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do ping -c 1 -w 1 -W 1 $i; done

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

# 장애 원복(노드 정상화)
## 노드(실제 컨테이너) 정상화 

# 변경된 리더 Speaker 파드 확인
# mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do ping -c 1 -w 1 -W 1 $i; done

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

image.png

  • 다음 실습을 위해 오브젝트 삭제
 kubectl delete pod,svc --all

BGP Mode

CleanShot 2024-09-29 at 15.49.36.png

CleanShot 2024-09-29 at 15.50.30.png

CleanShot 2024-09-29 at 15.50.43.png

CleanShot 2024-09-29 at 15.50.59.png

장점

CleanShot 2024-09-29 at 15.51.09.png

CleanShot 2024-09-29 at 15.51.21.png

CleanShot 2024-09-29 at 15.51.38.png

무중단이 가능하다

BGP 모드 설치

MetalLB Configmap 설정 (기존 L2모드에서도 전환이 가능)

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

image.png

생성

파드 생성

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'

# (옵션) 노드에서 BGP 패킷 캡쳐 확인
tcpdump -i enp0s8 -nnq tcp port 179
ngrep -tW byline -d enp0s8 '' 'tcp port 179'

image.png

BGP 정보 확인 (k8s-rtr) : 서비스(EX-IP)들을 32bit IP를 전달 받음 → 축약 대역 전파 설정 가능 ⇒ 다만, 노드들은 상대측 BGP 네트워크 정보를 받지 않는다!

image.png

ExternalTrafficPolicy

ExternalTrafficPolicy: Local 테스트를 해봅니다.

[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"}}'

image.png

  • 클라이언트 → 서비스(External-IP) 접속 : 라우터 ECMP 분산 접속, 클라이언트의 IP가 보존! image

  • 라우팅 테이블 정보 확인 (예시)

    Untitled7.png

  • (옵션) 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 externalTrafficPolicy에 따른 동작과 GCP의 Container-native LoadBalancer 알아보기

ExternalIP

ExternalIP 서비스 소개

  • ExternalIP 서비스는 특정 노드IP(포트)로 인입한 트래픽을 컨테이너로 보내(ClusterIP 를 사용), 외부에서 접속할 수 있게 합니다

    ⇒ NodePort 와 거의 유사하니, 특별한 이유가 없으면 NodePort (ExternalTrafficPolicy 등 옵션 사용 가능) 를 사용하자

  • ExternalIP 는 노드 1개 혹은 일부 노드들을 지정할 수 있다
  • ExternalTrafficPolicy 정책을 사용할 수 없다 → 즉 외부에서 ExternalIP 로 접근 시 무조건 Node의 IP로 DNAT 되어서 Client IP 수집할 수 없다
설정 항목 설명
spec.externalIPs 노드 IP 주소(ExternalIP)
spec.ports[].port ExternalIP 와 ClusterIP 에서 수신할 포트 번호
spec.ports[].targetPort 목적지 컨테이너 포트 번호

실습

cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-echo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: ndks-websrv
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-externalip
spec:
  type: ClusterIP
  externalIPs:
    - 192.168.10.101
    - 192.168.10.102
  ports:
    - name: svc-webport
      port: 9000
      targetPort: 8080
  selector:
    app: deploy-websrv
EOF
# 확인 : ExternalIP 도 결국 ClusterIP를 사용(포함)
kubectl get svc svc-externalip
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP                     PORT(S)    AGE
svc-externalip   ClusterIP   10.96.101.49   192.168.10.101,192.168.10.102   9000/TCP   46s

kubectl describe svc svc-externalip
Name:              svc-externalip
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=deploy-websrv
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.101.49
IPs:               10.96.101.49
External IPs:      192.168.10.101,192.168.10.102
Port:              svc-webport  9000/TCP
TargetPort:        8080/TCP
Endpoints:         172.16.158.18:8080,172.16.184.17:8080
Session Affinity:  None
Events:            <none>

# ExternalTrafficPolicy 설정이 없음
kubectl get svc svc-externalip -o yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2022-02-24T07:32:47Z"
  name: svc-externalip
  namespace: default
  resourceVersion: "1454"
  uid: a27a8ca6-44ef-4363-ba3f-9894b4af5a54
spec:
  clusterIP: 10.110.23.53
  clusterIPs:
  - 10.110.23.53
  externalIPs:
  - 192.168.10.101
  - 192.168.10.102
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - name: svc-webport
    port: 9000
    protocol: TCP
    targetPort: 8080
  selector:
    app: deploy-websrv
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

삭제

kubectl delete deploy deploy-echo; kubectl delete svc svc-externalip
© 2024 mont kim   •  Powered by Soopr   •  Theme  Moonwalk