前面我們創建了一個 Gateway 和 VirtualService 對象,用來對外暴露應用,然后我們就可以通過 ingressgateway
來訪問 Bookinfo 應用了。那么這兩個資源對象是如何實現的呢?
Gateway
資源是用來配置允許外部流量進入 Istio 服務網格的流量入口,用于接收傳入的 HTTP/TCP 連接。它會配置暴露的端口、協議等,但與 Kube.NETes Ingress 資源不同,不會包括任何流量路由配置,真正的路由規則是通過 VirtualService
來配置的。
我們再查看一下前面創建的 Gateway 對象的定義:
# samples/bookinfo/networking/bookinfo-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector: # 如果使用的是 Helm 方式安裝,則默認應該是 istio=ingress 標簽
istio: ingressgateway # 匹配 ingress gateway pod 的標簽(kubectl get pods -l istio=ingressgateway -n istio-system)
servers:
- port:
number: 8080
name: http
protocol: HTTP
hosts:
- "*"
這里定義的 Gateway 對象中有一個 selector
標簽選擇器,它會匹配 istio=ingressgateway
標簽的 Pod,其實就是 istio-ingressgateway
這個組件。
其實本質上 istio-ingressgateway
也是一個 Envoy 代理,用來作為 Istio 的統一入口網關,它會接收外部流量,然后根據 VirtualService
中定義的路由規則來進行流量的轉發。
我們可以查看下 istio-ingressgateway
的 Envoy 配置來驗證下:
# 進入 ingressgateway 組件所在的 Pod 中
$ kubectl exec -it istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -- /bin/bash
istio-proxy@istio-ingressgateway-9c8b9b586-s6s48:/$ ll /etc/istio/proxy
total 20
drwxrwsrwx 2 root istio-proxy 66 Nov 3 02:16 ./
drwxr-xr-x 7 root root 103 Nov 3 02:16 ../
srw-rw-rw- 1 istio-proxy istio-proxy 0 Nov 3 02:16 XDS=
-rw-r--r-- 1 istio-proxy istio-proxy 14130 Nov 3 02:16 envoy-rev.json
-rw-r--r-- 1 istio-proxy istio-proxy 2699 Nov 3 02:16 grpc-bootstrap.json
istio-proxy@istio-ingressgateway-9c8b9b586-s6s48:/$
在 istio-ingressgateway
組件的 Pod 目錄中有一個配置文件 envoy-rev.json
,這個文件就是 Envoy 的配置文件,該文件通過 istio 為 sidecar 注入的參數在啟動的時候修改或生成,由于這里采用的是 xDS 動態配置的方式,所以直接看不到前面我們添加的 Gateway 相關信息的,但是我們可以利用 Envoy 的 Admin 提供的 config_dump
來查看下配置文件:
kubectl exec istio-ingressgateway-9c8b9b586-s6s48 -c istio-proxy -n istio-system -- curl 'localhost:15000/config_dump' > ingressgateway_envoy_conf.json
istio envoy 默認配置為 json 格式,導出來的配置文件非常長(有 10000+行),我們可以先只看上層內容:
我們可以看到這個配置文件中其實就一個 configs
數組,每個元素都是一項配置,每個配置都指定了一個獨特的 @type
字段,來指定該配置是是干嘛的。接下來我們就來看下這個配置文件中的每個配置項都是干嘛的。
BootStrapConfigDump
用于在 Envoy 啟動時加載的一些靜態配置,包括類似 Sidecar 的環境變量等信息。我們也可以使用 istioctl proxy-config bootstrap
命令來查看這部分配置:
$ istioctl proxy-config bootstrap istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
bootstrap:
admin:
address:
socketAddress:
address: 127.0.0.1
portValue: 15000
profilePath: /var/lib/istio/data/envoy.prof
dynamicResources: # 動態配置發現服務信息
adsConfig:
apiType: GRPC
grpcServices:
- envoyGrpc:
clusterName: xds-grpc
setNodeOnFirstMessageOnly: true
transportApiVersion: V3
cdsConfig:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
ldsConfig:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
node: # 節點信息
cluster: istio-ingressgateway.istio-system
id: router~10.244.2.52~istio-ingressgateway-9c8b9b586-s6s48.istio-system~istio-system.svc.cluster.local
# ......
staticResources:
clusters:
- connectTimeout: 0.250s # prometheus cluster
loadAssignment:
clusterName: prometheus_stats
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: 127.0.0.1
portValue: 15000
name: prometheus_stats
type: STATIC
- connectTimeout: 0.250s # agent cluster
loadAssignment:
clusterName: agent
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: 127.0.0.1
portValue: 15020
name: agent
type: STATIC
- connectTimeout: 1s
loadAssignment:
clusterName: sds-grpc
endpoints:
- lbEndpoints:
- endpoint:
address:
pipe:
path: ./var/run/secrets/workload-spiffe-uds/socket
name: sds-grpc
type: STATIC
typedExtensionProtocolOptions:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
'@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicitHttpConfig:
http2ProtocolOptions: {}
- circuitBreakers:
thresholds:
- maxConnections: 100000
maxPendingRequests: 100000
maxRequests: 100000
- maxConnections: 100000
maxPendingRequests: 100000
maxRequests: 100000
priority: HIGH
connectTimeout: 1s
loadAssignment: # xds-grpc cluster
clusterName: xds-grpc
endpoints:
- lbEndpoints:
- endpoint:
address:
pipe:
path: ./etc/istio/proxy/XDS
maxRequestsPerConnection: 1
name: xds-grpc
type: STATIC
typedExtensionProtocolOptions:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
'@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicitHttpConfig:
http2ProtocolOptions: {}
upstreamConnectionOptions:
tcpKeepalive:
keepaliveTime: 300
- connectTimeout: 1s
DNSLookupFamily: V4_ONLY
dnsRefreshRate: 30s
loadAssignment: # zipkin cluster
clusterName: zipkin
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: zipkin.istio-system
portValue: 9411
name: zipkin
respectDnsTtl: true
type: STRICT_DNS
listeners:
- address:
socketAddress:
address: 0.0.0.0
portValue: 15090 # prometheus listener
filterChAIns:
- filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
httpFilters:
- name: envoy.filters.http.router
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
routeConfig:
virtualHosts:
- domains:
- '*'
name: backend
routes:
- match:
prefix: /stats/prometheus
route:
cluster: prometheus_stats
statPrefix: stats
- address:
socketAddress:
address: 0.0.0.0
portValue: 15021 # agent listener(健康檢查)
filterChains:
- filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
httpFilters:
- name: envoy.filters.http.router
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
routeConfig:
virtualHosts:
- domains:
- '*'
name: backend
routes:
- match:
prefix: /healthz/ready
route:
cluster: agent
statPrefix: agent
statsConfig:
# ......
tracing: # 鏈路追蹤
http:
name: envoy.tracers.zipkin
typedConfig:
'@type': type.googleapis.com/envoy.config.trace.v3.ZipkinConfig
collectorCluster: zipkin
collectorEndpoint: /api/v2/spans
collectorEndpointVersion: HTTP_JSON
sharedSpanContext: false
traceId128bit: true
上面的配置和之前我們介紹的 Envoy 配置基本一致,在上面配置中定義了一個 Prometheus 監聽器,用來暴露 Prometheus 監控指標,還定義了一個 Agent 監聽器,用來暴露健康檢查接口,另外還定義了一個 zipkin 集群,用來定義鏈路追蹤的配置。另外通過 dynamicResources
定義了動態配置發現服務信息,xds-grpc
就是用來定義 Envoy 與 Pilot 之間的 xDS 通信的。
ListenersConfigDump
這里存儲著 Envoy 的 listeners
配置,也就是 Envoy 的監聽器。Envoy 在攔截到請求后,會根據請求的地址與端口,將請求交給匹配的 listener
處理。
我們看到這個 ListenersConfigDump
中的 listener
配置分成了 static_listners
和 dynamic_listeners
,分別對應 Envoy 的靜態配置和動態配置,靜態配置,是 Envoy 配置文件中直接指定的,而 dynamic_listeners
的 listener 則是 istiod
通過 xDS
協議為 Envoy 下發的。
同樣我們也可以使用 istioctl proxy-config listener
命令來查看這部分配置:
istioctl proxy-config listener istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
對應的配置文件如下所示:
- accessLog:
- filter:
responseFlagFilter:
flags:
- NR
name: envoy.access_loggers.file
typedConfig:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
logFormat:
textFormatSource:
inlineString: |
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%
path: /dev/stdout
address:
socketAddress:
address: 0.0.0.0
portValue: 8080
continueOnListenerFiltersTimeout: true
filterChains:
- filters:
- name: istio_authn
typedConfig:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
typeUrl: type.googleapis.com/io.istio.network.authn.Config
- name: envoy.filters.network.http_connection_manager
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
accessLog:
- name: envoy.access_loggers.file
typedConfig:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
logFormat:
textFormatSource:
inlineString: |
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%
path: /dev/stdout
forwardClientCertDetails: SANITIZE_SET
httpFilters:
- name: istio.metadata_exchange
typedConfig:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
typeUrl: type.googleapis.com/io.istio.http.peer_metadata.Config
value:
upstream_discovery:
- istio_headers: {}
- workload_discovery: {}
upstream_propagation:
- istio_headers: {}
- name: envoy.filters.http.grpc_stats
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig
emitFilterState: true
statsForAllMethods: false
- name: istio.alpn
typedConfig:
"@type": type.googleapis.com/istio.envoy.config.filter.http.alpn.v2alpha1.FilterConfig
alpnOverride:
- alpnOverride:
- istio-http/1.0
- istio
- http/1.0
- alpnOverride:
- istio-http/1.1
- istio
- http/1.1
upstreamProtocol: HTTP11
- alpnOverride:
- istio-h2
- istio
- h2
upstreamProtocol: HTTP2
- name: envoy.filters.http.fault
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
- name: envoy.filters.http.cors
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: istio.stats
typedConfig:
"@type": type.googleapis.com/stats.PluginConfig
disableHostHeaderFallback: true
- name: envoy.filters.http.router
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
httpProtocolOptions: {}
normalizePath: true
pathWithEscapedSlashesAction: KEEP_UNCHANGED
rds:
configSource:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
routeConfigName: http.8080
requestIdExtension:k
typedConfig:
"@type": type.googleapis.com/envoy.extensions.request_id.uuid.v3.UuidRequestIdConfig
useRequestIdForTraceSampling: true
serverName: istio-envoy
setCurrentClientCertDetails:
cert: true
dns: true
subject: true
uri: true
statPrefix: outbound_0.0.0.0_8080
streamIdleTimeout: 0s
tracing:
# ......
upgradeConfigs:
- upgradeType: websocket
useRemoteAddress: true
name: 0.0.0.0_8080
trafficDirection: OUTBOUND
- address:
socketAddress:
address: 0.0.0.0
portValue: 15090
# ......
- address:
socketAddress:
address: 0.0.0.0
portValue: 15021
# ......
雖然上面看到的 listener 配置還是很長,但是我們應該也還是非常熟悉的,本質就是 Envoy 的配置文件中的 listener 配置。我們這里重點看下動態配置對應的配置,靜態的就是前面指定 prometheus 和 agent 對應的監聽器配置。
我們可以看到上面的動態配置對應的監聽器名稱為 0.0.0.0_8080
,對應的監聽地址為 0.0.0.0:8080
,也就是說在 Envoy 中監聽了 8080
端口:
address:
socketAddress:
address: 0.0.0.0
portValue: 8080
而前面我們是不是創建了一個 Gateway 資源對象,并指定了 8080 端口,其實這個端口就是我們前面創建的 Gateway 對象中定義的端口,這個監聽器的配置就是通過 istiod
通過 xDS
協議下發的。
那么請求是如何到達這個監聽器的呢?我們可以查看下 istio-ingressgateway
組建的 Service 數據:
$ kubectl get svc istio-ingressgateway -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.103.227.57 <pending> 15021:32459/TCP,80:31896/TCP,443:30808/TCP,31400:31535/TCP,15443:30761/TCP 46h
我們可以看到 istio-ingressgateway
組件的 Service 中定義了 5 個端口,還記得前面我們去訪問 Productpage 的時候是如何訪問的嗎?是不是通過 http://$GATEWAY_URL/productpage
訪問的,而我們這里沒有 LoadBalancer,所以直接使用 NodePort 形式訪問就行,最終我們是通過 http://NodeIP:31896/productpage
來訪問應用的,而這個 31896
端口對應 istio-ingressgateway
組件的 Service 中定義的 80
端口,也就是說我們的請求是通過 80
端口到達 istio-ingressgateway
組件的,那么這個 80
端口是如何到達 istio-ingressgateway
組件的呢?
$ kubectl describe svc istio-ingressgateway -n istio-system
Name: istio-ingressgateway
Namespace: istio-system
# ......
Port: http2 80/TCP
TargetPort: 8080/TCP
NodePort: http2 31896/TCP
Endpoints: 10.244.2.52:8080
我們查看 Service 的定義就明白了,實際上 istio-ingressgateway
這個 Service 定義的 80
端口對應的是 istio-ingressgateway
組件 Pod 的 8080
端口,也就是說我們的請求是通過 80
端口到達 istio-ingressgateway
組件的 8080
端口的,而這個 8080
端口就是我們前面在 Envoy 配置中看到的監聽器的端口了,所以當我們訪問 http://$GATEWAY_URL/productpage
的時候請求到達了 istio-ingressgateway
這個組件的 8080 端口了。
當請求到達 istio-ingressgateway
組件時,就會被這個監聽器所匹配,然后將請求交給 http_connection_manager
這個 filter 來處理,當然后面就是用各種具體的 filter 來處理請求了,比如 envoy.filters.http.fault
這個 filter 就是用來處理故障注入的,envoy.filters.http.router
則是用來處理路由轉發的、envoy.filters.http.cors
則是用來處理跨域請求的等等。
但是我們的請求進到 Envoy 后是又該如何路由呢?我應該將請求轉發到哪里去呢?這個是不是就是 Envoy 中的路由配置來決定的了,對于靜態配置我們清楚直接在 Envoy 配置文件中就可以看到,比如:
routeConfig:
virtualHosts:
- domains:
- "*"
name: backend
routes:
- match:
prefix: /healthz/ready
route:
cluster: agent
但是我們這里的路由配置是動態配置的,我們看到對應的配置中有一個 rds
字段,這個字段就是用來指定動態路由配置的,其中的 routeConfigName
字段就是用來指定對應的路由配置名稱的:
rds:
configSource:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
routeConfigName: http.8080
RoutesConfigDump
這里面保存著 Envoy 的路由配置,和 listeners 一樣,RoutesConfigDump
也分為 static_route_configs
和 dynamic_route_configs
,分別對應著靜態的路由配置和動態下發的路由配置。
同樣我們也可以使用 istioctl proxy-config route
命令來查看這部分配置:
istioctl proxy-config route istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
對應的配置如下所示:
- ignorePortInHostMatching: true
maxDirectResponseBodySizeBytes: 1048576
name: http.8080
validateClusters: false
virtualHosts:
- domains:
- "*"
includeRequestAttemptCount: true
name: "*:8080"
routes:
- decorator:
operation: productpage.default.svc.cluster.local:9080/productpage
match:
caseSensitive: true
path: /productpage
metadata:
filterMetadata:
istio:
config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
route:
cluster: outbound|9080||productpage.default.svc.cluster.local
maxGrpcTimeout: 0s
retryPolicy:
hostSelectionRetryMaxAttempts: "5"
numRetries: 2
retriableStatusCodes:
- 503
retryHostPredicate:
- name: envoy.retry_host_predicates.previous_hosts
typedConfig:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
timeout: 0s
- decorator:
operation: productpage.default.svc.cluster.local:9080/static*
match:
caseSensitive: true
prefix: /static
metadata:
filterMetadata:
istio:
config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
route:
cluster: outbound|9080||productpage.default.svc.cluster.local
maxGrpcTimeout: 0s
retryPolicy:
hostSelectionRetryMaxAttempts: "5"
numRetries: 2
retriableStatusCodes:
- 503
retryHostPredicate:
- name: envoy.retry_host_predicates.previous_hosts
typedConfig:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
timeout: 0s
- decorator:
operation: productpage.default.svc.cluster.local:9080/login
match:
caseSensitive: true
path: /login
metadata:
filterMetadata:
istio:
config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
route:
cluster: outbound|9080||productpage.default.svc.cluster.local
maxGrpcTimeout: 0s
retryPolicy:
hostSelectionRetryMaxAttempts: "5"
numRetries: 2
retriableStatusCodes:
- 503
retryHostPredicate:
- name: envoy.retry_host_predicates.previous_hosts
typedConfig:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
timeout: 0s
- decorator:
operation: productpage.default.svc.cluster.local:9080/logout
match:
caseSensitive: true
path: /logout
metadata:
filterMetadata:
istio:
config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
route:
cluster: outbound|9080||productpage.default.svc.cluster.local
maxGrpcTimeout: 0s
retryPolicy:
hostSelectionRetryMaxAttempts: "5"
numRetries: 2
retriableStatusCodes:
- 503
retryHostPredicate:
- name: envoy.retry_host_predicates.previous_hosts
typedConfig:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
timeout: 0s
- decorator:
operation: productpage.default.svc.cluster.local:9080/api/v1/products*
match:
caseSensitive: true
prefix: /api/v1/products
metadata:
filterMetadata:
istio:
config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
route:
cluster: outbound|9080||productpage.default.svc.cluster.local
maxGrpcTimeout: 0s
retryPolicy:
hostSelectionRetryMaxAttempts: "5"
numRetries: 2
retriableStatusCodes:
- 503
retryHostPredicate:
- name: envoy.retry_host_predicates.previous_hosts
typedConfig:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
timeout: 0s
- virtualHosts:
- domains:
- "*"
name: backend
routes:
- match:
prefix: /stats/prometheus
route:
cluster: prometheus_stats
- virtualHosts:
- domains:
- "*"
name: backend
routes:
- match:
prefix: /healthz/ready
route:
cluster: agent
后面的兩個 virtualHosts
就是我們的靜態路由配置,第一個是動態的路由配置,我們可以看到該配置的名稱就是 http.8080
,是不是和前面的 routeConfigName
是一致的。那么這個配置又是什么地方定義的呢?
其實仔細看這里面的配置和前面我們創建的 VirtualService
這個資源對象是不是很像,我們再看下前面創建的 VirtualService
對象的定義:
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
我們可以看到在 VirtualService
對象中定義了 5 個路由規則,而這里的 RoutesConfigDump
中也定義了 5 個路由規則,VirtualService
中定義的 5 個路由分別為 /productpage
、/static
、/login
、/logout
、/api/v1/products
,而 RoutesConfigDump
中定義的 5 個路由分別為 /productpage
、/static
、/login
、/logout
、/api/v1/products
,是不是一一對應的。最終匹配這些路由規則的請求是被轉發到 productpage
這個服務的 9080
端口的。
比如 /productpage
這個路由規則對應的 Envoy 配置如下所示:
- domains:
- "*"
includeRequestAttemptCount: true
name: "*:8080"
routes:
- decorator:
operation: productpage.default.svc.cluster.local:9080/productpage
match:
caseSensitive: true
path: /productpage
metadata:
filterMetadata:
istio:
config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
route:
cluster: outbound|9080||productpage.default.svc.cluster.local
maxGrpcTimeout: 0s
retryPolicy:
hostSelectionRetryMaxAttempts: "5"
numRetries: 2
retriableStatusCodes:
- 503
retryHostPredicate:
- name: envoy.retry_host_predicates.previous_hosts
typedConfig:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
timeout: 0s
這個配置就是我們熟悉的 Envoy 的關于虛擬主機部分的配置,比如當我們請求的路徑為 /productpage
時,就會被這個路由規則匹配到,然后就用通過 route
字段來描述我們的路由目標了,針對這個目錄,可以看到有一些類似于 retry_policy
、timeout
等字段來配置這個目標的超時、重試策略等,不過最重要的還是 cluster
這個字段,它指定了這個路由目標對應著哪個上游集群,Envoy 最終將請求發送到這個 Cluster,比如我們這里的集群名稱為 outbound|9080||productpage.default.svc.cluster.local
,關于其具體配置我們就要去查看 ClustersConfigDump
中的配置了。
ClustersConfigDump
該部分是用來存儲 Envoy 的集群配置的,同樣也分為 static_clusters
和 dynamic_active_clusters
,分別對應著靜態配置和動態下發的配置。這里的 Cluster 集群是 Envoy 內部的概念,它是指 Envoy 連接的一組邏輯相同的上游主機,并不是說 K8s 集群,只是大多數情況下我們可以把這個集群理解為 K8s 集群中的一個 Service,一個 Service 通常對應著一組 Pod,由這組 Pod 響應請求并提供同一種服務,而 Envoy 的這個集群實際可以理解成這種Pod 集合。不過 Envoy 的一個集群也不一定就對應著一個 Service,因為集群是一組邏輯相同的上游主機,所以也有可能是別的符合定義的東西,比如說是服務的一個特定版本(如只是 v2
版本的 reviews
服務)。istio 的版本灰度能力就是基于這個做的,因為兩個版本的同一服務實際上可以分成兩個集群。
同樣我們可以使用 istioctl proxy-config cluster
命令來查看這部分配置:
istioctl proxy-config cluster istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
該配置文件會非常長,它會將 K8s 集群中的 Service 都轉換成 Envoy 的 Cluster,這里我們只看下 productpage
這個服務對應的 Cluster 配置,如下所示:
- circuitBreakers: #
thresholds:
- maxConnections: 4294967295
maxPendingRequests: 4294967295
maxRequests: 4294967295
maxRetries: 4294967295
trackRemaining: true
commonLbConfig:
localityWeightedLbConfig: {}
connectTimeout: 10s
edsClusterConfig:
edsConfig:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
serviceName: outbound|9080||productpage.default.svc.cluster.local
filters:
- name: istio.metadata_exchange
typedConfig:
"@type": type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange
protocol: istio-peer-exchange
lbPolicy: LEAST_REQUEST
metadata:
filterMetadata:
istio:
services:
- host: productpage.default.svc.cluster.local
name: productpage
namespace: default
name: outbound|9080||productpage.default.svc.cluster.local
transportSocketMatches:
- match:
tlsMode: istio
name: tlsMode-istio
transportSocket:
name: envoy.transport_sockets.tls
typedConfig:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
commonTlsContext:
alpnProtocols:
- istio-peer-exchange
- istio
combinedValidationContext:
defaultValidationContext:
matchSubjectAltNames:
- exact: spiffe://cluster.local/ns/default/sa/bookinfo-productpage
validationContextSdsSecretConfig:
name: ROOTCA
sdsConfig:
apiConfigSource:
apiType: GRPC
grpcServices:
- envoyGrpc:
clusterName: sds-grpc
setNodeOnFirstMessageOnly: true
transportApiVersion: V3
initialFetchTimeout: 0s
resourceApiVersion: V3
tlsCertificateSdsSecretConfigs:
- name: default
sdsConfig:
apiConfigSource:
apiType: GRPC
grpcServices:
- envoyGrpc:
clusterName: sds-grpc
setNodeOnFirstMessageOnly: true
transportApiVersion: V3
initialFetchTimeout: 0s
resourceApiVersion: V3
tlsParams:
tlsMaximumProtocolVersion: TLSv1_3
tlsMinimumProtocolVersion: TLSv1_2
sni: outbound_.9080_._.productpage.default.svc.cluster.local
- match: {}
name: tlsMode-disabled
transportSocket:
name: envoy.transport_sockets.raw_buffer
typedConfig:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer
type: EDS
我們可以看到這個 Envoy Cluster 的名稱為 outbound|9080||productpage.default.svc.cluster.local
,和前面的路由配置中的 cluster
字段是一致的,名稱大多數會由 |
分成四個部分,分別是 inbound
或 outbound
代表入向流量或出向流量、端口號、subcluster
名稱(就是對應著 destination rule
里的 subset
)、Service FQDN
,由 istio 的服務發現進行配置,通過這個 name
我們很容易就能看出來這個集群對應的是 K8s 集群的哪個服務。
然后配置的負載均衡策略是 LEAST_REQUEST
,另外比較重要的這里的配置的類型為 type: EDS
,也就是會通過 EDS 來發現上游的主機服務,這個 EDS 的配置如下所示:
edsClusterConfig:
edsConfig:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
serviceName: outbound|9080||productpage.default.svc.cluster.local
基于 EDS 去動態發現上游主機的配置,其實在前面的 Envoy 章節我們已經介紹過了,和這里是不是幾乎是一致的,serviceName
其實就對應著 K8s 集群中的 productpage
這個 Service 對象的 9080 端口,而這個 Service 對象對應著一組 Pod,這組 Pod 就是我們的上游主機了。當然這是通過 xDS
協議下發的,我們可以通過 istioctl proxy-config endpoint
命令來查看這部分配置:
istioctl proxy-config endpoint istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
該部分數據非常多,下面只截取 productpage
相關的數據,如下所示:
- addedViaApi: true
circuitBreakers:
thresholds:
- maxConnections: 4294967295
maxPendingRequests: 4294967295
maxRequests: 4294967295
maxRetries: 4294967295
- maxConnections: 1024
maxPendingRequests: 1024
maxRequests: 1024
maxRetries: 3
priority: HIGH
edsServiceName: outbound|9080||productpage.default.svc.cluster.local
hostStatuses:
- address:
socketAddress:
address: 10.244.2.62
portValue: 9080
healthStatus:
edsHealthStatus: HEALTHY
locality: {}
stats:
- name: cx_connect_fail
- name: cx_total
value: "1"
- name: rq_error
- name: rq_success
value: "4"
- name: rq_timeout
- name: rq_total
value: "4"
- name: cx_active
type: GAUGE
- name: rq_active
type: GAUGE
weight: 1
name: outbound|9080||productpage.default.svc.cluster.local
observabilityName: outbound|9080||productpage.default.svc.cluster.local
可以看到上面的配置中就包含一個真正的后端服務地址:
address:
socketAddress:
address: 10.244.2.62
portValue: 9080
這個地址其實就是 productpage
這個 K8s Service 關聯的 Pod 的地址。這樣一個請求從進入到 Envoy 到最終轉發到后端服務的過程就清楚了。
SecretsConfigDump
由于網格中的 Envoy 之間互相通信會使用 mTLS 模式,因此每個 Envoy 通信時都需要提供本工作負載的證書,同時為了簽發證書還需要 istio ca 的根證書,這些證書的信息保存在該配置項之下。
總結
到這里我們就把 Envoy 的整個配置文件都理解了一遍,它們分別是 Bootstrap、Listeners、Routes、Clusters、Secrets 幾個配置,其中又涉及到 VirtualHost 等細分概念。
整體上一個請求在 Envoy 內部的處理與轉發過程中,listener、route、cluster 這幾個配置是緊密相連的,它們通過配置的 name 一層又一層地向下引用(listener 內的 filter 引用 route、route 內的 virtual_host 引用 cluster),形成了一條引用鏈,最終將請求從 listener 遞交到具體的 cluster。
我們可以使用 envoyui.solo.io 這個在線的 Envoy 配置可視化工具來查看 Envoy 的配置,只需要將我們的 Envoy 配置 dump 出來上傳上來即可:
經過上面的分析我們也明白了其實 Istio 并沒有實現很多復雜的邏輯,服務治理相關的功能比如負載均衡、故障注入、權重路由等都是 Envoy 本身就有的能力,Istio 只是將這些能力抽象成了一個個資源對象,然后通過 Envoy 的 xDS 協議下發到 Envoy 中,這樣就能夠實現對 Envoy 的流量治理了。所以重點還是需要我們先理解 Envoy 的配置,然后再去理解 Istio 的配置,這樣才能更好的理解 Istio,不然你就不清楚 Gateway、VirtualService 等這些資源對象到底是干什么的,它們是如何影響 Envoy 的配置的。
當然我們這里還只是分析的 Istio Ingress Gateway 的配置,而對于 Sidecar 模式的 Envoy 代理又是如何去配置的呢?它又是如何將 Pod 的流量進行攔截的呢?這些我們后面會繼續分析。