假设我们在阿里云上有一个托管的 k8s 集群, cn-shanghai, 在线下的某个机房有一个自己维护的 k8s 集群, wuxi. 现在我们希望通过基于 Istio 的 Mesh 来实现这两个集群之间的互联互通.
对最终效果, 我们希望:
我们先在 cn-shanghai 部署 helloworld-v1, 随后在 wuxi 尝试调用 helloworld.
✗ kubectl --context cn-shanghai get pods | grep helloworld
helloworld-v1-776f57d5f6-sxjtw 2/2 Running 0 162m
✗ kubectl --context wuxi exec -ti sleep-5597f78777-hq7vk -- curl helloworld.dev:5000/hello
Hello version: v1, instance: helloworld-v1-776f57d5f6-sxjtw
而当我们在 wuxi 部署 helloworld-v2 时, 从 wuxi 发布的调用均匀分布在两个集群.
✗ kubectl --context wuxi get pods | grep helloworld
helloworld-v2-7bd9f44595-db94r 2/2 Running 0 14s
✗ kubectl --context wuxi exec -ti sleep-5597f78777-hq7vk -- curl helloworld.dev:5000/hello
Hello version: v1, instance: helloworld-v1-776f57d5f6-sxjtw
✗ kubectl --context wuxi exec -ti sleep-5597f78777-hq7vk -- curl helloworld.dev:5000/hello
Hello version: v2, instance: helloworld-v2-7bd9f44595-db94r
显然, 当本集群存在被调用方时, 我们更希望调用尽量集中在集群内. 通过选择合适的 LocalityLoadBalancerSetting, 很轻松就可以实现类似需求.
✗ kubectl --context wuxi apply -f demo-dr.yaml
destinationrule.networking.istio.io/helloworld created
✗ kubectl --context wuxi exec -ti sleep-5597f78777-hq7vk -- curl helloworld.dev:5000/hello
Hello version: v2, instance: helloworld-v2-7bd9f44595-db94r
✗ kubectl --context wuxi exec -ti sleep-5597f78777-hq7vk -- curl helloworld.dev:5000/hello
Hello version: v2, instance: helloworld-v2-7bd9f44595-db94r
✗ kubectl --context wuxi exec -ti sleep-5597f78777-hq7vk -- curl helloworld.dev:5000/hello
Hello version: v2, instance: helloworld-v2-7bd9f44595-db94r
Istio 在文档中介绍了多种多集群的方案, 我们选择的是网络隔离下的多主, Install Multi-Primary on different networks.
多主架构中, 每个集群都拥有独立的 control plane (istiod) 来管理本集群的 mesh. 相较于单集群架构的主要区别在于:
对熟悉 Istio 的同学来说, 上图的架构应该是非常容易理解的, 唯一需要深入探究的应该是 Network Gateway 相关内容. Istio 通过几个方面实现了网络隔离下的跨集群调用:
wuxi 的 NetworkGateway 监听了 15443 端口并针对相关服务做路由.
k --context wuxi get -n ingress-cluster pods -l app=cluster-wuxi
NAME READY STATUS RESTARTS AGE
cluster-wuxi-6fdbb84466-zgwwr 1/1 Running 0 3h7m
✗ istioctl --context wuxi proxy-config listener cluster-wuxi-6fdbb84466-zgwwr.ingress-cluster --port 15443 | grep helloworld
0.0.0.0 15443 SNI: outbound_.5000_._.helloworld.dev.svc.cluster.local; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2 Cluster: outbound_.5000_._.helloworld.dev.svc.cluster.local
cn-shanghai 内 Envoy 接收到 Endpoint 中, wuxi 机房 Pod 的 IP 都被替换为 NetworkGateway 的对外地址.
✗ kubectl --context wuxi get -n ingress-cluster svc cluster-wuxi
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster-wuxi NodePort 172.23.234.78 10.18.10.196 15021:32125/TCP,15443:30880/TCP,15012:30500/TCP,15017:32230/TCP 3h50m
✗ istioctl --context cn-shanghai proxy-config endpoints sleep-75b85f5796-f6k89 --cluster "outbound|5000||helloworld.dev.svc.cluster.local"
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.18.10.196:30880 HEALTHY OK outbound|5000||helloworld.dev.svc.cluster.local
172.20.171.185:5000 HEALTHY OK outbound|5000||helloworld.dev.svc.cluster.local
Envoy LB 相关的配置中, 支持 priority 和 weight 两个属性, 当指定本地优先时, Istio 在向 Envoy 推送信息时会将集群内 Endpoint 的 priority 设置为 1, 进而实现本地优先.
istioctl --context wuxi proxy-config endpoints sleep-5597f78777-hq7vk --cluster "outbound|5000||helloworld.dev.svc.cluster.local" -o json | jq '.[].hostStatuses[] | [.address, .priority]'
[
{
"socketAddress": {
"address": "172.22.0.30",
"portValue": 5000
}
},
null
]
[
{
"socketAddress": {
"address": "10.0.139.33",
"portValue": 32652
}
},
1
]
我们线下和云上的两个集群, Node 之间是互通的,
所以并不一定需要将 Network Gateway 的 Service 设置为 LoadBalancer.
当你选择直接使用 NodePort 时, Istio 允许你通过注解 traffic.istio.io/nodeSelector
来申明对应的节点.
但是需要注意两点:
ExternalIP 这个要求基本使得这个方案不需要任何的操作性. 换一个思路, 我们选择了部分固定的节点, 将对应 IP 直接设置为 Service 的 externalIPs, 这时候将 traffic.istio.io/nodeSelector 设置为 {} 即可.
和大部分 k8s 使用者一样, 我们依赖 CoreDNS 提供的域名解析服务, 而 Istio 并没有直接解决这个问题. 已有的一些解决方案在复杂度和成熟度上并没有特别令人满意, 所以我们直接土法炼钢, 在 CI/CD 流程中直接同步 Service 到所有集群.
1
GopherDaily OP up 下
|