服务网格ASM为应用服务提供了跨地域流量分布和跨地域故障转移能力。跨地域流量分布功能可以将流量按照设定的权重路由至多个集群,实现多地域负载均衡。跨地域故障转移功能可以在某地域服务发生故障时,将该地域流量转移至其他地域,实现跨地域容灾。本文以Bookinfo应用为例,介绍如何使用跨地域故障转移和流量分布能力实现跨地域容灾和流量负载均衡。

网络规划

在进行操作前,您需要对vSwitch、VPC和集群的网段、名称等信息进行规划,本文规划如下:
说明 关于数据平面多集群地址规划的详细介绍,请参见多集群网络规划
  • vSwich和VPC的网络规划
    • vSwitch网络规划
      重要 为了避免使用CEN打通VPC网络后产生路由冲突,两个vSwitch不能使用相同的网段。
      对象vSwitch名称VPCIPv4网段
      集群vpc-hangzhou-switch-1vpc-hangzhou20.0.0.0/16
      vpc-shanghai-switch-1vpc-shanghai21.0.0.0/16
      服务网格vpc-hangzhou-switch-2vpc-hangzhou2192.168.0.0/24
    • VPC网络规划
      对象VPC名称RegionIPv4网段
      集群vpc-hangzhoucn-hangzhou20.0.0.0/8
      vpc-shanghaicn-shanghai21.0.0.0/8
      服务网格vpc-hangzhou2cn-hangzhou192.168.0.0/16
  • 集群的Pod和Service网络规划
    集群名称RegionVPCPod CIDRService CIDR
    ack-hangzhoucn-hangzhouvpc-hangzhou10.0.0.0/16172.16.0.0/16
    ack-shanghaicn-shanghaivpc-shanghai10.1.0.0/16172.17.0.0/16

步骤一:创建不同地域的集群

  1. 按照以上规划创建2个杭州和上海地域vSwitch,然后使用vSwitch创建VPC。具体操作,请参见创建交换机创建专有网络和交换机
  2. 使用上文创建的VPC和网络规划创建杭州和上海地域的集群。具体操作,请参见创建Kubernetes托管版集群
  3. 根据以上规划创建1个杭州地域的ASM实例。具体操作,请参见创建ASM实例

步骤二:使用CEN实现跨地域VPC网络互通

使用CEN,打通集群之间的VPC网络,以及集群和服务网格之间的VPC网络。

  1. 创建CEN实例。
    说明 本文使用旧版云企业网控制台创建CEN实例,您可以在云企业网控制台右上角单击回到旧版,使用旧版云企业网控制台。
    1. 登录云企业网控制台
    2. 云企业网实例页面,单击创建云企业网实例
    3. 创建云企业网实例面板设置参数,然后单击确定
      参数描述
      名称输入实例名称。

      名称长度为2~128个字符,以英文字母或中文开头,可包含数字、下划线(_)和短划线(-)。

      描述输入实例描述信息。
      实例类型实例类型,本文选择专有网络(VPC)。
      地域选择所选实例的地域,本文选择华东1(杭州)。
      网络实例选择要加载的实例,选择上文创建的杭州地域的VPC。
  2. 加载网络实例。
    1. 添加上海地域集群的VPC。
      1. 企业网实例页面,找到上文创建的云企业网实例,单击实例ID。
      2. 网络实例管理页签,单击加载网络实例
      3. 加载网络实例面板同账号页签下设置参数,然后单击确定
        参数描述
        实例类型实例类型,本文选择专有网络(VPC)
        地域选择所选实例的地域,本文选择华东2(上海)。
        网络实例选择要加载的实例,选择上文创建的上海地域的VPC。
    2. 添加杭州地域服务网格的VPC。
      1. 网络实例管理页签,单击加载网络实例
      2. 加载网络实例面板同账号页签下设置参数,然后单击确定
        参数描述
        实例类型实例类型,本文选择专有网络(VPC)
        地域选择所选实例的地域,本文选择华东1(杭州)。
        网络实例选择要加载的实例,选择上文创建的杭州地域的服务网格VPC。
  3. 购买带宽包,具体操作,请参见购买带宽包
  4. 设置跨地域互通带宽。
    1. 企业网实例页面,找到上文创建的云企业网实例,单击实例ID。
    2. 单击跨地域互通带宽管理页签,然后单击设置跨地域带宽
    3. 设置跨地域带宽面板设置带宽包为上文购买的带宽包,互通地域为华东2(上海)到华东1(杭州),然后单击确定
  5. 添加安全组规则。
    在两个集群的安全组内添加对方集群的Pod网络CIDR,允许对方集群的Pod网段地址访问本机。
    1. 登录容器服务管理控制台
    2. 集群列表页面单击ack-shanghai集群右侧操作列下的详情
    3. 集群信息页面单击基本信息页签。
      查看ack-shanghai集群的Pod网络CIDR,然后返回集群列表页面。
    4. 集群列表页面单击ack-hangzhou集群右侧操作列下的详情
    5. 集群信息页面单击集群资源页签,然后单击安全组右侧的安全组ID。
    6. 安全组详情页签入方向下单击手动添加
    7. 设置协议类型全部为ack-shanghai集群的Pod网络CIDR,其他为默认值,然后单击操作列下的保存
    8. 重复执行以上步骤,查看ack-hangzhou集群的Pod网络CIDR,然后在ack-shanghai集群的安全组中,添加ack-hangzhou集群的Pod网络CIDR。

步骤三:将Pod路由信息发布至CEN

  1. 登录容器服务管理控制台
  2. 集群列表页面单击ack-hangzhou集群右侧操作列下的详情
  3. 在集群信息页面单击集群资源页签,然后单击虚拟专有网络VPC右侧VPC ID。
  4. 在VPC的详情页面路由器基本信息区域查看路由器ID。
  5. 在专有网络控制台左侧导航栏单击路由表
  6. 路由表页面找到上文获取的路由器ID的路由实例名称,然后单击路由实例名称。
  7. 路由条目列表页签下单击自定义
  8. 单击Pod CIDR网段的子网段右侧的发布,本文为10.45.0.0/16的子网段。
  9. 发布路由的对话框单击确定
  10. 重复执行以上步骤,将ack-shanghai集群的Pod路由信息发布至CEN。
  11. 验证ack-hangzhou和ack-shanghai集群Pod路由信息是否都发布至CEN。
    在杭州地域路由表详情页面路由条目列表页签下单击动态。可以看到ack-shanghai集群PodCIDR的路由信息和vpc-shanghai-switch-1的IPv4网段的路由信息。在上海地域路由表详情页面也可以看到ack-hangzhou集群PodCIDR的路由信息和vpc-hangzhou-switch-1的IPv4网段的路由信息。说明ack-hangzhou和ack-shanghai集群Pod路由信息都发布至CEN。

步骤四:添加集群到ASM实例。

添加杭州地域和上海地域的集群到同一个ASM实例中。

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏,选择集群与工作负载管理 > Kubernetes集群,然后在右侧页面,单击添加
  5. 添加集群面板,选中杭州地域的集群,然后单击确定
  6. 重要提示对话框中单击确定
  7. 重复执行以上步骤,将上海地域的集群添加到该ASM实例中。

步骤五:在ASM中配置入口网关

  1. 查看ack-shanghai集群的ID。
    1. 登录容器服务管理控制台
    2. 集群列表页面单击ack-shanghai集群右侧操作列下的详情
    3. 在集群信息页面单击基本信息页签。
      基本信息区域查看集群ID。
  2. 登录ASM控制台
  3. 在左侧导航栏,选择服务网格 > 网格管理
  4. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  5. 在网格详情页面左侧导航栏,单击ASM网关,然后在右侧页面,单击创建
  6. 设置部署集群为ack-hangzhou集群,负载均衡类型公网访问,选择负载均衡类型,其他采用默认设置。然后单击创建
  7. ASM网关页面单击ingressgateway右侧操作列下的YAML
  8. 编辑面板补充ack-shanghai集群的ID,然后单击确定
    spec:
      clusterIds:
        - ack-hangzhou cluster-id
        - ack-shanghai cluster-id 

步骤六:部署演示应用BookInfo

  1. 使用kubectl连接到ack-hangzhou集群。具体操作,请参见获取集群KubeConfig并通过kubectl工具连接集群
  2. 使用以下内容,创建ack-hangzhou-k8s.yaml
    # Details service
    apiVersion: v1
    kind: Service
    metadata:
      name: details
      labels:
        app: details
        service: details
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: details
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-details
      labels:
        account: details
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: details-v1
      labels:
        app: details
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: details
          version: v1
      template:
        metadata:
          labels:
            app: details
            version: v1
        spec:
          serviceAccountName: bookinfo-details
          containers:
          - name: details
            image: docker.io/istio/examples-bookinfo-details-v1:1.16.2
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            securityContext:
              runAsUser: 1000
    ---
    # Ratings service
    apiVersion: v1
    kind: Service
    metadata:
      name: ratings
      labels:
        app: ratings
        service: ratings
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: ratings
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-ratings
      labels:
        account: ratings
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ratings-v1
      labels:
        app: ratings
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: ratings
          version: v1
      template:
        metadata:
          labels:
            app: ratings
            version: v1
        spec:
          serviceAccountName: bookinfo-ratings
          containers:
          - name: ratings
            image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            securityContext:
              runAsUser: 1000
    ---
    # Reviews service
    apiVersion: v1
    kind: Service
    metadata:
      name: reviews
      labels:
        app: reviews
        service: reviews
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: reviews
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-reviews
      labels:
        account: reviews
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: reviews-v1
      labels:
        app: reviews
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: reviews
          version: v1
      template:
        metadata:
          labels:
            app: reviews
            version: v1
        spec:
          serviceAccountName: bookinfo-reviews
          containers:
          - name: reviews
            image: docker.io/istio/examples-bookinfo-reviews-v1:1.16.2
            imagePullPolicy: IfNotPresent
            env:
            - name: LOG_DIR
              value: "/tmp/logs"
            ports:
            - containerPort: 9080
            volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: wlp-output
              mountPath: /opt/ibm/wlp/output
            securityContext:
              runAsUser: 1000
          volumes:
          - name: wlp-output
            emptyDir: {}
          - name: tmp
            emptyDir: {}
    ---
    # Productpage services
    apiVersion: v1
    kind: Service
    metadata:
      name: productpage
      labels:
        app: productpage
        service: productpage
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: productpage
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-productpage
      labels:
        account: productpage
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: productpage-v1
      labels:
        app: productpage
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: productpage
          version: v1
      template:
        metadata:
          labels:
            app: productpage
            version: v1
        spec:
          serviceAccountName: bookinfo-productpage
          containers:
          - name: productpage
            image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            volumeMounts:
            - name: tmp
              mountPath: /tmp
            securityContext:
              runAsUser: 1000
          volumes:
          - name: tmp
            emptyDir: {}
    ---
    	  
  3. 执行以下命令,在ack-hangzhou集群部署BookInfo应用。
    kubectl apply -f ack-hangzhou-k8s.yaml
  4. 使用kubectl连接到ack-shanghai集群。具体操作,请参见获取集群KubeConfig并通过kubectl工具连接集群
    说明 使用kubectl连接到ack-shanghai集群时,您需要将ack-hangzhou集群的kubeconfig切换成ack-shanghai集群的kubeconfig。
  5. 使用以下内容,创建ack-shanghai.yaml
    # Details service
    apiVersion: v1
    kind: Service
    metadata:
      name: details
      labels:
        app: details
        service: details
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: details
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-details
      labels:
        account: details
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: details-v1
      labels:
        app: details
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: details
          version: v1
      template:
        metadata:
          labels:
            app: details
            version: v1
        spec:
          serviceAccountName: bookinfo-details
          containers:
          - name: details
            image: docker.io/istio/examples-bookinfo-details-v1:1.16.2
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            securityContext:
              runAsUser: 1000
    ---
    # Ratings service
    apiVersion: v1
    kind: Service
    metadata:
      name: ratings
      labels:
        app: ratings
        service: ratings
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: ratings
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-ratings
      labels:
        account: ratings
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ratings-v1
      labels:
        app: ratings
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: ratings
          version: v1
      template:
        metadata:
          labels:
            app: ratings
            version: v1
        spec:
          serviceAccountName: bookinfo-ratings
          containers:
          - name: ratings
            image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            securityContext:
              runAsUser: 1000
    ---
    # Reviews service
    apiVersion: v1
    kind: Service
    metadata:
      name: reviews
      labels:
        app: reviews
        service: reviews
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: reviews
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-reviews
      labels:
        account: reviews
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: reviews-v2
      labels:
        app: reviews
        version: v2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: reviews
          version: v2
      template:
        metadata:
          labels:
            app: reviews
            version: v2
        spec:
          serviceAccountName: bookinfo-reviews
          containers:
          - name: reviews
            image: docker.io/istio/examples-bookinfo-reviews-v2:1.16.2
            imagePullPolicy: IfNotPresent
            env:
            - name: LOG_DIR
              value: "/tmp/logs"
            ports:
            - containerPort: 9080
            volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: wlp-output
              mountPath: /opt/ibm/wlp/output
            securityContext:
              runAsUser: 1000
          volumes:
          - name: wlp-output
            emptyDir: {}
          - name: tmp
            emptyDir: {}
    ---
    # Productpage services
    apiVersion: v1
    kind: Service
    metadata:
      name: productpage
      labels:
        app: productpage
        service: productpage
    spec:
      ports:
      - port: 9080
        name: http
      selector:
        app: productpage
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: bookinfo-productpage
      labels:
        account: productpage
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: productpage-v1
      labels:
        app: productpage
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: productpage
          version: v1
      template:
        metadata:
          labels:
            app: productpage
            version: v1
        spec:
          serviceAccountName: bookinfo-productpage
          containers:
          - name: productpage
            image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            volumeMounts:
            - name: tmp
              mountPath: /tmp
            securityContext:
              runAsUser: 1000
          volumes:
          - name: tmp
            emptyDir: {}
    ---
    	  
  6. 执行以下命令,在ack-shanghai集群部署BookInfo应用。
    kubectl apply -f ack-shanghai.yaml
  7. 使用kubectl连接到ASM。具体操作,请参见通过控制面kubectl访问Istio资源
    说明 使用kubectl连接到ASM时,您需要将ack-shanghai集群的kubeconfig切换成ASM的kubeconfig。
  8. 使用以下内容,创建asm.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: bookinfo-gateway
    spec:
      selector:
        istio: ingressgateway # use istio default controller
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "*"
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: bookinfo
    spec:
      hosts:
      - "*"
      gateways:
      - bookinfo-gateway
      http:
      - match:
        - uri:
            exact: /productpage
        - uri:
            prefix: /static
        - uri:
            exact: /login
        - uri:
            exact: /logout
        - uri:
            prefix: /api/v1/products
        route:
        - destination:
            host: productpage
            port:
              number: 9080
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: productpage
    spec:
      host: productpage
      subsets:
      - name: v1
        labels:
          version: v1
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: reviews
    spec:
      host: reviews
      subsets:
      - name: v1
        labels:
          version: v1
      - name: v2
        labels:
          version: v2
      - name: v3
        labels:
          version: v3
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: ratings
    spec:
      host: ratings
      subsets:
      - name: v1
        labels:
          version: v1
      - name: v2
        labels:
          version: v2
      - name: v2-mysql
        labels:
          version: v2-mysql
      - name: v2-mysql-vm
        labels:
          version: v2-mysql-vm
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: details
    spec:
      host: details
      subsets:
      - name: v1
        labels:
          version: v1
      - name: v2
        labels:
          version: v2
    ---
    	  
  9. 执行以下命令,在ASM创建路由规则。
    kubectl apply -f asm.yaml
  10. 验证Bookinfo应用是否部署成功。
    1. 登录容器服务管理控制台
    2. 在控制台左侧导航栏,单击集群
    3. 集群列表页面单击ack-hangzhou集群右侧操作列下的详情
    4. 在集群管理页左侧导航栏,选择网络 > 服务
    5. 服务页面顶部设置命名空间为Istio-system,查看istio-ingressgateway右侧外部端点下端口为80的IP地址。
    6. 在浏览器地址栏中输入入口网关IP地址/productpage
      多次刷新页面,可以看到以下图片轮流出现。review1review1

步骤七:使用跨地域流量分布和跨地域故障转移

设置跨地域流量分布

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏,选择网格实例 > 基本信息,然后在右侧页面,单击功能设置
  5. 基本信息页面单击跨地域负载均衡右侧的启用跨地域流量分布
    说明 如果您已经启用跨地域故障转移,您需要禁用跨地域故障转移后,才能启用跨地域流量分布。
  6. 跨地域流量分布对话框设置策略为cn-hangzhou,然后单击新建策略
  7. 单击展开图标,然后单击添加图标,设置目标为cn-hangzhou,权重为90%。
  8. 单击添加图标,设置目标为cn-shanghai,权重为10%,单击确认
  9. 执行以下命令,循环请求10次BookInfo应用,验证跨地域流量分布是否成功。
    for ((i=1;i<=10;i++));do curl http://<ack-hangzhou集群中80端口的入口网关地址>/productpage 2>&1|grep full.stars;done

    预期输出:

    <!-- full stars: -->
    <!-- full stars: -->

    可以看到访问10次,输出2行full stars,说明10次请求中9次路由到了ack-hangzhou集群的v1版本reviews服务,1次路由到了ack-shanghai集群的v2版本reviews服务,流量按照权重路由到多个集群成功。

设置跨地域故障转移

  1. 停用ack-hangzhou集群的review。
    1. 登录容器服务管理控制台
    2. 在控制台左侧导航栏,单击集群
    3. 在集群管理页左侧导航栏,选择工作负载 > 无状态
    4. 无状态页面设置命名空间为default,单击reviews-v1右侧操作列下的伸缩
    5. 伸缩页面设置所需容器组数量为0,然后单击确定
  2. 配置DestinationRule。
    配置DestinationRule,当1s内无法请求到reviews服务,该reviews服务将被移除1min。
    1. 登录ASM控制台
    2. 在左侧导航栏,选择服务网格 > 网格管理
    3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
    4. 在网格详情页面左侧导航栏,选择流量管理中心 > 目标规则
    5. 目标规则页面单击reviews右侧操作列下的YAML
    6. 编辑面板增加以下内容,然后单击确定
      spec:
        ......
        trafficPolicy:
          connectionPool:
            http:
              maxRequestsPerConnection: 1
          outlierDetection:
            baseEjectionTime: 1m
            consecutive5xxErrors: 1
            interval: 1s
      • maxRequestsPerConnection:最大连接数量。
      • baseEjectionTime:最小的移除时间长度。
      • consecutive5xxErrors:连续错误数量。
      • interval:移除检测的时间间隔。
  3. 启用跨地域故障转移。
    1. 在网格详情页面左侧导航栏,选择网格实例 > 基本信息
    2. 基本信息页面单击跨地域负载均衡右侧的启用跨地域故障转移
      说明 如果您已经启用跨地域流量分布,您需要禁用跨地域流量分布后,才能启用跨地域故障转移。
    3. 跨地域故障转移对话框中设置当策略源是cn-shanghai,源故障转移至cn-hangzhou,当策略源是cn-hangzhou,源故障转移至cn-shanghai,然后单击确认
  4. 执行以下命令,循环请求10次BookInfo应用,并对路由到v2版本reviews服务的结果数量进行统计。
    for ((i=1;i<=10;i++));do curl http://<ack-hangzhou集群中80端口的入口网关地址>/productpage 2>&1|grep full.stars;done|wc -l

    预期输出:

    20

    可以看到访问10次,返回20(每次路由到v2版本reviews服务会返回两行包含full stars的结果),说明10次请求全部路由到了ack-shanghai集群的v2版本reviews服务,跨地域故障转移成功。

FAQ

为什么通过云企业网CEN打通了集群VPC网络,在ASM中加入K8s集群仍然提示失败?

当您的集群处于不同地域,在打通集群VPC网络时,您若未购买跨地域流量包,或者正确设置跨地域流量,ASM控制平面将无法连接到数据平面集群,导致ASM添加集群失败。

针对以上这个问题,您需要重新在云企业网CEN中设置跨地域流量打通集群VPC网络,具体操作,请参见步骤二:使用CEN实现跨地域VPC网络互通