Think Deep,Work Lean

Istio之XDS讲解

Posted on By zack

Pod注入envoy sidecar之后,会通过pilot-agent grpc协议来访问pilot,pilot通过xds协议向envoy推送配置文件

什么是XDS?

LDS: 监听发现服务

RDS: 路由发现服务

CDS: 集群发现服务

SDS: 密钥发现服务

EDS: 端点发现服务

ADS: 聚合发现服务,pilot与envoy通过grpc连接,pilot检测到配置有更新时,会向enovy同步LDS/CDS/RDS/SDS配置,但是它们的配置同步是有顺序的,ADS对这些配置更新API聚合并排序。仅适用于grpc流,不适合rest。

xds

通过istioctl来查看xds信息

部署bookinfo应用示例后,会有以下Pod

# kubectl get po

NAME                              READY   STATUS        RESTARTS   AGE
details-v1-7d78fc5688-9k9ch       2/2     Running       0          155m
productpage-v1-844495cb4b-8dkxz   2/2     Running       0          156m
ratings-v1-55ccf46fb4-wg7xb       2/2     Running       0          155m
reviews-v1-68bb7b8c4f-lrjm5       2/2     Running       0          10d

查看proxy status,会显示xds同步信息,如果非SYSCED说明有问题,一般是网络问题导致Pilot不能顺利将配置下发

istioctl ps
NAME                                                                  CDS        LDS        EDS        RDS        PILOT                              VERSION
details-v1-65d64b5965-zsr2n.ns1                                       SYNCED     SYNCED     SYNCED     SYNCED     istiod-1-6-10-7db56f875b-fdzt8     1.6.10
details-v1-7d78fc5688-9k9ch.test                                      SYNCED     SYNCED     SYNCED     SYNCED     istiod-1-6-10-7db56f875b-fdzt8     1.6.10
details-v1-7d78fc5688-jmw6z.ns2                                       SYNCED     SYNCED     SYNCED     SYNCED     istiod-1-6-10-7db56f875b-fdzt8     1.6.10
kubesphere-router-ns2-7d448fb6b5-z77nq.kubesphere-controls-system     SYNCED     SYNCED     SYNCED     SYNCED     istiod-1-6-10-7db56f875b-fdzt8     1.6.10
...

查看Listener

# istioctl7 pc l productpage-v1-844495cb4b-8dkxz.test

ADDRESS       PORT  MATCH                                                                   DESTINATION
0.0.0.0       15001 ALL                                                                     Non-HTTP/Non-TCP
0.0.0.0       15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0                               Non-HTTP/Non-TCP
0.0.0.0       15006 Addr: 0.0.0.0/0                                                         Non-HTTP/Non-TCP
0.0.0.0       15006 Trans: tls; App: HTTP TLS; Addr: 0.0.0.0/0                              Non-HTTP/Non-TCP
0.0.0.0       15006 App: HTTP; Addr: 0.0.0.0/0                                              Non-HTTP/Non-TCP
0.0.0.0       15006 Addr: 10.233.70.26/32:15021                                             Non-HTTP/Non-TCP
0.0.0.0       15006 App: Istio HTTP Plain; Addr: 10.233.70.26/32:9080                       Non-HTTP/Non-TCP
0.0.0.0       15006 Addr: 10.233.70.26/32:9080                                              Non-HTTP/Non-TCP
0.0.0.0       15010 App: HTTP                                                               Non-HTTP/Non-TCP
0.0.0.0       15010 ALL                                                                     Non-HTTP/Non-TCP
10.233.17.226 15012 ALL                                                                     Non-HTTP/Non-TCP
0.0.0.0       15014 App: HTTP                                                               Non-HTTP/Non-TCP
0.0.0.0       15014 ALL                                                                     Non-HTTP/Non-TCP
0.0.0.0       15021 ALL                                                                     Non-HTTP/Non-TCP
0.0.0.0       15090 ALL                                                                     Non-HTTP/Non-TCP
0.0.0.0       16686 App: HTTP                                                               Non-HTTP/Non-TCP
0.0.0.0       16686 ALL                                                                     Non-HTTP/Non-TCP
10.233.0.159  19093 ALL                                                                     Non-HTTP/Non-TCP
10.233.0.159  19093 App: HTTP                                                               Non-HTTP/Non-TCP
10.233.40.24  50000 ALL                                                                     Non-HTTP/Non-TCP
10.233.40.24  50000 App: HTTP                                                               Non-HTTP/Non-TCP
...

15001端口所有流量通过

istioctl7 pc l productpage-v1-844495cb4b-8dkxz.test --port 15001 -o json | jq
[
  {
    "name": "virtualOutbound",
    "address": {
      "socketAddress": {
        "address": "0.0.0.0",
        "portValue": 15001
      }
    },
    "filterChains": [
      {
        "filters": [
          {
            "name": "istio.stats",
            "typedConfig": {
              "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
              "typeUrl": "type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm",
              "value": {
                "config": {
                  "configuration": "{\n  \"debug\": \"false\",\n  \"stat_prefix\": \"istio\"\n}\n",
                  "root_id": "stats_outbound",
                  "vm_config": {
                    "code": {
                      "local": {
                        "inline_string": "envoy.wasm.stats"
                      }
                    },
                    "runtime": "envoy.wasm.runtime.null",
                    "vm_id": "tcp_stats_outbound"
                  }
                }
              }
            }
          },
          {
            "name": "envoy.tcp_proxy",  # 主要看这个配置
            "typedConfig": {
              "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
              "statPrefix": "PassthroughCluster",
              "cluster": "PassthroughCluster"  # Cluster为PassthroughCluster,表明允许流量全部通过
            }
          }
        ],
        "name": "virtualOutbound-catchall-tcp"
      }
    ],
    "trafficDirection": "OUTBOUND",
    "hiddenEnvoyDeprecatedUseOriginalDst": true
  }
]

由于我们知道productpage的端口为9080,我们直接看下这个端口

# istioctl7 pc l productpage-v1-844495cb4b-8dkxz.test --port 9080 -o json | jq
[
  {
    "name": "0.0.0.0_9080",
    "address": {
      "socketAddress": {
        "address": "0.0.0.0",
        "portValue": 9080
      }
    },
    "filterChains": [
      {
        "filterChainMatch": {
          "applicationProtocols": [
            "http/1.0",
            "http/1.1",
            "h2c"
          ]
        },
        "filters": [
          {
            "name": "envoy.http_connection_manager",
            "typedConfig": {
              "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
              "statPrefix": "outbound_0.0.0.0_9080",
              "rds": {
                "configSource": {
                  "ads": {}
                },
                "routeConfigName": "9080" # rds的名字是9080
              },
              "httpFilters": [
                {
                  "name": "istio.metadata_exchange",
                  "typedConfig": {
                    "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
                    "typeUrl": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm",
                    "value": {
                      "config": {
                        "configuration": "{}\n",
                        "vm_config": {
                          "code": {
                            "local": {
                              "inline_string": "envoy.wasm.metadata_exchange"
                            }
                          },
                          "runtime": "envoy.wasm.runtime.null"
                        }
                      }
                    }
                  }
                },
                {
                  "name": "istio.alpn",
                  "typedConfig": {
                    "@type": "type.googleapis.com/istio.envoy.config.filter.http.alpn.v2alpha1.FilterConfig",
                    "alpnOverride": [
                      {
                        "alpnOverride": [
                          "istio-http/1.0",
                          "istio"
                        ]
                      },
                      {
                        "upstreamProtocol": "HTTP11",
                        "alpnOverride": [
                          "istio-http/1.1",
                          "istio"
                        ]
                      },
                      {
                        "upstreamProtocol": "HTTP2",
                        "alpnOverride": [
                          "istio-h2",
                          "istio"
                        ]
                      }
                    ]
                  }
                },
                {
                  "name": "envoy.cors",
                  "typedConfig": {
                    "@type": "type.googleapis.com/envoy.config.filter.http.cors.v2.Cors"
                  }
                },
                {
                  "name": "envoy.fault",
                  "typedConfig": {
                    "@type": "type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault"
                  }
                },
                {
                  "name": "istio.stats",
                  "typedConfig": {
                    "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
                    "typeUrl": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm",
                    "value": {
                      "config": {
                        "configuration": "{\n  \"debug\": \"false\",\n  \"stat_prefix\": \"istio\"\n}\n",
                        "root_id": "stats_outbound",
                        "vm_config": {
                          "code": {
                            "local": {
                              "inline_string": "envoy.wasm.stats"
                            }
                          },
                          "runtime": "envoy.wasm.runtime.null",
                          "vm_id": "stats_outbound"
                        }
                      }
                    }
                  }
                },
                {
                  "name": "envoy.router",
                  "typedConfig": {
                    "@type": "type.googleapis.com/envoy.config.filter.http.router.v2.Router"
                  }
                }
              ],
              "tracing": {
                "clientSampling": {
                  "value": 100
                },
                "randomSampling": {
                  "value": 1
                },
                "overallSampling": {
                  "value": 100
                }
              },
              "streamIdleTimeout": "0s",
              "useRemoteAddress": false,
              "generateRequestId": true,
              "upgradeConfigs": [
                {
                  "upgradeType": "websocket"
                }
              ],
              "normalizePath": true
            }
          }
        ]
      },
      {
        "filterChainMatch": {},
        "filters": [
          {
            "name": "istio.stats",
            "typedConfig": {
              "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
              "typeUrl": "type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm",
              "value": {
                "config": {
                  "configuration": "{\n  \"debug\": \"false\",\n  \"stat_prefix\": \"istio\"\n}\n",
                  "root_id": "stats_outbound",
                  "vm_config": {
                    "code": {
                      "local": {
                        "inline_string": "envoy.wasm.stats"
                      }
                    },
                    "runtime": "envoy.wasm.runtime.null",
                    "vm_id": "tcp_stats_outbound"
                  }
                }
              }
            }
          },
          {
            "name": "envoy.tcp_proxy",
            "typedConfig": {
              "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
              "statPrefix": "PassthroughCluster",
              "cluster": "PassthroughCluster"
            }
          }
        ],
        "metadata": {
          "filterMetadata": {
            "pilot_meta": {
              "fallthrough": true
            }
          }
        },
        "name": "PassthroughFilterChain"
      }
    ],
    "deprecatedV1": {
      "bindToPort": false
    },
    "listenerFilters": [
      {
        "name": "envoy.listener.tls_inspector",
        "typedConfig": {
          "@type": "type.googleapis.com/envoy.config.filter.listener.tls_inspector.v2.TlsInspector"
        }
      },
      {
        "name": "envoy.listener.http_inspector",
        "typedConfig": {
          "@type": "type.googleapis.com/envoy.config.filter.listener.http_inspector.v2.HttpInspector"
        }
      }
    ],
    "listenerFiltersTimeout": "0.100s",
    "continueOnListenerFiltersTimeout": true,
    "trafficDirection": "OUTBOUND"
  }
]

说明它走到了9080这个rds,看下这个rds,配置文件将近1000行,原因是把所有的route全部列出来了,下面我们只看与这个namespace下面的这个route

istioctl7 pc routes productpage-v1-844495cb4b-8dkxz.test --name 9080 -o json | jq | less #过滤reviews.test.svc
[
  {
    "name": "9080",  # route的名字
    "virtualHosts": [
      {
        "name": "allow_any", # 对于allow_any的virtualHosts全部放行
        "domains": [
          "*"
        ],
        "routes": [
          {
            "name": "allow_any",
            "match": {
              "prefix": "/"
            },
            "route": {
              "cluster": "PassthroughCluster",
              "timeout": "0s",
              "maxGrpcTimeout": "0s"
            }
          }
        ],
        "includeRequestAttemptCount": true
      },

      {
        "name": "details.test.svc.cluster.local:9080", # VirtualHosts,其实是http访问的时候的Header中的Host,若缺省则为url中的Host;类似nginx中的vhosts
        "domains": [ # 对应的dns
          "details.test.svc.cluster.local",
          "details.test.svc.cluster.local:9080",
          "details",
          "details:9080",
          "details.test.svc.cluster",
          "details.test.svc.cluster:9080",
          "details.test.svc",
          "details.test.svc:9080",
          "details.test",
          "details.test:9080",
          "10.233.16.147",
          "10.233.16.147:9080"
        ],
        "routes": [
          {
            "match": {
              "prefix": "/" # 路由匹配规则
            },
            "route": {
              "cluster": "outbound|9080|v1|details.test.svc.cluster.local", # cluster名字,当该vhosts匹配上这个路由后,路由至这个cluster,它的命名规则“流量方向|端口|版本|fqdn”
              "timeout": "0s",
              "retryPolicy": {
                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                "numRetries": 2, # 当出现上面的条件时重试,重试次数
                "retryHostPredicate": [
                  {
                    "name": "envoy.retry_host_predicates.previous_hosts"
                  }
                ],
                "hostSelectionRetryMaxAttempts": "5",
                "retriableStatusCodes": [
                  503
                ]
              },
              "maxGrpcTimeout": "0s"
            },
            "metadata": {
              "filterMetadata": {
                "istio": {
                  "config": "/apis/networking.istio.io/v1alpha3/namespaces/test/virtual-service/details"  # 对应的virtualService
                }
              }
            },
            "decorator": {
              "operation": "details.test.svc.cluster.local:9080/*"
            }
          }
        ],
        "includeRequestAttemptCount": true
      },
    ],
    "validateClusters": false
  }
]

通过fqdn来查找cluster信息

istioctl7 pc clusters productpage-v1-844495cb4b-8dkxz.test --fqdn details.test.svc.cluster.local

SERVICE FQDN                       PORT     SUBSET     DIRECTION     TYPE     DESTINATION RULE
details.test.svc.cluster.local     9080     -          outbound      EDS      details.test
details.test.svc.cluster.local     9080     v1         outbound      EDS      details.test
istioctl7 pc clusters productpage-v1-844495cb4b-8dkxz.test --fqdn details.test.svc.cluster.local -ojson | jq

[
  {
    "transportSocketMatches": [ # 在cluster里面有sds密钥配置信息
      {
        "name": "tlsMode-istio",
        "match": {
          "tlsMode": "istio"
        },
        "transportSocket": {
          "name": "envoy.transport_sockets.tls",
          "typedConfig": {
            "@type": "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext",
            "commonTlsContext": {
              "tlsCertificateSdsSecretConfigs": [
                {
                  "name": "default",
                  "sdsConfig": {
                    "apiConfigSource": {
                      "apiType": "GRPC",
                      "grpcServices": [
                        {
                          "envoyGrpc": {
                            "clusterName": "sds-grpc"
                          }
                        }
                      ]
                    },
                    "initialFetchTimeout": "0s"
                  }
                }
              ],
              "combinedValidationContext": {
                "defaultValidationContext": {
                  "matchSubjectAltNames": [
                    {
                      "exact": "spiffe://cluster.local/ns/test/sa/default"
                    }
                  ]
                },
                "validationContextSdsSecretConfig": {
                  "name": "ROOTCA",
                  "sdsConfig": {
                    "apiConfigSource": {
                      "apiType": "GRPC",
                      "grpcServices": [
                        {
                          "envoyGrpc": {
                            "clusterName": "sds-grpc"
                          }
                        }
                      ]
                    },
                    "initialFetchTimeout": "0s"
                  }
                }
              },
              "alpnProtocols": [
                "istio-peer-exchange",
                "istio"
              ]
            },
            "sni": "outbound_.9080_.v1_.details.test.svc.cluster.local"
          }
        }
      },
      {
        "name": "tlsMode-disabled",
        "match": {},
        "transportSocket": {
          "name": "envoy.transport_sockets.raw_buffer"
        }
      }
    ],
    "name": "outbound|9080|v1|details.test.svc.cluster.local",
    "type": "EDS",
    "edsClusterConfig": {
      "edsConfig": {
        "ads": {}
      },
      "serviceName": "outbound|9080|v1|details.test.svc.cluster.local"
    },
    "connectTimeout": "10s",
    "circuitBreakers": { # 熔断信息
      "thresholds": [
        {
          "maxConnections": 4294967295, # 最大连接数
          "maxPendingRequests": 4294967295,
          "maxRequests": 4294967295, # 最大请求数
          "maxRetries": 4294967295 # 最大重试次数
        }
      ]
    },
    "metadata": {
      "filterMetadata": {
        "istio": {
          "config": "/apis/networking.istio.io/v1alpha3/namespaces/test/destination-rule/details", # 匹配destinationRules
          "subset": "v1" # 版本v1
        }
      }
    },
    "filters": [
      {
        "name": "istio.metadata_exchange",
        "typedConfig": {
          "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
          "typeUrl": "type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange",
          "value": {
            "protocol": "istio-peer-exchange"
          }
        }
      }
    ]
  }
]

看下endpoint信息

istioctl7 pc endpoint productpage-v1-844495cb4b-8dkxz.test --cluster "outbound|9080|v1|details.test.svc.cluster.local"

ENDPOINT              STATUS      OUTLIER CHECK     CLUSTER
10.233.70.30:9080     HEALTHY     OK                outbound|9080|v1|details.test.svc.cluster.local

直接根据port查看一个eds信息

➜ istioctl pc endpoint httpbin-7c8bfbfd46-znvf5.test --port 80
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.233.64.75:80     HEALTHY     OK                outbound|80||kubesphere-router-test.kubesphere-controls-system.svc.cluster.local
10.233.64.76:80     HEALTHY     OK                outbound|8080||httpbin.test.svc.cluster.local
127.0.0.1:80        HEALTHY     OK                inbound|8080||httpbin.test.svc.cluster.local

总结

一个Pod会有很多监听器,它会列出协议栈上的所有的监听的IP+端口,只有属于自已的端口才会有策略,其余不属于自己协议栈的listener会直接被标识为noHttp/noTcp。

router会根据Vhosts信息(即Header里面的Host信息,或是Url中的host信息)来匹配Path确实路由规则,转发流量至对应的Cluster。router会对应vitualService信息,把流量路由到逻辑地址。

Cluster中有密钥信息(即sds),会对描述流量行为,如熔断/重试等,并把流量路由到真实的endpoint地址。

endpoint对应一个IP+端口,确定四层的一个具体入口。

通过curl获取配置文件

15000是 envoy admin 端口

kubectl exec reviews-v1-6d8bc58dd7-ts8kw -c istio-proxy curl http://127.0.0.1:15000/config_dump > config_dump

config_dump可以改成listener,cluster,endpoint,route,secret

通过admin页面查看配置项

$ istio-1.6.10/bin/istioctl dashboard envoy kubesphere-router-ns4-6d4c9fb7fc-v9xjj.kubesphere-controls-system

http://localhost:64178

然后打开admin管理页面

picture 1

页面上可以查看命令行的结果,不过会更直观些。

https://istio.io/latest/docs/ops/diagnostic-tools/proxy-cmd/