K8s에서 Traefik으로 gRPC 로드 밸런싱하기

K8s에서 Traefik으로 gRPC 로드 밸런싱하기

gRPC 로드 밸런싱하기?

Kubernetes에 공식블로그 중 인상적인 제목의 글이 있습니다. 바로 gRPC Load Balancing on Kubernetes without Tears글인데요. gRPC통신을 로드 밸런싱하기 위해서는 약간의 노력이 필요합니다. 우선 gRPC에 대해서 간략하게 알아보겠습니다.

gRPC란

gRPC는 꾸준히 인기를 끌고 있는 프로토콜입니다. gRPC는 다음과 같은 기능을 제공합니다.

  • 11개 언어에 대해 클라이언트 라이브러리 제공
  • Bi-directional Streaming 기능 제공
  • Binary Format을 사용하는 Protobuf를 사용하여 Text Format 기반  JSON보다 더 작은 직렬화된 데이터 크기

gRPC는 마이크로 서비스, 지점 간 실시간 통신등 다양한 시나리오에 적합한 프로토콜입니다. 하지만, Kubernetes에서 사용하기 위해서는 gRPC에 대한 이해가 필요합니다.

HTTP/2.0 vs HTTP/1.1

gRPC는 HTTP/2.0기반의 프로토콜입니다. HTTP/2.0은 HTTP/1.1과 달리 Multiplexing을 지원합니다. Multiplexing은 하나의 Connection에서 복수의 Stream을 동시에 처리하는 것을 의미합니다. Multiplexing은 HTTP Message를 독립적인 Frame으로 분해하여 보내고 수신단에서 다시 재조립합니다.

HTTP/1.1의 경우 하나의 Connection에서 하나의 Request만 처리할 수 있습니다.  따라서 하나의 Connection에서 동시에 복수의 Request를 처리하기 위해서 복수의 HTTP/1.1 Connection을 생성해야합니다.

반면에 HTTP/2.0의 경우 하나의 Connection에서 동시에 여러개의 Request를 처리할 수 있습니다. 따라서 동시에 복수의 Request가 발생해도 별도로 Connection을 생성할 필요가 없습니다.

L7 로드 밸런서 vs L4 로드 밸런서

Kubernetes의 서비스 타입인 로드 밸런서는 L4 로드 밸런서입니다. L4 로드 밸런서는 Client가 만든 Connection을 사용하여 Request를 분산시킵니다.  OSI Model을 참고하면 Network는 크게 7개의 Layer로 구성되어 있습니다. L4 로드 밸런서는 그 중 Network Layer, Transport Layer 수준에서 로드 밸런싱을 합니다. L4 로드 밸런서가 NAT(Network Address Translation)을 사용하여 Source/Destination IP를 변경합니다. 아래는 L4 로드 밸런서가 Packet(Network Layer PDU)의 Source/Destination IP를 변경을 나타낸 것입니다.

# Request
# Reference: https://levelup.gitconnected.com/l4-vs-l7-load-balancing-d2012e271f56
Source                 Dest         Source              Dest
 ----------- ---------- -------        ------- --------  --------
| Client IP | Segment  | LB IP |  --> | LB IP | Segment | Server |  
 ----------- ---------- -------        ------- --------  --------

# Response
Source                 Dest         Source              Dest
 ----------- ---------- -------        ------- --------  ---------
| Server IP | Segment  | LB IP |  --> | LB IP | Segment | ClientIP |  
 ----------- ---------- -------        ------- --------  ---------
L4 로드 밸런서가 Packet(Network Layer PDU)의 Source/Destination IP를 변경
Layer Protocal Data Unit
7 Application Data
6 Presentation Data
5 Session Data
4 Transport Segment, Datagram
3 Network Packet
2 Data Link Frame
1 Physical Bit

HTTP/1.1의 경우 L4로드 밸런서를 사용하면 다음 예시와 같이 로드 밸런싱이 진행됩니다.

L4 로드 밸런서를 사용하면 Client와 Server가 직접 Connection을 맺습니다. HTTP/1.x의 경우 복수의 요청을 처리하기 위해서 복수의 Connection을 생성합니다. Client가 보내는 요청에 대해서는 L4 로드 밸런서가 Client와 Server간에 맺어진 Connection을 사용하며 NAT을 사용하여 Request를 분산합니다.

반면에 HTTP/2.0와 L4 로드 밸런서를 사용하는 경우에는 하나의 Connection을 가지고 다수의 요청을 보내기 때문에 L4 로드 밸런서로는 로드 밸런싱이 되지 않습니다.

L4 로드 밸런서를 사용하면 Client와 Server가 직접 Connection을 맺습니다. HTTP/2.0의 경우 복수의 요청을 처리하기 위해서 Connection이 생성되지 않습니다. 따라서 Connection은 Client와 Server사이에서만 생성됩니다. L4 로드 밸런서는 최초로 Connection을 맺은 Server Pod1에만 Request를 보내고 나머지 Server Pod2에는 Request를 전달하지 않습니다.

Client를 통해서 L4 로드 밸런서에 gRPC Request를 보내고 터미널에서 확인해보면 다음과 같습니다.

$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
172.17.0.1
# 요청 중 Server Pod1의 Connection 파악하기
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
-> Connection이 생성되지 않음
# 요청 중 Server Pod2의 Connection 파악하기

Kubernetes에서 L7 로드 밸런서를 제공하지 않지만 Third Party L7 로드 밸런서를 찾아볼 수 있습니다 (e.g. Traefik, Linkerd, Istio). L7 로드 밸런서는 Application Layer에서 로드 밸런싱합니다. Client와 L7 로드 밸런서가 직접 Connection을 맺고 L7 로드 밸런서와 서버(Pod)와 Connection을 맺습니다. HTTP/2.0와 L7 로드 밸런서를 사용하는 경우에는 다음과 같이 로드 밸런싱이 진행됩니다.

Client와 L7 로드 밸런서 그리고 L7 로드 밸런서와 Server간에 Connection이 맺어집니다. Client가 요청을 보내게 되면 L7 로드 밸런서에서 이를 Application Layer까지 읽고 이를 다시 Server로 전달합니다.

Client를 통해서 L7 로드 밸런서에 gRPC Request를 보내기전과 후를 터미널에서 비교해보면 다음과 같습니다.  Client와 L7 로드 밸런서 그리고 L7 로드 밸런서와 Server Pod1,2와 Connection이 맺어지게 됩니다.

$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
10.96.0.1 # Kubernetes svc Cluster IP
# 요청 보내기전 L7 로드 밸런서(traefik) Connection 파악하기
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
-> Connection이 생성되지 않음
# 요청 보내기전 Server Pod1 Connection 파악하기
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
-> Connection이 생성되지 않음
# 요청 보내기전 Server Pod2 Connection 파악하기
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
172.17.0.5 # Server Pod1
172.17.0.4 # Server Pod2
10.96.0.1 # Kubernetes svc Cluster IP
# 요청 중 L7 로드 밸런서(traefik) Connection 파악하기
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
172.17.0.3 # L7 Loadbalancer의 IP
# 요청 중 Server Pod1 Connection 파악하기
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
172.17.0.3 # L7 Loadbalancer의 IP
# 요청 중 Server Pod2 Connection 파악하기

실습: Traefik으로 gRPC 로드 밸런싱하기

traefik은 로드 밸런싱 및 라우팅 기능을 제공하는 오픈소스입니다. traefik은 L7 로드 밸런싱 기능을 제공합니다.  [참고1, 참고2]

traefik을 이용하여 gRPC 통신을 로드 밸런싱하는 예제는 아래 링크를 참고하기 바랍니다.

public-examples/grpc-loadbalance-in-k8s at main · annotation-ai/public-examples
Public Examples (e.g. Blog). Contribute to annotation-ai/public-examples development by creating an account on GitHub.
  1. Cluster 구축하기

Minikube Kubernetes Cluster를 구축합니다.

$ make cluster

Kubernetes Cluster 구축 후 Node와 Pod를 확인해봅니다.

$ kubectl get node
NAME   STATUS   ROLES           AGE   VERSION
grpc   Ready    control-plane   13s   v1.24.1

$ kubectl get pod
No resources found in default namespace.

2. Traefik(L7 로드 밸런서)를 배포합니다.

Traefik을 배포한 후 Traefik Pod가 정상적으로 생성되었는지 확인합니다.

$ make traefik
$ kubectl get pod
NAME                       READY   STATUS    RESTARTS   AGE
traefik-78bbd5b657-ksdxg   1/1     Running   0          22s

3. gRPC Server와 L4 로드 밸런서를 배포합니다.

$ make grpc-server
$ kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
go-grpc-greeter-server-65c4c6f557-6cvxc   1/1     Running   0          13m
go-grpc-greeter-server-65c4c6f557-n27f6   1/1     Running   0          8m57s
traefik-7b59949b6c-7kvj5                  1/1     Running   0          3h38m
$ kubectl get svc
NAME                        TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGE
go-grpc-greeter-server      ClusterIP      10.97.255.22     <none>        50051/TCP         4h9m
kubernetes                  ClusterIP      10.96.0.1        <none>        443/TCP           4h51m
l4-go-grpc-greeter-server   LoadBalancer   10.102.165.112   127.0.0.1     50052:30539/TCP   3m42s
traefik                     LoadBalancer   10.102.18.230    127.0.0.1     50051:32488/TCP   3h36m

4. Minikube Tunneling하기

Minkube Tunneling을 하여 로드 밸런서를 호스트에서 접근할 수 있도록 노출시킵니다.

$ make tunnel

5. L4 로드 밸런서를 통해서 gRPC Request하고 Pod의 Log 상태 확인해보기

하나의 Server(Pod)에만 Request가 전달됩니다.

$ make request-to-l4
Client가 L4 로드 밸런서로 Request를 보낸다
$ kubectl logs go-grpc-greeter-server-65c4c6f557-6cvxc

2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:40 Received: world
Server의 로그 확인: Request 수신중
$ kubectl logs go-grpc-greeter-server-65c4c6f557-n27f6

-> None
Server의 로그 확인: Request를 수신하지 않음

6. L7 로드 밸런서를 통해서 gRPC Request하고 Pod의 Log 상태 확인해보기

두개의 Server(Pod)에 Request가 전달됩니다.

make request-to-l7
Client가 L7 로드 밸런서로 Request를 보낸다
$ kubectl logs go-grpc-greeter-server-65c4c6f557-6cvxc

2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:39 Received: world
2023/01/15 12:25:40 Received: world
Server의 로그 확인: Request 수신중
$ kubectl logs go-grpc-greeter-server-65c4c6f557-n27f6

2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
2023/01/15 12:30:05 Received: world
Server의 로그 확인: Request 수신중

맺음말

  • gRPC 통신의 로드 밸런싱시 유의사항에 대해서 알아보았습니다.
  • L4 로드 밸런서와 L7 로드 밸런서의 차이를 알아봤습니다.
  • L7 로드 밸런서(e.g. traefik)을 사용하면 gRPC 프로토콜을 사용하는 서비스에 대한 로드 밸런싱을 할 수 있습니다.

References