在《最佳实践在哪里-2:多集群流量管理》中,我们主要探讨了同一套微服务在多集群环境下的冗余部署以及集群内流量保持实现。不过,多集群部署的使用方式也并非只有这一种;根据实际情况,我们也有可能以其它的方式在多集群中部署服务。比如,我们选择多集群部署可能并非是需要它的故障隔离和故障转移能力,而只是希望服务和其依赖的数据库等外部资源部署在同一地域,以缩短调用时延;在这种情况下,我们可能会将包含多个微服务的一套应用程序部署在多个集群中,而非进行冗余部署。
我们以sleep服务和httpbin服务为例,演示上述的这种场景:
在这个例子中,由统一的服务网格控制面对集群1和集群2中的所有网格代理进行管理。服务之间的调用链为sleep -> httpbin;同时,只有集群1中部署了sleep Service、只有集群2中部署了httpbin Service。集群之间已经配置好了互访连通性(可以参考https://help.aliyun.com/document_detail/149550.html,进行多集群下互访连通性配置)。
应用部署
首先,我们准备两个集群,并参考https://help.aliyun.com/document_detail/150509.html,将两个集群都添加到同一个ASM实例中,这样我们就实现了使用同一个服务网格实例管理多个集群中的应用(多集群之间需要进行网络规划,参考https://help.aliyun.com/document_detail/424941.html)。
接下来,我们为两个集群的default命名空间打上istio-injection: enabled的标签,开启自动注入,并分别部署sleep和httpbin引用。
用kubectl连接到集群1,部署sleep应用:
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
---
apiVersion: v1
kind: Service
metadata:
name: sleep
labels:
app: sleep
service: sleep
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
terminationGracePeriodSeconds: 0
serviceAccountName: sleep
containers:
- name: sleep
image: curl:8.1.2
command: ["/bin/sleep", "infinity"]
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /etc/sleep/tls
name: secret-volume
volumes:
- name: secret-volume
secret:
secretName: sleep-secret
optional: true
---
sleep内部是一个curl容器,可以很方便地利用curl实现作为客户端pod的功能。
接下来用kubectl连接集群2,部署httpbin应用:
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
集群2内部署的httpbin应用是一个http Server工具箱,提供了多种测试http协议的工具箱接口,可以用作服务端pod使用。
通过分别部署在两个集群中的sleep和httpbin,我们目前理论上已经实现了上图的效果,那么现在就试试看吧!
多集群服务发现
我们直接执行进入集群1的sleep pod,尝试用curl调用httpbin服务。
$ kubectl exec -it sleep-86xxxxxxxx-xxxxx -c sleep -- sh
~ $ curl httpbin:8000
curl: (6) Could not resolve host: httpbin
遗憾的是,我们发现我们的设想似乎并不能work:此时跨集群调用并没能调通,显示的则是Could not resolve host: httpbin。这似乎与我们的设想不太一致,根据我们所知的信息,sleep容器发出的调用请求会被网格代理拦截,而网格代理是由多集群统一的服务网格控制面管理的,控制面应该已经为其下发了集群2中httpbin服务的端点信息,那么为什么会出现调用失败的情况呢?
其实非常简单,我们需要简单理解下网格代理拦截请求究竟拦截了个啥。
如果你学习过计算网络、或是刷过诸如“浏览器从敲下回车到显示网页都发生了啥”这种面试题,那么应该会知道:客户端在请求服务端时,若使用服务域名,则需要先向DNS服务器发送DNS请求,将域名解析为服务ip地址,再通过ip地址与服务端建立连接。
由于这层原因,网格代理拦截到客户端发送的请求时,实际得到的信息是请求目标的服务ip地址、而不是服务域名。而从域名到ip地址的解析,则仍然需要交给Kubentes集群的DNS服务去处理。在本例中,由于集群1中并没有部署名为httpbin的Service,自然也就无法解析httpbin这个域名,从而请求在DNS解析阶段就出错了,无法实际将请求发出。
如何解决这个问题呢?一种比较直观的思路是:既然问题在于集群1的DNS解析服务无法解析httpbin这个域名,让它能够解析就可以了;为此,我们需要在集群1中部署一个完全相同的httpbin Service,不需要为其部署工作负载,因为其存在意义仅仅是让集群1的域名解析可以正常工作。顺着这个思路,我们需要在两个集群中为所有服务都部署一个一模一样的Service,尽管有些Service可能在本集群内可能不存在对应的工作负载。
有没有更加简化的方法呢?我们知道,服务网格通过对接多个集群中的服务注册中心,其控制面也同样保存着服务ip地址到服务域名的映射、并且这些映射关系也都会被下发到多集群中每个网格代理之中。因此,使用网格代理代为响应DNS解析请求在理论上是完全可行的。
我们可以选择为网格代理启用“DNS代理功能”,启用DNS代理功能后,网格代理容器将拦截工作负载的DNS请求,以提高服务网格的性能和可用性。来自工作负载的所有请求将被重定向至网格代理容器。由于Sidecar代理容器在本地存储IP地址到本地域名的映射,网格代理容器将可以直接返回DNS响应给工作负载,避免再向远端的DNS服务发送请求。若DNS请求无法被网格代理容器处理,网格代理将会直接向集群的DNS服务转发DNS请求。
进入服务网格ASM的实例管理页面,选择Sidecar管理(数据面)> Sidecar代理配置,将启用DNS代理功能 开关打开。
由于Sidecar代理配置生效于Pod yaml,我们需要连接到集群1,然后重新部署Sleep应用,以令新的Sidecar代理配置生效,参考https://help.aliyun.com/document_detail/610989.html?spm=a2c4g.205486.0.0.7c31352akhtjko#p-4q2-0z4-6u6。
此时我们重新尝试执行进入集群1的sleep pod,用curl调用httpbin服务。
$ kubectl exec -it sleep-bfxxxxxx-xxxxx -c sleep -- sh
~ $ curl httpbin:8000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>httpbin.org</title>
...
请求成功了!证明DNS代理功能已经在正常工作~
小结
本文作为《最佳实践在哪里-2:多集群流量管理》的扩充,本文探讨了多集群环境下服务发现的基本问题,并探索了服务网格的DNS代理功能之最佳实践方式,我们发现DNS代理可以为我们带来以下好处:
-
DNS代理功能可以在多集群部署环境下大显身手,只用一个开关,我们在任意一个集群中部署的服务,都可以被其他集群中的工作负载以域名的形式访问,无需多余配置。
-
DNS代理功能还可以提高集群的性能与可用性。由于Pod发出的DNS解析请求都被位于Pod本地的网格代理容器所代理,DNS解析的响应效率将得到提高。同时,当集群中的DNS解析服务不可用时,DNS代理功能仍然可以保证服务之间通过域名的访问正常工作。
老子差点就没法调用我的意大利炮了,多亏有DNS代理功能!—— 李云龙