前面我們創(chuàng)建了一個(gè) Gateway 和 VirtualService 對(duì)象,用來(lái)對(duì)外暴露應(yīng)用,然后我們就可以通過(guò) ingressgateway 來(lái)訪(fǎng)問(wèn) Bookinfo 應(yīng)用了。那么這兩個(gè)資源對(duì)象是如何實(shí)現(xiàn)的呢?
Gateway 資源是用來(lái)配置允許外部流量進(jìn)入 Istio 服務(wù)網(wǎng)格的流量入口,用于接收傳入的 HTTP/TCP 連接。它會(huì)配置暴露的端口、協(xié)議等,但與 Kube.NETes Ingress 資源不同,不會(huì)包括任何流量路由配置,真正的路由規(guī)則是通過(guò) VirtualService 來(lái)配置的。
我們?cè)俨榭匆幌虑懊鎰?chuàng)建的 Gateway 對(duì)象的定義:
# samples/bookinfo/networking/bookinfo-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector: # 如果使用的是 Helm 方式安裝,則默認(rèn)應(yīng)該是 istio=ingress 標(biāo)簽
istio: ingressgateway # 匹配 ingress gateway pod 的標(biāo)簽(kubectl get pods -l istio=ingressgateway -n istio-system)
servers:
- port:
number: 8080
name: http
protocol: HTTP
hosts:
- "*"
這里定義的 Gateway 對(duì)象中有一個(gè) selector 標(biāo)簽選擇器,它會(huì)匹配 istio=ingressgateway 標(biāo)簽的 Pod,其實(shí)就是 istio-ingressgateway 這個(gè)組件。
其實(shí)本質(zhì)上 istio-ingressgateway 也是一個(gè) Envoy 代理,用來(lái)作為 Istio 的統(tǒng)一入口網(wǎng)關(guān),它會(huì)接收外部流量,然后根據(jù) VirtualService 中定義的路由規(guī)則來(lái)進(jìn)行流量的轉(zhuǎn)發(fā)。
我們可以查看下 istio-ingressgateway 的 Envoy 配置來(lái)驗(yàn)證下:
# 進(jìn)入 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 目錄中有一個(gè)配置文件 envoy-rev.json,這個(gè)文件就是 Envoy 的配置文件,該文件通過(guò) istio 為 sidecar 注入的參數(shù)在啟動(dòng)的時(shí)候修改或生成,由于這里采用的是 xDS 動(dòng)態(tài)配置的方式,所以直接看不到前面我們添加的 Gateway 相關(guān)信息的,但是我們可以利用 Envoy 的 Admin 提供的 config_dump 來(lái)查看下配置文件:
kubectl exec istio-ingressgateway-9c8b9b586-s6s48 -c istio-proxy -n istio-system -- curl 'localhost:15000/config_dump' > ingressgateway_envoy_conf.json
istio envoy 默認(rèn)配置為 json 格式,導(dǎo)出來(lái)的配置文件非常長(zhǎng)(有 10000+行),我們可以先只看上層內(nèi)容:
我們可以看到這個(gè)配置文件中其實(shí)就一個(gè) configs 數(shù)組,每個(gè)元素都是一項(xiàng)配置,每個(gè)配置都指定了一個(gè)獨(dú)特的 @type 字段,來(lái)指定該配置是是干嘛的。接下來(lái)我們就來(lái)看下這個(gè)配置文件中的每個(gè)配置項(xiàng)都是干嘛的。
BootStrapConfigDump
用于在 Envoy 啟動(dòng)時(shí)加載的一些靜態(tài)配置,包括類(lèi)似 Sidecar 的環(huán)境變量等信息。我們也可以使用 istioctl proxy-config bootstrap 命令來(lái)查看這部分配置:
$ 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: # 動(dòng)態(tài)配置發(fā)現(xiàn)服務(wù)信息
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: # 節(jié)點(diǎn)信息
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 配置基本一致,在上面配置中定義了一個(gè) Prometheus 監(jiān)聽(tīng)器,用來(lái)暴露 Prometheus 監(jiān)控指標(biāo),還定義了一個(gè) Agent 監(jiān)聽(tīng)器,用來(lái)暴露健康檢查接口,另外還定義了一個(gè) zipkin 集群,用來(lái)定義鏈路追蹤的配置。另外通過(guò) dynamicResources 定義了動(dòng)態(tài)配置發(fā)現(xiàn)服務(wù)信息,xds-grpc 就是用來(lái)定義 Envoy 與 Pilot 之間的 xDS 通信的。
ListenersConfigDump
這里存儲(chǔ)著 Envoy 的 listeners 配置,也就是 Envoy 的監(jiān)聽(tīng)器。Envoy 在攔截到請(qǐng)求后,會(huì)根據(jù)請(qǐng)求的地址與端口,將請(qǐng)求交給匹配的 listener 處理。
我們看到這個(gè) ListenersConfigDump 中的 listener 配置分成了 static_listners 和 dynamic_listeners,分別對(duì)應(yīng) Envoy 的靜態(tài)配置和動(dòng)態(tài)配置,靜態(tài)配置,是 Envoy 配置文件中直接指定的,而 dynamic_listeners的 listener 則是 istiod 通過(guò) xDS 協(xié)議為 Envoy 下發(fā)的。
同樣我們也可以使用 istioctl proxy-config listener 命令來(lái)查看這部分配置:
istioctl proxy-config listener istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
對(duì)應(yīng)的配置文件如下所示:
- 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 配置還是很長(zhǎng),但是我們應(yīng)該也還是非常熟悉的,本質(zhì)就是 Envoy 的配置文件中的 listener 配置。我們這里重點(diǎn)看下動(dòng)態(tài)配置對(duì)應(yīng)的配置,靜態(tài)的就是前面指定 prometheus 和 agent 對(duì)應(yīng)的監(jiān)聽(tīng)器配置。
我們可以看到上面的動(dòng)態(tài)配置對(duì)應(yīng)的監(jiān)聽(tīng)器名稱(chēng)為 0.0.0.0_8080,對(duì)應(yīng)的監(jiān)聽(tīng)地址為 0.0.0.0:8080,也就是說(shuō)在 Envoy 中監(jiān)聽(tīng)了 8080 端口:
address:
socketAddress:
address: 0.0.0.0
portValue: 8080
而前面我們是不是創(chuàng)建了一個(gè) Gateway 資源對(duì)象,并指定了 8080 端口,其實(shí)這個(gè)端口就是我們前面創(chuàng)建的 Gateway 對(duì)象中定義的端口,這個(gè)監(jiān)聽(tīng)器的配置就是通過(guò) istiod 通過(guò) xDS 協(xié)議下發(fā)的。
那么請(qǐng)求是如何到達(dá)這個(gè)監(jiān)聽(tīng)器的呢?我們可以查看下 istio-ingressgateway 組建的 Service 數(shù)據(jù):
$ 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 個(gè)端口,還記得前面我們?nèi)ピL(fǎng)問(wèn) Productpage 的時(shí)候是如何訪(fǎng)問(wèn)的嗎?是不是通過(guò) http://$GATEWAY_URL/productpage 訪(fǎng)問(wèn)的,而我們這里沒(méi)有 LoadBalancer,所以直接使用 NodePort 形式訪(fǎng)問(wèn)就行,最終我們是通過(guò) http://NodeIP:31896/productpage 來(lái)訪(fǎng)問(wèn)應(yīng)用的,而這個(gè) 31896 端口對(duì)應(yīng) istio-ingressgateway 組件的 Service 中定義的 80 端口,也就是說(shuō)我們的請(qǐng)求是通過(guò) 80 端口到達(dá) istio-ingressgateway 組件的,那么這個(gè) 80 端口是如何到達(dá) 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 的定義就明白了,實(shí)際上 istio-ingressgateway 這個(gè) Service 定義的 80 端口對(duì)應(yīng)的是 istio-ingressgateway 組件 Pod 的 8080 端口,也就是說(shuō)我們的請(qǐng)求是通過(guò) 80 端口到達(dá) istio-ingressgateway 組件的 8080 端口的,而這個(gè) 8080 端口就是我們前面在 Envoy 配置中看到的監(jiān)聽(tīng)器的端口了,所以當(dāng)我們?cè)L問(wèn) http://$GATEWAY_URL/productpage 的時(shí)候請(qǐng)求到達(dá)了 istio-ingressgateway 這個(gè)組件的 8080 端口了。
當(dāng)請(qǐng)求到達(dá) istio-ingressgateway 組件時(shí),就會(huì)被這個(gè)監(jiān)聽(tīng)器所匹配,然后將請(qǐng)求交給 http_connection_manager 這個(gè) filter 來(lái)處理,當(dāng)然后面就是用各種具體的 filter 來(lái)處理請(qǐng)求了,比如 envoy.filters.http.fault 這個(gè) filter 就是用來(lái)處理故障注入的,envoy.filters.http.router 則是用來(lái)處理路由轉(zhuǎn)發(fā)的、envoy.filters.http.cors 則是用來(lái)處理跨域請(qǐng)求的等等。
但是我們的請(qǐng)求進(jìn)到 Envoy 后是又該如何路由呢?我應(yīng)該將請(qǐng)求轉(zhuǎn)發(fā)到哪里去呢?這個(gè)是不是就是 Envoy 中的路由配置來(lái)決定的了,對(duì)于靜態(tài)配置我們清楚直接在 Envoy 配置文件中就可以看到,比如:
routeConfig:
virtualHosts:
- domains:
- "*"
name: backend
routes:
- match:
prefix: /healthz/ready
route:
cluster: agent
但是我們這里的路由配置是動(dòng)態(tài)配置的,我們看到對(duì)應(yīng)的配置中有一個(gè) rds 字段,這個(gè)字段就是用來(lái)指定動(dòng)態(tài)路由配置的,其中的 routeConfigName 字段就是用來(lái)指定對(duì)應(yīng)的路由配置名稱(chēng)的:
rds:
configSource:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
routeConfigName: http.8080
RoutesConfigDump
這里面保存著 Envoy 的路由配置,和 listeners 一樣,RoutesConfigDump 也分為 static_route_configs 和 dynamic_route_configs,分別對(duì)應(yīng)著靜態(tài)的路由配置和動(dòng)態(tài)下發(fā)的路由配置。
同樣我們也可以使用 istioctl proxy-config route 命令來(lái)查看這部分配置:
istioctl proxy-config route istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
對(duì)應(yīng)的配置如下所示:
- 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
后面的兩個(gè) virtualHosts 就是我們的靜態(tài)路由配置,第一個(gè)是動(dòng)態(tài)的路由配置,我們可以看到該配置的名稱(chēng)就是 http.8080,是不是和前面的 routeConfigName 是一致的。那么這個(gè)配置又是什么地方定義的呢?
其實(shí)仔細(xì)看這里面的配置和前面我們創(chuàng)建的 VirtualService 這個(gè)資源對(duì)象是不是很像,我們?cè)倏聪虑懊鎰?chuàng)建的 VirtualService 對(duì)象的定義:
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 對(duì)象中定義了 5 個(gè)路由規(guī)則,而這里的 RoutesConfigDump 中也定義了 5 個(gè)路由規(guī)則,VirtualService 中定義的 5 個(gè)路由分別為 /productpage、/static、/login、/logout、/api/v1/products,而 RoutesConfigDump 中定義的 5 個(gè)路由分別為 /productpage、/static、/login、/logout、/api/v1/products,是不是一一對(duì)應(yīng)的。最終匹配這些路由規(guī)則的請(qǐng)求是被轉(zhuǎn)發(fā)到 productpage 這個(gè)服務(wù)的 9080 端口的。
比如 /productpage 這個(gè)路由規(guī)則對(duì)應(yīng)的 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
這個(gè)配置就是我們熟悉的 Envoy 的關(guān)于虛擬主機(jī)部分的配置,比如當(dāng)我們請(qǐng)求的路徑為 /productpage 時(shí),就會(huì)被這個(gè)路由規(guī)則匹配到,然后就用通過(guò) route 字段來(lái)描述我們的路由目標(biāo)了,針對(duì)這個(gè)目錄,可以看到有一些類(lèi)似于 retry_policy、timeout等字段來(lái)配置這個(gè)目標(biāo)的超時(shí)、重試策略等,不過(guò)最重要的還是 cluster 這個(gè)字段,它指定了這個(gè)路由目標(biāo)對(duì)應(yīng)著哪個(gè)上游集群,Envoy 最終將請(qǐng)求發(fā)送到這個(gè) Cluster,比如我們這里的集群名稱(chēng)為 outbound|9080||productpage.default.svc.cluster.local,關(guān)于其具體配置我們就要去查看 ClustersConfigDump 中的配置了。
ClustersConfigDump
該部分是用來(lái)存儲(chǔ) Envoy 的集群配置的,同樣也分為 static_clusters 和 dynamic_active_clusters,分別對(duì)應(yīng)著靜態(tài)配置和動(dòng)態(tài)下發(fā)的配置。這里的 Cluster 集群是 Envoy 內(nèi)部的概念,它是指 Envoy 連接的一組邏輯相同的上游主機(jī),并不是說(shuō) K8s 集群,只是大多數(shù)情況下我們可以把這個(gè)集群理解為 K8s 集群中的一個(gè) Service,一個(gè) Service 通常對(duì)應(yīng)著一組 Pod,由這組 Pod 響應(yīng)請(qǐng)求并提供同一種服務(wù),而 Envoy 的這個(gè)集群實(shí)際可以理解成這種Pod 集合。不過(guò) Envoy 的一個(gè)集群也不一定就對(duì)應(yīng)著一個(gè) Service,因?yàn)榧菏?strong>一組邏輯相同的上游主機(jī),所以也有可能是別的符合定義的東西,比如說(shuō)是服務(wù)的一個(gè)特定版本(如只是 v2 版本的 reviews 服務(wù))。istio 的版本灰度能力就是基于這個(gè)做的,因?yàn)閮蓚€(gè)版本的同一服務(wù)實(shí)際上可以分成兩個(gè)集群。
同樣我們可以使用 istioctl proxy-config cluster 命令來(lái)查看這部分配置:
istioctl proxy-config cluster istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
該配置文件會(huì)非常長(zhǎng),它會(huì)將 K8s 集群中的 Service 都轉(zhuǎn)換成 Envoy 的 Cluster,這里我們只看下 productpage 這個(gè)服務(wù)對(duì)應(yīng)的 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
我們可以看到這個(gè) Envoy Cluster 的名稱(chēng)為 outbound|9080||productpage.default.svc.cluster.local,和前面的路由配置中的 cluster 字段是一致的,名稱(chēng)大多數(shù)會(huì)由 | 分成四個(gè)部分,分別是 inbound 或 outbound 代表入向流量或出向流量、端口號(hào)、subcluster 名稱(chēng)(就是對(duì)應(yīng)著 destination rule 里的 subset)、Service FQDN,由 istio 的服務(wù)發(fā)現(xiàn)進(jìn)行配置,通過(guò)這個(gè) name 我們很容易就能看出來(lái)這個(gè)集群對(duì)應(yīng)的是 K8s 集群的哪個(gè)服務(wù)。
然后配置的負(fù)載均衡策略是 LEAST_REQUEST,另外比較重要的這里的配置的類(lèi)型為 type: EDS,也就是會(huì)通過(guò) EDS 來(lái)發(fā)現(xiàn)上游的主機(jī)服務(wù),這個(gè) EDS 的配置如下所示:
edsClusterConfig:
edsConfig:
ads: {}
initialFetchTimeout: 0s
resourceApiVersion: V3
serviceName: outbound|9080||productpage.default.svc.cluster.local
基于 EDS 去動(dòng)態(tài)發(fā)現(xiàn)上游主機(jī)的配置,其實(shí)在前面的 Envoy 章節(jié)我們已經(jīng)介紹過(guò)了,和這里是不是幾乎是一致的,serviceName 其實(shí)就對(duì)應(yīng)著 K8s 集群中的 productpage 這個(gè) Service 對(duì)象的 9080 端口,而這個(gè) Service 對(duì)象對(duì)應(yīng)著一組 Pod,這組 Pod 就是我們的上游主機(jī)了。當(dāng)然這是通過(guò) xDS 協(xié)議下發(fā)的,我們可以通過(guò) istioctl proxy-config endpoint 命令來(lái)查看這部分配置:
istioctl proxy-config endpoint istio-ingressgateway-9c8b9b586-s6s48 -n istio-system -o yaml
該部分?jǐn)?shù)據(jù)非常多,下面只截取 productpage 相關(guān)的數(shù)據(jù),如下所示:
- 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
可以看到上面的配置中就包含一個(gè)真正的后端服務(wù)地址:
address:
socketAddress:
address: 10.244.2.62
portValue: 9080
這個(gè)地址其實(shí)就是 productpage 這個(gè) K8s Service 關(guān)聯(lián)的 Pod 的地址。這樣一個(gè)請(qǐng)求從進(jìn)入到 Envoy 到最終轉(zhuǎn)發(fā)到后端服務(wù)的過(guò)程就清楚了。
SecretsConfigDump
由于網(wǎng)格中的 Envoy 之間互相通信會(huì)使用 mTLS 模式,因此每個(gè) Envoy 通信時(shí)都需要提供本工作負(fù)載的證書(shū),同時(shí)為了簽發(fā)證書(shū)還需要 istio ca 的根證書(shū),這些證書(shū)的信息保存在該配置項(xiàng)之下。
總結(jié)
到這里我們就把 Envoy 的整個(gè)配置文件都理解了一遍,它們分別是 Bootstrap、Listeners、Routes、Clusters、Secrets 幾個(gè)配置,其中又涉及到 VirtualHost 等細(xì)分概念。

整體上一個(gè)請(qǐng)求在 Envoy 內(nèi)部的處理與轉(zhuǎn)發(fā)過(guò)程中,listener、route、cluster 這幾個(gè)配置是緊密相連的,它們通過(guò)配置的 name 一層又一層地向下引用(listener 內(nèi)的 filter 引用 route、route 內(nèi)的 virtual_host 引用 cluster),形成了一條引用鏈,最終將請(qǐng)求從 listener 遞交到具體的 cluster。
我們可以使用 envoyui.solo.io 這個(gè)在線(xiàn)的 Envoy 配置可視化工具來(lái)查看 Envoy 的配置,只需要將我們的 Envoy 配置 dump 出來(lái)上傳上來(lái)即可:
經(jīng)過(guò)上面的分析我們也明白了其實(shí) Istio 并沒(méi)有實(shí)現(xiàn)很多復(fù)雜的邏輯,服務(wù)治理相關(guān)的功能比如負(fù)載均衡、故障注入、權(quán)重路由等都是 Envoy 本身就有的能力,Istio 只是將這些能力抽象成了一個(gè)個(gè)資源對(duì)象,然后通過(guò) Envoy 的 xDS 協(xié)議下發(fā)到 Envoy 中,這樣就能夠?qū)崿F(xiàn)對(duì) Envoy 的流量治理了。所以重點(diǎn)還是需要我們先理解 Envoy 的配置,然后再去理解 Istio 的配置,這樣才能更好的理解 Istio,不然你就不清楚 Gateway、VirtualService 等這些資源對(duì)象到底是干什么的,它們是如何影響 Envoy 的配置的。
當(dāng)然我們這里還只是分析的 Istio Ingress Gateway 的配置,而對(duì)于 Sidecar 模式的 Envoy 代理又是如何去配置的呢?它又是如何將 Pod 的流量進(jìn)行攔截的呢?這些我們后面會(huì)繼續(xù)分析。






