이 스터디는 CloudNet@에서 진행하는 KANS 스터디를 참여하면서 공부하는 내용을 기록하는 블로그 포스팅입니다.
CloudNet@에서 제공해주는 자료들을 바탕으로 작성되었습니다.
Service
Service에 관한 전반적인 내용은 앞 내용에서 다뤘으니 참고바랍니다.
LoadBalancer
LoadBalancer(줄여서 LB)는 Public Cloud 환경에서 동작하는 LB Service와 On-premise 환경에서의 LB Service가 있습니다. 동일한 LB이지만 동작 방식에 차이가 있습니다. Public Cloud 사업자마다 LB의 동작에도 차이가 있으며, On-Premise 환경에서 동작하는 LB도 하드웨어 장비와 소프트웨어 기반으로 구분되고, LB 개발사에 따라 차이도 있습니다.
Cloud 환경에서의 LoadBalancer
가장 많이 사용되는 public cloud 사업자인 AWS의 AWS LoadBalancer에 대해 알아보겠습니다.
AWS에는 AWS 관리형 LoadBalancer인 ELB(ElasticLoadBalancer)가 있고, CLB(ClassicLoadBalancer), NLB(NetworkLoadBalancer), ALB(ApplicationLoadBalancer) 3가지의 LoadBalancer가 있습니다.
CLB(Classic LoadBalancer)
CLB는 클래식 로드밸런서로, NLB/ALB에 비해서 가장 적은 기능을 제공하며 가장 오래된 로그밸런서입니다.
NLB(Network LoadBalancer)
NLB는 네트워크 로드밸런서로 CLB/ALB에 비해 처리 속도가 굉장히 빠르고, Layer4 계층에서 동작하며 TCP/UDP/TLS 트래픽을 처리할 수 있습니다.
ALB(Application LoadBalancer)
ALB는 애플리케이션 로드밸런서로 HTTP/HTTPS/gRPC 트래픽을 전문으로 하며, Layer 7계층에서 동작합니다.
Kubernetes에서 LoadBalancer 서비스 리소스 생성 시 AWS CLB나 AWS NLB로 생성되며 Node에 있는 Pod로 트래픽이 부하분산되게 됩니다. AWS ALB는 Kubernetes에서 Ingress 리소스 생성 시 사용됩니다.
LB <-> Pod 통신 방법
Kubernetes에서 LoadBalancer Service 리소스 생성 시 AWS CLB나 NLB는 어떻게 Pod와 통신하는지 알아보겠습니다.
외부 -> LB -> Node -> Pod
외부 클라이언트가 LoadBalancer로 접근을 하고, LoadBalancer는 Pod가 있는 Node들로 부하분산하게 됩니다. 이때 Node의 NodePort를 목적지 Port로 전달하게 됩니다. Node의 NodePort로 진입한 후에는 iptables에 의해서 Pod로 랜덤 부하분산을 통해서 전달하게 됩니다. 즉, 부하 분산 과정이 2번 진행되게 됩니다.
외부에서 LoadBalancer에서 부하분산 처리 후 Node에게 전달하는 기본 과정은 Service의 NodePort Type과 동일합니다. 하지만, Node의 정보는 외부에 공개되지 않고 LB의 정보만 외부에 공개되며, 외부 클라이언트는 LB에 접속을 할 뿐 내부 노드의 정보를 알 수 없습니다.(보안에 유리하겠죠?!) 그 후 LB가 부하분산하여 Pod가 존재하는 Node들에게 트래픽을 전달하며, iptables 룰에서는 자신의 Node에 있는 Pod만 연결합니다.
전체적인 과정에서 DNAT이 2번 동작하게 되는데, 첫번째는 LB 접속 후 빠져 나갈 때, 두번째는 Node의 iptables 룰에서 Pod IP로 전달 시에 동작합니다. 이때, AWS NLB는 target이 instance일 경우 클라이언트 IP를 유지하고, iptables 룰일 경우 externalTrafficPolicy로 클라이언트 IP를 보존하게 됩니다. (Kubernetes는 Service(LB Type) API 만 정의하고 실제 구현은 add-on에 맡깁니다.)
즉, 외부 클라이언트가 LoadBalancer 접속 시 부하분산 되어 노드 도달 후 iptables 룰로 목적지 파드와 통신이 됩니다.
추가로, AWS ELB에서는 부하분산최적화 방법이 있는데, Node에 Pod가 없을 경우 health check(상태 검사)가 실패하여 해당 Node로는 외부 요청 트래픽을 전달하지 않습니다.
3번째 인스턴스(Node3)은 health check 실패로 외부 요청 트래픽이 전달되지 않습니다.
외부 -> LB -> PodIP
외부 클라이언트가 LoadBalancer를 통해서 접근을 하고, LoadBalancer에서 Pod의 IP로 직접 전달합니다. 이때, LB가 Pod의 IP를 알기 위해서 별도의 LB Controller를 구성하고, 해당 LB Controller가 LB에게 Pod의 IP를 동적으로 전달합니다. 부하 분산과정이 한번만 진행되기 때문에 효율적입니다.
On-Premise 환경에서의 LoadBalancer
On-Premise 환경에서 동작하는 하드웨어 기반으로 확인해보겠습니다.
Cloud 환경에서의 LB와 거의 비슷하게 별도의 장비로 접속 후 Node에 NodePort 혹은 Pod로 직접 전달하여 통신을 합니다. On-Premise 환경에서 동작하는 LB 서비스도 소프트웨어 기반의 동작은 별도의 네트워크 장비 없이 소프트웨어로 동작하며, 대표적으로 MetalLB, OpenELB, PubeLB, kube-vip 등이 있습니다.
MetalLB
MetalLB는 BareMetalLoadBalancer의 줄임말로 On-premise 환경에서 표준 프로토콜을 사용해서 LoadBalancer 서비스를 구현해주는 일종의 프로그램입니다.
동작 방식은 Kubernetes의 DaemonSet으로 Speaker Pod를 생성하여, External IP를 전파하고, Node의 IP 대신 External IP를 통해 외부에서 접속합니다. 이때, Speaker Pod는 External IP 전파를 위해서 표준 프로토콜인 ARP(Address Resolution Protocol) 혹은 BGP를 사용하며, MetalLB는 ARP를 사용하는 Layer2 mode와 BGP를 사용하는 BGP 모드 중 하나를 선택하여 동작합니다.
ARP(Address Resolution Protocol)
ARP는 네트워크 내에서 IP 주소를 물리적 MAC(Media Access Control) 주소로 변환하는 프로토콜입니다.
LAN(Local Area Network)에서 IP 패킷을 전달할 때, IP 주소는 네트워크 상의 논리적인 주소이지만, 실제로 데이터를 전송하기 위해서는 네트워크 카드의 고유한 MAC 주소가 필요합니다. ARP는 이를 해결해 줍니다.
BGP (Border Gateway Protocol)
BGP는 인터넷 상에서 최적의 경로로 트래픽을 전달하기 위한 라우팅 프로토콜입니다. 주로 인터넷 서비스 제공업체(ISP)들이 서로 데이터를 교환하기 위해 사용합니다.
MetalLB는 대부분의 Public Cloud의 환경에서는 동작하지 않습니다. 왜냐하면, 가상 서버의 IP에 매칭되는 MAC 주소가 아닌 IP에 대한 ARP요청을 차단하기 때문입니다. 그렇기 때문에 MetalLB 사용 시 External IP는 Public Cloud 사업자가 할당한 IP가 아니므로 External IP에 대한 요청이 차단되어 MetalLB의 동작이 불가능해집니다. 또한, 일부 CNI는 동작에 이슈가 있는데, Calico의 IPIP모드에 BGP를 사용하면서 MetalLB BGP 모드를 사용하면 BGP 동작에 충돌로 문제가 발생할 수도 있습니다.
MetalLB - Layer2 mode
(1) 리더 Speaker Pod 선출
LoadBalancer 서비스 리소스 생성 시 Metal LB Speaker Pod 중 리더를 선출합니다. 리더 Speaker Pod는 해당 LB 서비스의 External IP를 전파합니다. 이때, Layer2 모드이기 때문에 ARP 메시지로 동일 네트워크 대역 내에 브로드캐스트 주소로 전파합니다.
만약, 리더 선출 과정에서 장애가 발생 했을 경우 나머지 Speaker Pod들 중에서 다시 리더 Speaker Pod를 선출합니다.
- SVC1 서비스의 리더 Speaker Pod : Node1에 있는 Speaker Pod
- SVC2 서비스의 리더 Speaker Pod : Node3에 있는 Speaker Pod
(2) 외부 트래픽 유입
클라이언트 1은 SVC1 External IP로 접속을 시도합니다. 해당 트래픽은 SVC1 External IP정보를 전파하는 리더 Speaker Pod가 있는 Node1로 유입되게 됩니다. 마찬가지로 클라이언트2는 SVC2 External IP 정보를 전파하는 리더 Speaker Pod가 있는 Node3에 유입되게 됩니다.
(3) Node의 IPTABLES 규칙
Node에 유입된 후 IPTABLES 규칙에 의해 ClusterIP와 동일하게 해당 Service Endpoint에 따라 Pod를 찾습니다.
(4) 랜덤 부하분산
즉, 리더 파드가 선출되고 해당 리더 파드가 생성된 Node로만 트래픽이 유입되며 해당 Node에서 IPTABLES 규칙에 의해 부하 분산되어 Pod에 접속됩니다.
MetalLB 실습
실습 환경 구성
kind cluster 배포
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 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'
실습을 원활히 하기 위해 여러 tool을 설치하겠습니다.
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}}'
노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath="{.items[*].spec.podCIDR}"
kube-proxy 모드 확인 : iptables proxy 모드
kubectl describe configmap -n kube-system kube-proxy | grep mode
Pod 생성
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 -o wide
파드 IP주소를 변수 지정
WPOD1=$(kubectl get pod webpod1 -o jsonpath="{.status.podIP}")
WPOD2=$(kubectl get pod webpod2 -o jsonpath="{.status.podIP}")
변수 확인
echo $WPOD1 $WPOD2
Pod 접속 확인
docker exec -it myk8s-control-plane ping -i 1 -W 1 -c 1 $WPOD1
docker exec -it myk8s-control-plane curl -s --connect-timeout 1 $WPOD1 | egrep 'Hostname|RemoteAddr|Host:'
MetalLB 설치
Kubernetes manifests 로 설치
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml
matalLB crd 확인
kubectl get crd | grep metallb
생성된 리소스 확인
kubectl get all,configmap,secret,ep -n metallb-system
metallb-system 네임스페이스 생성, 파드(컨트롤러, 스피커) 생성, RBAC(서비스/파드/컨피그맵 조회 등등 권한들), SA 등을 확인해보겠습니다.
kubectl get ds,deploy -n metallb-system
metallb 컨트롤러는 디플로이먼트로 배포되기 때문에 따로 확인해보겠습니다.
kubectl get pod -n metallb-system -o wide
daemonset으로 배포되는 metallb speaker pod의 IP는 네트워크가 host 모드이므로 node의 IP를 그대로 사용합니다.
IPAddressPool 생성
kubectl explain ipaddresspools.metallb.io
LoadBalancer External IP로 사용할 IP 대역을 확인해보겠습니다.
IPAddressPools는 MetalLB에서 IP 주소를 관리하는 개념으로, Kubernetes 클러스터에서 외부에서 접근할 수 있는 IP 주소들을 정의하고 할당하는 리소스입니다. IPAddressPool은 여러 개의 IP 주소 범위를 묶어 관리할 수 있는 방식이며, 이를 통해 특정 서비스나 로드 밸런서가 사용할 수 있는 IP 주소들을 지정할 수 있습니다
즉, IPAddressPool을 통해 MetalLB는 Service를 위한 외부 IP 주소를 관리하고, Service가 생성될 때 해당 IP 주소를 동적으로 할당할 수 있습니다.
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
L2Advertisement 생성
kubectl explain l2advertisements.metallb.io
설정한 IPpool을 기반으로 Layer2 모드로 LoadBalancer IP 사용 허용하도록 설정하기 위해 l2advertisements를 확인 후 생성하도록 하겠습니다.
L2Advertisement는 네트워크에서 2계층(Layer 2) 방식으로 IP 주소를 알리는 기능입니다. L2Advertisement는 네트워크에서 IP 주소를 2계층(데이터 링크 계층)에서 브로드캐스트(광역 송신)하여 같은 로컬 네트워크에 있는 다른 장치들이 이 IP를 통해 통신할 수 있게 합니다.
MetalLB에서 L2Advertisement는 IP Pool에 있는 IP 주소가 네트워크에서 어떻게 알림(광역 송신)될지를 설정하는 리소스입니다. 예를 들어, 특정 서비스에 외부 IP를 할당하고 그 IP를 MetalLB가 같은 네트워크 상의 다른 노드들에게 광고하는 방식으로 사용됩니다. 이를 통해 클러스터 내에서 IP 주소를 특정 노드나 네트워크 인터페이스에서만 광고할 수 있게 설정할 수 있습니다
즉, L2advertisement는 Kubernetes 클러스터 내의 서비스가 외부 네트워크에 IP 주소를 광고하는 방식을 정의합니다.
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: my-l2-advertise
namespace: metallb-system
spec:
ipAddressPools:
- my-ippool
EOF
서비스 생성 및 확인
서비스 생성
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
LoadBalancer 타입의 서비스 생성 확인
kubectl get service,ep
EXTERNAL-IP가 Service 마다 할당되며, 실습 환경에 따라 다를 수 있습니다. 이때, LoadBalancer 타입의 Service는 NodePort와 ClusterIP 를 포함하고, External IP 로 접속 시 사용하는 포트는 PORT(S) 의 앞에 있는 값을 사용합니다. 만약 Node의 IP에 NodePort 로 접속 시 사용하는 포트는 PORT(S) 의 뒤에 있는 값을 사용하게 됩니다.
해당 Service의 External IP에 리더 Speaker Pod가 어떤 노드에 있는지 확인
kubectl describe svc | grep Events: -A5
서비스 접속 테스트
현재 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}')
원활한 실습을 위해 service의 external IP를 변수로 지정합니다.
mypc/mypc2 에서 접속 테스트
for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do echo ">> Access Service External-IP : $i <<" ; docker exec -it mypc curl -s $i | grep Hostname ; echo ; done
mypc/mypc2 에서 접속 테스트 - 부하분산
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $SVC1EXIP | grep Hostname; done | sort | uniq -c | sort -nr"
즉, 클라이언트에서 서비스(External-IP) 접속 시 리더 Speaker Pod가 존재하는 Node 인입 후 Service 에 매칭된 iptables rules 에 따라 랜덤 부하 분산되어서(SNAT) Pod로 접속합니다.
Failover
장애 발생 전 worker node1의 speaker pod가 svc1, svc3의 리더 speaker pod 역할을 하고 있는 상태에서 worker node1에 장애가 발생하게 되면 speaker pod들은 worker node1의 장애를 인지하게 됩니다. 이후 장애가 발생한 speaker pod가 소유한 service의 External IP에 대한 리더 Pod를 다시 선출하게 됩니다. 이때, 장애 발생 시 이를 확인하는 시간과 ARP 정보가 전파되는 시간이 필요하여 대략 20초~1분의 장애 지속 시간이 발생하게 됩니다.
'스터디 이야기 > Kubernetes Advanced Networking Study' 카테고리의 다른 글
[KANS] Gateway API (1) | 2024.10.12 |
---|---|
[KANS] Ingress (2) | 2024.10.09 |
[KANS] Service - ClusterIP, NodePort (1) | 2024.09.28 |
[KANS] Calico CNI (1) | 2024.09.19 |