본문 바로가기
K8s

LoadBalancer(MetalLB)

by 식사법 2024. 10. 4.

5주차 (LoadBalancer(MetalLB)

LoadBalancer란?

Kubernetes에서 LoadBalancer 서비스 유형은 외부 클라이언트가 클러스터 내에서 실행 중인 애플리케이션에 접근할 수 있도록 외부 로드 밸런서를 사용하여 서비스를 노출하는 역할을 합니다. 이 기능은 서비스로 들어오는 트래픽을 적절한 노드 및 파드로 전달하는 데 중요합니다.

주요 개념

  • 외부 노출
    • LoadBalancer 서비스 유형은 Kubernetes 서비스가 외부에서 접근 가능하도록 설계되어 있습니다. Kubernetes 자체는 로드 밸런싱 기능을 제공하지 않지만, 외부 클라우드 공급자와의 통합을 통해 이 기능을 활용할 수 있습니다.
  • 클라우드 공급자 의존성
    • Kubernetes는 외부 트래픽을 클러스터 내부의 서비스로 전달하기 위해 외부 로드 밸런서를 필요로 합니다. AWS, GCP, Azure와 같은 클라우드 환경은 자체 로드 밸런서를 제공하며, Kubernetes는 이를 자동으로 설정하여 트래픽을 처리합니다.
  • NodePort 설정
    • LoadBalancer 서비스를 생성하면 Kubernetes는 먼저 NodePort 유형의 서비스를 프로비저닝합니다. 이 과정에서 클러스터 내 각 노드에 열려 있는 포트가 할당되고, 이 포트를 통해 외부 트래픽이 적절한 서비스로 전달됩니다. 이후 클라우드 공급자의 로드 밸런서는 이 NodePort로 트래픽을 라우팅하도록 설정됩니다.
  • 트래픽 라우팅 및 DNAT
    • 트래픽 흐름을 보면, 외부에서 들어오는 트래픽은 로드 밸런서를 통해 분산되어 Kubernetes 노드에 도달합니다. 노드에 도착한 트래픽은 iptables 규칙을 통해 목적지 주소 변환(DNAT) 이 이루어지며, 올바른 파드로 전달됩니다.
  • ExternalTrafficPolicy 설정
    • externalTrafficPolicy 옵션은 로드 밸런서가 트래픽을 처리하는 방식을 결정하는데, 이 설정을 Local로 지정하면 해당 트래픽이 도착한 노드에 있는 파드로만 트래픽을 전달하게 됩니다. 이를 통해 불필요한 네트워크 홉을 줄일 수 있지만, 노드 및 파드의 가용성 관리가 중요합니다.
  • 헬스 체크
    • 로드 밸런서에는 보통 헬스 체크 기능이 포함되어 있어, 트래픽이 정상적으로 동작하는 노드 및 파드로만 전달되도록 합니다. 특정 노드에 건강한 파드가 없을 경우, 로드 밸런서는 그 노드로 트래픽을 보내지 않습니다.

Service - LoadBalancer의 한계

  • 리소스 비효율성
    • 새로운 LoadBalancer 서비스를 생성할 때마다 새로운 외부 로드 밸런서 인스턴스가 생성됩니다. 클라우드 환경에서 이 과정은 리소스를 많이 사용하고 비용 효율성이 떨어질 수 있습니다. 특히 HTTP 서비스에서 이런 비효율성이 두드러집니다.
  • Ingress 컨트롤러 추천
    • HTTP(S) 트래픽의 경우, Ingress 컨트롤러를 사용하는 것이 더 효율적입니다. Ingress는 TLS 종료를 처리하고 도메인 또는 경로 기반으로 트래픽을 라우팅하여 리소스를 더 효율적으로 사용합니다.
  • 온프레미스 환경의 제한
    • LoadBalancer 서비스는 클라우드 환경에서 원활하게 동작하지만, 온프레미스(on-premises) 환경에서는 클라우드 기반 로드 밸런서가 없기 때문에 로드 밸런싱 구현이 어렵습니다. 이런 환경에서는 MetalLBOpenELB 같은 솔루션을 사용하여 클라우드 없이 로드 밸런서 기능을 구현할 수 있습니다.

MetalLB ?

MetalLB온프레미스 환경에서 사용 가능한 서비스로, 클라우드 환경의 LoadBalancer 서비스와 비슷한 기능을 제공합니다.

MetalLB의 주요 특징

  • BareMetal LoadBalancer 역할
    • MetalLB는 클라우드 공급자의 외부 로드 밸런서가 없는 환경, 특히 온프레미스 또는 자체 데이터 센터에서 Kubernetes 서비스에 외부 IP를 할당하고, 외부 트래픽을 클러스터 내로 라우팅하는 기능을 제공합니다.
  • External IP 전파 방식
    • MetalLB는 서비스에 할당된 External IP를 전파하기 위해 ARP(IPv4) 와 NDP(IPv6), 또는 BGP와 같은 프로토콜을 사용합니다. 이 중 Layer 2(ARP/NDP)와 BGP 두 가지 모드를 지원하여 네트워크 트래픽을 적절한 노드로 라우팅합니다.
  • BGP 모드
    • MetalLB는 BGP(Border Gateway Protocol) 를 활용하여 외부 네트워크 장비와 연동할 수 있습니다. 이를 통해 BGP 스피커로서 클러스터의 외부 IP를 라우터에 전파하고, 트래픽이 클러스터로 올 수 있게 합니다. 그러나 일반적인 BGP 데몬과 달리, MetalLB는 외부에서 광고하는 네트워크 대역 정보를 받지 않습니다.
  • CNI 호환성
    • MetalLB는 일부 네트워크 플러그인(CNI)과 호환되며, 특히 Calico IPIP(BGP) 와 같이 동시에 작동할 수 있습니다. 하지만 네트워크 구성에 따라 연동 이슈가 발생할 수 있으므로, 설정 시 주의가 필요합니다.

MetalLB의 두 가지 모드

1. Layer 2 모드 (ARP/NDP)

  • 동작 방식 - MetalLB는 ARP/NDP를 사용하여 해당 노드의 IP를 외부에 광고하고, 트래픽을 해당 노드로 전달합니다. 이때, 서비스의 외부 IP를 수신한 클라이언트는 ARP 요청을 통해 해당 IP와 연결된 노드를 찾게 되고, 노드로 트래픽을 전송하게 됩니다.
  • 리더 스피커 - MetalLB는 여러 노드 중 하나를 리더 스피커로 지정하여 해당 노드로 트래픽을 전달합니다. 리더 노드에 장애가 발생하면 다른 스피커 노드가 리더로 선출되어 서비스 중단을 최소화합니다.

2. BGP 모드

  • 동작 방식 - MetalLB는 BGP를 통해 외부 라우터에 클러스터의 IP 정보를 전파합니다. 외부 라우터는 MetalLB에서 전송하는 BGP 정보를 받아 클러스터의 서비스로 트래픽을 전달할 수 있게 됩니다.
  • ECMP(등가 비용 다중 경로) - BGP 모드는 ECMP를 지원하여 트래픽을 여러 노드로 부하 분산할 수 있습니다. 이를 통해 고가용성과 확장성을 높일 수 있습니다.
  • 라우터 설정 - BGP 모드를 사용할 때는 네트워크 팀과의 협력이 필요합니다. 외부 라우터에서 MetalLB가 광고하는 IP 정보를 적절히 처리할 수 있도록 설정해야 하기 때문입니다.

MetalLB의 제한 사항

1. Single-node 병목 문제

  • MetalLB는 하나의 리더 스피커 노드에 모든 트래픽을 집중시키기 때문에, 리더 노드가 병목 현상을 일으킬 수 있습니다. 공식 가이드는 아니지만, 외부 라우터에서 ECMP 설정을 통해 트래픽을 분산시키는 방법이 있습니다.

2. Failover 속도

  • 리더 노드에 장애가 발생할 경우, 새로운 리더 노드가 선출되기까지 시간이 다소 걸릴 수 있습니다(약 10초에서 20초). 이로 인해 짧은 시간 동안 서비스에 영향을 미칠 수 있습니다.

MetalLB 설치

Kubernetes manifests 로 설치

**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**

image.png

생성된 리소스 확인 : metallb-system 네임스페이스 생성, 파드(컨트롤러, 스피커) 생성, RBAC(서비스/파드/컨피그맵 조회 등등 권한들), SA 등

# 생성된 리소스 확인 : 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**

image.png

image.png

image.png

image.png

컨피그맵 생성 : 모드 및 서비스 대역 지정

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

kind network 중 컨테이너(노드) IP(대역) 확인 : 172.18.0.2~ 부터 할당되며, control-plane 이 꼭 172.18.0.2가 안될 수 도 있습니다.

image.png

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"]*

image.png

image.png

image.png

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"]*    

image.png

image.png

# (옵션) 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
---
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

image.png

image.png

image.png

# 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 툴 사용
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  # 매칭 사용 가능

image.png

image.png

$ kubectl describe servicel2status -n metallb-system

Name:         l2-g4nn9
Namespace:    metallb-system
Labels:       metallb.io/node=myk8s-worker3
              metallb.io/service-name=svc3
              metallb.io/service-namespace=default
Annotations:  <none>
API Version:  metallb.io/v1beta1
Kind:         ServiceL2Status
Metadata:
  Creation Timestamp:  2024-10-04T06:22:40Z
  Generate Name:       l2-
  Generation:          1
  Owner References:
    API Version:     v1
    Kind:            Pod
    Name:            speaker-c9cxk
    UID:             ed331a86-4ecc-4437-b434-e8ade5a59b9c
  Resource Version:  14804
  UID:               31f1279e-a041-44c6-9bb0-fffb3d829efb
Spec:
Status:
  Node:               myk8s-worker3
  Service Name:       svc3
  Service Namespace:  default
Events:               <none>

Name:         l2-p2v6s
Namespace:    metallb-system
Labels:       metallb.io/node=myk8s-worker
              metallb.io/service-name=svc1
              metallb.io/service-namespace=default
Annotations:  <none>
API Version:  metallb.io/v1beta1
Kind:         ServiceL2Status
Metadata:
  Creation Timestamp:  2024-10-04T06:22:40Z
  Generate Name:       l2-
  Generation:          1
  Owner References:
    API Version:     v1
    Kind:            Pod
    Name:            speaker-wpz8t
    UID:             c3603a4d-8774-428d-aca9-fb6f2d5a4b8c
  Resource Version:  14805
  UID:               19bccf99-7610-48af-a8aa-3ac7ee509f6f
Spec:
Status:
  Node:               myk8s-worker
  Service Name:       svc1
  Service Namespace:  default
Events:               <none>

Name:         l2-q7f8n
Namespace:    metallb-system
Labels:       metallb.io/node=myk8s-worker
              metallb.io/service-name=svc2
              metallb.io/service-namespace=default
Annotations:  <none>
API Version:  metallb.io/v1beta1
Kind:         ServiceL2Status
Metadata:
  Creation Timestamp:  2024-10-04T06:22:40Z
  Generate Name:       l2-
  Generation:          1
  Owner References:
    API Version:     v1
    Kind:            Pod
    Name:            speaker-wpz8t
    UID:             c3603a4d-8774-428d-aca9-fb6f2d5a4b8c
  Resource Version:  14806
  UID:               19dc62b3-1958-414e-a90a-fb7f76594b16
Spec:
Status:
  Node:               myk8s-worker
  Service Name:       svc2
  Service Namespace:  default
Events:               <none>
$ kubectl get servicel2status -n metallb-system -o json --watch # watch 모드

{
    "apiVersion": "metallb.io/v1beta1",
    "kind": "ServiceL2Status",
    "metadata": {
        "creationTimestamp": "2024-10-04T06:22:40Z",
        "generateName": "l2-",
        "generation": 1,
        "labels": {
            "metallb.io/node": "myk8s-worker3",
            "metallb.io/service-name": "svc3",
            "metallb.io/service-namespace": "default"
        },
        "name": "l2-g4nn9",
        "namespace": "metallb-system",
        "ownerReferences": [
            {
                "apiVersion": "v1",
                "kind": "Pod",
                "name": "speaker-c9cxk",
                "uid": "ed331a86-4ecc-4437-b434-e8ade5a59b9c"
            }
        ],
        "resourceVersion": "14804",
        "uid": "31f1279e-a041-44c6-9bb0-fffb3d829efb"
    },
    "spec": {},
    "status": {
        "node": "myk8s-worker3",
        "serviceName": "svc3",
        "serviceNamespace": "default"
    }
}
{
    "apiVersion": "metallb.io/v1beta1",
    "kind": "ServiceL2Status",
    "metadata": {
        "creationTimestamp": "2024-10-04T06:22:40Z",
        "generateName": "l2-",
        "generation": 1,
        "labels": {
            "metallb.io/node": "myk8s-worker",
            "metallb.io/service-name": "svc1",
            "metallb.io/service-namespace": "default"
        },
        "name": "l2-p2v6s",
        "namespace": "metallb-system",
        "ownerReferences": [
            {
                "apiVersion": "v1",
                "kind": "Pod",
                "name": "speaker-wpz8t",
                "uid": "c3603a4d-8774-428d-aca9-fb6f2d5a4b8c"
            }
        ],
        "resourceVersion": "14805",
        "uid": "19bccf99-7610-48af-a8aa-3ac7ee509f6f"
    },
    "spec": {},
    "status": {
        "node": "myk8s-worker",
        "serviceName": "svc1",
        "serviceNamespace": "default"
    }
}
{
    "apiVersion": "metallb.io/v1beta1",
    "kind": "ServiceL2Status",
    "metadata": {
        "creationTimestamp": "2024-10-04T06:22:40Z",
        "generateName": "l2-",
        "generation": 1,
        "labels": {
            "metallb.io/node": "myk8s-worker",
            "metallb.io/service-name": "svc2",
            "metallb.io/service-namespace": "default"
        },
        "name": "l2-q7f8n",
        "namespace": "metallb-system",
        "ownerReferences": [
            {
                "apiVersion": "v1",
                "kind": "Pod",
                "name": "speaker-wpz8t",
                "uid": "c3603a4d-8774-428d-aca9-fb6f2d5a4b8c"
            }
        ],
        "resourceVersion": "14806",
        "uid": "19dc62b3-1958-414e-a90a-fb7f76594b16"
    },
    "spec": {},
    "status": {
        "node": "myk8s-worker",
        "serviceName": "svc2",
        "serviceNamespace": "default"
    }
}

서비스 접속 테스트

# 현재 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 에서 접속 테스트
docker exec -it mypc curl -s $SVC1EXIP
docker exec -it mypc curl -s $SVC1EXIP | grep Hostname

image.png

image.png

image.png

# 부하분산 접속됨
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"

# 지속적으로 반복 접속
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 exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $SVC2EXIP | egrep 'Hostname|RemoteAddr'; date '+%Y-%m-%d %H:%M:%S' ; echo ;  sleep 1; done"
docker exec -it mypc zsh -c "while true; do curl -s --connect-timeout 1 $SVC3EXIP | egrep 'Hostname|RemoteAddr'; date '+%Y-%m-%d %H:%M:%S' ; echo ;  sleep 1; done"

# 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

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

image.png

image.png

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

image.png

image.png

**# 장애 재연**
## 리더 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$

IPVS Proxy 모드

# 파일 작성
cat <<EOT> kind-svc-2w-ipvs.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
    ipvs:
      strictARP: true
- 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
  kubeProxyMode: "ipvs"        
EOT

# k8s 클러스터 설치
kind create cluster --config kind-svc-2w-ipvs.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

image.png

# kube-proxy configmap 확인
kubectl describe cm -n kube-system kube-proxy
...
mode: ipvs
ipvs: # 아래 각각 옵션 의미 조사해보자!
  excludeCIDRs: null
  minSyncPeriod: 0s
  scheduler: ""
  strictARP: true  # MetalLB 동작을 위해서 true 설정 변경 필요
  syncPeriod: 0s
  tcpFinTimeout: 0s
  tcpTimeout: 0s
  udpTimeout: 0s
...

# strictARP: true는 ARP 패킷을 보다 엄격하게 처리하겠다는 설정입니다.
## IPVS 모드에서 strict ARP가 활성화되면, 노드의 인터페이스는 자신에게 할당된 IP 주소에 대해서만 ARP 응답을 보내게 됩니다. 
## 이는 IPVS로 로드밸런싱할 때 ARP 패킷이 잘못된 인터페이스로 전달되는 문제를 방지합니다.
## 이 설정은 특히 클러스터 내에서 여러 노드가 동일한 IP를 갖는 VIP(Virtual IP)를 사용하는 경우 중요합니다.

# 노드 별 네트워트 정보 확인 : kube-ipvs0 네트워크 인터페이스 확인
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
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -br -c addr show kube-ipvs0; echo; done
>> node myk8s-control-plane <<
kube-ipvs0       DOWN           10.200.1.1/32 10.200.1.10/32 

>> node myk8s-worker <<
kube-ipvs0       DOWN           10.200.1.10/32 10.200.1.1/32 

>> node myk8s-worker2 <<
kube-ipvs0       DOWN           10.200.1.1/32 10.200.1.10/32 

>> node myk8s-worker3 <<
kube-ipvs0       DOWN           10.200.1.10/32 10.200.1.1/32 

for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -d -c addr show kube-ipvs0; echo; done
>> node myk8s-control-plane <<
11: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether 9e:1d:ca:21:c6:d1 brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 0 maxmtu 0 
    dummy numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 
    inet 10.200.1.10/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.200.1.1/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

>> node myk8s-worker <<
11: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether fa:21:0a:00:a7:6c brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 0 maxmtu 0 
    dummy numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 
    inet 10.200.1.1/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.200.1.10/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

>> node myk8s-worker2 <<
11: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether ba:e9:75:56:db:00 brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 0 maxmtu 0 
    dummy numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 
    inet 10.200.1.10/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.200.1.1/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

>> node myk8s-worker3 <<
11: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether a2:1d:9c:e6:ad:84 brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 0 maxmtu 0 
    dummy numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 
    inet 10.200.1.1/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.200.1.10/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

image.png

# kube-ipvs0 에 할당된 IP(기본 IP + 보조 IP들) 정보 확인 
kubectl get svc,ep -A
NAMESPACE     NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes   ClusterIP   10.200.1.1    <none>        443/TCP                  3m8s
kube-system   kube-dns     ClusterIP   10.200.1.10   <none>        53/UDP,53/TCP,9153/TCP   3m7s

# ipvsadm 툴로 부하분산 되는 정보 확인 : 서비스의 IP와 서비스에 연동되어 있는 파드의 IP 를 확인
## Service IP(VIP) 처리를 ipvs 에서 담당 -> 이를 통해 iptables 에 체인/정책이 상당 수준 줄어듬
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ipvsadm -Ln ; echo; done

## IPSET 확인
docker exec -it myk8s-worker ipset -h
docker exec -it myk8s-worker ipset -L

# iptables 정보 확인 : 정책 갯수를 iptables proxy 모드와 비교해보자
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-control-plane  iptables -t $i -S ; echo; done
>> IPTables Type : filter <<
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N KUBE-FIREWALL
-N KUBE-FORWARD
-N KUBE-IPVS-FILTER
-N KUBE-IPVS-OUT-FILTER
-N KUBE-KUBELET-CANARY
-N KUBE-NODE-PORT
-N KUBE-PROXY-FIREWALL
-N KUBE-SOURCE-RANGES-FIREWALL
-A INPUT -m comment --comment "kubernetes ipvs access filter" -j KUBE-IPVS-FILTER
-A INPUT -m comment --comment "kube-proxy firewall rules" -j KUBE-PROXY-FIREWALL
-A INPUT -m comment --comment "kubernetes health check rules" -j KUBE-NODE-PORT
-A INPUT -j KUBE-FIREWALL
-A FORWARD -m comment --comment "kube-proxy firewall rules" -j KUBE-PROXY-FIREWALL
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
-A OUTPUT -m comment --comment "kubernetes ipvs access filter" -j KUBE-IPVS-OUT-FILTER
-A OUTPUT -j KUBE-FIREWALL
-A KUBE-FIREWALL ! -s 127.0.0.0/8 -d 127.0.0.0/8 -m comment --comment "block incoming localnet connections" -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A KUBE-IPVS-FILTER -m set --match-set KUBE-LOAD-BALANCER dst,dst -j RETURN
-A KUBE-IPVS-FILTER -m set --match-set KUBE-CLUSTER-IP dst,dst -j RETURN
-A KUBE-IPVS-FILTER -m set --match-set KUBE-EXTERNAL-IP dst,dst -j RETURN
-A KUBE-IPVS-FILTER -m set --match-set KUBE-EXTERNAL-IP-LOCAL dst,dst -j RETURN
-A KUBE-IPVS-FILTER -m set --match-set KUBE-HEALTH-CHECK-NODE-PORT dst -j RETURN
-A KUBE-IPVS-FILTER -m conntrack --ctstate NEW -m set --match-set KUBE-IPVS-IPS dst -j REJECT --reject-with icmp-port-unreachable
-A KUBE-NODE-PORT -m comment --comment "Kubernetes health check node port" -m set --match-set KUBE-HEALTH-CHECK-NODE-PORT dst -j ACCEPT
-A KUBE-SOURCE-RANGES-FIREWALL -j DROP

What's next:
    Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug myk8s-control-plane
    Learn more at https://docs.docker.com/go/debug-cli/

>> IPTables Type : nat <<
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER_OUTPUT
-N DOCKER_POSTROUTING
-N KIND-MASQ-AGENT
-N KUBE-KUBELET-CANARY
-N KUBE-LOAD-BALANCER
-N KUBE-MARK-MASQ
-N KUBE-NODE-PORT
-N KUBE-POSTROUTING
-N KUBE-SERVICES
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A PREROUTING -d 192.168.65.254/32 -j DOCKER_OUTPUT
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -d 192.168.65.254/32 -j DOCKER_OUTPUT
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -d 192.168.65.254/32 -j DOCKER_POSTROUTING
-A POSTROUTING -m addrtype ! --dst-type LOCAL -m comment --comment "kind-masq-agent: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom KIND-MASQ-AGENT chain" -j KIND-MASQ-AGENT
-A DOCKER_OUTPUT -d 192.168.65.254/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.11:46763
-A DOCKER_OUTPUT -d 192.168.65.254/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.11:37612
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p tcp -m tcp --sport 46763 -j SNAT --to-source 192.168.65.254:53
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 37612 -j SNAT --to-source 192.168.65.254:53
-A KIND-MASQ-AGENT -d 10.10.0.0/16 -m comment --comment "kind-masq-agent: local traffic is not subject to MASQUERADE" -j RETURN
-A KIND-MASQ-AGENT -m comment --comment "kind-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE
-A KUBE-LOAD-BALANCER -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SERVICES -s 127.0.0.0/8 -j RETURN
-A KUBE-SERVICES ! -s 10.10.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
-A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT
-A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT

What's next:
    Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug myk8s-control-plane
    Learn more at https://docs.docker.com/go/debug-cli/

>> IPTables Type : mangle <<
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N KUBE-IPTABLES-HINT
-N KUBE-KUBELET-CANARY

What's next:
    Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug myk8s-control-plane
    Learn more at https://docs.docker.com/go/debug-cli/

>> IPTables Type : raw <<
-P PREROUTING ACCEPT
-P OUTPUT ACCEPT

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

# 각 노드 bash 접속
docker exec -it myk8s-control-plane bash
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
docker exec -it myk8s-worker3 bash
----------------------------------------
exit
----------------------------------------

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 직접 지정 혹은 IP 지정 없이 배포
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
혹은
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity

docker ps

목적지(backend) 파드(Pod) 생성 : 3pod.yaml

cat <<EOT> 3pod.yaml
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
---
apiVersion: v1
kind: Pod
metadata:
  name: webpod3
  labels:
    app: webpod
spec:
  nodeName: myk8s-worker3
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
EOT

클라이언트(TestPod) 생성 : netpod.yaml

cat <<EOT> netpod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: net-pod
spec:
  nodeName: myk8s-control-plane
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOT

서비스(ClusterIP) 생성 : svc-clusterip.yaml

cat <<EOT> svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc-clusterip
spec:
  ports:
    - name: svc-webport
      port: 9000        # 서비스 IP 에 접속 시 사용하는 포트 port 를 의미
      targetPort: 80    # 타킷 targetPort 는 서비스를 통해서 목적지 파드로 접속 시 해당 파드로 접속하는 포트를 의미
  selector:
    app: webpod         # 셀렉터 아래 app:webpod 레이블이 설정되어 있는 파드들은 해당 서비스에 연동됨
  type: ClusterIP       # 서비스 타입
EOT

생성 및 확인 : IPVS Proxy 모드

# 생성
kubectl apply -f 3pod.yaml,netpod.yaml,svc-clusterip.yaml

# 파드와 서비스 사용 네트워크 대역 정보 확인 
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"

image.png

# 확인
kubectl get pod -owide
kubectl get svc svc-clusterip
kubectl describe svc svc-clusterip
kubectl get endpoints svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip

image.png

# 노드 별 네트워트 정보 확인 : kube-ipvs0 네트워크 인터페이스 확인
## ClusterIP 생성 시 kube-ipvs0 인터페이스에 ClusterIP 가 할당되는 것을 확인
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c addr; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -br -c addr show kube-ipvs0; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -d -c addr show kube-ipvs0; echo; done

# 변수 지정
CIP=$(kubectl get svc svc-clusterip -o jsonpath="{.spec.clusterIP}")
CPORT=$(kubectl get svc svc-clusterip -o jsonpath="{.spec.ports[0].port}")
echo $CIP $CPORT

# ipvsadm 툴로 부하분산 되는 정보 확인
## 10.200.1.216(TCP 9000) 인입 시 3곳의 목적지로 라운드로빈(rr)로 부하분산하여 전달됨을 확인 : 모든 노드에서 동일한 IPVS 분산 설정 정보 확인
## 3곳의 목적지는 각각 서비스에 연동된 목적지 파드 3개이며, 전달 시 출발지 IP는 마스커레이딩 변환 처리
docker exec -it myk8s-control-plane ipvsadm -Ln -t $CIP:$CPORT
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ipvsadm -Ln -t $CIP:$CPORT ; echo; done

image.png

image.png


# ipvsadm 툴로 부하분산 되는 현재 연결 정보 확인 : 추가로 --rate 도 있음
docker exec -it myk8s-control-plane ipvsadm -Ln -t $CIP:$CPORT --stats
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ipvsadm -Ln -t $CIP:$CPORT --stats ; echo; done

docker exec -it myk8s-control-plane ipvsadm -Ln -t $CIP:$CPORT --rate
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ipvsadm -Ln -t $CIP:$CPORT --rate ; echo; done

image.png

image.png

# iptables 규칙 확인 : ipset list 를 활용
docker exec -it myk8s-control-plane iptables -t nat -S | grep KUBE-CLUSTER-IP

# ipset list 정보를 확인 : KUBE-CLUSTER-IP 이름은 아래 6개의 IP:Port 조합을 지칭
# 예를 들면 ipset list 를 사용하지 않을 경우 6개의 iptables 규칙이 필요하지만, ipset 사용 시 1개의 규칙으로 가능
docker exec -it myk8s-control-plane ipset list KUBE-CLUSTER-IP
Name: KUBE-CLUSTER-IP
Type: hash:ip,port
Revision: 7
Header: family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0x6343ff52
Size in memory: 456
References: 3
Number of entries: 5
Members:
10.200.1.1,tcp:443
10.200.1.10,tcp:53
10.200.1.10,udp:53
10.200.1.245,tcp:9000
10.200.1.10,tcp:9153

image.png

IPVS 정보 확인 및 서비스 접속 확인

for i in **control-plane worker worker2 worker3**; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i **ipvsadm -Ln -t $CIP:$CPORT** ; echo; done

image.png

# 변수 지정
CIP=$(kubectl get svc svc-clusterip -o jsonpath="{.spec.clusterIP}")
CPORT=$(kubectl get svc svc-clusterip -o jsonpath="{.spec.ports[0].port}")
echo $CIP $CPORT

****# **컨트롤플레인** 노드에서 ipvsadm **모니터링** 실행 : ClusterIP 접속 시 아래 처럼 연결 정보 확인됨
watch -d "docker exec -it myk8s-control-plane **ipvsadm -Ln -t $CIP:$CPORT --stats; echo;** docker exec -it myk8s-control-plane **ipvsadm -Ln -t $CIP:$CPORT --rate"**

--------------------------

# 서비스 IP 변수 지정 : svc-clusterip 의 ClusterIP주소
SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP})
echo $SVC1

image.png

image.png

# TCP 80,9000 포트별 접속 확인 : 출력 정보 의미 확인
kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000
kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000 | grep Hostname
kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000 | grep Hostname

image.png

# 서비스(ClusterIP) 부하분산 접속 확인 : 부하분산 비률 확인
kubectl exec -it net-pod -- zsh -c "for i in {1..10};   do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
kubectl exec -it net-pod -- zsh -c "for i in {1..100};  do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
kubectl exec -it net-pod -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
혹은
kubectl exec -it net-pod -- zsh -c "for i in {1..100};   do curl -s $SVC1:9000 | grep Hostname; sleep 1; done"
kubectl exec -it net-pod -- zsh -c "for i in {1..100};   do curl -s $SVC1:9000 | grep Hostname; sleep 0.1; done"
kubectl exec -it net-pod -- zsh -c "for i in {1..10000}; do curl -s $SVC1:9000 | grep Hostname; sleep 0.01; done"

# 반복 접속
kubectl exec -it net-pod -- zsh -c "while true; do curl -s --connect-timeout 1 $SVC1:9000 | egrep 'Hostname|RemoteAddr|Host:'; date '+%Y-%m-%d %H:%M:%S' ; echo '--------------' ;  sleep 1; done"

image.png

image.png

'K8s' 카테고리의 다른 글

AKS-eks  (0) 2024.11.02
Cilium CNL  (1) 2024.10.26
Istio - Mode : Sidecar, Ambient  (0) 2024.10.19