zoukankan      html  css  js  c++  java
  • Istio多集群(1)-多控制面

    Istio多集群(1)-多控制面

    参考自官方文档

    复制控制面

    本节将使用多个主集群(带控制面的集群)来部署Istio多集群,每个集群都有自己的控制面,集群之间使用gateway进行通信。

    由于不使用共享的控制面来管理网格,因此这种配置下,每个集群都有自己的控制面来管理后端应用。为了策略执行和安全目的,所有的群集都处于一个公共的管理控制之下。

    通过复制共享服务和命名空间,并在所有集群中使用一个公共的根CA证书,可以实现单个Istio服务网格跨集群通信。

    要求

    • 两个或多个kubernees集群,版本为1.17,1.18,1.19
    • 在每个kubernetes集群上部署Istio控制面
    • 每个集群中的istio-ingressgateway的服务IP地址必须能够被所有的集群访问,理想情况下,使用L4网络负载平衡器(NLB)。
    • 根CA。跨集群通信需要在服务之间使用mutual TLS。为了在跨集群通信时启用mutual TLS,每个集群的Istio CA必须配置使用共享的CA证书生成的中间CA。出于演示目的,将会使用Istio的samples/certs安装目录下的根CA证书。

    在每个集群中部署Istio控制面

    1. 使用自定义的根CA为每个集群生成中间CA证书,使用共享的根CA来为跨集群的通信启用mutual TLS。

      出于演示目的,下面使用了istio样例目录中的证书。在真实部署时,应该为每个集群选择不同的CA证书,这些证书由一个共同的根CA签发。

    2. 在每个集群中运行如下命令来为所有集群部署相同的Istio控制面。

      • 为生成的CA创建一个kubernetes secret。它与在Istio中插入自定义的CA一文中的方式类似。

        生产中不能使用samples 目录中的证书,有安全风险。

        $ kubectl create namespace istio-system
        $ kubectl create secret generic cacerts -n istio-system 
            --from-file=samples/certs/ca-cert.pem 
            --from-file=samples/certs/ca-key.pem 
            --from-file=samples/certs/root-cert.pem 
            --from-file=samples/certs/cert-chain.pem
        
      • 部署Istio,部署后会在istio-system命名空间中创建一个pod istiocoredns,用于提供到global域的DNS解析,其配置文件如下:

        # cat Corefile
        .:53 {
              errors
              health
        
              # Removed support for the proxy plugin: https://coredns.io/2019/03/03/coredns-1.4.0-release/
              grpc global 127.0.0.1:8053
              forward . /etc/resolv.conf {
                except global
              }
        
              prometheus :9153
              cache 30
              reload
            }
        
        $ istioctl install -f manifests/examples/multicluster/values-istio-multicluster-gateways.yaml
        

    配置DNS

    当为远端集群中的服务提供DNS解析时,现有应用程序无需修改即可运行,因为应用程序通常会访问通过DNS解析出的IP。Istio本身并不需要DNS在服务之间路由请求。本地服务会共享一个共同的DNS前缀(即,svc.cluster.local)。kubernetes DNS为这些服务提供了DNS解析。

    为了给远端集群提供一个类似的服务配置,需要使用格式<name>.<namespace>.global来命名远端集群中的服务。Istio附带了一个Core DNS服务,可以为这些服务提供DNS解析。为了使用该DNS,kubernetes的DNS必须配置为.global的域名存根。

    在每个需要调用远程的服务的集群中创建或更新一个现有的k8s的ConfigMap,本环境中使用的coredns为1.7.0版本,使用的配置文件如下:

    注意不能直接采用官方配置文件,可能会因为不同版本的配置原因导致k8s的coredns无法正常启动。正确做法是在kube-system命名空间下获取k8s coredns的configmap配置,然后在后面追加global域有关的配置即可。

    另外使用如下命令apply之后,k8s的coredns可能并不会生效,可以手动重启k8s的dns pod来使其生效。注意如下配置需要在cluster1和cluster2中同时生效。

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns
      namespace: kube-system
    data:
      Corefile: |
        .:53 { #k8s的coredns的原始配置
            errors
            health {
               lameduck 5s
            }
            ready
            kubernetes cluster.local in-addr.arpa ip6.arpa {
               pods insecure
               fallthrough in-addr.arpa ip6.arpa
               ttl 30
            }
            prometheus :9153
            forward . /etc/resolv.conf {
               max_concurrent 1000
            }
            cache 30
            loop
            reload
            loadbalance
        }
        global:53 { #新增了对global的解析,将其转发到istio-system下面的istiocoredns服务,即上面istio创建的coredns
            errors
            cache 30
            forward . $(kubectl get svc -n istio-system istiocoredns -o jsonpath={.spec.clusterIP}):53
        }
    EOF
    

    在创建第4步的serviceEntry之后可以使用如下方式判断cluster1的DNS解析是否正确:

    • 首先在cluster1中的sleep容器中通过istio的coredns解析httpbin.bar.global,10.96.199.197为istio的coredns的service

      # nslookup -type=a httpbin.bar.global 10.96.199.197
      Server:         10.96.199.197
      Address:        10.96.199.197:53
      
      Name:   httpbin.bar.global
      Address: 240.0.0.2 #解析成功
      
    • 在cluster1中的sleep容器中通过k8s的coredns解析httpbin.bar.global,10.96.0.10为k8s的coredns的service,可以看到k8s的coredns将httpbin.bar.global的解析转发给了istio的coredns,并且解析成功。

      # nslookup -type=a httpbin.bar.global 10.96.0.10
      Server:         10.96.0.10
      Address:        10.96.0.10:53
      
      Name:   httpbin.bar.global
      Address: 240.0.0.2 #解析成功
      

    DNS的解析路径为:sleep容器的/etc/resolv.conf-->k8s coredns-->istio coredns-->istio-coredns-plugin

    istio-coredns-plugin是istio的CoreDNS gRPC插件,用于从Istio ServiceEntries中提供DNS记录。(注:该插件将集成到Istio 1.8的sidecar中,后续将会被废弃)

    可以在istio-coredns-plugin的log日志中查看到对global域的操作:

    # crictl inspect e8d3f73c4d38d|grep "logPath"
        "logPath": "/var/log/pods/istio-system_istiocoredns-75dd7c7dc8-cg55l_8c960b74-419c-44e8-8992-58293e36d6fd/istio-coredns-plugin/0.log"
    

    例如该插件会读取下面创建的到httpbin.bar.globalServiceEntries,并将其做DNS映射:

    ... info    Reading service entries at 2020-10-09 17:53:38.710306063 +0000 UTC m=+19500.216843506
    ... info    Have 1 service entries
    ... info    adding DNS mapping: httpbin.bar.global.->[240.0.0.2]
    

    配置应用服务

    一个集群中的服务如果需要被远端集群访问,就需要在远端集群中配置一个ServiceEntry。service entry使用的host格式为<name>.<namespace>.global,name和namespace分别对应服务的name和namespace。

    为了演示跨集群访问,在一个集群中配置sleep服务,使用该服务访问另一个集群中的httpbin服务。

    • 选择两个Istio集群,分别为cluster1 cluster2

    • 使用如下命令列出集群的上下文:

      # kubectl config get-contexts
      CURRENT   NAME            CLUSTER         AUTHINFO        NAMESPACE
      *         kind-cluster1   kind-cluster1   kind-cluster1
                kind-cluster2   kind-cluster2   kind-cluster2
      
    • 使用环境变量保存集群的上下文名称:

      # export CTX_CLUSTER1=$(kubectl config view -o jsonpath='{.contexts[0].name}')
      # export CTX_CLUSTER2=$(kubectl config view -o jsonpath='{.contexts[1].name}')
      # echo "CTX_CLUSTER1 = ${CTX_CLUSTER1}, CTX_CLUSTER2 = ${CTX_CLUSTER2}"
      CTX_CLUSTER1 = kind-cluster1, CTX_CLUSTER2 = kind-cluster2
      
    配置用例服务
    1. cluster1集群中部署sleep应用

      $ kubectl create --context=$CTX_CLUSTER1 namespace foo
      $ kubectl label --context=$CTX_CLUSTER1 namespace foo istio-injection=enabled
      $ kubectl apply --context=$CTX_CLUSTER1 -n foo -f samples/sleep/sleep.yaml
      $ export SLEEP_POD=$(kubectl get --context=$CTX_CLUSTER1 -n foo pod -l app=sleep -o jsonpath={.items..metadata.name})
      
    2. cluster2集群中部署httpbin应用

      $ kubectl create --context=$CTX_CLUSTER2 namespace bar
      $ kubectl label --context=$CTX_CLUSTER2 namespace bar istio-injection=enabled
      $ kubectl apply --context=$CTX_CLUSTER2 -n bar -f samples/httpbin/httpbin.yaml
      
    3. 暴露cluster2的网关地址

      本地部署的kubernetes由于没有loadBalancer,因此使用nodeport方式(如果使用kind部署kubernetes,此时需要手动修改service istio-ingressgateway的nodeport,使其与kind暴露的端口一致)。

      export INGRESS_PORT=$(kubectl -n istio-system --context=$CTX_CLUSTER2 get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
      

      INGRESS_HOST的获取方式如下

      export INGRESS_HOST=$(kubectl --context=$CTX_CLUSTER2 get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
      
    4. 为了允许cluster1中的sleep访问cluster2中的httpbin,需要在cluster1中为httpbin创建一个service entry。service entry的host名称的格式应该为<name>.<namespace>.global,name和namespace分别对应远端服务的name和namespace。

      为了让DNS解析.global域下的服务,需要给这些服务分配虚拟IP地址。

      每个.global DNS域下的服务都必须在集群中拥有唯一的虚拟IP。

      如果global服务已经有了实际的VIPs,那么可以直接使用这类地址,否则建议使用范围为240.0.0.0/4的E类IP地址。应用使用这些IP处理流量时,流量会被sidecar捕获,并路由到合适的远端服务。

      不能使用多播地址(224.0.0.0 ~ 239.255.255.255),因为默认情况下不会有到达这些地址的路由。同时也不能使用环回地址(127.0.0.0/8),因为发往该地址的流量会被重定向到sidecar的inbound listener。

      $ kubectl apply --context=$CTX_CLUSTER1 -n foo -f - <<EOF
      apiVersion: networking.istio.io/v1alpha3
      kind: ServiceEntry
      metadata:
        name: httpbin-bar
      spec:
        hosts: #外部服务的主机名
        # must be of form name.namespace.global
        - httpbin.bar.global
        # Treat remote cluster services as part of the service mesh
        # as all clusters in the service mesh share the same root of trust.
        location: MESH_INTERNAL #标注为网格内的服务,使用mTLS交互
        ports: # 对端bar服务的端口
        - name: http1
          number: 8000
          protocol: http
        resolution: DNS #使用DNS服务器进行域名解析,endpoints的address字段可能是一个域名
        addresses: # 下面指定了httpbin.bar.global:8000服务对应的一个后端${INGRESS_HOST}:30615
        # the IP address to which httpbin.bar.global will resolve to
        # must be unique for each remote service, within a given cluster.
        # This address need not be routable. Traffic for this IP will be captured
        # by the sidecar and routed appropriately.
        - 240.0.0.2 # host对应的虚拟地址,必须包含,否则istio-coredns-plugin无法进行DNS解析
        endpoints:
        # This is the routable address of the ingress gateway in cluster2 that
        # sits in front of sleep.foo service. Traffic from the sidecar will be
        # routed to this address.
        - address: ${INGRESS_HOST} # 将其替换为对应的node地址值即可
       ports:
            http1: 30001 # 替换为对应的nodeport
      EOF
      

      由于使用了nodeport方式,因此需要使用容器15443端口对应的nodeport端口,使用如下方式获取:

      # kubectl --context=$CTX_CLUSTER2 get svc -n istio-system istio-ingressgateway -o=jsonpath='{.spec.ports[?(@.port==15443)].nodePort}'
      30001
      

      上述配置会将cluster1httpbin.bar.global服务的所有端口上的流量(通过mutual TLS)路由到$INGRESS_HOST:15443

      网关的15443端口是一个感知SNI的Envoy配置,在安装Istio控制面时部署。到达15443端口的流量会在目标集群的内部服务的pod上进行负载均衡(即cluster2httpbin.bar)。

      下面是从cluster1的sleep中导出的istio-proxy配置,可以看到httpbin.bar.global的后端为172.18.0.5:30615,即$INGRESS_HOST:$NODE_PORT

      "cluster": {
      "load_assignment": {
       "cluster_name": "outbound|8000||httpbin.bar.global",
       "endpoints": [
        {
         "locality": {},
         "lb_endpoints": [
          {
           "endpoint": {
            "address": {
             "socket_address": {
              "address": "172.18.0.4",
              "port_value": 30001
             }
            }
           },
           "load_balancing_weight": 1
          }
         ],
         "load_balancing_weight": 1
        }
       ]
      },
      	  ...
      },
      

      对应的路由如下,可以看到240.0.0.2只是作为了SNI的一种,将匹配到的请求转发给上面的"cluster": "outbound|8000||httpbin.bar.global"进行处理:

      "route_config": {
      "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
      "name": "8000",
      "virtual_hosts": [
       ...
       {
        "name": "httpbin.bar.global:8000",
        "domains": [
         "httpbin.bar.global",
         "httpbin.bar.global:8000",
         "240.0.0.2",
         "240.0.0.2:8000"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "cluster": "outbound|8000||httpbin.bar.global",
           ...
      },
      

      另外需要注意的是cluster1和cluster2都使用了一个Gateway和DestinationRule,对从sleep到httpbin的*.global域的请求使用mTLS进行加密,并在网关上使用AUTO_PASSTHROUGH模式,此模式会根据SNI将请求直接转发给后端应用,无需virtualservice进行绑定。

      apiVersion: networking.istio.io/v1beta1
      kind: Gateway
      spec:
      selector:
      istio: ingressgateway
      servers:
      - hosts:
        - '*.global'
        port:
          name: tls
          number: 15443
          protocol: TLS
        tls:
          mode: AUTO_PASSTHROUGH
      
      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      spec:
      host: '*.global'
      trafficPolicy:
        tls:
          mode: ISTIO_MUTUAL
      
    5. 校验可以通过sleep服务访问httpbin服务。

      # kubectl exec --context=$CTX_CLUSTER1 $SLEEP_POD -n foo -c sleep -- curl -I httpbin.bar.global:8000/headers
      
      HTTP/1.1 200 OK
      server: envoy
      date: Wed, 14 Oct 2020 22:44:00 GMT
      content-type: application/json
      content-length: 554
      access-control-allow-origin: *
      access-control-allow-credentials: true
      x-envoy-upstream-service-time: 8
      

      在官方文档中使用如上命令即可在cluster1的sleep Pod中访问cluster2的httpbin服务。但从上面分析可以看到,当SNI为httpbin.bar.global的请求到达cluster2的ingress pod上时,它会按照k8s的coredns配置将该请求转发到istio的coredns进行解析,但cluster2并没有配置httpbin.bar.global对应的serviceentry,因此,istio的coredns也无法解析该dns,返回503错误。在cluster2的istio-coredns-plugin容器的日志中可以找到如下信息:

      ... info    Query A record: httpbin.bar.global.->{httpbin.bar.global. 1 1}
      ... info    Could not find the service requested
      ... info    DNS query  ;; opcode: QUERY, status: NOERROR, id: 64168
      

      在cluster2中创建如下serviceentry:

      $ kubectl apply --context=$CTX_CLUSTER2 -n bar -f - <<EOF
      apiVersion: networking.istio.io/v1alpha3
      kind: ServiceEntry
      metadata:
      name: httpbin-bar
      spec:
      hosts:
        - httpbin.bar.global
        location: MESH_INTERNAL
        ports:
        - name: http1
          number: 8000
          protocol: http
        resolution: DNS
        addresses:
        - 240.0.0.3
        endpoints:
        - address: httpbin.bar.svc.cluster.local #httpbin的k8s service
      EOF
      

      httpbin生成的cluster如下,可以看到后端地址为httpbin.bar.svc.cluster.local,直接通过k8s的DNS即可解析该地址。

           "cluster": {
            "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
            "name": "outbound|8000||httpbin.bar.global",
            "type": "STRICT_DNS",
      	  ...
            "load_assignment": {
             "cluster_name": "outbound|8000||httpbin.bar.global",
             "endpoints": [
              {
               "locality": {},
               "lb_endpoints": [
                {
                 "endpoint": {
                  "address": {
                   "socket_address": {
                    "address": "httpbin.bar.svc.cluster.local",
                    "port_value": 8000
                   }
                  }
                 },
                 "load_balancing_weight": 1
                }
               ],
               "load_balancing_weight": 1
              }
             ]
            },
      	  ...
           },
      

      在cluster2中创建一个sleep pod,并在该pod中访问cluster2的bar命名空间下的httpbin服务,可以看到访问成功:

      # curl -I httpbin.bar.global:8000/headers
      HTTP/1.1 200 OK
      server: envoy
      date: Sat, 10 Oct 2020 12:40:23 GMT
      content-type: application/json
      content-length: 554
      access-control-allow-origin: *
      access-control-allow-credentials: true
      x-envoy-upstream-service-time: 206
      

      在cluster2的ingress pod中导出与15443端口有关的listeners配置如下,可以看到AUTO_PASSTHROUGH模式下的listener并没有通过route_config_name指定到达cluster的路由(不需要通过virtualservice进行服务映射),仅通过SNI进行请求转发。

         "dynamic_listeners": [
          {
           "name": "0.0.0.0_15443",
           "active_state": {
            "version_info": "2020-10-14T19:52:24Z/14",
            "listener": {
             "@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
             "name": "0.0.0.0_15443",
             "address": {
              "socket_address": {
               "address": "0.0.0.0",
               "port_value": 15443
              }
             },
             "filter_chains": [
              {
               "filter_chain_match": {
                "server_names": [
                 "*.global"
                ]
               },
               "filters": [
                ...
                {
                 "name": "istio.stats",
                 ...
                },
                {
                 "name": "envoy.filters.network.tcp_proxy",
                 "typed_config": {
                  "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
                  "stat_prefix": "BlackHoleCluster",
                  "cluster": "BlackHoleCluster"
                 }
                }
               ]
              }
             ],
             "listener_filters": [
              {
               ...
             ],
             "traffic_direction": "OUTBOUND"
            },
            "last_updated": "2020-10-14T19:53:22.089Z"
           }
          }
         ]
      
    卸载
    $ kubectl delete --context=$CTX_CLUSTER1 -n foo -f samples/sleep/sleep.yaml
    $ kubectl delete --context=$CTX_CLUSTER1 -n foo serviceentry httpbin-bar
    $ kubectl delete --context=$CTX_CLUSTER1 ns foo
    
    $ kubectl delete --context=$CTX_CLUSTER2 -n bar -f samples/httpbin/httpbin.yaml
    $ kubectl delete --context=$CTX_CLUSTER2 ns bar
    
    $ unset SLEEP_POD CLUSTER2_GW_ADDR CLUSTER1_EGW_ADDR CTX_CLUSTER1 CTX_CLUSTER2
    

    FAQ

    • cluster1和cluster2通信时,需要保证cluster1和cluster2的根证书是相同的。可以通过对比cluster1的sleep和cluster2的httpbin导出的istio sidecar的如下ROOTCA证书配置来判断是否一致。可能发生证书不一致的原因是

      • 先创建istio,后创建cacerts根证书
      • 重建istio时,没有删除之前错误的istio-system命名空间下的老的证书
      • 重建istio后,没有清理foo或bar命名空间下的pod,secret资源。

      因此在重建istio前,务必删除istio-system和foo/bar命名空间下的所有资源

         "dynamic_active_secrets": [
          {
           "name": "default",
           "secret": {
            "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
            "name": "default",
            ...
           }
          },
          {
           "name": "ROOTCA",
           "secret": {
            "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
            "name": "ROOTCA",
            "validation_context": {
             "trusted_ca": {
              "inline_bytes": "LS0tLxxx="
             }
            }
           }
          }
         ]
      

      参考:

    • Using CoreDNS to Conceal Network Identities of Services in Istio

  • 相关阅读:
    HDU 2544 最短路
    HDU 3367 Pseudoforest
    USACO 2001 OPEN
    HDU 3371 Connect the Cities
    HDU 1301 Jungle Roads
    HDU 1879 继续畅通工程
    HDU 1233 还是畅通工程
    HDU 1162 Eddy's picture
    HDU 5745 La Vie en rose
    HDU 5744 Keep On Movin
  • 原文地址:https://www.cnblogs.com/charlieroro/p/13729480.html
Copyright © 2011-2022 走看看