grpc
GRPC有很多好处,例如:
- 使用相同连接的许多请求多路复用。
- 支持典型的客户服务器请求响应以及双工流。
- 使用一个快速,非常轻的二进制协议,其结构化数据作为服务之间的通信媒介。
上面的所有内容都使GRPC成为非常吸引人的交易,但是GRPC特别考虑负载平衡。
问题
让我们深入研究这个问题。
为此,我们需要设置。设置包括以下:
- GRPC服务器,我们称其为
Greet Server
。 - 一个充当REST网关的客户,在内部也是GRPC客户端。我们称其为
Greet Client
。
我们还在使用kubernetes进行演示,因此有大量的yaml清单文件。让我在下面解释一下:
招呼服务员deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: greetserver-deploy
spec:
replicas: 3
selector:
matchLabels:
run: greetserver
template:
metadata:
labels:
run: greetserver
spec:
containers:
- image: hiteshpattanayak/greet-server:1.0
imagePullPolicy: IfNotPresent
name: greetserver
ports:
- containerPort: 50051
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
以上是Greet Server
的部署主fest,它旋转了3个Greet Server
的复制品。
Greet Server
使用hiteshpattanayak/greet-server:1.0
图像。
另外,部署的每个豆荚都暴露了50051
端口。
环境变量:pod_ip和pod_name被注入到吊舱中。
上面服务器中的每个吊舱都做什么?
他们暴露了期望first_name
和last_name
的rpc
或服务,作为回应,他们以这种格式返回一条消息:
reponse from Greet rpc: Hello, <first_name> <last_name> from pod: name(<pod_name>), ip(<pod_ip>).
从回应中,我们可以推断出哪个POD完成了我们的请求。
etch.svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
run: greetserver
name: greetserver
namespace: default
spec:
ports:
- name: grpc
port: 50051
protocol: TCP
targetPort: 50051
selector:
run: greetserver
以上是Greet server service
的服务清单。这基本上充当了Greet Server
Pods上方的代理。
服务的selector
部分与每个吊舱的labels
部分匹配。
engerclient-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: greetclient-deploy
spec:
replicas: 1
selector:
matchLabels:
run: greetclient
template:
metadata:
labels:
run: greetclient
spec:
containers:
- image: hiteshpattanayak/greet-client:4.0
name: greetclient
ports:
- containerPort: 9091
env:
- name: GRPC_SERVER_HOST
value: greetserver.default.svc.cluster.local
- name: GRPC_SVC
value: greetserver
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
以上是Greet Client
的部署主fest,它旋转了1个Greet Client
的复制品。
如上所述,POD运行了充当REST网关的应用程序,并与Greet Server
联系以处理请求。
此部署使用hiteshpattanayak/greet-client:4.0
图像。
4.0
标记的图像具有负载平衡问题。
还pod(s)暴露端口9091
。
engerclient-svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
run: greetclient
name: greetclient
namespace: default
spec:
ports:
- name: restgateway
port: 9091
protocol: TCP
targetPort: 9091
selector:
run: greetclient
type: LoadBalancer
上面的服务只是将流量重定向到Greet Client
pods。
问候 - ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: greet-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: greet.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: greetclient
port:
name: restgateway
上面的入口是将Greet Client Service
暴露于集群外部。
注意:
默认情况下,minikube
没有启用入口
- 启用是否检查:
minikube addons list
- 启用入口插件:
minikube addons enable ingress
etch-clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default
name: service-reader
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "watch", "list"]
entry-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: service-reader-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: service-reader
subjects:
- kind: ServiceAccount
name: default
namespace: default
需要群集角色和群集角色绑定,因为default
服务帐户无权获取服务详细信息。
在内部试图获取服务详细信息,因此需要绑定。
在下面的序列中创建设置:
kubectl create -f greet-clusterrole.yaml
kubectl create -f greet-clusterrolebinding.yaml
kubectl create -f greetserver-deploy.yaml
kubectl get po -l 'run=greetserver' -o wide
<<com
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
greetserver-deploy-7595ccbdd5-67bmd 1/1 Running 0 91s 172.17.0.4 minikube <none> <none>
greetserver-deploy-7595ccbdd5-k6zbl 1/1 Running 0 91s 172.17.0.3 minikube <none> <none>
greetserver-deploy-7595ccbdd5-l8kmv 1/1 Running 0 91s 172.17.0.2 minikube <none> <none>
com
kubectl create -f greet.svc.yaml
kubectl get svc
<<com
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
greetserver ClusterIP None <none> 50051/TCP 77s
com
kubectl create -f greetclient-deploy.yaml
kubectl get po -l 'run=greetclient' -o wide
<<com
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
greetclient-deploy-6bddb94df-jwr25 1/1 Running 0 35s 172.17.0.6 minikube <none> <none>
com
kubectl create -f greet-client.svc.yaml
kubectl get svc
<<com
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
greetclient LoadBalancer 10.110.255.115 <pending> 9091:32713/TCP 22s
greetserver ClusterIP None <none> 50051/TCP 5m14s
com
kubectl create -f greet-ingress.yaml
kubectl get ingress
<<com
NAME CLASS HOSTS ADDRESS PORTS AGE
greet-ingress nginx greet.com 192.168.49.2 80 32s
com
由于我们通过greet-ingress
将Greet Client
暴露于群集外部,因此可以通过以下方式访问端点。
因此,当我们提出卷曲请求时:
请求#1
curl --request POST \
--url http://greet.com/greet \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Hitesh",
"last_name": "Pattanayak"
}'
<<com
Response
reponse from Greet rpc: Hello, Hitesh Pattanayak from pod: name(greetserver-deploy-7595ccbdd5-l8kmv), ip(172.17.0.2).
com
请求#2
curl --request POST \
--url http://greet.com/greet \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Hitesh",
"last_name": "Pattanayak"
}'
<<com
Response
reponse from Greet rpc: Hello, Hitesh Pattanayak from pod: name(greetserver-deploy-7595ccbdd5-l8kmv), ip(172.17.0.2).
com
请求#3
curl --request POST \
--url http://greet.com/greet \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Hitesh",
"last_name": "Pattanayak"
}'
<<com
Response
reponse from Greet rpc: Hello, Hitesh Pattanayak from pod: name(greetserver-deploy-7595ccbdd5-l8kmv), ip(172.17.0.2).
com
因此,无论我提出的许多要求,问题都在同一服务器中。这是因为http/2的粘性性质。
GRPC的优势成为自己的危险。
可以找到复制问题的代码库。
GRPC客户侧负载平衡
我们之前已经讨论了与负载平衡的GRPC挑战之一。
由于GRPC连接的粘性性质而发生的。
现在我们将讨论如何解决该问题。
这个特殊的解决方案很简单。
加载余额的责任落在客户本身上。
特别是,客户端并不意味着最终用户。所有GRPC服务器都有最终用户使用的REST网关。
这是因为GRPC使用的协议HTTP2尚未具有浏览器支持。
因此,休息网关充当GRPC服务器的GRPC客户端。这就是为什么GRPC主要用于内部通信的原因。
早些时 可以将代码转介给here。
变化
代码更改
对于此解决方案,我们使用hiteshpattanayak/greet-client:11.0
图像。 codebase的变化以下:
更新的客户部署清单:
apiVersion: apps/v1
kind: Deployment
metadata:
name: greetclient-deploy
spec:
replicas: 1
selector:
matchLabels:
run: greetclient
template:
metadata:
labels:
run: greetclient
spec:
containers:
- image: hiteshpattanayak/greet-client:11.0
name: greetclient
ports:
- containerPort: 9091
env:
- name: GRPC_SVC
value: greetserver
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- 在拨打服务器时配置负载平衡策略。
- 在拨打服务器时配置以终止连接。
a.conn, err = grpc.Dial(
servAddr,
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithBlock(),
opts,
)
- 拨号时使用的服务器地址需要服务器的DNS地址。
var serverHost string
if host := kubernetes.GetServiceDnsName(client, os.Getenv("GRPC_SVC"), os.Getenv("POD_NAMESPACE")); len(host) > 0 {
serverHost = host
}
servAddr := fmt.Sprintf("%s:%s", serverHost, serverPort)
无头服务
- 同样早些时候复制我们为
Greet server pods
创建的服务(engerserver)的问题时,也是普通的ClusterIP
类型。此解决方案需要无头聚类服务。
apiVersion: v1
kind: Service
metadata:
labels:
run: greetserver
name: greetserver
namespace: default
spec:
ports:
- name: grpc
port: 50051
protocol: TCP
targetPort: 50051
selector:
run: greetserver
clusterIP: None
这里要注意的一件重要的事情是,这是一种特殊类型的ClusterIP
服务,称为Headless
服务。
在这种service
中,未指定服务类型。默认情况下,类型变为ClusterIP
。这意味着该服务在集群中可用。
如果您已经有要重复使用的现有DNS条目,则可以设置.spec.clusterIP
。
如果将.spec.clusterIP
设置为None
,它可以制造服务headless
,这意味着当客户端将请求发送到无头服务时,它将恢复该服务代表的所有POD的列表(在这种情况下,带有标签run: greetserver
)。
kubernetes允许客户通过DNS查找发现POD IP。通常,当您执行DNS查找服务时,DNS服务器会返回单个IP'服务群集IP。但是,如果您告诉kubernetes,您不需要一个群集IP来服务(您可以通过将clusterip字段设置为服务规范中的non),而DNS服务器将返回POD IPS而不是单个服务IP。 DNS服务器不会返回单个DNS记录,而是将返回该服务的多个记录,每个指向当时的单个POD的IP。因此,客户可以进行简单的DNS查找,并获取服务中所有POD的IPS。然后,客户可以使用该信息连接到一个,许多或全部。
基本上,该服务现在让客户决定如何连接到Pods。
验证无头服务DNS查找
创建无头服务:
kubectl create -f greet.svc.yaml
创建一个实用程序吊舱:
kubectl run dnsutils --image=tutum/dnsutils --command -- sleep infinity
通过在POD上运行nslookup
命令来验证
kubectl exec dnsutils -- nslookup greetserver
<<com
Result
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: greetserver.default.svc.cluster.local
Address: 172.17.0.4
Name: greetserver.default.svc.cluster.local
Address: 172.17.0.3
Name: greetserver.default.svc.cluster.local
Address: 172.17.0.2
您可以看到无头服务解决到通过服务连接的所有POD的IP地址。
将其与返回的无头服务的输出对比。
kubectl exec dnsutils -- nslookup greetclient
<<com
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: greetclient.default.svc.cluster.local
Address: 10.110.255.115
com
现在让我们通过向裸露的入口提出卷曲请求来测试更改。
请求#1
curl --request POST \
--url http://greet.com/greet \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Hitesh",
"last_name": "Pattanayak"
}'
<<com
Response
reponse from Greet rpc: Hello, Hitesh Pattanayak from pod: name(greetserver-deploy-7595ccbdd5-k6zbl), ip(172.17.0.3).
com
请求#2
curl --request POST \
--url http://greet.com/greet \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Hitesh",
"last_name": "Pattanayak"
}'
<<com
Response
reponse from Greet rpc: Hello, Hitesh Pattanayak from pod: name(greetserver-deploy-7595ccbdd5-67bmd), ip(172.17.0.4).
com
请求#3
curl --request POST \
--url http://greet.com/greet \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Hitesh",
"last_name": "Pattanayak"
}'
<<com
Response
reponse from Greet rpc: Hello, Hitesh Pattanayak from pod: name(greetserver-deploy-7595ccbdd5-l8kmv), ip(172.17.0.2).
com
问题不再存在。
但是,我们在这里失去的是GRPC保留连接更长的能力,并通过它们多重多个请求,从而减少了延迟。
GRPC LookAside负载平衡
之前我们讨论了:
- 与GRPC的负载平衡挑战
- 如何通过客户侧负载平衡来应对上述挑战
即使我们能够解决负载平衡问题,但我们交易了GRPC的主要优势之一,这是长期连接的。
因此,在这篇文章中,我们希望达到负载平衡(仍然是客户端),但我们不会权衡以上提及的GRPC的优势。
当我说加载余额的责任落在client side
上时,我想重新求助,客户端并不意味着最终用户。所有GRPC服务器都有最终用户使用的REST网关。由于缺乏浏览器支持,GRPC服务没有直接暴露。
看起来平衡
此负载平衡器的目的是解决要连接的GRPC服务器。
目前,此负载平衡器以两种方式工作:循环和随机。
负载平衡器本身是基于GRPC的,并且由于负载不会太多,只有一个POD就足够了。
它揭示了一个名为lookaside
的服务和一个称为Resolve
的RPC,该服务期望路由类型以及有关GRPC服务器(例如Kubernetes服务名称和名称空间)的一些详细信息。
使用服务名称和名称空间,它将获取与之关联的Kubernetes端点对象。从端点对象服务器IP可以找到。
这些IP将存储在内存中。这些IP时不时会根据间隔集进行刷新。对于解决IP的每个请求,它将根据请求中的路由类型旋转IPS。
可以找到lookaside负载平衡器的代码here。
我们正在使用image hiteshpattanayak/lookaside:9.0
用于lookaside pod。
豆荚清单就是这样:
apiVersion: v1
kind: Pod
metadata:
labels:
run: lookaside
name: lookaside
namespace: default
spec:
containers:
- image: hiteshpattanayak/lookaside:9.0
name: lookaside
ports:
- containerPort: 50055
env:
- name: LB_PORT
value: "50055"
由于它是GRPC服务器,因此暴露的端口为50055
。
公开POD的服务清单如下:
apiVersion: v1
kind: Service
metadata:
labels:
run: lookaside-svc
name: lookaside-svc
namespace: default
spec:
ports:
- port: 50055
protocol: TCP
targetPort: 50055
selector:
run: lookaside
clusterIP: None
我也为此选择了headless
服务,但对此没有这种需求。
更新了ClusterRole
,以包括获取endpoints
和pod
详细信息的能力
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default
name: service-reader
rules:
- apiGroups: [""]
resources: ["services", "pods", "endpoints"]
verbs: ["get", "watch", "list"]
招待客户的变化
Greet Client
现在是integrated,带有LookAside LoadBalancer。
客户端是set使用RoundRobin
路由类型,但可以通过configmap或环境变量进行配置。
删除设置load-balancing
策略,并通过拨号时设置WithBlock
选项强制终止连接。
来自
conn, err := grpc.Dial(
servAddr,
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithBlock(),
opts,
)
conn, err := grpc.Dial(
servAddr,
opts,
)
因此,它如何解决较早的负载平衡问题,我们进行了终止长时间连接以实现负载平衡的交易。
我们所做的是将以前的连接存储到服务器并重复使用,但要为每个请求旋转。
if c, ok := a.greetClients[host]; !ok {
servAddr := fmt.Sprintf("%s:%s", host, serverPort)
fmt.Println("dialing greet server", servAddr)
conn, err := grpc.Dial(
servAddr,
opts,
)
if err != nil {
log.Printf("could not connect greet server: %v", err)
return err
}
a.conn[host] = conn
a.currentGreetClient = proto.NewGreetServiceClient(conn)
a.greetClients[host] = a.currentGreetClient
} else {
a.currentGreetClient = c
}
结论
GRPC是由于效率,速度和奇偶校验而用于微服务内部通信的绝佳解决方案。但是,持续时间连接虽然有优势,但持续的连接会导致棘手的负载平衡。在本文的帮助下,我们找到了处理它的方法。
有多种方法可以通过Linkerd和Istio等服务网格处理。但是,在不设置服务网格的情况下,使用解决方案是很方便的。
,如果您喜欢我的内容,您会考虑在LinkedIn上关注我。