在如今高度分布式的世界中,單主機(jī)架構(gòu)越來(lái)越多地被多個(gè)更小的微服務(wù)架構(gòu)所取代(無(wú)論好壞),代理和負(fù)載均衡技術(shù)似乎有了復(fù)興。除之前經(jīng)典技術(shù)了之外,近年來(lái)還出現(xiàn)了幾種新的代理技術(shù),這些技術(shù)以各種技術(shù)實(shí)現(xiàn),通過(guò)不同的功能推廣自己,例如輕松集成到某些云提供商(“云原生”),高性能和低內(nèi)存占用,或動(dòng)態(tài)配置。
可以說(shuō)兩種最流行的“經(jīng)典”代理技術(shù)是NGINX(C)和HAProxy(C),而其中包含的一些新生力量是Zuul(Java),Linkerd(Rust),Traefik(Go),Caddy(Go)和Envoy(C ++)。
所有這些技術(shù)都具有不同的功能集,并且針對(duì)某些特定方案或托管環(huán)境(例如,Linkerd經(jīng)過(guò)微調(diào)以便在Kubernetes中使用)。
在這篇文章中,我不打算對(duì)這些進(jìn)行比較,而只關(guān)注一個(gè)特定的場(chǎng)景:如何使用Envoy作為Kubernetes中運(yùn)行的服務(wù)的負(fù)載均衡器。
Envoy是一個(gè)“高性能C ++分布式代理”,最初在Lyft實(shí)現(xiàn),但從那時(shí)起就獲得了廣泛采用。它性能高,資源占用少,支持“控制平面”API管理的動(dòng)態(tài)配置,并提供一些高級(jí)功能,如各種負(fù)載平衡算法,速率限制,熔斷和鏡像。
出于多種原因,我選擇Envoy作為負(fù)載均衡器代理。
- 除了能夠使用控制平面API動(dòng)態(tài)控制外,它還支持簡(jiǎn)單,硬編碼的基于YAML的配置,這對(duì)我的目的很方便,并且易于上手。
- 它內(nèi)置了對(duì)其調(diào)用的服務(wù)發(fā)現(xiàn)技術(shù)的支持,該技術(shù)STRICT_DNS建立在查詢DNS記錄的基礎(chǔ)上,并期望看到具有IP地址的A記錄,用于上游集群的每個(gè)節(jié)點(diǎn)。這使得Kubernetes的無(wú)頭服務(wù)變得簡(jiǎn)單易用。
- 它支持各種負(fù)載平衡算法,其中包括:
輪詢r(jià)ound_robin最小請(qǐng)求least_request隨機(jī)random。
在開(kāi)始使用Envoy之前,我通過(guò)service類型的對(duì)象訪問(wèn)Kubernetes中的服務(wù)LoadBalancer,這是從Kubernetes外部訪問(wèn)服務(wù)的一種非常典型的方式。負(fù)載均衡器服務(wù)的確切工作方式取決于托管環(huán)境。 如果它首先支持它。我使用的是Google Kubernetes Engine,其中每個(gè)負(fù)載均衡器服務(wù)都映射到TCP級(jí)別的Google Cloud負(fù)載均衡器,該負(fù)載均衡器僅支持輪詢r(jià)ound_robin算法。
1.為應(yīng)用程序創(chuàng)建Headless Service
在Kubernetes中有一種稱為Headless Service的特定服務(wù),恰好與Envoy的STRICT_DNS服務(wù)發(fā)現(xiàn)模式一起使用非常方便。
Headless Service不提供單個(gè)IP和負(fù)載平衡到底層pod,而是它只有DNS配置,它為我們提供A記錄,其中包含與標(biāo)簽選擇器匹配的所有pod的pod的IP地址。
此服務(wù)類型旨在用于我們希望實(shí)現(xiàn)負(fù)載平衡以及自己維護(hù)與上游pod的連接的場(chǎng)景,這正是我們可以使用Envoy執(zhí)行的操作。
我們可以通過(guò)設(shè)置.spec.clusterIP字段來(lái)創(chuàng)建Headless Service "None"。因此,假設(shè)我們的應(yīng)用程序pod具有app標(biāo)簽myapp,我們可以使用以下yaml創(chuàng)建Headless Service。
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
clusterIP: None
ports:
- name: web
port: 80
protocol: TCP
targetPort: 80
selector:
app: myapp
sessionAffinity: None
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myapp
namespace: default
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:1.10
ports:
- containerPort: 80
myapp運(yùn)行情況
root@k8s-master-1:~# kubectl get pod,svc -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/myapp-c78bcd8fb-fkkfh 1/1 Running 0 31m 172.20.3.103 192.168.2.13 <none> <none>
pod/myapp-c78bcd8fb-plnkt 1/1 Running 0 126m 172.20.2.62 192.168.2.12 <none> <none>
pod/myapp-c78bcd8fb-s87sv 1/1 Running 0 31m 172.20.1.207 192.168.2.11 <none> <none>
pod/myapp-envoy-696b6d764d-nm6lv 1/1 Running 1 18h 172.20.3.101 192.168.2.13 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.68.0.1 <none> 443/TCP 60d <none>
service/myapp ClusterIP None <none> 80/TCP 122m app=myapp
service/myapp-envoy ClusterIP 10.68.246.10 <none> 80/TCP,9901/TCP 18h app=myapp-envoy
現(xiàn)在,如果我們檢查Kubernetes集群內(nèi)的服務(wù)的DNS記錄,我們將看到具有IP地址的單獨(dú)A記錄。如果我們有3個(gè)pod,我們會(huì)看到類似于此的DNS摘要。
root@k8s-master-1:~# kubectl run --attach busybox --rm --image=busybox:1.27 --restart=Never -- sh -c "sleep 4 && nslookup myapp.default"
If you don't see a command prompt, try pressing enter.
Server: 10.68.0.2
Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
Name: myapp.default
Address 1: 172.20.2.62 172-20-2-62.myapp.default.svc.cluster.local
Address 2: 172.20.1.207 172-20-1-207.myapp.default.svc.cluster.local
Address 3: 172.20.3.103 172-20-3-103.myapp.default.svc.cluster.local
pod "busybox" deleted
Envoy的STRICT_DNS的服務(wù)發(fā)現(xiàn)工作原理是,它維護(hù)的DNS服務(wù)器返回的所有A記錄的IP地址,并每?jī)擅腌娝⑿陆MIP地址。
2.創(chuàng)建Envoy 鏡像
在不提供動(dòng)態(tài)API形式的控制平面的情況下使用Envoy的最簡(jiǎn)單方法是將硬編碼配置添加到靜態(tài)yaml文件中。
以下是對(duì)域名myapp給出的IP地址進(jìn)行負(fù)載均衡的基本配置。
envoy.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { host_rewrite: myapp, cluster: myapp_cluster, timeout: 60s }
http_filters:
- name: envoy.router
clusters:
- name: myapp_cluster
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ${ENVOY_LB_ALG}
hosts: [{ socket_address: { address: ${SERVICE_NAME}, port_value: 80 }}]
docker-entrypoint.sh
#!/bin/sh
set -e
echo "Generating envoy.yaml config file..."
cat /tmpl/envoy.yaml.tmpl | envsubst \$ENVOY_LB_ALG,\$SERVICE_NAME > /etc/envoy.yaml
echo "Starting Envoy..."
/usr/local/bin/envoy -c /etc/envoy.yaml
Dockerfile
FROM envoyproxy/envoy:latest
COPY envoy.yaml /tmpl/envoy.yaml.tmpl
COPY docker-entrypoint.sh /
RUN chmod 500 /docker-entrypoint.sh
RUN apt-get update && \
apt-get install gettext -y && \
rm -rf /var/lib/apt/list/* && \
rm -rf /var/cache/apk/*
ENTRYPOINT ["/docker-entrypoint.sh"]
3.創(chuàng)建Envoy的Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myapp-envoy
labels:
app: myapp-envoy
spec:
selector:
matchLabels:
app: myapp-envoy
template:
metadata:
labels:
app: myapp-envoy
spec:
containers:
- name: myapp-envoy
image: 314315960/myapp-envoyproxy:latest
imagePullPolicy: IfNotPresent
env:
- name: "ENVOY_LB_ALG"
value: "LEAST_REQUEST"
- name: "SERVICE_NAME"
value: "myapp"
ports:
- name: http
containerPort: 80
- name: envoy-admin
containerPort: 9901
應(yīng)用此yaml后,Envoy代理應(yīng)該可以運(yùn)行,您可以通過(guò)將請(qǐng)求發(fā)送到Envoy服務(wù)的主端口來(lái)訪問(wèn)底層服務(wù)。
在此示例中,我僅添加了ClusterIP類型的服務(wù),但如果要從群集外部訪問(wèn)代理,還可以使用LoadBalancer服務(wù)或Ingress對(duì)象。

4.Troubleshooting
在Envoy配置文件中,您可以看到admin部分,它配置Envoy的管理端點(diǎn)。這可用于檢查有關(guān)代理的各種診斷信息。
一些有用的節(jié)點(diǎn):
-
/config_dump: 打印代理的完整配置,這有助于驗(yàn)證pod上是否有正確的配置 -
/clusters: 顯示Envoy發(fā)現(xiàn)的所有上游節(jié)點(diǎn),以及為每個(gè)節(jié)點(diǎn)處理的請(qǐng)求數(shù)。這對(duì)于檢查負(fù)載平衡算法是否正常工作非常有用。


5.驗(yàn)證
myapp-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myapp-ingress
namespace: default
spec:
rules:
- host: myapp.k8s.io
http:
paths:
- path: /
backend:
serviceName: myapp
servicePort: web
myapp-envoy-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myapp-envoy-ingress
namespace: default
spec:
rules:
- host: myapp-envoy.k8s.io
http:
paths:
- path: /
backend:
serviceName: myapp-envoy
servicePort: 80


參考文檔:
https://jimmysong.io/posts/envoy-archiecture-and-terminology/
https://blog.markvincze.com/how-to-use-envoy-as-a-load-balancer-in-kubernetes/
https://kubernetes.io/docs/concepts/services-networking/ingress/
