Engineering/Kubernetes (K8S)

[Kubernetes] 쿠버네티스 서비스(Service) Deep Dive - (5) LoadBalancer

[앙금빵] 2022. 6. 17.

 

리마인드 - 서비스 타입

서비스에는 여러 종류가 있으나, 주로 사용하는 타입은 3가지이다.

 

① Cluster IP 타입
쿠버네티스 내부에서만 포드에서 접근할 때 사용한다. 외부로 Pod를 노출하지 않기 때문에 쿠버네티스 클러스터 내부에서만 사용되는 Pod에 적합하다.

② NodePort 타입
Pod에 접근할 수 있는 포트를 Cluster의 모든 Node에 동일하게 개방한다. 따라서 외부에 Pod에 접근할 수 있는 서비스 타입이다. 접근할 수 있는 포트는 랜덤으로 정해지지만 특정 포트로 접근하도록 설정할 수도 있다.

③ LoadBalancer 타입
클라우드 플랫폼에서 제공하는 Load Balancer를 동적으로 Provisioning하여 포드에 연결한다. NodePort 타입과 마찬가지로 외부에서 포드에 접근할 수 있는 서비스 타입이다. 일반적으로 AWS, GCP 등과 같은 클라우드 플랫폼 환경에서만 이용할 수 있다.


서비스 - 로드밸런서 특징

앞단에서 모든 노드에 특정 포트를 할당하는 특징을 지닌 노드포트에 대한 서비스를 알아보았다. 쿠버네티스가 제공하는 다른 서비스 유형으로는 로드밸런서타입의 서비스 유형을 제공한다. 로드밸런서는 노드 앞단에 배치되며 각 노드로 트래픽을 분산시킬 수 있는 특징을 지닌다.

 

모든 쿠버네티스 클러스터에서 로드밸런서 서비스 타입을 지원하지 않는다. 그러나, 클라우드 환경에서 클러스터를 운영중이라면 대부분 제공한다.  

 

로드밸런서를 통해 얻는 이점  

외부 서버에 직접적으로 포트 번호 공개할 필요 없이 직접 접근을 하지 않아도 로드밸런서의 엔드포인트를 통해 어플리케이션에 접근할 수 있다.

 

첫째, 안정적인 서비스 끝점(엔드포인트) 제공

쿠버네티스는 각 노드의 생성 및 삭제가 자유롭다. 그때마다 노드 IP를 추적하는 일은 비효율적이며, 안정적인 서비스 Endpoint를 제공을 필요로 한다. ClusterIP 타입의 서비스가 Pod 레벨에서의 안정적인 엔드포인트를 제공하는 것이라면,  로드밸런서는 노드레벨에서의 안정적인 엔드포인트를 제공한다.

 

로드밸런서가 클러스터 서버 앞단에 존재함으로서 클라이언트 입장에서 각각의 서버 IP(master, worker)를 알 필요 없이 로드밸런서의 IP 또는 도메인 주소만 가지고 요청을 보낼 수 있는 편리성을 제공한다.

 

둘째, 정상적인 노드 대상으로 트래픽 라우팅

직접적으로 특정 노드로 요청을 보내는 대신, 로드밸런서를 통해 사용가능한(Available) 노드 대상으로 트래픽을 전달한다. 또한, 로드밸런서는 모든 노드에 균등하게 트래픽을 전달하는 특징을 지닌다.

 


 

로드밸런서 실습

svc.kiada.loadbalancer.yaml

apiVersion: v1
kind: Service
metadata:
  name: kiada
spec:
  type: LoadBalancer
  selector:
    app: kiada
  ports:
  - name: http
    port: 80
    nodePort: 30080 # 쿠버네티스에서 자동으로 할당되지 않도록 직접 지정
    targetPort: 8080
  - name: https
    port: 443
    nodePort: 30443
    targetPort: 8443

 

클라우드 환경이었다면 생성한 서비스의 External-IP가 자동으로 할당된다. 만약 External-IP가 보이지 않는다면 아직 IP가 프로비저닝 되어있지 않았거나, 이용하고 있는 클러스터가 LoadBalancer 타입을 지원하지 않기 때문이다.

나의 실습환경은 베어메탈(온프레미스)에서 진행하였으며, LoadBalancer 타입을 지원하지 않는다. 그렇기에, MetalLB 를 별도로 설치함으로서 로드밸런서 실습을 진행하였다.

 

베어메탈 환경에서 LB 생성 시 Pending 상태 지속

 

MetalLB 설치는 https://metallb.universe.tf/installation/ 링크를 통해 진행할 수 있다.  편의상 아래 스크립트를 참고하여 설치를 진행해보자.
(※ 참고: adresses 항목에서의 IP 대역대는 사용하는 환경에 따라 달라진다. IP 충돌이 나지 않도록 대역대를 잘 설정하자.)

#!/usr/bin/env bash
  
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml


kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.1.240-192.168.1.250 #IP 충돌되지 않게 대역대 유의
EOF

MetalLB 설치 후 External-IP 할당 확인

root@k8s-m:~/Study/tmp# curl 192.168.1.240
KUBERNETES IN ACTION DEMO APPLICATION v0.5

...중간 생략


==== REQUEST INFO
Request processed by Kiada 0.5 running in pod "kiada-001" on node "k8s-w1".
Pod hostname: kiada-001; Pod IP: 172.16.228.71; Node IP: 192.168.100.101; Client IP: ::ffff:172.16.29.0

 

외부 트래픽 정책 설정

앞 포스팅에서 Nodeport를 통해 외부 클라이언트가 서비스에 접근하는 것을 해보았다. NodePort로 구축한 경우 비록 다른 노드에서 접근하더라도 파드와 통신이 이루어 지는 것을 확인하였는데, 이는 추가적인 Network Hop이 생긴 것이므로 latency 증가요인이 된다.

#노드포트를 통한 파드 접근
root@k8s-m:~# curl 192.168.100.10:30080

... 중간내용 생략 ...

==== REQUEST INFO
Request processed by Kiada 0.5 running in pod "kiada-003" on node "k8s-w2".
Pod hostname: kiada-003; Pod IP: 172.16.46.4; Node IP: 192.168.100.102; Client IP: ::ffff:172.16.29.0

 

다른 노드로 연결을 포워딩할때 Source IP 가 클라이언트가 요청했던 Node IP로 대체되게 된다(소스 네트워크 주소 변환 SNAT). 이는 파드안에서 동작하는 어플리케이션에서는 정확하게 어디에서 요청이 오는지 확인이 어렵게 되며 클라이언트의 IP를 알아야하는 일부 어플리케이션에서 문제가 될 수 있다. 예를 들면 파드 안에 동작하는 웹서버에서 access.log에 실제 Client IP를 기록할 수 없다.

 

Node IP가 Souce IP로 변환되어야 할 이유로 원래 요청받은 노드로 패킷을 반환한 뒤, 클라이언트로 다시 패킷을 반환이 보장되어야 하기 때문이다.

 

Local External Traffic Policy 장단점

앞서 언급한 Network Hop 문제로 인한 Latency 증가와 SouceIP obfuscation(난독화) 문제를 해결하기 위해 서비스 필드 항목에 externalTrafficPolicy 를 추가함으로써 해결할 수 있다. externalTrafficPolicy를 통해 외부의 연결을 수신한 노드에서 실행중인 파드로만 외부 트래픽을 전달하도록 서비스를 구성하여 추가적인 홉을 방지할 수 있다.

 

External Traffic 정책: Cluster vs Local

아래 그림과 같이 노드 2개에 파드가 구성되어 있다고 가정을 해보자.

 

externalTrafficPolicy : Cluster

모든 노드내에 있는 전체 파드 개수에 기준으로 균등하게 트래픽을 분배한다. 총 파드 개수가 3개이므로 전체 트래픽의 각 33% 비중으로 분배되게 된다. 이는 추가적인 네트워크 홉이 요구되며 ClientIP는 obfuscate(난독화)된다.

 

externalTrafficPolicy : Local

모든 노드내에 있는 전체 노드 개수에 기준으로 1차 분류한 뒤 노드 내에 있는 파드 개수를 기준으로 2차적으로 분류하여 균등하게 트래픽을 분배한다. 총 노드 개수 2개, 총 파드 개수는 3개이므로 1차적으로 전체 트래픽의 각 50% 비중으로 분배되게 되며, 노드 B에서 동작하는 Pod는 2개이기에 50%를 2개로 균등하게 나누어 25%로 분배되게 된다. 즉, 50% / 25% 25% 비중으로 트래픽이 분배된다.

 

이 옵션 같은 경우 추가적인 네트워크 홉이 요구되지 않으며, Source IP는 ClientIP 가 된다.

 


참조

 

댓글