WellSpring

5주차 - LoadBalancer(MetalLB), IPVS + LoxiLB 본문

KANS3 - k8s Advanced Networking Study

5주차 - LoadBalancer(MetalLB), IPVS + LoxiLB

daniel00324 2024. 10. 1. 14:32
더보기

※ 본 게재 글은 gasida님의 KANS(Kubernetes Advanced Networking Study) 강의내용과 실습예제 및 kubernetes, MetalLB 공식 가이드 문서, "쿠버네티스입문" 서적 등을 참고하여 작성하였습니다. 


0. 실습환경 준비

 

▼ 실습을 위한 K8S 설치 : macOS(아래 가이드 대로), AWS EC2(CloudFormation 배포 후 가이드 참고), Windows(vagrant + virtualbox 설치 후 가이드 참고, 비권장)

더보기

AWS EC2(CloudFormation 으로 AWS EC2 Ubuntu 에 kind 설치 상태 확인 후 아래 가이드 참고) 윈도우 PC 사용자 경우 권장 실습 환경

1) 사전 준비 : AWS 계정, SSH keypair

2) 구성 : VPC 1개(퍼블릭 서브넷 2개), EC 인스턴스 1대(Ubuntu 22.04 LTS, t3.xlarge)

☞ CloudFormation 스택 실행 시 파라미터를 기입하면, 해당 정보가 반영되어 배포됩니다.

  CloudFormation 에 EC2의 UserData 부분(Script 실행)으로 실습 환경에 필요한 기본 설정들이 자동으로 진행됩니다.

 

Step1. CloudFormation 스택 배포

# YAML 파일 다운로드
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 mylab --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region ap-northeast-2
예시) 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

# [모니터링] CloudFormation 스택 상태 : 생성 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2

# EC2 SSH 접속
ssh -i ~/.ssh/[my-Pem].pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)

 

Step2. Ubuntu EC2 SSH 접속 : 기본 정보 확인 ← 스택 생성 시작 후 5분 후 접속 할 것

# SSH 접속 : ubuntu 계정
ssh -i ~/.ssh/[my-PEM].pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
------------------------
# 현재 디렉터리 확인
pwd

# 계정 정보 확인
whoami
id

# ubuntu 버전 확인
lsb_release -a

# 설치 툴 버전 확인
kind version
docker version # 출력 하단에 'WARNING: bridge-nf-call-iptables' 에러 없이 출력 되는지 꼭 확인!
kubectl version --client=true
helm version

▶ 실습환경 개요

실습 환경은 K8S v1.31.0 , CNI(Kindnet, Direct Routing mode) , IPTABLES proxy mode - 노드(실제로는 컨테이너) 네트워크 대역 : 172.18.0.0/16 - 파드 사용 네트워크 대역 : 10.10.0.0/16 ⇒ 각각 10.10.1.0/24, 10.10.2.0/24, 10.10.3.0/24, 10.10.4.0/24 - 서비스 사용 네트워크 대역 : 10.200.1.0/24

 

출처 : https://kubernetes.io/docs/concepts/cluster-administration/networking/

 

출처 : https://kschoi728.tistory.com/260

 

 

▶ Cluster 환경 만들기

더보기

 

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


# k8s v1.31.0 버전 확인
kubectl get node

# 노드 labels 확인
kubectl get nodes -o jsonpath="{.items[*].metadata.labels}" | jq

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'

# 파드CIDR 과 Service 대역 확인 : CNI는 kindnet 사용
kubectl get cm -n kube-system kubeadm-config -oyaml | grep -i subnet
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"

# MultiCIDRServiceAllocator : https://kubernetes.io/docs/tasks/network/extend-service-ip-ranges/
kubectl get servicecidr
NAME         CIDRS           AGE
kubernetes   10.200.1.0/24   2m13s

# 노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath="{.items[*].spec.podCIDR}"
10.10.0.0/24 10.10.4.0/24 10.10.3.0/24 10.10.1.0/24

# kube-proxy configmap 확인
kubectl describe cm -n kube-system kube-proxy
...
mode: iptables
iptables:
  localhostNodePorts: null
  masqueradeAll: false
  masqueradeBit: null
  minSyncPeriod: 1s
  syncPeriod: 0s
...


# 노드 별 네트워트 정보 확인 : CNI는 kindnet 사용
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

# 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

# 각 노드 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
----------------------------------------

# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# arp scan 해두기
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# mypc2 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc2 --network kind --ip 172.18.0.200 nicolaka/netshoot sleep infinity # IP 지정 실행 시
IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc2 --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'


# kube-ops-view 설치
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30000 --set env.TZ="Asia/Seoul" --namespace kube-system

# myk8s-control-plane 배치
kubectl -n kube-system edit deploy kube-ops-view
---
spec:
  ...
  template:
    ...
    spec:
      nodeSelector:
        mynode: control-plane
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        operator: "Equal"
        effect: "NoSchedule"
---

# 설치 확인
kubectl -n kube-system get pod -o wide -l app.kubernetes.io/instance=kube-ops-view

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : macOS 사용자
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=2"

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : Windows 사용자
echo -e "KUBE-OPS-VIEW URL = http://192.168.50.10:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://192.168.50.10:30000/#scale=2"

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : AWS_EC2 사용자
echo -e "KUBE-OPS-VIEW URL = http://$(curl -s ipinfo.io/ip):30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://$(curl -s ipinfo.io/ip):30000/#scale=2"

[ 실행 결과 - 한 눈에 보기 ]

 

 

 

 

 

 

[ 옵션 ] 프로메테우스 스텍 띄우기 ( 윈도우 실습 환경은 상당히 느려서, 아래 프로메테우스-스택 설치는 Skip 할 것 )

더보기
#
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
  service:
    type: NodePort
    nodePort: 30001

  prometheusSpec:
    podMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelectorNilUsesHelmValues: false
    nodeSelector:
      mynode: control-plane
    tolerations:
    - key: "node-role.kubernetes.io/control-plane"
      operator: "Equal"
      effect: "NoSchedule"


grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: kans1234

  service:
    type: NodePort
    nodePort: 30002
  nodeSelector:
    mynode: control-plane
  tolerations:
  - key: "node-role.kubernetes.io/control-plane"
    operator: "Equal"
    effect: "NoSchedule"

  sidecar:
    dashboards:
      enabled: true
  dashboards:
    default:
      custom-dashboard:
        gnetId: 20162  # MetalLB 대시보드 ID
        datasource: Prometheus  # 사용할 데이터소스 이름을 명시
        revision: 1    # 대시보드의 버전

defaultRules:
  create: false
alertmanager:
  enabled: false

EOT

# 배포
kubectl create ns monitoring
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 62.3.0 -f monitor-values.yaml --namespace monitoring

# 확인
helm list -n monitoring

# Grafana 접속 계정 : admin / kans1234 : macOS 사용자
echo -e "Prometheus URL = http://localhost:30001"
echo -e "Grafana URL = http://localhost:30002"

# Grafana 접속 계정 : admin / kans1234 : Windows 사용자
echo -e "Prometheus URL = http://192.168.50.10:30001"
echo -e "Grafana URL = http://192.168.50.10:30002"

# Grafana 접속 계정 : admin / kans1234 : AWS_EC2 사용자
echo -e "Prometheus URL = http://$(curl -s ipinfo.io/ip):30001"
echo -e "Grafana URL = http://$(curl -s ipinfo.io/ip):30002"


# (참고) helm 삭제
helm uninstall -n monitoring kube-prometheus-stack

 

[ Prometheus & Grafana dashboard 띄우기 ]

[ helm 이용한 Prometheus & Grafana 설치 ]
[ Grafana dashboard 설정 ]

1. Metal LB

1-1. 로드밸런서

 - LoadBalancer 는 k8s 대표적인 서비스 타입 중 하나로, 외부 Client 에서 노드 내부의 POD 접속 시 중간에서 세션의 부하분산을

   통해 어플리케이션 서비스의 균형적인 서비스를 제공할 수 있는 오브젝트 입니다.

   ( Ref. Link - https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer )

 

[참조]  k8s 공식 document 에서 제공하는 LoadBalancer 의 특징 및 사용법

더보기

◎ 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

1) 클라우드 환경의 LoadBalancer

 - AWS CLB, NLB, ALB가 존재한다.

  ♣ CLB (Classical Load Balancer) : 3가지 타입 중 가장 오래된 것으로 상대적으로 적은 기능을 제공

  ♣ NLB (Network Load Balancer) : 3가지 타입 중 네트웍 처리속도가 가장 빠르고, Layer-4 에서 동작한다.

  ♣ ALB (Application Load Balancer) : HTTP/HTTPs/gPRC 트래픽 전문처리하며, Layer-7 계층에서 동작한다.

 

2) Kubernetes 상에서의 LB 통신방식(2가지)

 < Type-1 > 

 

 

외부 클라이언트에서 로드밸런서로 접근하여,

     NodePort 의 목적지 포트로 traffic 전달 후,

     IPtables 에 의해 random 하게 POD 로 통신

 

 

< Type- 2 >

 

로드밸런서에서 POD IP 로 직접 Traffic 전달

    ( 별도의 LB 컨트롤러 구성이 필요하며, 

      해당 LB 컨트롤러가 POD IP를 로드밸런서에게

      직접 전달함 )

 

 


1-2. Metal LB - Layer2 모드

 - Metal LB 는 Baremetal LoadBalancer 의 약자로, 온프레미스에서 표준 프로토콜을 통해 Loadbalancer 를 구현해 주는 프로그램이다.

 - K8S의 daemonset 으로 Speaker-POD 를 생성하여, External-IP를 전파한다.

 - Speaker-PODExternal-IP를 전파하기 위해, ARP를 사용하는 Layer-2 모드BGP 모드 2개를 사용한다.

※ Metal LB는 대부분의 Public Cloud Platform에서 동작하지 않는다. 이유는 가상서버의 Mac 주소가 아닌 IP에 대한 ARP를 차단하기 

    때문이다. 또한 일부 CNI 는 제대로 동작하지 않는다. 예를 들어,  칼리코의 IPIP에 BGP를 사용하면서, Metal LB의 BGP를 사용할

    경우, BGP 동작에 충돌이 발생한다. 해당 사유로 운영환경에서 사용시 호환성 등 충분한 테스트 이후 사용할 것을 권장한다.

    Ref. Link - https://metallb.universe.tf/installation/network-addons/

 

1) Layer2 모드 통신흐름

  - Layer2 모드는 ARP를 통해 External IP를 전파한다.

  - Layer2 모드에서는 서비스 IP로 향하는 모든 traffic 이 한 노드를 통해서 지나가므로, 엄밀히 말하면 Load-balancing이 되지 않는다.

[ Metal LB의 Layer2 모드 동작 ]

 

(1)  Loadbalancer 서비스 생성 시, Metal LB 스피커 파드들 중 리더 스피커가 선출된다.

      리더 스피커의 역할이 External IP를 전파하는 것이고, 그림에서 SVC1은 노드 1에 있는 Pod가 스피커 파드로,

      SVC2는 노드 3에 있는 Pod가 스피커 파드로 각자 자신의 LB Service에 External IP를 ARP를 통해 전파하고 있다.

 

(2) 클라이언트1은 SVC1 External IP로 접속을 시도하고 스피커 파드가 있는 노드 1으로 Traffic이 들어오고,

     클라이언트2는 SVC2 External IP로 접속을 시도하고 스피커 파드가 있는 노드 3으로 Traffic이 들어온다.

 

(3) 노드에 인입 후, IPtables 규칙에 의해 Cluster IP와 동일하게 서비스 End-Point에 파드들로 Random 부하분산이 된다. 

 

[ More ... ]

더보기

 

  1. speaker 파드가 BGP로 서비스 정보(EXTERNAL-IP)를 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속
  2. 권장 사용 환경 : 테스트 및 소규모의 환경(동일 네트워크 1개 사용)에서 클라이언트 IP 보존이 필요 없을 때
https://metallb.universe.tf/configuration/_advanced_l2_configuration/

※ 한계점 (2가지) 

 - 첫째, single leader-elected node 가 모든 traffic 을 처리하므로, 해당 사항이 bottle-neck이 될 수 있다.

 - 둘째, leader-elected node가 이상이 발생할 경우, 내부적으로 gratuitous packet alarm을 주고 받은 후, 

   새로운 leader node를 선정하여 서비스를 재게하게 되는데 이때 수초에서 수십초까지 시간 지연이 발생하게 된다.

   이는 기존 client가 가지고 있는 세션 cache의 flushing time이 필요하기 때문이며, buggy client 에서 발생하는

   planned failover 라 할찌라도 기존 leader node가 client cache flushing이 완료되는 때까지 버텨주어야 장애시간을 줄일 수 있다.

2) 서비스 LoadBalancer Type 통신흐름

- 요약 : 외부 클라이언트로드벨런서 접속 시, 부하분산 되어 노드 도달 후  iptables 룰목적지 파드와 통신됨

♣ 외부에서 로드밸런서 (부하분산) 처리 후, NodePort 이후 과정은 동일하다.

♣ 노드는 외부에 공개되지 않고, 로드밸런서만 공개되어 외부에서는 내부 노드 정보를 알 수 없다.

로드밸런서가 부하분산 하여 파드가 존재하는 노드들에게 Traffic 전달한다. 

    iptables 룰에서는 자신의 노드에 있는 파드만 연결한다. ( externalTrafficPolicy: local )

DNAT 2번 동작한다 => ( 첫번 째, 로드밸런서 접속 후 빠져나갈 때 / 두번 째, 노드의iptables 룰에서 파드 IP전달 시 )

♣ 외부 클라이언트 IP 유지 : AWS NLB는 Target이 Instance일 경우 유지하며, iptables 룰 경우도 externalTrafficPolicy 일 경우 유지한다.

♣ 쿠버네티스는 Service (LB Type) API 만 정의하고 실제 구현은 add-on에 맡긴다.

 

※ 부하분산 최적화 : 노드에 파드가 없을 경우, 노드 헬스체크실패하여 해당 노드로 외부 요청 Traffic 전달하지 않는다.

 

 

 

[ 서비스 loadbalancer 부족한 점 ]

☞ 서비스 생성 시 마다 LB가 생성되어 비효율적임

   → HTTP의 경우, Ingress 통해 자원 효율화 가능

HTTP/HTTPS 처리에 일부 부족함(LTS 종료, 도메인기반 라우팅 등)
   →
Ingress 통해 기능 동작 가능

 

 

 


1-3. BGP 모드

Cluster 에 있는 모든 멤버 노드들은 라우터와 BGP 세션 peering을 맺고 있다.

 

 [ More ... ]

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

1) 특징

  - BGP 모드에서는 커넥션 당 모든 패킷이 하나의 TCP or UDP 세션을 통해서 통신이 이루어진다.

    이러한 특징은 여러 클러스터 노드들에 분산되어 있는 POD와의 통신에 있어서 큰 장점이 될 수 있다.

   ( 통신경로를 찾아가는 점에서의 Traffic loss가 줄어듬, loadbalancer 타입의 경우, 접점 노드 내부에서

     kube-proxy iptables 정책에 의해 다른 노드의 POD로 가는 경우도 있음에 주목하자!! )

 

2) 통신 설명 요약

  - Speaker 파드가 BGP로 서비스(External IP) 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속

   a) speaker 파트에 BGP가 동작하여 서비스 정보(External IP)를 전파함

     . 기본 IP 주소(32bt)를 전파하며, 설정으로 축약된 네트워크 정보를 전파 가능

     . BGP 커뮤니티,  localpref 등 BGP 관련 설정 가능

     . IP 주소 마지막이 0과 255를 처리 못하는 라우터 장비 있을 경우, avoid-buggy-ips:true 옵션으로 처리 가능

   b) 외부 클라이언트에서 SVC(서비스, External IP)로 접속이 가능하며, 라우터에서 ECMP 라우팅을 통해 부하분산 접속

    

3) 제한 사항

  - 라우터에서 서비스로 인입이 되기 때문에, 네트워크 팀과의 협업을 적극 권장함

   . 노드(speaker) 파드 장애 시, BGP Timer 설정 등 구성 및 네트워크 환경에 맞는 최적화 작업 필요

   . ECMP 부하 분산 접속 시 특정 파트에 물리거나 세선 고정, flapping 등의 다양한 문제상황에 대응이 필요

   . BGP 라우팅 설정 및 라우팅 전파 관련 최적화 설정 필요

 

Ref. Link - https://metallb.universe.tf/concepts/bgp/


1.4 실습환경 소개

▼ 기본 정보 확인

더보기
# 파드와 서비스 사용 가능 네트워크 대역
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 <<"; 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

파드 생성

더보기
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

파드 접속 확인

더보기
# 파드 정보 확인
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

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

 

[ 실행 결과 ]

1.4.1 Metal LB 설치

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

  ※ 참고 : kube-proxy 의 ipvs 모드 사용 시 'strictARP: true' 설정 필요

▼ 간단하게 manifests설치 진행! - https://github.com/metallb/metallb/tree/main/config/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 crd 확인
kubectl get crd | grep metallb
bfdprofiles.metallb.io                      2024-09-28T15:24:06Z
bgpadvertisements.metallb.io                2024-09-28T15:24:06Z
bgppeers.metallb.io                         2024-09-28T15:24:06Z
communities.metallb.io                      2024-09-28T15:24:06Z
ipaddresspools.metallb.io                   2024-09-28T15:24:06Z
l2advertisements.metallb.io                 2024-09-28T15:24:06Z
servicel2statuses.metallb.io                2024-09-28T15:24:06Z

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

## 데몬셋으로 배포되는 metallb 스피커 파드의 IP는 네트워크가 host 모드이므로 노드의 IP를 그대로 사용
kubectl get pod -n metallb-system -o wide
NAME                          READY   STATUS    RESTARTS   AGE     IP           NODE                  NOMINATED NODE   READINESS GATES
controller-679855f7d7-2dvbm   2/2     Running   0          9m17s   10.10.2.6    myk8s-worker3         <none>           <none>
speaker-jfvh9                 2/2     Running   0          9m17s   172.18.0.2   myk8s-worker          <none>           <none>
speaker-l2tdn                 2/2     Running   0          9m17s   172.18.0.5   myk8s-worker3         <none>           <none>
speaker-pzs8z                 2/2     Running   0          9m17s   172.18.0.3   myk8s-worker2         <none>           <none>
speaker-vfsdj                 2/2     Running   0          9m17s   172.18.0.4   myk8s-control-plane   <none>           <none>

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

 

[ 실행 결과 - 한 눈에 보기 ]

[ Namespace "metallb-system" 에 할당된 Role 및 DS "speaker" 상세 정보 ]

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

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

더보기
# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind
# kind network 중 컨테이너(노드) IP(대역) 확인 : 172.18.0.2~ 부터 할당되며, control-plane 이 꼭 172.18.0.2가 안될 수 도 있음
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'

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

 

 

 

[ Tip & Tips - Stern 설치하기 ]

더보기

[ 참조 - stern 설치 ]  (  Ref. Link -  공홈  , Cherry-Server,  블로그  )

 

Step1. GO language 설치

## Ubuntu
sudo apt install golang
## Mac
brew install go

 

 Step2. Govendor 설치

  - external package를 import하는데 사용하는 툴입니다. 이를 통해 dependency를 쉽게 import할 수 있습니다.

## Ubuntu
sudo apt install govendor

 

※ govendor 명령어가 수행되지 않을 경우, "go env"  명령어로 "GOPATH", "GOBIN" 을 확인한다.

   필요 시, 환경변수를 잡아주자!!  ( 영구 적용을 위해 /etc/profile or ~/.bashrc 에 등록 )

   ( export PATH=$PATH:/root/go/bin )

 

Step3. strun 설치하기

mkdir -p $GOPATH/src/github.com/werker
cd $GOPATH/src/github.com/werker
git clone https://github.com/stern/stern && cd stern
## 버전 업그레이드 필요 ( needed GO-version above 1.22 )
## https://www.cherryservers.com/blog/install-go-ubuntu 참조
## download Fetch Version : https://go.dev/dl/
wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz -O go.tar.gz

## go file extract
sudo tar -xzvf go.tar.gz -C /usr/local

## env. setting
echo export PATH=$HOME/go/bin:/usr/local/go/bin:$PATH >> ~/.profile
source ~/.profile
## or echo export PATH=$HOME/go/bin:/usr/local/go/bin:$PATH >> ~/.bashrc
go version  ## version 1.23 확인
##govendor sync
go install

[ 참조 - Trouble shooting ]

 

Step4. 모니터링 띄워보기

stern -n kube-system kube-proxy-*

1.4.2 서비스 생성 및 확인

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


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

 

[ 실행 결과 - 한 눈에 보기 ]

 

  

1.4.3 서비스 접속 테스트

☞ 이제 드디어 k8s 클러스터 외부에서 노드의 IP를 감추고(?) VIP/Port 를 통해 k8s 클러스터 내부의 애플리케이션에 접속 할 수 있다!

더보기
# 현재 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
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 | grep Hostname ; echo ; done

## RemoteAddr 주소는 어떻게 나오나요? 왜 그럴까요?
##  NodePort 기본 동작과 동일하게 인입한 노드의 인터페이스로 SNAT 되어서 최종 파드로 전달됨
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


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

 

[ 실행 결과 ]

 

1.4.4 IPtables 정책 적용 확인 (옵션)

더보기
[ IPtables 동작 순서 : 해당 순서로 대상을 찾아갈 수 있다!! ]

 

3) 단계에서 마킹된 대상에 대해 source IP NAT가 된다.

 

# 현재 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

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



# 컨트롤플레인에서 확인 : 너무 복잡해서 리턴 트래픽에 대해서는 상세히 분석 정리하지 않습니다.
docker exec -it myk8s-worker bash
----------------------------------------

# iptables 확인
iptables -t filter -S
iptables -t nat -S
iptables -t nat -S | wc -l
iptables -t mangle -S

# iptables 상세 확인 - 매칭 패킷 카운트, 인터페이스 정보 등 포함
iptables -nvL -t filter
iptables -nvL -t nat
iptables -nvL -t mangle

# rule 갯수 확인
iptables -nvL -t filter | wc -l
iptables -nvL -t nat | wc -l

# 규칙 패킷 바이트 카운트 초기화
iptables -t filter --zero; iptables -t nat --zero; iptables -t mangle --zero

# 정책 확인 : 아래 정책 내용은 핵심적인 룰(rule)만 표시했습니다!
iptables -t nat -nvL
iptables -t nat -S


PREROUTING
# SVC1(External-IP) 접속 시 iptables 에서 DNAT 되어 파드로 전달된다 (SNAT 도 동작)

iptables -t nat -S PREROUTING
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

iptables -t nat -S KUBE-SERVICES

SVC1EXIP=<직접입력>
SVC1EXIP=172.18.255.202
iptables -t nat -S KUBE-SERVICES |grep $SVC1EXIP
-A KUBE-SERVICES -d 172.18.255.202/32 -p tcp -m comment --comment "default/svc1:svc1-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-DLGPAL4ZCYSJ7UPR

iptables -t nat -S KUBE-EXT-DLGPAL4ZCYSJ7UPR
-A KUBE-EXT-DLGPAL4ZCYSJ7UPR -m comment --comment "masquerade traffic for default/svc1:svc1-webport external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-DLGPAL4ZCYSJ7UPR -j KUBE-SVC-DLGPAL4ZCYSJ7UPR

iptables -t nat -S KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

iptables -t nat -S KUBE-SVC-DLGPAL4ZCYSJ7UPR
-A KUBE-SVC-DLGPAL4ZCYSJ7UPR ! -s 10.10.0.0/16 -d 10.200.1.82/32 -p tcp -m comment --comment "default/svc1:svc1-webport cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport -> 10.10.1.6:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-USUIKZUHVJLOFB4D
-A KUBE-SVC-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport -> 10.10.3.6:80" -j KUBE-SEP-SXKPWGGXEDMXXU4H

#
iptables -t nat -S KUBE-SEP-USUIKZUHVJLOFB4D
-A KUBE-SEP-USUIKZUHVJLOFB4D -s 10.10.1.6/32 -m comment --comment "default/svc1:svc1-webport" -j KUBE-MARK-MASQ
-A KUBE-SEP-USUIKZUHVJLOFB4D -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 10.10.1.6:80

iptables -t nat -S KUBE-SEP-SXKPWGGXEDMXXU4H
-A KUBE-SEP-SXKPWGGXEDMXXU4H -s 10.10.3.6/32 -m comment --comment "default/svc1:svc1-webport" -j KUBE-MARK-MASQ
-A KUBE-SEP-SXKPWGGXEDMXXU4H -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 10.10.3.6:80


POSTROUTING
iptables -t nat -S POSTROUTING

iptables -t nat -S KUBE-POSTROUTING
-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


# SVC2(External-IP) 접속 시 iptables 에서 DNAT 되어 파드로 전달된다 (SNAT 도 동작) : 위와 상동!
# SVC3(External-IP) 접속 시 iptables 에서 DNAT 되어 파드로 전달된다 (SNAT 도 동작) : 위와 상동!

 

[ 실행 결과 - 한 눈에 보기 ]

1.4.5 Fail-over 테스트

스피커 노드의 장애 시, Gratitude ARP broadcasting 으로 신규 스피커 노드를 선정하게 된다.

해당 동작 시, 수초 ~ 수 십초의 시간이 소요되어, Metal LB의 채택, 사용 시에는 서비스 정상화 시간 관련

응용팀과 충분한 사전 검토 및 협의가 필요하다.

 

[ 실습 코드 ]

더보기

※ 리더 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


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

## 지속적으로 반복 접속 상태 모니터링
### 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"
Hostname: webpod1
RemoteAddr: 172.18.0.2:25432
2024-09-29 06:31:07

2024-09-29 06:31:09

Hostname: webpod2
RemoteAddr: 172.18.0.2:26011
2024-09-29 06:31:10

2024-09-29 06:31:12

2024-09-29 06:31:14
...

# 변경된 리더 Speaker 파드 확인
# mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; 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
kubectl get node -owide # mac 주소에 매칭되는 IP(노드) 찾기


# 장애 원복(노드 정상화)
## 노드(실제 컨테이너) 정상화 
docker start <svc1 번 리더 Speaker 파드가 존재하는 노드(실제는 컨테이너)>
docker start myk8s-worker

# 변경된 리더 Speaker 파드 확인
# mypc/mypc2 에서 현재 SVC EXTERNAL-IP를 담당하는 리더 Speaker 파드 찾기
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; 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
kubectl get node -owide # mac 주소에 매칭되는 IP(노드) 찾기

 

[ 실습 결과 - 한 눈에 보기 ]

1.5 MetalLB - BGP 모드

 ( 라우터 실제 구성연동 등 실습환경 구성 어려움으로 생성 시나리오만 소개 )

## 아래와 같이 init 프로세스 실행 가능한 컨테이너 이미지로 기동 후, 해당 컨테이너에 bgp 설치 및
## dummy Interface(lo0) 셋팅 후 실습환경 생성한다.
docker run -d alpine /sbin/init

 

1.5.1 MetalLB -BGP 모드 설정

MetalLB는 외부 BGP 라우터와 1개 이상의 세션을 맺기 위해서 하기 사항에 대한 이해를 해야 한다.

Metallb 가 연결하고자 하는 만큼의 BGPPeer 의 인스턴스 생성이 필요하다.

한 개의 BGP router 와 1개의 IP address range에 대한 기본적인 구성을 위해서 4개의 정보가 필요하다.

  • The router IP address that MetalLB should connect to,
  • The router’s AS number,
  • The AS number MetalLB should use,
  • An IP address range expressed as a CIDR prefix.

▼ MetalLB ConfigMap 생성

더보기
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

▼ avoid-buggy-ips:true  → andling buggy networks  -  Link

더보기
Some old consumer network equipment mistakenly blocks IP addresses ending in .0 and .255, because of misguided smurf protection.
If you encounter this issue with your users or networks, you can set avoid-buggy-ips: true on an address pool to mark .0 and .255 addresses as unusable.

리눅스 라우터에 BGP 설정 (예시) ← 미리 되어 있음

더보기
router bgp 64513
bgp router-id 192.168.10.254
maximum-paths 4
network 10.1.1.0/24
neighbor 192.168.10.10  remote-as 64512
neighbor 192.168.10.101 remote-as 64512
neighbor 192.168.10.102 remote-as 64512
  ...

1.5.2 서비스 생성 및 확인

▼ POD 생성

더보기
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'
Netid State Recv-Q Send-Q          Local Address:Port              Peer Address:Port           Process
tcp   ESTAB 0      0               192.168.10.10:39171           192.168.10.254:179             users:(("speaker",pid=3057,fd=27))
...

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

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

더보기
# 서비스(EX-IP) 3개의 각각 IP에 대해서 K8S 노드로 ECMP 라우팅 정보가 업데이트되었다
ip -c route | grep 172.20. -A3
	172.20.1.1 proto zebra metric 20
		nexthop via 192.168.10.10 dev enp0s8 weight 1
		nexthop via 192.168.10.101 dev enp0s8 weight 1
		nexthop via 192.168.10.102 dev enp0s8 weight 1
	172.20.1.2 proto zebra metric 20
		nexthop via 192.168.10.10 dev enp0s8 weight 1
		nexthop via 192.168.10.101 dev enp0s8 weight 1
		nexthop via 192.168.10.102 dev enp0s8 weight 1
	172.20.1.3 proto zebra metric 20
		nexthop via 192.168.10.10 dev enp0s8 weight 1
		nexthop via 192.168.10.101 dev enp0s8 weight 1
		nexthop via 192.168.10.102 dev enp0s8 weight 1

# vtysh 를 이용한 Quagga 정보 확인
vtysh -c 'show ip route bgp'
vtysh -c 'show bgp ipv4 unicast summary'
vtysh -c 'show ip bgp'
vtysh -c 'show running-config'

----------------------
# Quagga 모드 진입
vtysh

# 라우팅 정보 확인
show ip route bgp
B>* 172.20.1.1/32 [20/0] via 192.168.10.10, enp0s8, 00:30:34
  *                      via 192.168.10.101, enp0s8, 00:30:34
  *                      via 192.168.10.102, enp0s8, 00:30:34
B>* 172.20.1.2/32 [20/0] via 192.168.10.10, enp0s8, 00:30:34
  *                      via 192.168.10.101, enp0s8, 00:30:34
  *                      via 192.168.10.102, enp0s8, 00:30:34
B>* 172.20.1.3/32 [20/0] via 192.168.10.10, enp0s8, 00:30:34
  *                      via 192.168.10.101, enp0s8, 00:30:34
  *                      via 192.168.10.102, enp0s8, 00:30:34
...

# BGP 상세 정보 확인
show bgp ipv4 unicast summary
show ip bgp
show running-config
...
exit

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

1.5.3 서비스 접속 테스트

▼ 클라이언트(k8s-pc) → 서비스(External-IP) 접속 테스트

더보기
# 라우팅 테이블 정보 확인
ip -c route | grep zebra
	10.1.1.0/24 via 192.168.20.254 dev enp0s8 proto zebra metric 20
	172.20.1.1 via 192.168.20.254 dev enp0s8 proto zebra metric 20
	172.20.1.2 via 192.168.20.254 dev enp0s8 proto zebra metric 20
	172.20.1.3 via 192.168.20.254 dev enp0s8 proto zebra metric 20

# vtysh 를 이용한 Quagga 정보 확인
vtysh -c 'show ip route bgp'
vtysh -c 'show bgp ipv4 unicast summary'
vtysh -c 'show ip bgp'
vtysh -c 'show running-config'

# 현재 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

# 접속 테스트
curl -s --interface 192.168.20.100 $SVC1EXIP | grep Hostname
curl -s --interface 192.168.20.100 $SVC2EXIP | grep Hostname
curl -s --interface 192.168.20.100 $SVC3EXIP | grep Hostname

# RemoteAddr 주소 확인
curl -s --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr|Host:'
curl -s --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr|Host:'
curl -s --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr|Host:'

# 부하분산 접속됨
for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC1EXIP | grep Hostname; done | sort | uniq -c | sort -nr
for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC2EXIP | grep Hostname; done | sort | uniq -c | sort -nr
for i in {1..100}; do curl -s --interface 192.168.20.100 $SVC3EXIP | grep Hostname; done | sort | uniq -c | sort -nr

# (옵션) 지속적으로 접속
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done

※ 클라이언트 → 서비스(External-IP) 접속 시 : 라우터 ECMP 분산 접속으로 노드 인입 후 Service 에 매칭된 iptables rules 에 따라 랜덤 부하 분산되어서(SNAT) 파드로 접속!

 

클라이언트의 IP는 인입한 노드의 IP로 SNAT 되어 목적지 파드로 접속됨

 

▼ (옵션) iptables 정책 적용 확인 : 2.3 의 iptables 정책과 동일!

더보기
# 정책 확인 : 아래 정책 내용은 핵심적인 룰(rule)만 표시했습니다!
iptables -t nat -S

# SVC(External-IP) 접속 시 iptables 에서 DNAT 되어 파드로 전달된다 (SNAT 도 동작)
-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-SERVICES -d 172.20.1.2/32 -p tcp -m comment --comment "default/svc2:svc2-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-52NWEQM6QLDFKRCQ
-A KUBE-SERVICES -d 172.20.1.3/32 -p tcp -m comment --comment "default/svc3:svc3-webport loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-CY3XQR2NWKKCV4WA

-A KUBE-FW-CY3XQR2NWKKCV4WA -m comment --comment "default/svc3:svc3-webport loadbalancer IP" -j KUBE-SVC-CY3XQR2NWKKCV4WA
-A KUBE-SVC-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-RR4AIWWPC73NKIRU
-A KUBE-SVC-DLGPAL4ZCYSJ7UPR -m comment --comment "default/svc1:svc1-webport" -j KUBE-SEP-M5K3PIW2STI5BGSH

-A KUBE-SEP-RR4AIWWPC73NKIRU -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 172.16.46.15:80
-A KUBE-SEP-M5K3PIW2STI5BGSH -p tcp -m comment --comment "default/svc1:svc1-webport" -m tcp -j DNAT --to-destination 172.16.46.16:80

▼ (옵션) BGP 패킷 분석

더보기

▷ speaker 파드가 외부 라우터에게 BGP Update로 서비스(External-IP)를 32bit 로 전파

 서비스 오브젝트 삭제 시 BGP Update 에 Withdrawn Routes 정보로 전달하게 되고, 라우터가 받게 되면 해당 네트워크 정보를 바로 삭제한다.

 

1.5.4 externalTrafficPolicy: Local 테스트

▼ 서비스에 externalTrafficPolicy: Local 설정

더보기
[k8s-pc] 지속적으로 접속 시도 : 아래 중 하나 실행
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
while true; do curl -s --connect-timeout 1 --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr'; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done

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

리눅스 라우터에서 BGP 정보 확인

더보기
[k8s-rtr] 파드가 있는 노드만 nexthop 올라옴 (현재 마스터 노드에 파드가 없는 상태)
ip -c route
172.20.1.1 proto zebra metric 20
        nexthop via 192.168.100.102 dev enp0s8 weight 1
        nexthop via 192.168.100.101 dev enp0s8 weight 1
172.20.1.2 proto zebra metric 20
        nexthop via 192.168.100.101 dev enp0s8 weight 1
        nexthop via 192.168.100.102 dev enp0s8 weight 1
172.20.1.3 proto zebra metric 20
        nexthop via 192.168.100.102 dev enp0s8 weight 1
        nexthop via 192.168.100.101 dev enp0s8 weight 1
...

 

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

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

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

1.5.5 Failover 테스트

▼ 현재 k8s-pc 라우팅 테이블도 32bit 라우팅 정보를 받고 있다 → k8s-rtr 에서 라우팅 축약을 설정해보자

더보기
[k8s-pc] 라우팅 정보
ip -c route | grep zebra
	10.1.1.0/24 via 192.168.20.254 dev enp0s8 proto zebra metric 20
	172.20.1.1 via 192.168.20.254 dev enp0s8 proto zebra metric 20
	172.20.1.2 via 192.168.20.254 dev enp0s8 proto zebra metric 20
	172.20.1.3 via 192.168.20.254 dev enp0s8 proto zebra metric 20

# 모니터링 설정
watch -d 'ip route | grep zebra'

[k8s-rtr] 라우팅 설정
vtysh

show running-config
conf t
router bgp 64513
 aggregate-address 172.20.1.0/24 summary-only
 exit
 exit
write
exit

[k8s-pc] 
# 라우팅 정보 확인
ip -c route

# 접속 테스트
curl -s --interface 192.168.20.100 $SVC1EXIP | egrep 'Hostname|RemoteAddr|Host:'
curl -s --interface 192.168.20.100 $SVC2EXIP | egrep 'Hostname|RemoteAddr|Host:'
curl -s --interface 192.168.20.100 $SVC3EXIP | egrep 'Hostname|RemoteAddr|Host:'

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

★ 실습 종료 시, MetalLB 삭제

kubectl delete -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/metallb.yaml
kubectl delete -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/namespace.yaml

★  IPVS 실습을 위해 kind 클러스터 삭제 : kind delete cluster --name myk8s


2. IPVS  proxy 모드 

- 실습환경 요약

더보기

실습 환경은 K8S v1.31.0 , CNI(Kindnet, Direct Routing mode) , IPVS proxy mode - 노드(실제로는 컨테이너) 네트워크 대역 : 172.18.0.0/16 - 파드 사용 네트워크 대역 : 10.10.0.0/16 ⇒ 각각 10.10.1.0/24, 10.10.2.0/24, 10.10.3.0/24, 10.10.4.0/24 - 서비스 사용 네트워크 대역 : 10.200.1.0/24

 

▶서비스 생성

더보기
# 파일 작성
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


# 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

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

 

[ 실행 결과 - 한 눈에 보기 ]

2-1. 서비스 개요

♣ 3가지 proxy 모드 중 가장 효율적인 모드이다.

♣ Netfilter에서 동작하는 Layer-4 로드밸런서 

  : 넷필터에서 사용하는 iptables 가 상당히 적기 때문에

    Traffic 처리에서 효율적이다.

다양한 분산 알고리즘 사용

  - Round-Robin

  - Least-Connection

  - Destination Hashing

  - Sourcing Hashing

[ More ... ]

더보기

◈ IPVS 프록시 모드 (kernel IPVS , iptables APIs → netfilter subsystem) http://www.linuxvirtualserver.org/software/ipvs.html

 

 IPVS( IP Virtual Service) 모드에서 Kube-Proxy는 service-IP 에서 Endpoint-IP 를 연결해 주기 위해  kernel IPVS

    iptables APIs 를 통해 traffic redirection을 해주는 Rule을 생성한다.

 ( The IPVS proxy mode is based on netfilter hook function that is similar to iptables mode, but uses a hash table as the underlying data structure and works in the kernel space. That means kube-proxy in IPVS mode redirects traffic with lower latency than kube-proxy in iptables mode, with much better performance when synchronizing proxy rules. Compared to the iptables proxy mode, IPVS mode also supports a higher throughput of network traffic. )

 

  IPVS Mode는 Linue Kernel에서 제공하는 L4 Load Balacner인 IPVS가 Service Proxy 역할을 수행하는 Mode이다.

  Packet Load Balancing 수행시 IPVS가 iptables보다 높은 성능을 보이기 때문에 IPVS Modeiptables Mode보다 높은 성능을
     보여준다.

IPVS 프록시 모드는 iptables 모드와 유사한 넷필터 후크 기능을 기반으로 하지만, 해시 테이블을 기본 데이터 구조로 사용하고
    커널 스페이스에서 동작한다.

이는 IPVS 모드의 kube-proxyiptables 모드의 kube-proxy보다 지연 시간이 짧은 트래픽을 리다이렉션하고, 프록시 규칙을
     동기화할 때 성능이 훨씬 향상됨을 의미한다.

다른 프록시 모드와 비교했을 때, IPVS 모드는 높은 네트워크 트래픽 처리량도 지원한다.


[ 부하분산 방식 - 약어 설명 ] - 참고 : https://net711.tistory.com/entry/lvs-리눅스-l4-만들기

 IPVS 부하분산 : rr, wrr, lc, wlc, lblc, lblcr, sh, dh, sed, nq

. rr (Round Robin): Traffic is equally distributed amongst the backing servers.

. wrr (Weighted Round Robin): Traffic is routed to the backing servers based on the weights of the servers. Servers with higher weights receive new connections and get more requests than servers with lower weights.

. lc (Least Connection): More traffic is assigned to servers with fewer active connections.

. wlc (Weighted Least Connection): More traffic is routed to servers with fewer connections relative to their weights, that is, connections divided by weight.

. lblc (Locality based Least Connection): Traffic for the same IP address is sent to the same backing server if the server is not overloaded and available; otherwise the traffic is sent to servers with fewer connections, and keep it for future assignment.

. lblcr (Locality Based Least Connection with Replication): Traffic for the same IP address is sent to the server with least connections. If all the backing servers are overloaded, it picks up one with fewer connections and add it to the target set. If the target set has not changed for the specified time, the most loaded server is removed from the set, in order to avoid high degree of replication.

. sh (Source Hashing): Traffic is sent to a backing server by looking up a statically assigned hash table based on the source IP addresses.

. dh  (Destination Hashing): Traffic is sent to a backing server by looking up a statically assigned hash table based on their destination addresses.

. sed (Shortest Expected Delay): Traffic forwarded to a backing server with the shortest expected delay. The expected delay is 
  (C + 1) / U if sent to a server, where C is the number of connections on the server and U is the fixed service rate (weight) of the
  server.

. nq (Never Queue): Traffic is sent to an idle server if there is one, instead of waiting for a fast one; if all servers are busy, the algorithm falls back to the sed behavior. 


 [ 중요 - 참고 ]

To run kube-proxy in IPVS mode, you must make IPVS available on the node before starting kube-proxy.

When kube-proxy starts in IPVS proxy mode, it verifies whether IPVS kernel modules are available. If the IPVS kernel modules are not detected, then kube-proxy exits with an error.

 

☞ Netfilter hook function : 6개의 Netfilter Hook Function 을 사용 → Local Hook 사용 이유는 IPVS Dummy Interface 사용하기 때문이다.

출처 : https://ssup2.github.io/theory_analysis/Linux_LVS_IPVS/

IPSET - 링크

1) iptables로 5000건 이상의 룰셋이 등록 되었을때 시스템의 성능이 급격하게 떨어지는 것을 방지하기 위해
    'IP들의 집합' 으로 관리하여 룰셋을 줄인다.

2) 아래는 ipvs proxy mode 사용 시 설정되는 기본 ipset 내용

 

2-2. IPVS 정보 확인 ( 심화 - 실습 )

더보기
# kube-proxy 로그 확인 :  기본값 부하분산 스케줄러(RoundRobin = RR)
kubectl stern -n kube-system -l k8s-app=kube-proxy --since 2h | egrep '(ipvs|IPVS)'
...
kube-proxy-2qmw7 kube-proxy I0929 07:41:23.512897       1 server_linux.go:230] "Using ipvs Proxier"
kube-proxy-2qmw7 kube-proxy I0929 07:41:23.514074       1 proxier.go:364] "IPVS scheduler not specified, use rr by default" ipFamily="IPv4"
kube-proxy-2qmw7 kube-proxy I0929 07:41:23.514157       1 proxier.go:364] "IPVS scheduler not specified, use rr by default" ipFamily="IPv6"
kube-proxy-v5lcv kube-proxy I0929 07:41:23.523480       1 proxier.go:364] "IPVS scheduler not specified, use rr by default" ipFamily="IPv6"

# 기본 모드 정보 확인
kubectl get cm -n kube-system kube-proxy -o yaml | egrep 'mode|strictARP|scheduler'
      scheduler: ""
      strictARP: true
    mode: ipvs

# ipvsadm 툴로 부하분산 되는 정보 확인 : RR 부하분산 스케줄러 확인
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ipvsadm -Ln ; echo; done
...
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.200.1.1:443 rr
  -> 172.18.0.2:6443              Masq    1      3          0    
...

# 커널 파라미터 확인
# (심화 옵션) strictARP - 링크 설정(유사한)이유
# --ipvs-strict-arp : Enable strict ARP by setting arp_ignore to 1 and arp_announce to 2
# arp_ignore : ARP request 를 받았을때 응답 여부 - 0(ARP 요청 도착시, any Interface 있으면 응답), 1(ARP 요청을 받은 Interface 가 해당 IP일때만 응답)
# arp_announce : ARP request 를 보낼 때 'ARP Sender IP 주소'에 지정 값 - 0(sender IP로 시스템의 any IP 가능), 2(sender IP로 실제 전송하는 Interface 에 IP를 사용)
docker exec -it myk8s-worker tree /proc/sys/net/ipv4/conf/kube-ipvs0
docker exec -it myk8s-worker cat /proc/sys/net/ipv4/conf/kube-ipvs0/arp_ignore
docker exec -it myk8s-worker cat /proc/sys/net/ipv4/conf/kube-ipvs0/arp_announce

# all 은 모든 인터페이스에 영항을 줌, 단 all 과 interface 값이 다를때 우선순위는 커널 파라미터 별로 다르다 - 링크
docker exec -it myk8s-worker sysctl net.ipv4.conf.all.arp_ignore
docker exec -it myk8s-worker sysctl net.ipv4.conf.all.arp_announce
docker exec -it myk8s-worker sysctl net.ipv4.conf.kube-ipvs0.arp_ignore
docker exec -it myk8s-worker sysctl net.ipv4.conf.kube-ipvs0.arp_announce
docker exec -it myk8s-worker sysctl -a | grep arp_ignore
docker exec -it myk8s-worker sysctl -a | grep arp_announce

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

 

[ 실행 결과 - 한 눈에 보기 ]

▼ 목적지(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

 목적지(backend) 파드(Pod) 생성 : 3pod.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"

# 확인
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

# 노드 별 네트워트 정보 확인 : 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

# 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


# 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

 

[ 실행 결과 - 한 눈에 보기 ]

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

# 변수 지정
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

# 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

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

 

[ 실행 결과 - 한 눈에 보기 ]

★ 실습 완료 후, 환경 삭제 : AWS EC2에서 CloudFormation 으로 환경 구성 했을 경우, 모든 실습 완료 후에는 꼭 삭제 확인 해주시기 바랍니다.

# CloudFormation 스택 삭제
aws cloudformation delete-stack --stack-name mylab

# [모니터링] CloudFormation 스택 상태 : 삭제 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

 


3. LoxiLB 

 [ 개요 정리 - 특장점 ]

 - eBPF 핵심 엔진으로 사용하고,  GO language를 기반으로 개발된 클라우드 네이티브 로드 밸런서

 - Kubernetes 기반의 워크로드를 위한 오픈소스 기반 external 로드 밸런서 솔루션이다.

 - LoxiLB는 초저지연 포워딩과 Programmability 가 가능한 Kubernetes 표준 기반의 로드 벨런싱 서비스를 제공한다.

 - 배포, 부트스트래핑, 구성, 프로비저닝, 확장, 업그레이드, 마이그레이션, 라우팅, 모니터링 및 리소스 관리와 같은

   외부 로드밸런서 관리 작업을 자동화 한다.

 - 기본적으로 온프레미스 및 에지 클라우드 환경을 지원하도록 설계되었지만 모든 클라우드 환경에서 동일하게

   실행 가능합니다.

 

https://ko.loxilb.io/

 

[ 추천 영상 -  Link : https://youtu.be/MleGRc7Rzu4?si=ea7ekhlwmOFn88C6 ]

 

[ More ... ]

더보기

1. eBPF 란?

 - extended Berkley Packet Filter 의 준말로, Kernel level에서 Byte Code로 동작하는 경량화된 VM을 지칭함

 

2. XDP 란?

 - eXpress Data Path의 준말로, Driver Space를 로드하여 커널의 고성능 패킷 처리를 가능하도록 하는 기술

 

3. LoxiLB

 - eBPF/XDP + smartNIC 레벨까지 네트워크 오프로딩을 통해 초고속 패킷 처리를 제공함

 

[ 출처 : Loxi LB 소개 https://youtu.be/MleGRc7Rzu4?si=ea7ekhlwmOFn88C6 ]

 

[ LoxiLB 동작모드 ]
[ k8s 환경에서 API call에 대한 traffic 을 loxiLB가 받아서, loxiLB kernel 단으로 내려서 처리 ]
[ layer-2 offloading 으로 고속 네트워킹 Traffic 처리제공 ]
[ Loxi Flow Expector 통해서 Traffic Observability 제공 ]
[ SCTP 기반 서비스 차별화 - 5G core ~ Edge cluster 서비스 제공 최적화 ]
[ Multi-Cluster - Global LB 필요한 환경 가정 : WRR 기반 개발 ~ 운영 점진적 이관 개발 중 ]
[ 외부에서 인입된 Traffic / Calico 기반의 BGP Peering 기본 / LoxiLB가 중계역할 제공 ]

[ 마무리 ]

 - 이번 주도 Topic은 3-꼭지 인데, 안에 들어가 보니 따라가야 할 내용이 너무 너무 많아서 ㅜㅜ  눈물이 앞을 가리는 시간 이었습니다.

   그래도, 가시다님의 열강에 힘입어 다시금 따라가려고 노력해 보았는데, 쉽지는 않군요..

   실습 따라하며 IPVS 로드밸런싱 로직의 우수함(?)을 몸소 느껴 보는 시간이었던 것 같습니다.

   추후 여력이 된다면 LOXILB에 대해서도 deep-dive 해 보아야 겠다는 생각이 듭니다.


[ 도움이 되는 링크모음 ]