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

- 작성자: 류원탁 (GitHub)
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 |
----------- ---------- ------- ------- -------- ---------
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
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
-> 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
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
-> Connection이 생성되지 않음
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
-> 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
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
172.17.0.3 # L7 Loadbalancer의 IP
$ netstat -tn | awk '/EST/{print $5}' | sed 's/:.*//'
172.17.0.3 # L7 Loadbalancer의 IP
실습: Traefik으로 gRPC 로드 밸런싱하기
traefik은 로드 밸런싱 및 라우팅 기능을 제공하는 오픈소스입니다. traefik은 L7 로드 밸런싱 기능을 제공합니다. [참고1, 참고2]
traefik을 이용하여 gRPC 통신을 로드 밸런싱하는 예제는 아래 링크를 참고하기 바랍니다.
- 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
$ 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
$ kubectl logs go-grpc-greeter-server-65c4c6f557-n27f6
-> None
6. L7 로드 밸런서를 통해서 gRPC Request하고 Pod의 Log 상태 확인해보기
두개의 Server(Pod)에 Request가 전달됩니다.
make request-to-l7
$ 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
$ 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
맺음말
- gRPC 통신의 로드 밸런싱시 유의사항에 대해서 알아보았습니다.
- L4 로드 밸런서와 L7 로드 밸런서의 차이를 알아봤습니다.
- L7 로드 밸런서(e.g. traefik)을 사용하면 gRPC 프로토콜을 사용하는 서비스에 대한 로드 밸런싱을 할 수 있습니다.
References
- https://grpc.io/
- https://developers.google.com/protocol-buffers/docs/overview
- https://en.wikipedia.org/wiki/OSI_model
- https://web.dev/performance-http2/#request-and-response-multiplexing
- https://doc.traefik.io/traefik/
- https://doc.traefik.io/traefik/routing/services/
- https://doc.traefik.io/traefik/user-guides/grpc/
- https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip