⭐ 가시다(gasida) 님이 진행하는 KANS(**K**ubernetes **A**dvanced **N**etworking **S**tudy)3기 실습 게시글입니다.
게시글 상 소스코드, 사진에서 ****굵게**** '''코드쉘''' 에 대한 부분이 들어가있을수도 있습니다.
⭐코드에서 실습 시 사용하는 변수가 들어갈 수 있습니다. 아래의 정보를 사용하고있습니다.
#route53 Domain: jjongguet.com
#AWS Secret Key: jjongkey
#Aws .pem Key: jjong-key.pem
개발 실습환경
#
cat <<EOT> kind-svc-2w.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
"InPlacePodVerticalScaling": true #실행 중인 파드의 리소스 요청 및 제한을 변경할 수 있게 합니다.
"MultiCIDRServiceAllocator": true #서비스에 대해 여러 CIDR 블록을 사용할 수 있게 합니다.
nodes:
- role: control-plane
labels:
mynode: control-plane
topology.kubernetes.io/zone: ap-northeast-2a
extraPortMappings: #컨테이너 포트를 호스트 포트에 매핑하여 클러스터 외부에서 서비스에 접근할 수 있도록 합니다.
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- containerPort: 30004
hostPort: 30004
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs: #API 서버에 추가 인수를 제공
runtime-config: api/all=true #모든 API 버전을 활성화
controllerManager:
extraArgs:
bind-address: 0.0.0.0
etcd:
local:
extraArgs:
listen-metrics-urls: http://0.0.0.0:2381
scheduler:
extraArgs:
bind-address: 0.0.0.0
- |
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0
- role: worker
labels:
mynode: worker1
topology.kubernetes.io/zone: ap-northeast-2a
- role: worker
labels:
mynode: worker2
topology.kubernetes.io/zone: ap-northeast-2b
- role: worker
labels:
mynode: worker3
topology.kubernetes.io/zone: ap-northeast-2c
networking:
podSubnet: 10.10.0.0/16 #파드 IP를 위한 CIDR 범위를 정의합니다. 파드는 이 범위에서 IP를 할당받습니다.
serviceSubnet: 10.200.1.0/24 #서비스 IP를 위한 CIDR 범위를 정의합니다. 서비스는 이 범위에서 IP를 할당받습니다.
EOT
# k8s 클러스터 설치
kind create cluster --config kind-svc-2w.yaml --name myk8s --image kindest/node:v1.31.0
docker ps
# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping git vim arp-scan -y'
for i in worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping -y'; echo; done
# 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"
ARP SCAN 이후에 mypc 배포하여 정해진 ip대역(172.18.0.200) 에 배포된 모습
kube-ops-view 수정해서 배포된 NODE 가 변경되는 모습
LoadBalancer (쿠버네티스 컴포넌트) 소개
docs: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer
- Exposes the Service externally using an external load balancer: 외부 로드밸런서(쿠버네티스 컴포넌트 외부를 뜻함) 를 통해 외부로 서비스를 노출시킬 수 있다.
- Kubernetes does not directly offer a load balancing component: 쿠버네티스는 직접적으로 로드밸런서 컴포넌트를 제공하고있지않다.
- Disabling load balancer NodePort allocation : k8s v1.24[stable] - Link: 노드포트 할당을 취소할수있다.
서비스(LoadBalancer) 부족한 점(퍼블릭클라우드 환경)
- 서비스(LoadBalancer) 생성 시 마다 LB(예 AWS NLB)가 생성되어 자원 활용이 비효율적임 ⇒ HTTP 경우 인그레스(Ingress) 를 통해 자원 활용 효율화 가능!
- 서비스(LoadBalancer)는 HTTP/HTTPS 처리에 일부 부족함(TLS 종료, 도메인 기반 라우팅 등) ⇒ 인그레스(Ingress) 를 통해 기능 동작 가능!
- (참고) 온프레미스 환경에서 제공 불가능??? ⇒ MetalLB 혹은 OpenELB(구 PorterLB) 를 통해서 온프레미스 환경에서 서비스(LoadBalancer) 기능 동작 가능!
MetalLB요약
MetalLB Docs : MetalLB is a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols - Link
- MetalLB 는 BareMetal LoadBalancer 에서 이름을 따옴
MetalLB 의 궁극적인 목적: 외부 클라이언트에서 Pod 로 접근해야하는데 ‘어떻게 효율적으로 접근하게 할수 있을까’ 에 대한 부분임
MetalLB - Layer2 모드
MetalLB 는 daemonset 형태로 배포되기떄문에, 모든 노드에 MetalLB 의 파드가 생성된다.
MetalLB파드는 서로 갖고있는 정보를 다른노드에 전파하는 방식을 사용하고있는데, 이러한 특성떄문에 speaker파드라고 부른다.
각각의 서비스마다 스피커 파드 중 리더가 선출되고, 이를 리더스피커파드라고 부른다.
리더스피커파드가 존재한 노드로만 트래픽을 받게되어, 노드의 iptables 정책에 따라 분산되어 파드에 접근하게된다.
- 리더 파드가 선출되고 해당 리더 파드가 생성된 노드로만 트래픽이 인입되어 해당 노드에서 iptables 분산되어 파드로 접속
- 권장 사용 환경 : 테스트 및 소규모의 환경(동일 네트워크 1개 사용)에서 클라이언트 IP 보존이 필요 없을 때
⇒ Leader Speaker 파드에 접근 → iptables 에 따라 분배 하는 프로세스로 진행됨
MetalLB - BGP 모드
라우터에 BGP 로 Service External IP 를 전파한다
- speaker 파드가 BGP로 서비스 정보(EXTERNAL-IP)를 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속
- 권장 사용 환경 : 규모가 있고, 클라이언트 IP보존과 장애 시 빠른 절체가 필요하며, 네트워크 팀 협조가 가능할때
MetalLB 소개
1.2 Layer 2 모드 (ARP/NDP) - 링크
Layer2 모드에서 서비스 접속
MetalLB 컨트롤러 파드는 그림에서 생략하였습니다
모드 소개
: 리더 파드가 선출되고 해당 리더 파드가 생성된 노드로만 트래픽이 인입되어 해당 노드에서 iptables 분산되어 파드로 접속
- 서비스(로드밸런서) 'External IP' 생성 시 speaker 파드 중 1개가 리더가 되고, 리더 speaker 파드가 존재하는 노드로 서비스 접속 트래픽이 인입되게 됩니다
- 데몬셋으로 배포된 speaker 파드는 호스트 네트워크를 사용합니다 ⇒ "NetworkMode": "host"
- 리더는 ARP(GARP, Gratuitous APR)로 해당 'External IP' 에 대해서 자신이 소유(?)라며 동일 네트워크에 전파를 합니다
- 만약 리더(노드)가 장애 발생 시 자동으로 나머지 speaker 파드 중 1개가 리더가 됩니다.
- 멤버 리스터 및 장애 발견은 hashicorp 의 memberlist 를 사용 - Gossip based membership and failure detection
- Layer 2에서 멤버 발견 및 자동 절체에 Keepalived(VRRP)도 있지만 사용하지 않은 이유 - 링크
제한 사항
: single-node bottlenecking, potentially slow failover
- single-node bottlenecking : 서비스 1개 생성 사용 시, 모든 서비스 접근 트래픽은 리더 파드가 존재하는 노드로만 인입되어서 부하가 집중
- ⇒ 공식 가이드는 아니지만, 외부 라우터에서 접근 시 ECMP 로 부하 분산이 가능 - 아래 2.6 참고
- potentially slow failover : 리더(노드)가 장애 시 나머지 노드 리더가 선출되고, ARP 전파 및 갱신완료 전까지는 장애가 발생됨 (대략 10초~20초 정도)
- ⇒ 공식 가이드는 아니지만, 외부 라우터에서 노드(파드) 장애 헬스 체크로 빠르게 절체 가능 - 아래 2.6 참고
1.3 BGP 모드 - 링크
BGP 모드에서 서비스 접속
모드 소개
: speaker 파드가 BGP로 서비스 정보(EXTERNAL-IP)를 전파 후, 외부에서 라우터를 통해 ECMP 라우팅으로 부하 분산 접속
- speaker 파드에 BGP 가 동작하여 서비스 정보(EXTERNAL-IP)를 전파한다
- 기본은 IP주소(32bit)를 전파하며, 설정으로 축약된 네트워크 정보를 전파할 수 있다 → bgp-advertisements 에 aggregation-length 설정
- BGP 커뮤니티, localpref 등 BGP 관련 설정을 할 수 있다
- IP 주소 마지막이 0 과 255 를 처리를 못하는 라우터 장비가 있을 경우 avoid-buggy-ips: true 옵션으로 할당되지 않게 할 수 있다
- 외부 클라이언트에서 SVC(서비스, EXTERNAL-IP)로 접속이 가능하며, 라우터에서 ECMP 라우팅을 통해 부하 분산 접속 할 수 있다
- 일반적으로 ECMP 는 5-tuple(프로토콜, 출발지IP, 목적지IP, 출발지Port, 목적지Port) 기준으로 동작합니다.
- 물론 라우터 장비에 따라 다양한 라우팅(분산) 처리가 가능합니다
제한 사항
: 라우터에서 서비스로 인입이 되기 때문에, 라우터의 관련 설정이 중요한 만큼 네트워크팀과 협업을 적극 권장
- 노드(speaker) 파드 장애 시 BGP Timer 설정 등 구성하고 있는 네트워크 환경에 맞게 최적화 작업이 필요
- ECMP 부하 분산 접속 시 특정 파드에 몰리거나 혹은 세션 고정, flapping 등 다양한 환경에 대응이 필요
- BGP 라우팅 설정 및 라우팅 전파 관련 최적화 설정이 필요
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:'
ControlPlane 노드에서 ping, curl 명령어가 접근가능한지 확인
정상작동 확인
2. MetalLB - Layer 2 모드
2.1 MetalLB 설치 - 링크
- 설치 방법 지원 : Kubernetes manifests, using Kustomize, or using Helm
- 참고 : kube-proxy 의 ipvs 모드 사용 시 'strictARP: true' 설정 필요
https://kschoi728.tistory.com/264
간단하게 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**
- 컨피그맵 생성 : 모드 및 서비스 대역 지정
- 서비스(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 대역
## MetalLB는 서비스를 위한 외부 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 사용 허용
## Kubernetes 클러스터 내의 서비스가 외부 네트워크에 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"]*
ipaddresspool 배포된 상태
L2advertisement 배포된 상태
- (참고) 로그 확인 : 아래 로그 -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 # 매칭 사용 가능
2.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-speark 에서 Accounce 되었다는걸 확인가능
# 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** 툴 사용
## Unicast reply from 172.18.255.200: 해당 IP 주소에서 응답을 받았음을 의미합니다.
## Sent 1 probes (1 broadcast(s)): 하나의 ARP 요청을 보냈고, 브로드캐스트 방식으로 요청을 전송했음을 나타냅니다.
## Received 1 response(s): 하나의 응답을 수신했음을 나타냅니다.
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 # 매칭 사용 가능
netshoot 이미지에는 다양한 네트워크 툴이 설치되어있는데, arping 라는 툴을 사용해서 arp 통신을 진행하는게 가능함. mypc(외부클라이언트) 에서 service external endpoint 로 접근하는거 확인가능
packet 은 Loss 되지만 mypc의 arptable 이 동적으로 갱신되었는데
버츄얼아이피(172.18.255.200) 와 통신을 하려면 맥주소(02:42:ac:12:00:04) 를 소유하고있는 노드인 아이피주소(172.18.0.4) 인 노드(myk8s-worker)
이 정보로 알 수 있는 점
⇒ 무조건 특정 서비스는 특정 노드로 가게된다. 병목이 생겨도 특정 트래픽은 특정 노드로만 향한다
2.3 서비스 접속 테스트
클라이언트(mypc, mypc2) → 서비스(External-IP) 접속 테스트
# 현재 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 # NodePort Type
docker exec -it myk8s-control-plane curl -s 10.200.1.82 # ClusterIP Tpye
- 클라이언트 → 서비스(External-IP) 접속 시 : 리더 파드가 존재하는 노드 인입 후 Service 에 매칭된 iptables rules 에 따라 랜덤 부하 분산되어서(SNAT) 파드로 접속!
- 클라이언트의 IP는 리더 파드가 존재하는 노드의 IP로 SNAT 되어 목적지 파드로 접속됨
외부에서 주소만 사용해서 접근가능한것 성공
ControlPlane 에서 svc1의 주소(NodePort, ClusterIP)로 접근하는거 확인가능!