V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
GopherDaily
V2EX  ›  Kubernetes

基于 istio 的 k8s 多集群实践

  •  1
     
  •   GopherDaily · 2023-04-05 17:00:34 +08:00 · 1829 次点击
    这是一个创建于 589 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设我们在阿里云上有一个托管的 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 的多集群方案

    Istio 在文档中介绍了多种多集群的方案, 我们选择的是网络隔离下的多主, Install Multi-Primary on different networks.

    Multiple primary clusters on separate networks

    多主架构中, 每个集群都拥有独立的 control plane (istiod) 来管理本集群的 mesh. 相较于单集群架构的主要区别在于:

    • 每个集群的 istiod 都同时监听所有集群的资源变化.
    • 集群之间的访问需要通过 Network Gateway.

    对熟悉 Istio 的同学来说, 上图的架构应该是非常容易理解的, 唯一需要深入探究的应该是 Network Gateway 相关内容. Istio 通过几个方面实现了网络隔离下的跨集群调用:

    • 需要用户创建 Ingress Gateway 并指定暴露给其他集群的服务.
    • Istio 在向 Envoy 推送 Endpoint 之前, 将非本集群的地址替换为对应的 Network Gateway.
    • 在发送给 Network Gateway 的请求中, 通过 SNI 记录目标服务.

    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 到所有集群.

    GopherDaily
        1
    GopherDaily  
    OP
       2023-04-06 13:08:39 +08:00
    up 下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5545 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 06:45 · PVG 14:45 · LAX 22:45 · JFK 01:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.